Building a new Job Service Job in Infor CRM Web- Step by Step

Using the power of the job service can open up a lot of tasks within the web client. By executing out of the web context they are a perfect place to run long running processes and because they can be scheduled easily, can allow a lot of cool things to happen.

There is not a lot of documentation on exactly how to create a new job service job however which is where this post comes in. The first thing to know are the jobs are compiled assemblies. That means we are going to use Visual Studio to build this. I am not going to go into the ins and outs of using VS. What I will do is to define what you need minimally to put a job together.

Step 1
In Visual Studio, create an class library project.

Step 2
Add the required assemblies. All of these files can be found in a deployed Infor CRM client web site Bin folder. The assemblies required are:

  • NHibernate
  • Quartz
  • Sage.Entity.Interfaces (to work with the entity model)
  • Sage.Platform
  • Sage.Scheduling
  • Sage.SalesLogix.BusinessRules (if relying on that for existing methods)

 

Step 3
Add custom usings (the custom ones are after the line break)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Globalization; 

using Quartz;
using Sage.Entity.Interfaces;
using Sage.Platform.Orm;
using Sage.Platform.Scheduling;
using Sage.SalesLogix.BusinessRules;
using System.ComponentModel;

Step 4
Create your public class name. This should inherit from SystemJobBase if you want the job to be tagged as a system job in the web client. The signature would look like:

public class MyJobIsCool : SystemJobBase
{
}

The class name will be used to select it as a job available to be used within the Application Architect, so name it something meaningful.

In addition, you should add attributes to your class as follows:

[DisallowConcurrentExecution]
[Description("My Description")]
[DisplayName("My Name")]
public class MyJobIsCool : SystemJobBase
{
}

These attributes are exposed by the job service and are what shows in the web client as the job name and description.

Step 5
Create the OnExecute method within your class. Only one method is required for your class. An overriden OnExecute. That signature look like this:

protected override void OnExecute()
{
}

Within that method there are a couple of built in outputs you can use:

  • Phase (string)- corresponds to the output of the Phase column in the Job Manager within the web client. Used as a main level status field.
  • PhaseDetail (string)- corresponds to the Phase Detail column in the Job Manager within the web client. Used as a more granular status within the main Phase.
  • Progress (decimal?)- corresponds to the progress bar percent complete (0-100). You can decide how this is set, either for the overall task or for within the Phases you are working in. Setting it to 100 does not mean anything, it is just informational.

The following is a completed sample file that runs a query retrieving all contacts where the last name starts with A. It then loops through the records and updates all of their phone numbers to be a specific value. (not a real valuable example but hey)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Globalization; 

using Quartz;
using Sage.Entity.Interfaces;
using Sage.Platform.Orm;
using Sage.Platform.Scheduling;
using Sage.SalesLogix.BusinessRules;
using System.ComponentModel;


namespace FX.JobService
{    
    [DisallowConcurrentExecution]
    [Description("My Description")]
    [DisplayName("My Name")]
    public class MyJobIsCool : SystemJobBase
    {
        private static void Log(string Message)
        {
            using (System.IO.TextWriter writer = new System.IO.StreamWriter(@"C:\Temp\Test_debug_log.txt", true))
            {
                writer.WriteLine(string.Format("{0} - {1}", DateTime.Now, Message));
            }
        }

        private static readonly string _entityDisplayName = typeof(IContact).GetDisplayName();
        protected override void OnExecute()
        {
            //Set the main phase status
            Phase = "Running";
            //Set the phase detail status
            PhaseDetail = "Starting";            
            using (var session = new SessionScopeWrapper())
            {
                //Set the phase detail status
                PhaseDetail = string.Format("Composing Query {0}", _entityDisplayName);
                
                // Now we build our list of records
                // I chose contacts starting with A for a lastname                
                var contacts = session.QueryOver<IContact>()
                    .WhereRestrictionOn(c => c.LastName).IsLike("a%")
                    .List<IContact>();

                //now work with my list of contacts
                if (contacts != null)
                {
                    //Set the main phase status
                    Phase = "Doing the work";
                    //Set the phase detail status
                    PhaseDetail = string.Format("Processing {1} {0} records", _entityDisplayName, contacts.Count);
                    //Write to my text file
                    Log(PhaseDetail);

                    //Initialize a counter
                    var counter = 0;
                    foreach (var contact in contacts)
                    {
                        //Here we can do a sample update like this:
                        contact.WorkPhone = "2128675309";
                        contact.Save();

                        // halt processing if interrupt requested by job server
                        if (Interrupted)
                        {
                            PhaseDetail = "Job was stopped by a user";
                            Log(PhaseDetail);
                            return;
                        }
                        // update job progress percentage
                        Progress = 100M * ++counter / contacts.Count;                        
                    }
                }
                else
                {
                    // no records to process
                    //Set the phase detail status
                    PhaseDetail = string.Format("No qualifying records for {0}", _entityDisplayName);
                    //Write to my text file
                    Log(PhaseDetail);
                }
            }
            //Set the main phase status
            Phase = "Complete";
            //Set the phase detail status
            PhaseDetail = "Complete";
            //Write to my text file
            Log("We are done!");            
        }
    }
}
 

Step 6
Compile the assembly. Now take the compiled dll file and copy it into the Saleslogix Job Service portal, under the Support Files/bin folder. If you do this in Application Architect at this point you will need to close the AA and re-open it to ensure the job is available for the next step.

Step 7
Add the job to the list of jobs available in the job service portal.

  • In the AA, under portal manager, double click on the Saleslogix Job Service portal.
  • Under the Jobs tab, right click on the Jobs folder and choose Add.
  • To the right in the type name, do the drop down. The list will compile. You should now see your assembly name space.
  • Expand it out to your public class name (i.e. MyJobIsCool).
  • Save your changes to the portal.

Step 8
Deploy the SLXJobService portal. It is possible you might need to restart the SLX Job Server service. Not sure on that.

You should now be able to log in to the web client, and under administration/job manager and be able to see your new job listed under the Definitions tab.

ABOUT THE AUTHOR

Kris Halsrud

Kris Halsrud is a Senior Analyst / Developer for Customer FX Corporation.

5 Comments

  1. Hi Kris.
    I follow the steps and I receive this error when I try to Run:
    [QuartzScheduler_QuartzSchedulerThread] ERROR Sage.SalesLogix.Scheduling.JobStore – Error retrieving job, setting trigger state to ERROR.
    Quartz.JobPersistenceException: Couldn’t retrieve job because the BLOB couldn’t be deserialized: Could not load file or assembly ‘test’ or one of its dependencies. The system cannot find the file specified. —>

    Any idea?

    Reply
    • Uriel, the error you posted indicates “system cannot find the file specified” but the rest is cut off. What is the file it is referencing in the error?

  2. Ryan, how are you? Hope very well

    thanks for the quick reply.

    The error referencing my own DLL, in this case for example: AutoAssignWorkflows

    [QuartzScheduler_QuartzSchedulerThread] ERROR Sage.SalesLogix.Scheduling.JobStore – Error retrieving job, setting trigger state to ERROR.
    Quartz.JobPersistenceException: Couldn’t retrieve job because the BLOB couldn’t be deserialized: Could not load file or assembly ‘AutoAssignWorkflows’ or one of its dependencies. The system cannot find the file specified. —> System.IO.FileNotFoundException: Could not load file or assembly ‘AutoAssignWorkflows’ or one of its dependencies. The system cannot find the file specified.-

    Reply
    • Here’s some possibilities of things to look at:

      1) Make sure your DLL is using the correct version of .NET for the version of CRM you’re using

      2) Does your DLL reference other assemblies that are not available or not valid for the context of the job service? Keep in mind the error might not be referring to your DLL specifically, it could be referencing one of the assemblies your DLL is referencing.

      3) Maybe try removing your DLL, ensure all is working properly with the job server, then re-add your DLL.

      4) I’ve posted a document that might shed more light on any possible steps missed: http://customerfx.com/document/an-introduction-to-job-server-and-creating-jobs-in-infor-crm-saleslogix/

      Ryan

  3. Thanks!
    I’m working on it and it seems to worl.
    regards

    Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

Subscribe To Our Newsletter

Join our mailing list to receive the latest Infor CRM (Saleslogix) news and product updates!

You have Successfully Subscribed!