Using a .NET Framework Class to Build a Task

The DCS Software Factory provides the WF Task template. This template uses a model that is based on Windows Workflow Foundation (WF). WF lets you model business processes in a natural manner, but sometimes you need to implement tasks by using more traditional, procedural code. For example, you might have existing code in the form of C# classes and methods that is too expensive or too complex to re-implement as a WF workflow.

Dd632328.note(en-us,MSDN.10).gifNote:
You can deploy a service that uses tasks implemented as .NET Framework classes in exactly the same way that you deploy a service that uses tasks implemented by using workflows. For more information, see Deploying DCS Services.

A task is a method in a class that implements the Microsoft.ConnectedIndustry.ProcessExecution.Common.ITask interface and that is tagged with the Microsoft.ConnectedIndustry.ProcessExecution.Common.TaskMethod attribute. ITask is a marker interface that the DCS deployment tools use to identify a class that contains tasks. It does not specify any additional methods or properties. The TaskMethod attribute provides metadata that the DCS runtime uses to identify a method that implements a task.

Dd632328.note(en-us,MSDN.10).gifNote:
The WF Task template in the DCS Software Factory generates two items for a task; a workflow and its associated code file, and an envelope file. You implement the business logic for a task by using the workflow and the code file. The envelope file holds a wrapper class that contains a method that runs the workflow. The wrapper class uses the Microsoft.ConnectedIndustry.ProcessExecution.WorkflowHost.WorkflowHost class to start a new workflow host environment, and then runs the workflow in this host environment. The wrapper class indirectly implements the ITask interface, and the method that runs the workflow is tagged with the TaskMethod attribute.

A task runs when a client application invokes an operation in a service. An operation is simply a logical identifier in a service, and the same operation can be implemented by using different tasks. The DCS runtime determines which task to use based on filters defined and associated with each task. For more information, see Defining a Filter for a Task.

The method that implements a task can take a single parameter that corresponds to the request message sent by a client application, and can optionally return a value that corresponds to the response message sent by the service.

The following procedure shows how to implement a task called GetSunshineHours by using a .NET Framework class. This task returns the number of hours of sunshine reported by a city in the last 24-hour period. The example assumes that you have already used the DCS Software Factory to create a DCS service solution, and that you have used the BLSpecific template to add a project called WeatherService to this solution. The namespace for the classes in the WeatherService project is ContosoWeatherService. For more information about using the DCS Software Factory to create a new solution, see Building a Service by Using a Specific Workflow.

The example also assumes that you have defined the following request, response, error, and exception messages that the task can send and receive:

  • Weather Request Class
  • Weather Response Class
  • Weather Exception and Weather Error Classes

WeatherRequest Class

The WeatherRequest class contains a string that holds the name of a city. The client application creates an instance of this class and populates the CityName property.


                    using System;
using System.ServiceModel;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using Microsoft.ConnectedIndustry.ServiceModel.Common;

namespace ContosoWeatherService
{
    [Serializable]
    [DataContract(Namespace = "http://ContosoWeatherService")]
    public class WeatherRequest : MessageBase
    {
        private string cityName;

        public WeatherRequest() : base()
        {
        }

        [DataMember]
        public string CityName
        {
            get { return cityName; }
            set { cityName = value; }
        }
    }
}

                  

WeatherResponse Class

The WeatherResponse class contains a decimal value that indicates the number of hours of sunshine reported by the city specified in the request message.


                    using System;
using System.ServiceModel;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using Microsoft.ConnectedIndustry.ServiceModel.Common;

namespace ContosoWeatherService
{
    [Serializable]
    [DataContract(Namespace = "http://ContosoWeatherService")]
    public class WeatherResponse : MessageBase
    {
        private decimal weatherData;

        public WeatherResponse() : base()
        {
        }

        [DataMember]
        public decimal WeatherData
        {
            get { return weatherData; }
            set { weatherData = value; }
        }
    }
}

                  

WeatherException and WeatherError Classes

The WeatherException class and WeatherError class are the default exception message and error message classes generated by using the Add Error and Exception Classes recipe of a Messages project built by using the DCS Software Factory.


                    using System;
using System.Runtime.Serialization;
using Microsoft.ConnectedIndustry.ServiceModel.Common;

namespace ContosoWeatherService
{
    [Serializable]
    public class WeatherException : FrameworkException
    {

        /// <summary>
        /// Initializes a new instance of the WeatherExceptionException class.
        /// </summary>
        public WeatherException() : base()
        {
        }

        /// <summary>
        /// Initializes a new instance of the WeatherExceptionException class with a specified error message. 
        /// </summary>
        /// <param name="message">A message that describes the error.</param>
        public WeatherException(string message) : base(message)
        {
        }

        /// <summary>
        /// Initializes a new instance of the WeatherExceptionException class 
        /// with a specified error message and a reference to the inner exception 
        /// that is the cause of this exception.
        /// </summary>
        /// <param name="message">The error message that explains the reason for the exception.</param>
        /// <param name="innerException">The exception that is the cause of the current exception. 
        /// If the innerException parameter is not a null reference, the current exception is raised 
        /// in a catch block that handles the inner exception.</param>
        public WeatherException(string message, Exception innerException) : base(message, innerException)
        {
        }

        /// <summary>
        /// Initializes a new instance of the WeatherExceptionException class with serialized data. 
        /// </summary>
        /// <param name="info">The object that holds the serialized object data.</param>
        /// <param name="context">The contextual information about the source or destination.</param>
        public WeatherException(SerializationInfo info, StreamingContext context) : base(info, context)
        {
        }
    }
}

                  


                    using System;
using System.ServiceModel;
using System.Runtime.Serialization;
using Microsoft.ConnectedIndustry.ServiceModel.Common;

namespace ContosoWeatherService
{
    /// <summary>
    /// This class contains information about an exception
    /// occurred during the execution of a task.
    /// </summary>
    [Serializable]
    [DataContract(Namespace = "http://ContosoWeatherService")]
    public class WeatherError : FrameworkError
    {
        #region Private Constants
        private const string MessageUnknown = "An error occurred during the execution of a  task";

        /// <summary>
        /// Initializes a new instance of the WeatherError class.
        /// </summary>
        public WeatherError() : base()
        {
            // TODO: Type logic here
        }

        /// <summary>
        /// Initializes a new instance of the WeatherError class.
        /// </summary>
        /// <param name="exception">The occurrence of FrameworkException causing the failure.</param>
        public WeatherError(FrameworkException exception) : base(exception)
        {
            // TODO: Type logic here
        }
    }
}

                  

For more information about defining messages, see Defining Request and Response Messages and Defining Exception and Error Classes.


To implement a task by using a .NET Framework class

  1. Add a new folder called GetSunshineHours to the WeatherService project.
  2. Add a new C# class called GetSunshineHoursTask.cs to the GetSunshineHours folder.
  3. Modify the using statements at the top of the GetSunshineHoursTask.cs file to bring the Microsoft.ConnectedIndustry.ProcessExecution.Common namespace into scope, as follows.
  4. 
                          using System;
    using Microsoft.ConnectedIndustry.ProcessExecution.Common;
    
                        
    
  5. Modify the definition of the GetSunshineHoursTask class to implement the ITask interface, as follows.
  6. 
                          namespace ContosoWeatherService.GetSunshineHours
    {
        class GetSunshineHoursTask : ITask
        {
        }
    }
    
                        
    
  7. Add the public method GetSunshineHours to the GetSunshineHoursTask class. This method takes a ContsosoWeatherService.WeatherRequest object as a parameter, and returns a ContsosoWeatherService.WeatherResponse object.
  8. 
                          public ContosoWeatherService.WeatherResponse GetSunshineHours(ContosoWeatherService.WeatherRequest request)
    {
    }
    
                        
    
  9. Dd632328.note(en-us,MSDN.10).gifNote:
    The GetSunshineHours method implements the request/response messaging pattern. To define a task that implements a one-way operation, specify a request message as the method parameter and set the return type of the method to void.

  10. Prefix the GetSunshineHours method with the TaskMethod attribute. This attribute has properties that specify the name and version of the task. You can specify the version as a major and minor version number, or as a Version object.
  11. Dd632328.note(en-us,MSDN.10).gifNote:
    The DCS Management Services console uses the name specified by the TaskMethod attribute to identify the task. The operation contract also refers to the task by this name when you add it to the service contract.

  12. 
                          TaskMethod["ContosoWeatherService.GetSunshineHours", 1, 0)]
    public ContosoWeatherService.WeatherResponse GetSunshineHours(ContosoWeatherService.WeatherRequest request)
    {
    }
    
                        
    
  13. Dd632328.note(en-us,MSDN.10).gifNote:
    Important: If you define multiple tasks with the same scope for the same operation, the behavior of the DCS runtime is unpredictable and unable to guarantee which of these tasks will execute. It is important not to apply duplicate instances of the TaskMethod attribute to methods that implement tasks in the same scope.

  14. In the body of the GetSunshineHours method, add the logic to retrieve the number of hours of sunshine for the city specified in the request message, and return this value in the response message.
  15. 
                          TaskMethod["ContosoWeatherService.GetSunshineHours", 1, 0)]
    public ContosoWeatherService.WeatherResponse GetSunshineHours(ContosoWeatherService.WeatherRequest request)
    {
        try
        {
            string cityName = request.CityName;
            decimal numberOfSunshineHours = 0M;
    
            // add logic to retrieve the number of hours of sunshine for the city
            ...
    
            ContosoWeatherService.WeatherResponse response = new ContosoWeatherService.WeatherResponse();
            response.WeatherData = numberOfSunshineHours;
            return response
        }
        catch (Exception ex)
        {
            ...
        }
    }
    
                        
    
  16. In the exception handler, you can raise instrumentation exception and warning messages by using the static FireExceptionEvent and FireWarningEvent methods of the Microsoft.ConnectedIndustry.ProcessExecution.Common.Instrumentation.InstrumentationProvider class. These methods record events in the trace logs (if tracing is enabled) and raise the CFF_ExceptionEvent a CFF_WarningEvent WMI events.
  17. 
                          ...
    using Microsoft.ConnectedIndustry.ProcessExecution.Common.Instrumentation;
    ...
    TaskMethod["ContosoWeatherService.GetSunshineHours", 1, 0)]
    public ContosoWeatherService.WeatherResponse GetSunshineHours(ContosoWeatherService.WeatherRequest request)
    {
        try
        {
            ...
        }
        catch (Exception ex)
        {
            InstrumentationProvider.FireExceptionEvent("Exception in GetSunshineHours operation", ex);
            return null;
        }
    }
    
                        
    
  18. If your task requires information from the Context object transmitted by the client application, you can obtain this data by using the static Current property of the Microsoft.ConnectedIndustry.ServiceModel.Common.Context class.
  19. 
                          ...
    using Microsoft.ConnectedIndustry.ServiceModel.Common;
    ...
    TaskMethod["ContosoWeatherService.GetSunshineHours", 1, 0)]
    public ContosoWeatherService.WeatherResponse GetSunshineHours(ContosoWeatherService.WeatherRequest request)
    {
        ...
        Context context = Context.Current;
        ...
    }
    
                        
    
  20. Dd632328.note(en-us,MSDN.10).gifNote:
    An operation must enable the DCS context policy to provide to support passing Context data into a task. For more information, see Configuring Context Policy for an Operation.

The following example shows a complete implementation of the GetSunshineHours task for the ContosoWeather service.


                    using System;
using Microsoft.ConnectedIndustry.ProcessExecution.Common;
using Microsoft.ConnectedIndustry.ProcessExecution.Common.Instrumentation;
using Microsoft.ConnectedIndustry.ServiceModel.Common;

namespace ContosoWeatherService.GetSunshineHours
{
    class GetSunshineHoursTask : ITask
    {
        [TaskMethod("ContosoWeatherService.GetSunshineHours", 1, 0)]
        public ContosoWeatherService.WeatherResponse GetSunshineHours(ContosoWeatherService.WeatherRequest request)
        {
            try
            {
                Context context = Context.Current;
                string cityName = request.CityName;

                // add logic to retrieve the number of hours sunshine for the city
                ...

                ContosoWeatherService.WeatherResponse response = new ContosoWeatherService.WeatherResponse();
                response.WeatherData = numberOfSunshineHours;
                return response;
            }
            catch (Exception ex)
            {
                InstrumentationProvider.FireExceptionEvent("Exception in GetSunshineHours operation", ex);
                return null;
            }
        }
    }
}

                  

All DCS services publish a service contract. The service contract defines the operations that a service makes available to client applications and that DCS publishes through the service proxy. Each operation is defined by using an operation contract. The DCS Software Factory uses WCF attributes to generate the service contract and add operation contracts for each operation. If you add a task to an existing operation, you can use the existing operation contract. However, if you are using a .NET Framework class to implement a task for a new operation, then you must add the operation contract to the service contract.

In a DCS service generated by using the DCS Software Factory, the service contract is defined in a C# file that is named after the project that contains the service. For example, if you create a DCS service in a project called WeatherService, the service contract is defined in a file called WeatherService.cs in that project.

The next example shows the WeatherService.cs file with the service contract for the WeatherService service, and the operation contract for the GetSunshineHours operation. The key elements in the operation contract are:

  • The OperationContract attribute, which specifies the SOAP action and reply action for the operation. These are strings that indicate the purpose of the operation. It is good practice to specify the action as a combination of the HTTP namespace of the service and the name of the operation, and the reply action should have the suffix response. For more information on the OperationContract attribute, see OperationContractAttribute Class.
  • The FaultContract attribute, which you use to indicate the types of exceptions that your operation can throw. For more information on the FaultContract attribute, see FaultContractAttribute Class.
  • The TaskFactoryServiceName attribute, which specifies the type of the task that the DCS service factory should use to determine which task to run to perform the operation. The argument that you specify for this attribute must match the value provided in the TaskMethod attribute when you defined the task method.
  • The operation definition, which specifies the name of the method that the DCS runtime calls when it receives a SOAP request that matches the action specified in the OperationContract attribute.
  • 
                          namespace ContosoWeatherService
    {
        [ServiceContract(Name="WeatherService", Namespace="http://ContosoWeatherService")]
        public interface IWeatherService : IService
        {
            ...
    
            [OperationContract(Action = "ContosoWeatherService.GetSunshineHours", ReplyAction = "ContosoWeatherService3.GetSunshineHoursResponse")]
            [FaultContract(typeof(ContosoWeatherService.WeatherError))]
            [FaultContract(typeof(FrameworkError))]
            [TaskFactoryServiceNameAttribute("ContosoWeatherService3.GetSunshineHours")]
            ContosoWeatherService.WeatherResponse GetSunshineHours(ContosoWeatherService.WeatherRequest request);
            ...
        }
    
                        
    
Dd632328.note(en-us,MSDN.10).gifNote:
The ProxyBuilder utility also uses the information specified in the service contract to generate the proxy class that client applications use to connect to the service.

The operation contract is an interface that specifies the capabilities of the service. You must provide a method that implements each of the operations defined in the service contract. However, if you are adding a task to an existing operation, you do not need to modify the code generated by the DCS Software Factory.

Dd632328.note(en-us,MSDN.10).gifNote:
The DCS Software Factory adds a class that implements the service contract in the same file as the service contract. Add your method to this class.

An operation can be implemented by one or more tasks. The DCS runtime decides which task to run based on information provided in the Context object transmitted from the client application. For more information, see Task Filters.

The DCS runtime supplies the TaskFactory generic class. This class examines the task configurations in the DCS configuration database, and then invokes the task that matches the task filters. (Administrators use the DCS Management Services console to define filters.) If you use a .NET Framework class to implement a task for a new operation, you should use the TaskFactory class in the method that defines your operation. You should also be prepared to catch any exceptions that your task might throw, and then pass them back to the client application by using the fault as specified by the fault contract for the operation. The following code is an example of the TaskFactory class with exception handling.


                    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
public class WeatherService : IWeatherService
{
    ...
    [OperationBehavior]
    public ContosoWeatherService.WeatherResponse GetSunshineHours(
        ContosoWeatherService.WeatherRequest request)
    {
        try
        {
            return new TaskFactory<ContosoWeatherService.WeatherRequest, ContosoWeatherService.WeatherResponse>().Invoke(OperationContext.Current.IncomingMessageHeaders.Action, request);
        }
        catch (ContosoWeatherService.WeatherException ex)
        {
            Tracer.WriteException(TraceEventType.Error, "GetTemperature", ex, Microsoft.ConnectedIndustry.ServiceModel.Common.Context.Current);
            throw new FaultException<ContosoWeatherService.WeatherError>(new ContosoWeatherService.WeatherError(ex), ErrorMessage);
        }
        catch (FrameworkException ex)
        {
            Tracer.WriteException(TraceEventType.Error, "GetTemperature", ex, Microsoft.ConnectedIndustry.ServiceModel.Common.Context.Current);
            throw new FaultException<FrameworkError>(new FrameworkError(ex), ErrorMessage);
        }
        catch (Exception ex)
        {
            Tracer.WriteException(TraceEventType.Error, "GetTemperature", ex, Microsoft.ConnectedIndustry.ServiceModel.Common.Context.Current);
            throw new FaultException<FrameworkError>(new FrameworkError(ex), ErrorMessage);
        }
    }
    ...
}

                  

The type parameters in the TaskFactory class are the types of the request and response messages. The Invoke operation expects a string that specifies the SOAP action of the operation (which should match the value of the Action argument of the OperationContract attribute for the operation), and the request message. The SOAP action is available in the SOAP header of the incoming request message. You can access the incoming request message header by using the static Current property of the OperationContext class. For more information, see OperationContext Class.

Show: