Реализация сервиса Web API

В системе имеется возможность создавать свой собственный веб сервис. Список уже существующих веб сервисов можно увидеть, прописав в строке запроса ~/API/Help и выбрав «Список доступных публичных веб сервисов», либо прописав в строке запроса ~/API/Help/Services

Существует две точки расширения, с помощью которых можно создавать веб сервисы: EleWise.ELMA.Web.Service.IPublicAPIWebService и EleWise.ELMA.Services.Public.IPublicService.

Между этими способами есть разница. Например, первый способ (IPublicAPIWebService) отличается своей простотой реализации. Один интерфейс, его реализация, атрибуты для описания и всё – веб сервис готов. Хорошо подходит для работы с каким-то одним определённым объектом, который не имеет наследников. Методы такого веб сервиса либо возвращают конкретный объект, либо ничего не возвращают. Однако, такой веб сервис нельзя расширить. Только если переписывать исходный код.

Веб сервис, созданный с помощью этой точки расширения, использует стандартную технологию WCF (Windows Communication Foundation). Все классы, которые участвуют в методах надо отметить атрибутом DataContract. Все поля и свойства классов, которые будут участвовать в методах, нужно отметить атрибутом DataMember.

Технология WCF имеет ряд ограничений. Веб сервисы, созданные на этой технологии, не поддерживают перегрузку методов, т.е. методы с одинаковыми именами, но разным набором входящих параметров, недопустимы. Также вы не можете использовать полиморфизм: нельзя определить метод сервиса, который принимает (или возвращает) переменную базового типа и передавать в него (или возвращать из него) экземпляры производных типов. Обойти это ограничение можно указав перечень «известных типов» с помощью атрибута KnownType.

Второй способ (IPublicService) сложнее в реализации, т.к. надо наследоваться от 3 точек расширения. Но в данном случае, веб сервис можно легко расширить сторонним модулем, зная, Uid этого веб сервиса (при переходе в описание веб сервиса его уид можно найти в адресной строке). Хорошо подходит для работы с такими объектами, как Задачи, Документы, Сообщения и т.д. Методы веб сервиса принимают и возвращают тип WebData, который может содержать в себе всё что угодно. Тем самым, один метод получает возможность возвращать один или более параметров. Существует возможность добавить описание к входящим и выходящим параметрам (см. ниже).

Работа с классом WebData

Для удобной работы с WebData нужно подключить библиотеку EleWise.ELMA.SDK и подключить пространство имен EleWise.ELMA.Common.Models.

Для создания веб даты из уже имеющегося или готового объекта можно использовать функции CreateFromObject и CreateFromEntity, причем CreateFromEntity работает только для объектов системы ELMA. Эти методы являются статическими, поэтому вызываются так: WebData.CreateFromEntity() / WebData.CreateFromObject() с передачей соответствующих параметров.

Для поиска элементов по веб дате используются методы FindItem и FindByPath. Функция FindItem возвращает WebDataItem, лучше всего использовать эту функцию для поиска простых значений, таких как строка, число, гуид и т.д. Для поиска целого объекта используют функцию FindByPath (возвращается WebData).

Пример: Загружается задача из веб сервиса. Чтобы получить рабочее место автора задачи, используется такая строка: data.FindByPath(“CreationAuthor.WorkPlace”). Чтобы просто получить автора задачи, используется строка: data.FindByPath(“CreationAuthor”).

Для преобразования веб даты в объект с заполненными полями используется функция SaveToEntity

Пример создания веб сервиса при помощи IPublicAPIWebService

Сервис, созданный при помощи точки расширения IPublicAPIWebService, добавляется в список всех веб сервисов следующим образом:

Ссылка на данный веб сервис сформируется в виде ~/PublicAPI/PublicServiceExample/ExampleWeb. Можно найти данную ссылку, кликнув на «Перейти к описанию WSDL».

Пример класса точки расширения

using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using EleWise.ELMA.ComponentModel;
using EleWise.ELMA.Model.Attributes;
using EleWise.ELMA.Model.Managers;
using EleWise.ELMA.Services.Public;
using EleWise.ELMA.Web.Service;
using PublicServiceExample.Models;

namespace PublicServiceExample.Services
{
    [ServiceContract(Namespace =  APIRouteProvider.ApiServiceNamespaceRoot)]
    [Description("Сервис-пример")]
    [WsdlDocumentation("Сервис-пример")]
    public interface IExampleWebService
    {
        [OperationContract]
        [WebGet(UriTemplate = "/Delete?id={id}")]
        [AuthorizeOperationBehavior]
        [FaultContract(typeof(PublicServiceException))]
        [Description("Удаление объекта IExampleObject")]
        [WsdlDocumentation("Удаление объекта IExampleObject")]
        void Delete([WsdlParamOrReturnDocumentation("Идентификатор удаляемого объекта")] long id);
    }


    /// <summary>
    /// Класс, позволяющий добавить публичный веб сервис уровня модуля
    /// </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 сама по себе пустая, там нет никаких методов или свойств. Она нужна лишь для того, чтобы создаваемый веб сервис добавился в общий список

Алгоритм создания веб сервиса:

  1. Создаем интерфейс (Пример: IExampleWebService), внутри которого объявляются методы, которые будут находится в создаваемом веб сервисе
  2. Создаем класс (Пример: ExampleWebService), который является наследником IPublicAPIWebService и созданного вами интерфейса (в данном случае - IExampleWebService).
  3. Переопределяем методы, объявленные в интерфейсе. Обычно внутри этих методов сразу прописывается код, который должен быть выполнен при их вызове.
  4. Создаем гуид и навешиваем на созданный класс атрибут Uid, в параметры которого передается созданный гуид. ВСЕГДА создавайте новый гуид! Наличие одинаковых гуидов может привести к ошибке при запуске системы.
Важно!
При создании класса и интерфейса нужно обязательно навешивать все атрибуты, которые указаны в примере. 

Более подробно о атрибутах:

  1. Description – описание интерфейса, класса, метода и т.д.
  2. WsdlDocumentation – описание интерфейса, класса, метода и т.д., которое отображается в описании веб сервиса
  3. WsdlParamOrReturnDocumentation – описание для параметров метода и возвращаемого значения. Чтобы добавить описание для возвращаемого параметра, нужно использовать [return: WsdlParamOrReturnDocumentation("Ваше описание")].
  4. ServiceContract – обязательный атрибут для интерфейса. При его отсутствии система просто не запустится.
  5. OperationContract – обязательный атрибут для метода. При его отсутствии при попытке перейти к описанию метода вы получите ошибку.
  6. FaultContract – при какой-либо ошибке при исполнении метода выкидывается тот тип ошибки, который вы передали в аргументы этого атрибута
  7. WebGet – шаблон формируемой ссылки. Отображается здесь:

Пример создания веб сервиса при помощи IPublicService

При создании сервиса одной точки расширения IPublicService недостаточно. Необходимы еще IPublicServiceMethodsProvider, PublicServiceMethodExecutor. Созданный сервис добавляется в список всех веб сервисов следующим образом:

Ссылка на данный веб сервис сформируется в виде ~/API/Example. Можно найти данную ссылку, кликнув на «Перейти к описанию WSDL».

Написание веб сервиса начинается с создания класса, унаследованного от IPublicService.

Пример класса точки расширения IPublicService

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>
    /// Класс, позволяющий добавить описание для публичного сервиса
    /// </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>
        /// Уникальный идентификатор сервиса
        /// </summary>
        public Guid Uid
        {
            get { return UID; }
        }

        /// <summary>
        /// Идентификатор модуля. Можно получить из <see cref="AssemblyModuleAttribute.Uid"/>
        /// </summary>
        public Guid ModuleUid
        {
            get { return __ModuleInfo.UID; }
        }

        /// <summary>
        /// Имя сервиса. 
        /// Должно содержать только английские символы.
        /// Используется как часть пути доступа к сервису
        /// </summary>
        public string Name
        {
            get { return "Example"; }
        }

        /// <summary>
        /// Описание сервиса
        /// </summary>
        public string Description
        {
            get { return SR.T("Сервис-пример для работы с объектом"); }
        }
    }
}

Описание членов созданного класса:

  1. Guid – Уникальный идентификатор сервиса. Пример его использования будет ниже.
  2. ModuleUid – Уникальный идентификатор модуля
  3. Name – Имя сервиса. Должно содержать только английские символы.
  4. Description – Описание сервиса.

Следующим шагом нужно создать методы веб сервиса. Для каждого метода создается свой класс, унаследованный от PublicServiceMethodExecutor.

Пример класса точки расширения PublicServiceMethodExecutor

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 PublicServiceExample.Models;
using EleWise.ELMA.Security.Models;

namespace PublicServiceExample.API
{
    /// <summary>
    /// Класс - исполнитель для метода создания объекта (пример)
    /// </summary>
    public class ExampleObjectMethodCreateExecutor : PublicServiceMethodExecutor
    {
        
        #region Static for Method

        public const string MethodName = "Create";

        public static string MethodDescription
        {
            get { return SR.T("Создать новый объект (пример)"); }
        }

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

        public static string ExmapleItemDescription
        {
            get
            {
                return SR.T("Объект (пример)");
            }
        }

        public static TypeSerializationDescriptor ResultDescriptor
        {
            get
            {
                return new TypeSerializationDescriptorBuilder()
                    .Item(q => q.Name("Result").Descriptor(SR.T("Результат выполнения. True, если выполнение завершено успешно.")))
                    .Item(q => q.Name("Id").Descriptor(SR.T("Идентификатор созданного объекта (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>
        /// Создать объект (пример) <see cref="IExampleObject"/> из данных
        /// </summary>
        /// <param name="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("Невозможно создать объект, т.к. задан идентификатор"));

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

Описание членов созданного класса:

  1. MethodName – Наименование метода.
  2. MethodDescription – Описание метода.
  3. ParameterDescriptor – Описание параметров метода.
  4. ResultDescriptor – Описание свойств возвращаемого объекта WebData.
  5. Execute – Самый главный метод. Именно его код выполняется при вызове описываемого метода.
  6. В остальных методах описывается внутренняя логика вызываемого метода. В данном случае, это создание объекта с помощью ExampleCreateObject и сохранение его в базу с помощью метода DoCreateExampleAction

Созданные методы нужно добавить в веб сервис. Для этого нужно создать класс, унаследованный от IPublicServiceMethodProvider.

Пример класса точки расширения IPublicServiceMethodProvider

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>
    /// Провайдер для публичных методов (пример)
    /// </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
                    }
            };
        }
    }
}

Данная точка расширения содержит лишь один метод – GetMethods(), который возвращает список методов для веб сервиса. Методы добавляются при помощи класса PublicServiceMethod.

Описание членов класса PublicServiceMethod:

  1. ServiceUid – Идентификатор сервиса, к которому относится данный метод.
  2. Name – Имя метода. Должно содержать только английский символы, т.к. используется как часть пути доступа к методу.
  3. Description – Описание метода.
  4. Version – Версия метода.
  5. ParametersDescriptor – Описание данных для входящего параметра.
  6. ResultDescriptor – Описание результата выполнения.

Осталась еще одна точка, которая не нужна для создания веб сервиса, но её работа может быть весьма полезна. Это IPublicServiceEventHandler – обработчик событий, работающий для методов веб сервиса.

Пример класса точки расширения IPublicServiceEventHandler

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 PublicServiceExample.Models;

namespace PublicServiceExample.Services
{
    /// <summary>
    /// Обработчик событий, работающий для методов веб-сервиса
    /// </summary>
    [Component]
    public class ExampleServiceEventHandler : IPublicServiceEventHandler
    {
        /// <summary>
        /// Перед выполнением действия
        /// </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>
        /// После выполнения действия
        /// </summary>
        /// <param name="e"></param>
        public void ActionExecuted(PublicServiceMethodEventArgs e)
        {
            
        }
    }
}

Данный класс имеет всего две функции:

  1. ActionExecuting – действия, необходимые сделать перед выполнением метода
  2. ActionExecuted – действия, необходимые сделать после выполнения метода

Обе функции принимают один входящий параметр – класс PublicServiceMethodEventArgs.

Описание членов класса PublicServiceMethodEventArgs:

  1. ServiceUid – Уникальный идентификатор сервиса, откуда был вызван метод.
  2. MethodName – Имя метода, который был вызван.
  3. MethodVersion – Версия метода, который был вызван.
  4. Parameters – Параметры вызываемого метода.
  5. Result – Результат выполнения вызываемого метода. 

Прикрепленные файлы