从 .NET 3.5 开始 WCF 已经支持用 WebHttpBinding 构建 RESTful Web 服务,基于 WCF 框架的 Web Api 还是建立在 WCF Message 栈上,因为 REST 的工作原理有所不同,它不需要依赖 SOAP 协议,因此 WCF 消息管道对于它经过了特殊的消息优化。但 REST 集成在 WCF 消息管道上还是不理想,所以微软提出在 ASP.NET 平台上构建REST服务,也就有了现在 ASP.NET MVC 4 中的 Web Api。
引用 WCF 在 Codeplex 上的声明:
Announcement: WCF Web API is now ASP.NET Web API! ASP.NET Web API released with ASP.NET MVC 4 Beta.
The WCF Web API and WCF support for jQuery content on this site wll removed by the end of 2012.
如果对 REST WCF 框架熟悉的童鞋,可以参看下面的 WCF Web Api 到 ASP.NET Web Api 的映射表:
WCF Web API | ASP.NET Web API |
Service | Web API controller |
Operation | Action |
Service contract | Not applicable |
Endpoint | Not applicable |
URI templates | ASP.NET Routing |
Message handlers | Same |
Formatters | Same |
Operation handlers | Filters, model binders |
创建出的工程中,Controllers 目录下会有一个 ValuesController.cs 注意它继承于 ApiController
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Web.Http; namespace MvcApplication1.Controllers { public class ValuesController : ApiController { // GET /api/values public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } // GET /api/values/5 public string Get(int id) { return "value"; } // POST /api/values public void Post(string value) { } // PUT /api/values/5 public void Put(int id, string value) { } // DELETE /api/values/5 public void Delete(int id) { } } }在 Global.cs 中,注册了 Api 的 Url Map: api/{controller}/{id} 每个"Action"是通过 Http谓词(GET/POST/PUT/DELETE)映射的。
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); }
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcApplication1.Models { public class Task { public string Id { get; set; } public string Title { get; set; } public string Content { get; set; } public int Status { get; set; } } }
(3) 增加一个自定义 Repository
using System; using System.Collections.Generic; using System.Linq; using System.Web; using MvcApplication1.Models; namespace MvcApplication1.Repositories { public class TaskRepository { private List<Task> _tasks; public TaskRepository() { _tasks = new List<Task> { new Task { Id="T001", Title="title1", Content="content1" }, new Task { Id="T002", Title="title2", Content="content2" }, new Task { Id="T003", Title="title3", Content="content3" }, new Task { Id="T004", Title="title4", Content="content4" }, }; } public IEnumerable<Task> GetAll() { return _tasks; } public Task FindById(string id) { return _tasks.FirstOrDefault(t => t.Id == id); } public void Add(Task task) { _tasks.Add(task); } public void RemoveById(string id) { var task = _tasks.FirstOrDefault(t => t.Id == id); if (task != null) _tasks.Remove(task); } } }(4) 增加 TaskController
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Web.Http; using MvcApplication1.Models; namespace MvcApplication1.Controllers { public class TasksController : ApiController { private Repositories.TaskRepository _taskRepository = new Repositories.TaskRepository(); // GET /api/tasks public IQueryable<Task> Get() { return _taskRepository.GetAll().AsQueryable(); } // GET /api/tasks/5 public Task Get(string id) { return _taskRepository.FindById(id); } // POST /api/tasks public void Post(Task task) { _taskRepository.Add(task); } // DELETE /api/tasks/5 public void Delete(string id) { _taskRepository.RemoveById(id); } } }运行:
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; using System.Web; using MvcApplication1.Models; namespace MvcApplication1.Repositories { public class TaskPictureFormatter : MediaTypeFormatter { protected override bool CanWriteType(Type type) { return (type == typeof(Task)); } public TaskPictureFormatter() { SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg")); } protected override System.Threading.Tasks.Task OnWriteToStreamAsync(Type type, object value, System.IO.Stream stream, System.Net.Http.Headers.HttpContentHeaders contentHeaders, FormatterContext formatterContext, System.Net.TransportContext transportContext) { var task = value as Task; if (task != null) { var data = System.IO.File.ReadAllBytes(HttpContext.Current.Server.MapPath("~/TaskImages/" + task.Id + ".png")); return stream.WriteAsync(data, 0, data.Length); } else { throw new HttpException((int)System.Net.HttpStatusCode.NotFound, "task is not found", null); } } } }注意:当找不到对应的图片时,抛出 HttpException 这样可以给客户端更加友好的错误提示。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web.Http; using System.Web.Http.SelfHost; namespace WebApiSelfHostTest { class Program { static void Main(string[] args) { var config = new HttpSelfHostConfiguration("http://localhost:8080"); config.Routes.MapHttpRoute( "API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional }); using (HttpSelfHostServer server = new HttpSelfHostServer(config)) { server.OpenAsync().Wait(); Console.WriteLine("HttpServer is opening, Press Enter to quit."); Console.ReadLine(); } } } public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } public class ProductsController : ApiController { public IEnumerable<Product> GetAllProducts() { return new List<Product> { new Product() { Id = 1, Name = "Gizmo 1", Price = 1.99M }, new Product() { Id = 2, Name = "Gizmo 2", Price = 2.99M }, new Product() { Id = 3, Name = "Gizmo 3", Price = 3.99M } }; } } }