zoukankan      html  css  js  c++  java
  • 使用ASP.NET Web Api构建基于REST风格的服务实战系列教程【四】——实现模型工厂,依赖注入以及格式配置

    系列导航地址http://www.cnblogs.com/fzrain/p/3490137.html

    前言

    在上一篇中,我们已经初步开始使用Web Api了,但同时出现了一些很多不足之处,本章我们就着重来解决这些不足。

    上篇导航:http://www.cnblogs.com/fzrain/p/3510035.html

    配置JSON的格式

    Web Api提供Xml和JSON作为返回数据的格式,框架会自动把这些格式注入管线。客户端可以通过Http请求头部来声明需要的数据格式,我们可以通过在“WebApiConfig”这个类来配置JSON数据的格式:

    public static class WebApiConfig
       {
            public static void Register(HttpConfiguration config)
            {
            var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();   
         jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
       }
        }

    首先根据HttpConfiguration对象获得jsonFormatter对象,然后设置ContractResolver属性。那么以后当我们使用JSON数据格式的时候就是“Camel”风格的了。

    用Ninject实现依赖注入

    如果读者是第一次接触依赖注入这个概念的话,可以参考:http://www.cnblogs.com/xray2005/archive/2009/07/28/1532908.html

    OK,接下来我们就来实现依赖注入,在Controller文件夹中创建一个类“BaseApiController”继承自“APIController”。由于我们打算使用构造函数注入模式,因此它的构造函数接受一个ILearning类型的变量,下面上代码:

    public class BaseApiController : ApiController
        {
            private ILearningRepository _repo;
     
            public BaseApiController(ILearningRepository repo)
            {
                _repo = repo;
            }
     
            protected ILearningRepository TheRepository
            {
                get
                {
                    return _repo;
                }
            }
        }

    将我们的“CoursesController”继承自“BaseApiController”,接下来就是使用Ninject框架来建立2者之间的关联:

    首先使用NuGet来添加3个程序集:

    • Ninject
    • Ninject.Web.Common
    • WebApiContrib.IoC.Ninject

    添加好上述引用后,在APP_Start文件夹下就会出现一个类“NinjectWebCommon”,这个类就是在我们项目中配置依赖关系的。在之前的系列中,我们创建了“LearningRepository”,在它的构造函数中需要接受一个LearningContext对象(前篇导航:http://www.cnblogs.com/fzrain/p/3503952.html),因此我们也将这个依赖关系配置进来:

    public static class NinjectWebCommon
        {
     
            private static IKernel CreateKernel()
            {
                var kernel = new StandardKernel();
                kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
                kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
     
                //Suport WebAPI Injection
                GlobalConfiguration.Configuration.DependencyResolver = new WebApiContrib.IoC.Ninject.NinjectResolver(kernel);
     
                RegisterServices(kernel);
                return kernel;
            }
     
            private static void RegisterServices(IKernel kernel)
            {
                kernel.Bind<LearningContext>().To<LearningContext>().InRequestScope();
                kernel.Bind<ILearningRepository>().To<LearningRepository>().InRequestScope();
            }
        }

    我们使用了Ninject配置了Learningcontext对象,使得在http请求范围共用一个context对象,这么做对于创建复杂对象是非常好的。关于Ninject对象范围,可以参考:http://music.573114.com/Blog/Html/EB43/815024.html

    实现模型工厂模式

    模型工厂帮助我们创建需要响应给客户端的模型,因此我们将创建一些区别于领域模型(domain model)的新模型,新模型将与领域模型映射。例如:“Course”将映射到”courseModel”,”Tutor”将映射到“TutorModel“。同时应当考虑对象间的依赖关系。

    为了实现这个功能,我们在”Model”文件夹中创建这几个类”SubjectModel“,”TutorModel“,”CourseModel“,”EnrollmentModel“,这些类就是一些简单的”POCO”类,用来响应给客户端的,下面上代码:

    public class SubjectModel
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
     
    public class TutorModel
    {
        public int Id { get; set; }
        public string Email { get; set; }
        public string UserName { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Data.Enums.Gender Gender { get; set; }
     
    }
     
    public class CourseModel
    {
        public int Id { get; set; }
        public string Url { get; set; }
        public string Name { get; set; }
        public double Duration { get; set; }
        public string Description { get; set; }
        public TutorModel Tutor { get; set; }
        public SubjectModel Subject { get; set; }
     
    }
     
    public class EnrollmentModel
    {
        public DateTime EnrollmentDate { get; set; }
        public CourseModel Course { get; set; }
    }

    有了这些响应给客户端的类,我们还需要一个创建这些类对象的工厂——”ModelFactory“:

    public class ModelFactory
        {
            public ModelFactory()
            {
     
            }
     
            public CourseModel Create(Course course)
            {
                return new CourseModel()
                {
                    Id = course.Id,
                    Name = course.Name,
                    Duration = course.Duration,
                    Description = course.Description,
                    Tutor = Create(course.CourseTutor),
                    Subject = Create(course.CourseSubject)
                };
            }
     
            public TutorModel Create(Tutor tutor)
            {
                return new TutorModel()
                {
                    Id = tutor.Id,
                    Email = tutor.Email,
                    UserName = tutor.UserName,
                    FirstName = tutor.FirstName,
                    LastName = tutor.LastName,
                    Gender = tutor.Gender
                };
            }
     
            public SubjectModel Create(Subject subject)
            {
                return new SubjectModel()
                {
                    Id = subject.Id,
                    Name = subject.Name
                };
            }
     
            public EnrollmentModel Create(Enrollment enrollment)
            {
                return new EnrollmentModel()
                {
                    EnrollmentDate = enrollment.EnrollmentDate,
                    Course =  Create(enrollment.Course)
                };
            }
        }

    我们做的很简单,重载了Create方法,传入领域模型即可创建我们响应给客户端的模型,在这里我们可以很轻易的控制对象间的依赖关系(CourseModel引用TutorModel,CourseModel引用SubjectModel)

    到此为止我们已经解决了2个瑕疵:

       (1)对象间的循环依赖

       (2)控制了返回客户端的字段(Password不会响应给客户端了)

    由于我们可能要在各个Controller中使用到ModelFactory对象,因此我们在BaseController中添加如下代码:

    public class BaseApiController : ApiController
        {
             private ModelFactory _modelFactory;
     
             protected ModelFactory TheModelFactory
            {
                get
                {
                    if (_modelFactory == null)
                    {
                        _modelFactory = new ModelFactory();
                    }
                    return _modelFactory;
                }
            }
        }

    在介绍”CoursesController”的变化之前,我们先解决一下之前提到的2个问题:

       (1)对于每个资源返回一个URI

       (2)对于单个资源返回一个Http响应码

    为每个资源添加URI:

    做法不复杂因为我们已经创建了模型工厂,举个简单的例子——如果我们要返回一个URI,要通过一下步骤:

       1.给ModelFactory的构造函数传入一个”HttpRequestMessage“对象来创建”System.Web.Http.Routing.UrlHelper“对象,它会根据我们在WebApiConfig中配置的路由名字来构造URI

       2.在”BaseApiController“中的”ModelFactory“构造函数中传入”System.Web.Http.Routing.UrlHelper“对象

       3.在”CourseModel”中新增一个属性”URL“

    public class ModelFactory
        {
            private System.Web.Http.Routing.UrlHelper _UrlHelper;
     
            public ModelFactory(HttpRequestMessage request)
            {
                _UrlHelper = new System.Web.Http.Routing.UrlHelper(request);
            }
        }
    public class BaseApiController : ApiController
        {
             private ModelFactory _modelFactory;
     
             protected ModelFactory TheModelFactory
            {
                get
                {
                    if (_modelFactory == null)
                    {
                        _modelFactory = new ModelFactory(Request);
                    }
                    return _modelFactory;
                }
            }
        }
    class ModelFactory
        {
            public CourseModel Create(Course course)
            {
                return new CourseModel()
                {
                    Url = _UrlHelper.Link(“Courses”, new { id = course.Id }),
                    Id = course.Id,
                    /*Other CourseModel properties remain the same*/
                };
            }

    关于模型工厂的更多介绍,可以参考:http://pluralsight.com/training/courses/TableOfContents?courseName=implementing-restful-aspdotnet-web-api(英文的,而且收费,唉。。 亚历山大)

    为单个资源返回Http状态码:

    Web Api框架中有一个”HttpResponseMessage“类可以用来返回Http状态码。有的时候使用状态码代替model来响应给客户端会更好,下面的例子就是在“Courses‘中的Getcourse(int id)方法中响应一个状态码。下面是我们最终修改后的CoursesController的代码:

    public class CoursesController : BaseApiController
        {
            public CoursesController(ILearningRepository repo)
                : base(repo)
            {
            }
     
            public IEnumerable<CourseModel> Get()
            {
                IQueryable<Course> query;
     
                query = TheRepository.GetAllCourses();
     
                var results = query
                              .ToList()
                              .Select(s => TheModelFactory.Create(s));
     
                return results;
            }
     
            public HttpResponseMessage GetCourse(int id)
            {
                try
                {
                    var course = TheRepository.GetCourse(id);
                    if (course != null)
                    {
                        return Request.CreateResponse(HttpStatusCode.OK, TheModelFactory.Create(course));
                    }
                    else
                    {
                        return Request.CreateResponse(HttpStatusCode.NotFound);
                    }
     
                }
                catch (Exception ex)
                {
                    return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
                }
            }
        }

    总结

    到此为止我们总共完成了以下的改变:

       1.将”ILearningRepository“注入到”CoursesController“的构造函数中

       2.使用模型工厂模式创建了CourseModel以及关联属性ToturModel和SubjectModel

      3.当资源没找到时我们返回404的状态码,发生异常我们返回400(badRequest)状态码,成功时返回200状态码

    为了测试结果,我们发送一个Get请求(http://localhost:{your_port}/api/courses)在这里我们很好的控制了模型的返回字段,同时也解决了对象间循环依赖的问题:

    [
        {
            "id": 1,
            "url": "http://localhost:3300/api/courses/1",
            "name": "History Teaching Methods 1",
            "duration": 3,
            "description": "The course will talk in depth about: History Teaching Methods 1",
            "tutor": {
                "id": 1,
                "email": "Ahmad.Joudeh@outlook.com",
                "userName": "AhmadJoudeh",
                "firstName": "Ahmad",
                "lastName": "Joudeh",
                "gender": 0
            },
            "subject": {
                "id": 1,
                "name": "History"
            }
        },
        {
            "id": 2,
            "url": "http://localhost:3300/api/courses/2",
            "name": "History Teaching Methods 2",
            "duration": 3,
            "description": "The course will talk in depth about: History Teaching Methods 2",
            "tutor": {
                "id": 1,
                "email": "Ahmad.Joudeh@outlook.com",
                "userName": "AhmadJoudeh",
                "firstName": "Ahmad",
                "lastName": "Joudeh",
                "gender": 0
            },
            "subject": {
                "id": 1,
                "name": "History"
            }
        },

    下一章我们来解释Http方法(put,post,delete)。

    本章代码:http://pan.baidu.com/s/1ntjq5Dn

     
    作者:FZRAIN
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    css换行
    <a>标签里的<img>标签点击虚线框
    iframe子页面调用父页面元素
    快捷键
    用css绘制三角形
    解决div被embed,object覆盖问题
    一些兼容问题
    兼容padding
    记一次用html2canvas将页面内容生成海报并保存图片到本地
    PUPPETEER安装遇到 ERROR:CHROMIUM REVISION IS NOT DOWNLOADED.的解决办法
  • 原文地址:https://www.cnblogs.com/fzrain/p/3520442.html
Copyright © 2011-2022 走看看