系列導航地址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)。