четверг, 10 апреля 2014 г.

Restful API

Точное определение restful web service можно найти по первой ссылке в Google, тут я расскажу про задачи, которые могут быть решены с использованием таких сервисов, а также покажу реализацию 4-х методов, которые должны быть в restful  сервисах (Get, Post, Put, Delete).
Итак, задача: у нас есть web приложение, которое отвечает за управление группировкой космических аппаратов, но кроме этого приложения существуют еще сторонние программы, которым необходим доступ к начальным условиям. Для решения этой задачи был сделан api controller, который реализовывал все необходимые операции. Контроллер возвращает либо JSON либо XML, в зависимости от заголовка запроса, но на реализации это никак не сказывается.

    [Authorize]
    public class NuController : ApiController
    {

        private readonly IMapper _nuMapper = new NuMapper();

        [Inject]
        public IUnitOfWork UnitOfWork { get; set; }


        // GET api/Nu
        public IEnumerable<NuViewModel> GetNUs()
        {
            var nus = UnitOfWork.NuRepository.Get();
            var nuViewModels = nus.Select(nu => 
            (NuViewModel)_nuMapper.Map(nu, typeof(NU), 
            typeof(NuViewModel))).ToList();
            return nuViewModels;
        }

        //// GET api/Nu/5
        public NuViewModel GetNu(int id)
        {
            var nu = UnitOfWork.NuRepository.GetById(id);
            if (nu == null)
            {
                throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
            }
            var nuViewModel = (NuViewModel)_nuMapper.Map(nu, typeof (NU), typeof (NuViewModel));
            return nuViewModel;
        }

        //// GET api/Nu/GetSpacecrartNu/3
        public IEnumerable<NuViewModel> GetSpacecraftNu(int id)
        {
            var nus = UnitOfWork.NuRepository.Get().Where(p => p.SpacecraftInitialData_ID == id);
            var nuViewModels = nus.Select(nu => 
            (NuViewModel)_nuMapper.Map(nu,typeof(NU),
            typeof(NuViewModel))).ToList();
            return nuViewModels;
        }

        //// PUT api/Nu/5
        [Authorize(Roles = "admin")]
        public HttpResponseMessage PutNu(int id, NuViewModel nuVm)
        {
            if (!ModelState.IsValid || nuVm == null)
            {
                return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Bad request");
            }

            if (id != nuVm.ID_NU)
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }

            var nu = (NU)_nuMapper.Map(nuVm, typeof (NuViewModel), typeof (NU));
            UnitOfWork.NuRepository.Update(nu);

            try
            {
                UnitOfWork.Save();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
            }

            return Request.CreateResponse(HttpStatusCode.OK);
        }

        //// POST api/Nu
        [Authorize(Roles = "admin")]
        public HttpResponseMessage PostNu(NuViewModel nuVm)
        {
            if (ModelState.IsValid && nuVm != null)
            {
                var nu = (NU)_nuMapper.Map(nuVm, typeof(NuViewModel), typeof(NU));
                UnitOfWork.NuRepository.Insert(nu);
                UnitOfWork.Save();

                HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Created, nu);
                var link = Url.Link("DefaultApi", new {id = nu.ID_NU});
                if (link != null)
                {
                    response.Headers.Location = new Uri(link);
                }
                return response;
            }
            return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Bad request");
        }

        //// DELETE api/Nu/5
        [Authorize(Roles = "admin")]
        public HttpResponseMessage DeleteNu(int id)
        {
            var nu = UnitOfWork.NuRepository.GetById(id);
            if (nu == null)
            {
                return Request.CreateResponse(HttpStatusCode.NotFound);
            }

            UnitOfWork.NuRepository.Delete(nu);

            try
            {
                UnitOfWork.Save();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
            }

            return Request.CreateResponse(HttpStatusCode.OK, nu);
        }

    }
Тут есть несколько особенностей: во-первых: Ninject из коробки не работает с api контроллером, так как api контроллер и обычный контроллер создаются по-разному. Для решения этой проблемы были написаны два класса: NinjectScope:
 
public class NinjectScope : IDependencyScope
    {
        protected IResolutionRoot resolutionRoot;

        public NinjectScope(IResolutionRoot kernel)
        {
            resolutionRoot = kernel;
        }


        public object GetService(Type serviceType)
        {
            IRequest request = resolutionRoot.CreateRequest(serviceType, null,
            new Parameter[0], true, true);
            return resolutionRoot.Resolve(request).SingleOrDefault();
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            IRequest request = resolutionRoot.CreateRequest(serviceType, null,
            new Parameter[0], true, true);
            return resolutionRoot.Resolve(request).ToList();
        }

        public void Dispose()
        {
            var disposable = (IDisposable) resolutionRoot;
            if (disposable != null)
            {
                disposable.Dispose();
            }
            resolutionRoot = null;
        }

    }
И NinjectResolver:
public class NinjectResolver : NinjectScope, IDependencyResolver
    {
        private readonly IKernel _kernel;
        public NinjectResolver(IKernel kernel) : base(kernel)
        {
            _kernel = kernel;
        }

        public IDependencyScope BeginScope()
        {
            return new NinjectScope(_kernel.BeginBlock());
        }
    }
После этого дописываем строчку в NingectWebCommon.cs в метод:
private static IKernel CreateKernel(){
GlobalConfiguration.Configuration.DependencyResolver = new NinjectResolver(kernel);
}
И все работает. Во-вторых: роутинг к api контроллеру не поддерживает namespace, поэтому сделать контроллер в какой либо области приложения не получится и во избежание ошибок в Global.asax необходимо, чтобы строка WebApiConfig.Register(GlobalConfiguration.Configuration); была выше строк с регистрацией областей (Admin, Default). По умолчанию в аpi контроллере всего 4 action-a, для добавления нового action в контроллер необходимо добавить роут:
config.Routes.MapHttpRoute("NuApi",
                "api/{controller}/{action}/{id}",
                new {id = RouteParameter.Optional},
                new {controller = "Nu"}
                );
Доступ к нему осуществляется по полному адресу.
Тестирование и доступ: Юнит-тесты api контроллера практически не отличаются от тестирования обычного:
private NuController _nuController;

        [SetUp]
        public void Setup()
        {
            _nuController = DependencyResolver.Current.GetService<NuController>();
        }

        [Test]
        public void GetNus_Execute_ResultIsListNuVieModel()
        {
            var result = _nuController.GetNUs();
            Assert.That(result, Is.InstanceOf<List<NuViewModel>>());
        }
Так же запросы методами put и delete можно проверить программой Fiddler.


Через Html – helpers форму к Api контроллеру можно отправить следующим образом:
@using (@Html.BeginRouteForm("NuAp", new { controller = "Spacecraft", httproute = "true" },
 FormMethod.Post, new { @role = "form" }))
где DefaultApi – имя роута.
Отправка форм с помощью Ajax (JQuery).
Метод post: сначала необходимо изменить стандартное поведение при нажатии кнопки submit (unbind или off), затем в метод $.post передаем сериализованные данные формы:
this.formSubmitt = function onSubmit() {
        $('#form').unbind('submit').bind('submit', function () {
            $.post(this.action, $('#form').serialize())
                .success(function (result) {
                    $('#message').html(result.Message);
                    $('#successResult').show();
                }).error(function() {
                    $('#errorResult').show();
                });
            return false;
        });
    };
Put и delete отличаются только методами, вместо $.post необходимо писать полный ajax запрос:
 $.ajax({
url: api/Nu/5,
type: put,
data: $('#form').serialize(),
success: function (result) {
                    $('#message').html(result.Message);
                    $('#successResult').show();
}
}) 

Комментариев нет:

Отправить комментарий