Adding custom Web API service

The system allows you to create your own web service. To find a list of all the existing web services, go to Web API help, by adding ~/API/Help to the URL of ELMA web application – go to List of available public services. You can also see Web API help in ELMA online demo:

 

You can create web services using these two extension points: EleWise.ELMA.Web.Service.IPublicAPIWebService and EleWise.ELMA.Services.Public.IPublicService.

 There is a difference between them:

  1. IPublicAPIWebService is rather simple to implement; all you need to create a web service is one interface, its implementation and description attributes. Such web services are good for working with one specific object that has no heirs. Methods of these web services either return a certain object or return nothing at all. However, you cannot extend this kind of web services (the only way to do that would be modifying the source code).

 A web service created with this extension point applies the standard WCF (Windows Communication Foundation) technology. All the classes that participate in methods must be marked with the DataContract attribute. All  class properties that will participate in methods must be marked with the DataMember attribute.

 The WCF technology has certain limitations. Web services that are based on it do not support method overloading (methods with the same names and different input parameters are not allowed). In addition to that, you cannot use polymorphism: you cannot define a service’s method that would accept (or return) both the base type variable and the variables, derived from it. You can solve this problem by defining a list of known types using the KnownType attribute. 

  1. Implementation of IPublicService is more complicated because you must implement three extension points. These web services are extensible and are good for working with such objects as Tasks, Documents, Messages and so on. The web service methods receive and return the WebData type, which can contain any information. Thus, a single method can return one or more parameters. You can also add a description to input and output parameters of methods (see below).

Using WebData class

To work with WebData, add references to the EleWise.ELMA.SDK assembly and use the EleWise.ELMA.Common.Models namespace.

To create WebData from an existing object you can use the CreateFromObject and CreateFromEntity methods. CreateFromEntity can be used for ELMA objects only. Both these methods are static and should be called in the following way: WebData.CreateFromEntity() / WebData.CreateFromObject(). You will also need to pass the respective parameters to these methods.

To search for items in WebData use the FindItem and FindByPath methods. The FindItem method returns WebDataItem. It is best to use this function when searching for items as a string, number, GUID etc. When searching for objects, use the FindByPath function (returns WebData).

Example: You load a task using a web service. To get the creation author´s workplace, you can use the following expression: data.FindByPath(“CreationAuthor.WorkPlace”). To get the task author, you can use this one: data.FindByPath(“CreationAuthor”).

To convert WebData into an object, use the SaveToEntity method.

Example of creating a web service with IPublicAPIWebService

A service created using the IPublicAPIWebService extension point is added to the list of all web services in the following way:

The link to this web service is formed in the following way: ~/PublicAPI/PublicAPIWebService/ExampleWeb.

Extension point implementation

namespace PublicServiceExample.Services
{
    [ServiceContract(Namespace = APIRouteProvider.ApiServiceNamespaceRoot)]
    [Description("Sample service")]
    [WsdlDocumentation("Sample service")]
    public interface IExampleWebService
    {
        [OperationContract]
        [WebGet(UriTemplate = "/Delete?id={id}")]
        [AuthorizeOperationBehavior]
        [FaultContract(typeof(PublicServiceException))]
        [Description("Delete IExampleObject")]
        [WsdlDocumentation("Delete IExampleObject")]
        void Delete ([WsdlParamOrReturnDocumentation("ID of deletable object")] long id);
    }


    /// <summary>
    /// CLass that allows adding public web service (module level)
    /// </summary>
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, MaxItemsInObjectGraph = int.MaxValue)]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
    [ServiceKnownType("GetEntityKnownTypes", typeof(ServiceKnownTypeHelper))]
    [Component]
    [Uid(GuidS)]
    public class ExampleWebService : IExampleWebService, IPublicAPIWebService
    {
        public const string GuidS = "CABC668A-2E63-4DE2-B183-F1E7F421F98F";

        public void Delete (string id)
        {
            long objectId;
            if (Int64.TryParse(id, out objectId))
            {
                var example = EntityManager<IExampleObject, long>.Instance.Load(objectId);
                example.Delete();
            }
        }

        public void Delete (long id)
        {
            var example = EntityManager<IExampleObject, long>.Instance.Load(id);
            example.Delete();
        }
    }
}

IPublicAPIWebService extension point has no methods or properties. Its purpose is to add the created web service to the general list of services.

 Web service creation algorithm:

  1. Create an interface (for example, IExampleWebService). In this interface, you define the methods for the created web service.
  2. Create a class (for example, ExampleWebService) and inherit it from IPublicAPIWebServiceand from the created interface (in our case, IExampleWebService).
  3. Override the methods declared in the interface.
  4. Create new GUID and add the Uid attribute to the class that you previously created. The Guid is passed to the parameters of the Uid ALWAYS create a new GUID! Having the same GUIDs can cause an error when the system is started. 
Important

When you create the class and the interface, always add all the attributes listed in our example. 

 More about attributes:

  1. Description– description of an interface, class, method etc.
  2. WsdlDocumentation– description of an interface, class, method, etc. displayed in the description of the web service
  3. WsdlParamOrReturnDocumentation– description of the method parameters and returned value. To add a description for a returned value, use [return:WsdlParamOrReturnDocumentation("Your description")].

  1. ServiceContract– a required interface attribute. The system will not run without it.
  2. OperationContract– a required method attribute. You will not be able to access the method description without it.
  3. FaultContract– if an error occurs when executing the method, the error type that you had passed to the arguments of this attribute is shown.
  4. WebGet– link template. It is displayed here:

Example of creating a web service using IPublicService

To create a web service, you will need to implement IPublicService, IPublicServiceMethodsProvider and PublicServiceMethodExecutor extension points. After that, you will see the created service among other services:

The link to this web service is formed in the following way: ~/API/Example.

You should start creating the web service with the implementation of IPublicService extension point.

Implementation of IPublicService extension point

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EleWise.ELMA;
using EleWise.ELMA.ComponentModel;
using EleWise.ELMA.Modules.Attributes;
using EleWise.ELMA.Services.Public;

namespace PublicServiceExample.Services
{
    /// <summary>
    /// This class allows adding descriptino for a public service
    /// </summary>
    [Component]
    public class ExamplePublicService : IPublicService
    {
        public static Guid UID = new Guid(UID_S);
        public const string UID_S = "54204971-105E-4281-9B98-5EFC3FB7F09F";

        /// <summary>
        /// Service GUID
        /// </summary>
        public Guid Uid
        {
            get
            {
                return UID;
            }
        }

        /// <summary>
        /// Module ID. You can get it from <see cref="AssemblyModuleAttribute.Uid"/>
        /// </summary>
        public Guid ModuleUid
        {
            get
            {
                return __ModuleInfo.UID;
            }
        }

        /// <summary>
        /// Service name
        /// Must consists of latin letters only
        /// It is used as part of service URL
        /// </summary>
        public string Name
        {
            get
            {
                return "Example";
            }
        }

        /// <summary>
        /// Service description
        /// </summary>
        public string Description
        {
            get
            {
                return SR.T("Sample service for working with the object");
            }
        }
    }
}

Description of the members of the created class:

  1. Guid– Service’s unique ID. Below you will find an example of how it is used.
  2. ModuleUid– Module’s unique ID.
  3. Name– Service name. Must contain Latin symbols only.
  4. Description– Service description.

 

For the next step, we need to create the web service’s methods. For each method, we must create a class implementation of PublicServiceMethodExecutor extension point. In our module, we will put these classes to the API folder.

Implementation of PublicServiceMethodExecutor extension point

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EleWise.ELMA;
using EleWise.ELMA.Common;
using EleWise.ELMA.Common.Models;
using EleWise.ELMA.Runtime.NH;
using EleWise.ELMA.Security.Services;
using EleWise.ELMA.Services;
using EleWise.ELMA.Services.Public;
using PublicService.Models;
using EleWise.ELMA.Security.Models;

namespace PublicServiceExample.API
{
    /// <summary>
    /// Class-implementation of the method that creates new objects (sample)
    /// </summary>
    public class ExampleObjectMethodCreateExecutor : PublicServiceMethodExecutor
    {

        #region Static for Method

        public const string MethodName = "Create";

        public static string MethodDescription
        {
            get
            {
                return SR.T("Create new object (sample)");
            }
        }

        public static TypeSerializationDescriptor ParametersDescriptor
        {
            get
            {
                return new TypeSerializationDescriptorBuilder()
                    .Description(ExmapleItemDescription)
                    .Descriptor;
            }
        }

        public static string ExmapleItemDescription
        {
            get
            {
                return SR.T("Object (sample)");
            }
        }

        public static TypeSerializationDescriptor ResultDescriptor
        {
            get
            {
                return new TypeSerializationDescriptorBuilder()
                    .Item(q => q.Name("Result").Descriptor(SR.T("Executon result. True, if completed successfully")))
                    .Item(q => q.Name("Id").Descriptor(SR.T("ID of created object (Int64)")))
                    .Descriptor;
            }
        }

        #endregion

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="parameters"></param>
        public ExampleObjectMethodCreateExecutor (WebData parameters)
            : base(parameters)
        {
        }

        protected virtual WebData GetResult (long id)
        {
            return WebData.CreateFromObject(new
            {
                Result = true,
                Id = id
            });
        }

        protected virtual WebData GetError ()
        {
            return WebData.CreateFromObject(new
            {
                Result = false
            });
        }

        public override WebData Execute ()
        {
            return ExecuteInternal();
        }

        protected virtual WebData ExecuteInternal ()
        {
            if (Parameters == null || Parameters.Items == null)
                return GetError();

            var example = CreateExampleObject(Parameters);

            if (example == null)
                return GetError();

            DoCreateExampleAction(example);

            return GetResult(example.Id);
        }

        /// <summary>
        /// Create object (sample) <see cref="IExampleObject"/> from WebData
        /// </summary>
        /// <param name="webData">WebData</param>
        /// <returns></returns>
        public static IExampleObject CreateExampleObject (WebData webData)
        {
            if (webData == null)
                return null;
            var example = new EleWise.ELMA.Serialization.EntityJsonSerializer().ConvertFromSerializable<IExampleObject>(webData.ToDictionary());
            return example;
        }

        protected static IUnitOfWorkManager UnitOfWorkManager
        {
            get
            {
                return Locator.GetServiceNotNull<IUnitOfWorkManager>();
            }
        }

        private static void DoCreateExampleAction (IExampleObject example)
        {
            if (!example.IsNew())
                throw new InvalidOperationException(SR.T("Cannot creathe the object, because object with this ID already exists"));

            using (var unitOfWork = UnitOfWorkManager.Create(String.Empty, true))
            {
                try
                {
                    example.Save();
                }
                catch (Exception)
                {
                    unitOfWork.Rollback();
                    throw;
                }
                unitOfWork.Commit();
            }
        }
    }
}

Description of the members of the created class:

  1. MethodName– Method’s name.
  2. MethodDescription– Method’s description.
  3. ParameterDescriptor– Description of method’s parameters.
  4. ResultDescriptor – Descriptions of properties of the returned object. This object has WebData format.

5. Execute– The main method; its code is executed when the described method is called.

6.The rest of the methods describe the internal logic of the called method. In this case, it is creating an object with CreateExampleObjectmethod and saving it to the database using the DoCreateExampleAction

Next, we need to add the methods that we created, to the web service. To do that, we need to implement the IPublicServiceMethodProvider extension point.

Implementation of IPublicServiceMethodProvider extension point

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EleWise.ELMA.ComponentModel;
using EleWise.ELMA.Services.Public;
using PublicServiceExample.API;

namespace PublicServiceExample.Services
{
    /// <summary>
    /// Provider of public methods (sample)
    /// </summary>
    [Component]
    public class ExampleServiceMethodsProvider : IPublicServiceMethodsProvider
    {
        public IEnumerable<IPublicServiceMethod> GetMethods ()
        {
            return new IPublicServiceMethod[]
            {
                //Create
                new PublicServiceMethod(
                    ExamplePublicService.UID,
                    ExampleObjectMethodCreateExecutor.MethodName,
                    ExampleObjectMethodCreateExecutor.MethodDescription,
                    new Version(1,0,0),
                    data => new ExampleObjectMethodCreateExecutor(data))
                    {
                        ParametersDescriptor = ExampleObjectMethodCreateExecutor.ParametersDescriptor,
                        ResultDescriptor = ExampleObjectMethodCreateExecutor.ResultDescriptor
                    }
            };
        }
    }
}

This extension point contains only one method – GetMethods(), that returns a list of methods of the web service. You can define methods using the PublicServiceMethod class.

Description of the PublicServiceMethod class members:

  1. ServiceUid– ID of the method’s web service.
  2. Name– Method name. Must contain only Latin symbols because it is used as part of the method’s URL.
  3. Description– Method description.
  4. Version– Method version.
  5. ParametersDescriptor– Description of method’s input parameter.
  6. ResultDescriptor– Description of execution results.

There is one more extension point, IPublicServiceEventHandler. It is not required for creation of a web service but can be rather useful. It is an event handler working for the web service methods.

Lastly, we need to create the handler of the web service’s methods. To do that, we need to implement the IPublicServiceEventHandler extension point.

Implementation of IPublicServiceEventHandler extension point

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EleWise.ELMA.ComponentModel;
using EleWise.ELMA.Model.Managers;
using EleWise.ELMA.Services.Public;
using PublicService.Models;

namespace PublicServiceExample.Services
{
    /// <summary>
    /// Event handler for web service's methods
    /// </summary>
    [Component]
    public class ExampleServiceEventHandler : IPublicServiceEventHandler
    {
        /// <summary>
        /// Before completing an action
        /// </summary>
        /// <param name="e"></param>
        public void ActionExecuting (PublicServiceMethodEventArgs e)
        {
            if (e.ServiceUid == ExamplePublicService.UID && e.MethodName == "Create")
            {
                var example = EntityManager<IExampleObject, long>.Instance.Load(2);
                example.Delete();
            }
        }

        /// <summary>
        /// After the action is completed
        /// </summary>
        /// <param name="e"></param>
        public void ActionExecuted (PublicServiceMethodEventArgs e)
        {

        }
    }
}

This class has 2 methods: 

  1. ActionExecuting– actions that must be done before executing the method
  2. ActionExecuted– actions that must be done after executing the method

 Both methods have the same input parameter – the PublicServiceMethodEventArgs class.

 Description of the PublicServiceMethodEventArgs class members: 

  1. ServiceUid– UID of the method’s web service.
  2. MethodName– Name of the method called.
  3. MethodVersion– Version of the method called.
  4. Parameters– Parameters of the method called.
  5. Result – Execution results of the called method.