[译]Create a Web API in MVC 6
原文: http://www.asp.net/vnext/overview/aspnet-vnext/create-a-web-api-with-mvc-6
ASP.NET 5.0的一个目标是合并MVC和Web API frameworkd.
创建空白ASP.NET 5项目
打开Visual Studio 2015. 在File菜单, 选择 New > Project.
在New Project对话框中, 点击Templates > Visual C# > Web, 选择ASP.NET Web Application项目模板. 起名为 "TodoApi" .
在New ASP.NET Project对话框中, 选择"ASP.NET 5.0 Empty"模板.
下面的图片显示了项目的结构.
项目包括下面的文件:
- global.json包含解决方案级别的设置, 包含了项目之间的依赖关系.
- project.json包含项目的配置.
- Project_Readme.html是一个readme文件.
- Startup.cs 包含了startup和配置代码.
Startup
类定义在Startup.cs文件中, 配置了ASP.NET请求管道. 当你使用了空白的项目模板, Startup
class 基本上不包含什么代码添加请求管道:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
// Nothing here!
}
}
现在就可以运行程序了, 但是他不包含任何功能. 如果使用"Starter Web" 模板,会自己默认就配置了一些框架,例如MVC 6, Entity Framework, authentication, logging等.
添加欢迎页面
打开project.json文件. 这个文件包含了项目的一些配置. dependencies
部分列出了项目需要的NuGet包和类库. 添加Microsoft.AspNet.Diagnostics包:
"dependencies": { "Microsoft.AspNet.Server.IIS": "1.0.0-beta1", // Add this: "Microsoft.AspNet.Diagnostics": "1.0.0-beta1" },
当你在输入的时候, Visual Studio的智能提示会列出一些NuGet包.
智能提示也会提供包的版本号:
接下来, 打开Startup.cs添加下面的代码.
using System; using Microsoft.AspNet.Builder; namespace TodoApi { public class Startup { public void Configure(IApplicationBuilder app) { // New code app.UseWelcomePage(); } } }
按F5后你会看到类似于下面的一个欢迎页面:
创建Web API
创建一个web API管理ToDo条目. 首先添加ASP.NET MVC 6.
在project.json的dependencies的section添加MVC 6包:
"dependencies": { "Microsoft.AspNet.Server.IIS": "1.0.0-beta1", "Microsoft.AspNet.Diagnostics": "1.0.0-beta1", // New: "Microsoft.AspNet.Mvc": "6.0.0-beta1" },
下一步添加MVC请求管道. 在Startup.cs中,
- 添加
using
statement语句. usingMicrosoft.Framework.DependencyInjection
- 在
Startup
类中添加下面的方法.
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); }
上面的代码添加了所有MVC6需要的依赖. 项目启动的时候会自动调用
ConfigureServices
. - 在Configure方法中, 添加下面的代码.
UseMvc
方法添加MVC 6到请求管道.public void Configure(IApplicationBuilder app) { // New: app.UseMvc(); }
下面是完整的Startup
类:
using System; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; // New using: using Microsoft.Framework.DependencyInjection; namespace TodoApi { public class Startup { // Add this method: public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } public void Configure(IApplicationBuilder app) { // New: app.UseMvc(); app.UseWelcomePage(); } } }
添加模型
using System.ComponentModel.DataAnnotations; namespace TodoApi.Models { public class TodoItem { public int Id { get; set; } [Required] public string Title { get; set; } public bool IsDone { get; set; } } }
添加控制器
using Microsoft.AspNet.Mvc; using System.Collections.Generic; using System.Linq; using TodoApi.Models; namespace TodoApi.Controllers { [Route("api/[controller]")] public class TodoController : Controller { static readonly List<TodoItem> _items = new List<TodoItem>() { new TodoItem { Id = 1, Title = "First Item" } }; [HttpGet] public IEnumerable<TodoItem> GetAll() { return _items; } [HttpGet("{id:int}", Name = "GetByIdRoute")] public IActionResult GetById (int id) { var item = _items.FirstOrDefault(x => x.Id == id); if (item == null) { return HttpNotFound(); } return new ObjectResult(item); } [HttpPost] public void CreateTodoItem([FromBody] TodoItem item) { if (!ModelState.IsValid) { Context.Response.StatusCode = 400; } else { item.Id = 1+ _items.Max(x => (int?)x.Id) ?? 0; _items.Add(item); string url = Url.RouteUrl("GetByIdRoute", new { id = item.Id }, Request.Scheme, Request.Host.ToUriComponent()); Context.Response.StatusCode = 201; Context.Response.Headers["Location"] = url; } } [HttpDelete("{id}")] public IActionResult DeleteItem(int id) { var item = _items.FirstOrDefault(x => x.Id == id); if (item == null) { return HttpNotFound(); } _items.Remove(item); return new HttpStatusCodeResult(204); // 201 No Content } } }
上面的控制器实行了基本的增删改查操作:
Request (HTTP method + URL) | Description |
---|---|
GET /api/todo | Returns all ToDo items |
GET /api/todo/id | Returns the ToDo item with the ID given in the URL. |
POST /api/todo | Creates a new ToDo item. The client sends the ToDo item in the request body. |
DELETE /api/todo/id | Deletes a ToDo item. |
请求:
GET http://localhost:5000/api/todo HTTP/1.1 User-Agent: Fiddler Host: localhost:5000
响应:
HTTP/1.1 200 OK Content-Type: application/json;charset=utf-8 Server: Microsoft-HTTPAPI/2.0 Date: Thu, 30 Oct 2014 22:40:31 GMT Content-Length: 46 [{"Id":1,"Title":"First Item","IsDone":false}]
解释代码
Routing路由
[Route] attribute 定义了这个控制器URL模板:
[Route("api/[controller]")]
在上面的例子中“[controller]” 替代controller的类名, 减去“Controller”后缀. api/todo匹配TodoController
控制器.
HTTP 方法
[HttpGet], [HttpPost] and [HttpDelete] attributes 定义了action的Http方法
[HttpGet] public IEnumerable<TodoItem> GetAll() {} [HttpGet("{id:int}", Name = "GetByIdRoute")] public IActionResult GetById (int id) {} [HttpPost] public void CreateTodoItem([FromBody] TodoItem item) {} [HttpDelete("{id:int}")] public IActionResult DeleteItem(int id) {}
在上面的例子中 GetById
和 DeleteItem
, 的定义了参数. 完整的路由模板是“api/[controller]/{id:int}”.
在 “{id:int}” 片段中, id是一个参数, and “:int”限制了参数的类型是整形. 下面的URL会被匹配:
http://localhost/api/todo/1
http://localhost/api/todo/42
但是下面的URL不会匹配:
http://localhost/api/todo/abc
注意GetById
和 DeleteItem
同样也有一个方法参数名为id. 例如, http://localhost/api/todo/42
, id的值会被设置为42.
CreateTodoItem
方法展示了另外一种形式的参数绑定:
[HttpPost] public void CreateTodoItem([FromBody] TodoItem item) {}
[FromBody] attribute告诉框架会将请求内容序列会为TodoItem
参数.
下面展示了请求URL和匹配的对应的控制器方法:
Request | Controller Action |
---|---|
GET /api/todo | GetAll |
POST /api/todo | CreateTodoItem |
GET /api/todo/1 | GetById |
DELETE /api/todo/1 | DeleteItem |
GET /api/todo/abc | none – returns 404 |
PUT /api/todo | none – returns 404 |
Action的返回值
TodoController
展示了几种不同的返回值.
GetAll
方法返回一个CLR对象.
[HttpGet] public IEnumerable<TodoItem> GetAll() { return _items; }
默认返回值是JSON, 但是用户可以请求其它的格式. 例如, 下面请求一个XML响应.
GET http://localhost:5000/api/todo HTTP/1.1 User-Agent: Fiddler Host: localhost:5000 Accept: application/xml
Response:
HTTP/1.1 200 OK Content-Type: application/xml;charset=utf-8 Server: Microsoft-HTTPAPI/2.0 Date: Thu, 30 Oct 2014 22:40:10 GMT Content-Length: 228 <ArrayOfTodoItem xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/TodoApi.Models"><TodoItem><Id>1</Id><IsDone>false</IsDone><Title>First Item</Title></TodoItem></ArrayOfTodoItem>
GetById
方法返回IActionResult
:
[HttpGet("{id:int}", Name = "GetByIdRoute")] public IActionResult GetById (int id) { var item = _items.FirstOrDefault(x => x.Id == id); if (item == null) { return HttpNotFound(); } return new ObjectResult(item); }
如果没有找到对应的todo, 返回HttpNotFound
.
最后, CreateTodoItem
展示如何直接修改reponse的属性返回.
[HttpPost] public void CreateTodoItem([FromBody] TodoItem item) { // (some code not shown here) Context.Response.StatusCode = 201; Context.Response.Headers["Location"] = url; }
依赖注入
MVC 6直接把依赖注入集成在了框架中:
using System.Collections.Generic; namespace TodoApi.Models { public interface ITodoRepository { IEnumerable<TodoItem> AllItems { get; } void Add(TodoItem item); TodoItem GetById(int id); bool TryDelete(int id); } }
实现.
using System; using System.Collections.Generic; using System.Linq; namespace TodoApi.Models { public class TodoRepository : ITodoRepository { readonly List<TodoItem> _items = new List<TodoItem>(); public IEnumerable<TodoItem> AllItems { get { return _items; } } public TodoItem GetById(int id) { return _items.FirstOrDefault(x => x.Id == id); } public void Add(TodoItem item) { item.Id = 1 + _items.Max(x => (int?)x.Id) ?? 0; _items.Add(item); } public bool TryDelete(int id) { var item = GetById(id); if (item == null) { return false; } _items.Remove(item); return true; } } }
在控制器中使用构造函数依赖注入:
[Route("api/[controller]")] public class TodoController : Controller { // Remove this code: //static readonly List<TodoItem> _items = new List<TodoItem>() //{ // new TodoItem { Id = 1, Title = "First Item" } //}; // Add this code: private readonly ITodoRepository _repository; public TodoController(ITodoRepository repository) { _repository = repository; }
更新代码使用仓储操作数据:
[HttpGet] public IEnumerable<TodoItem> GetAll() { return _repository.AllItems; } [HttpGet("{id:int}", Name = "GetByIdRoute")] public IActionResult GetById(int id) { var item = _repository.GetById(id); if (item == null) { return HttpNotFound(); } return new ObjectResult(item); } [HttpPost] public void CreateTodoItem([FromBody] TodoItem item) { if (!ModelState.IsValid) { Context.Response.StatusCode = 400; } else { _repository.Add(item); string url = Url.RouteUrl("GetByIdRoute", new { id = item.Id }, Request.Scheme, Request.Host.ToUriComponent()); Context.Response.StatusCode = 201; Context.Response.Headers["Location"] = url; } } [HttpDelete("{id}")] public IActionResult DeleteItem(int id) { if (_repository.TryDelete(id)) { return new HttpStatusCodeResult(204); // 201 No Content } else { return HttpNotFound(); } }
为了依赖注入能正常的工作, 我们需要注册仓储到依赖注入系统. 在Startup
类中, 添加下面的代码:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // New code services.AddSingleton<ITodoRepository, TodoRepository>(); }
当应用运行的时候,框架会自动将TodoRepository
注入到控制器. 因为我们使用AddSingleton
注册ITodoRepository
, 在整个应用程序的生命周期中会使用同一个实例.