The JAMS API can be used to create custom Execution Methods with .NET code. Developers can also design the source editor used by the Execution Method. This topic describes the steps involved in this process as well as some best practices for designing a custom Execution Method.
When creating a new execution method there are four key components:
To get started, open Visual Studio and create an empty Class Library project. For this example name the project JAMSIntegratedDll as shown in the screenshot below.
Once the project has been created add a Reference to JAMSHostBase.dll and JAMSShr.dll. These two assemblies can be found within the Scheduler directory where JAMS is installed.
Within the Solution Explorer add a new Class named WriteFileSource.cs to the JAMSIntegratedDll project. This class contains the properties that can be edited on the Job’s Source tab. Add the following code to the class.
![]() |
Note: in this example the JavaScriptSerializer class is used to handle JSON serialization, however you may find it necessary to use a different serialization class if your Execution Method requires properties with more complex Types. In this case you must add a reference to System.Web.Extensions in order to access the JavaScriptSerializer class. |
Adding a new Class |
Copy Code
|
---|---|
using System; using System.Web.Script.Serialization; namespace JAMSIntegratedDll { /// <summary> /// WriteFileSource defines the properties that the execution method requires. These /// properties are edited through the WriteFileControl on the Job's Source tab. /// </summary> public class WriteFileSource { public WriteFileSource() { } #region Properties /// <summary> /// The name of the file to write the Message to. /// </summary> public string FileName { get; set; } /// <summary> /// The content to write to the file. /// </summary> public string Message { get; set; } #endregion #region Methods /// <summary> /// Returns the JSON Job source /// </summary> /// <returns></returns> public string GetSource() { string serializedObject = String.Empty; try { JavaScriptSerializer serializer = new JavaScriptSerializer(); // // Serialize the Job source // serializedObject = serializer.Serialize(this); } catch (Exception ex) { throw new ApplicationException("Error while exporting WriteFileSource", ex); } return serializedObject; } /// <summary> /// Returns a WriteFileSource object from the JSON Job Source /// </summary> /// <param name="source"></param> /// <returns></returns> public static WriteFileSource Create(string source) { WriteFileSource jobSource; source = source.Trim(); try { if (string.IsNullOrWhiteSpace(source)) { // // Empty string, create an empty WriteFileSource // jobSource = new WriteFileSource(); } else { JavaScriptSerializer serializer = new JavaScriptSerializer(); // // Deserialize the Job Source from JSON // jobSource = serializer.Deserialize<WriteFileSource>(source); } } catch (Exception ex) { throw new ApplicationException("Error while importing WriteFileSource", ex); } return jobSource; } #endregion } } |
In addition to defining the Job’s properties this class also contains the methods Create and GetSource. These methods are used by the Source Editor to convert the Job source to and from the JSON format for storage in the JAMS database.
In the Solution Explorer add a new UserControl to the project named WriteFileControl. Insert the code below to the UserControl’s code-behind and ignore any errors pertaining to missing controls. The controls to the designer will be added in the next step.
WriteFileControl.cs |
Copy Code
|
---|---|
using System; using System.Windows.Forms; using MVPSI.JAMS; using MVPSI.JAMSExtension; namespace JAMSIntegratedDll { /// <summary> /// The WriteFileControl is displayed as the Job's source editor and is used to /// specify the data required by the Job. /// </summary> public partial class WriteFileControl : UserControl, IEditJobSource { private WriteFileSource m_WriteFileSource; public WriteFileControl() { InitializeComponent(); } /// <summary> /// Returns the control used to edit the Job source. /// </summary> public Control EditorControl { get { return this; } } /// <summary> /// The JAMS Job being edited /// </summary> public Job Job { get; set; } /// <summary> /// Gets or Sets the Job source. This property is called by JAMS when loading and saving the Job source /// </summary> public string SourceCode { get { if (m_WriteFileSource == null) { return String.Empty; } else { m_WriteFileSource.FileName = txtFileName.Text; m_WriteFileSource.Message = txtMessage.Text; } return m_WriteFileSource.GetSource(); } set { try { // // Get the WriteFileSource object from the JSON source // m_WriteFileSource = WriteFileSource.Create(value); } catch (Exception ex) { MessageBox.Show(ex.ToString(), "Unable to load source", MessageBoxButtons.OK, MessageBoxIcon.Error); m_WriteFileSource = WriteFileSource.Create(String.Empty); } // // Set the control values from the WriteFileSource // txtFileName.Text = m_WriteFileSource.FileName; txtMessage.Text = m_WriteFileSource.Message; } } /// <summary> /// Marks the Job source as modified so it will be saved /// </summary> /// <param name="e"></param> private void OnSourceCodeChanged(EventArgs e) { if (SourceCodeChanged != null) { SourceCodeChanged(this, e); } } public event EventHandler SourceCodeChanged; /// <summary> /// Mark the Job source as modified after the FileName changes /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void txtFileName_TextChanged(object sender, EventArgs e) { OnSourceCodeChanged(e); } /// <summary> /// Mark the Job source as modified after the Message changes /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void txtMessage_TextChanged(object sender, EventArgs e) { OnSourceCodeChanged(e); } public void SaveState() { } } } |
Next, on the Designer, drag out two Labels and Text Boxes as shown in the screenshot below. The Text Boxes should be named txtFileName and txtMessage. Be sure to map the TextChanged event on both TextBox controls to the corresponding event in the code-behind or else JAMS won’t save your source.
In the Solution Explorer add a class named WriteFileHost.cs. This class contains the code that occurs when the JAMS Job is running. The Execute method is where your Job does the actual work. Insert the following code:
Adding a Class |
Copy Code
|
---|---|
using System; using System.Collections.Generic; using MVPSI.JAMS.Host; using System.IO; namespace JAMSIntegratedDll { public class WriteFileHost : IJAMSHost { private FileStream fs; private FinalResults results; private WriteFileSource m_WriteFileSource; #region IJAMSHost Members /// <summary> /// Prepare for execution /// </summary> /// <param name="serviceProvider"></param> /// <param name="attributes"></param> /// <param name="parameters">Collection of Parameters for the Job.</param> public void Initialize(IServiceProvider serviceProvider, Dictionary<string, object> attributes, Dictionary<string, object> parameters) { results = new FinalResults(); try { // // Validate that the Job contains a parameter named WriteCount to indicate how many times to write the source to the file // if (!parameters.ContainsKey("WriteCount")) { WriteErrorInfo("A Parameter named \"WriteCount\" must be specified with the number of times to write the Message to the File!", "Initialize"); } } catch (Exception ex) { WriteErrorInfo(ex.Message, "Initialize"); } } /// <summary> /// Write a Message to the specified file /// </summary> /// <param name="serviceProvider"></param> /// <param name="attributes"></param> /// <param name="parameters">Collection of Parameters for the Job.</param> /// <returns></returns> public FinalResults Execute(IServiceProvider serviceProvider, Dictionary<string, object> attributes, Dictionary<string, object> parameters) { string jobSourceFile = String.Empty; int writeCount = 0; // // If there were no errors during Initialize write content to the file // if (results.FinalSeverity != 3) { try { // // Read the WriteCount parameter on the Job // string writeCountRaw = parameters["WriteCount"].ToString(); if (!String.IsNullOrWhiteSpace(writeCountRaw) && !Int32.TryParse(writeCountRaw, out writeCount)) { WriteErrorInfo(String.Format("The value of WriteCount could not be converted to an integer: {0}", writeCountRaw), "Execute"); } if (results.FinalSeverity != 3) { // // Get the Job source file // jobSourceFile = attributes["CommandFilename"] as string; // // Get the deserialized Job source // m_WriteFileSource = WriteFileSource.Create(File.ReadAllText(jobSourceFile)); // // Write to the Job Log // Console.WriteLine("Writing to File: {0}", m_WriteFileSource.FileName); Console.WriteLine("Writing Message: {0}", m_WriteFileSource.Message); Console.WriteLine("Write Count: {0}", writeCount); // // Write the Message to the specified FileName // using (StreamWriter writer = new StreamWriter(m_WriteFileSource.FileName)) { for (int i = 0; i < writeCount; i++) { writer.WriteLine(m_WriteFileSource.Message); } } // // Set the Final Results object to Success // results.FinalSeverity = 0; results.FinalStatus = "The operation completed successfully"; results.FinalStatusCode = 0; } } catch (Exception ex) { WriteErrorInfo(ex.Message, "Execute"); } } return results; } /// <summary> /// Cleans up resources from execution /// </summary> /// <param name="serviceProvider"></param> /// <param name="attributes"></param> /// <param name="parameters">Collection of Parameters for the Job.</param> public void Cleanup(IServiceProvider serviceProvider, Dictionary<string, object> attributes, Dictionary<string, object> parameters) { // // Cleanup any resources // } /// <summary> /// Handles the Job being canceled /// </summary> /// <param name="serviceProvider"></param> public void Cancel(IServiceProvider serviceProvider) { // // Handle the Job being cancelled // } #endregion /// <summary> /// Write the error message to the Job log and set the Final Results to error /// </summary> /// <param name="errorMessag"></param> /// <param name="methodName"></param> private void WriteErrorInfo(string errorMessag, string methodName) { Console.WriteLine(errorMessag); results.FinalSeverity = 3; results.FinalStatus = "Execption occured during " + methodName; results.FinalStatusCode = 1; } } } |
Now that all the classes have been created, build your solution and verify that there are no errors. Next, navigate to your project’s bin folder and copy the JAMSIntegratedDll.pdb and .dll files from release/debug. Paste both files into the Client and Scheduler directories where JAMS is installed. By default, this will be: C:\Program Files\MVPSI\JAMS\
For this example all the classes were created within a single Project. However, you could also define the Source Editor, Host, and Source classes in separate projects. When using this approach, the Source project is referenced by the other two projects. The assemblies would then be deployed as follows:
JAMS Directory | Assembly |
Client | Source Editor, Source |
Scheduler | Source, Host |
Open the JAMS Client and navigate to the Execution Method view. Click Add and name the new Method WriteFileCustom. When prompted for a Base Execution Method select the SQLCommand. Open the Method properties and select the Execution tab. Update the values as shown below to reference your classes:
Save changes to the Method. Next, create a new JAMS Job and select WriteFileCustom as the Execution Method. Continue through the wizard until the Source step. You should see the custom form controls to specify the source and enter the following values:
Finally, add a parameter to the Job named WriteCount with a default value of 3 and then save changes to the Job. Run the Job. You should see the file getting created and the specified content being written three times.