返璞归真 asp.net mvc (10) - asp.net mvc 4.0 新特性之 Web API
返璞归真 asp.net mvc (10) - asp.net mvc 4.0 新特性之 Web API
作者:webabcd
介绍
asp.net mvc 之 asp.net mvc 4.0 新特性之 Web API
- 开发一个 CRUD 的 Demo,服务端用 Web API,并使其支持 jsonp 协议,客户端用 jQuery
示例
1、自定义一个 JsonMediaTypeFormatter,以支持 jsonp 协议
MyJsonFormatter.cs
/* * 自定义一个 JsonMediaTypeFormatter,以支持 jsonp 协议 */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Formatting; using System.Net.Http.Headers; using System.Threading.Tasks; using System.Web; namespace MVC40.Controllers { public class MyJsonFormatter : JsonMediaTypeFormatter { // jsonp 回调的函数名称 private string JsonpCallbackFunction; public MyJsonFormatter() { } public override bool CanWriteType(Type type) { return true; } // 每个请求都先来这里 public override MediaTypeFormatter GetPerRequestFormatterInstance(Type type, System.Net.Http.HttpRequestMessage request, MediaTypeHeaderValue mediaType) { var formatter = new MyJsonFormatter() { JsonpCallbackFunction = GetJsonCallbackFunction(request) }; // 增加一个转换器,以便枚举值与枚举名间的转换 formatter.SerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); // 增加一个转换器,以方便时间格式的序列化和饭序列化 var dateTimeConverter = new Newtonsoft.Json.Converters.IsoDateTimeConverter(); dateTimeConverter.DateTimeFormat = "yyyy-MM-dd HH:mm:ss"; formatter.SerializerSettings.Converters.Add(dateTimeConverter); // 排版返回的 json 数据,使其具有缩进格式,以方便裸眼查看 formatter.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented; return formatter; } // 序列化的实现 public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext) { if (string.IsNullOrEmpty(JsonpCallbackFunction)) return base.WriteToStreamAsync(type, value, stream, content, transportContext); StreamWriter writer = null; try { writer = new StreamWriter(stream); writer.Write(JsonpCallbackFunction + "("); writer.Flush(); } catch (Exception ex) { try { if (writer != null) writer.Dispose(); } catch { } var tcs = new TaskCompletionSource<object>(); tcs.SetException(ex); return tcs.Task; } return base.WriteToStreamAsync(type, value, stream, content, transportContext) .ContinueWith(innerTask => { if (innerTask.Status == TaskStatus.RanToCompletion) { writer.Write(")"); writer.Flush(); } }, TaskContinuationOptions.ExecuteSynchronously) .ContinueWith(innerTask => { writer.Dispose(); return innerTask; }, TaskContinuationOptions.ExecuteSynchronously) .Unwrap(); } // 从请求 url 中获取其参数 callback 的值 private string GetJsonCallbackFunction(HttpRequestMessage request) { if (request.Method != HttpMethod.Get) return null; var query = HttpUtility.ParseQueryString(request.RequestUri.Query); var queryVal = query["callback"]; if (string.IsNullOrEmpty(queryVal)) return null; return queryVal; } } }
2、在 Global 中做的一些配置
Global.asax.cs
using MVC40.Controllers; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http.Formatting; using System.Net.Http.Headers; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; namespace MVC40 { public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); // 添加一个转换器 IsoDateTimeConverter,其用于日期数据的序列化和反序列化 var dateTimeConverter = new IsoDateTimeConverter(); dateTimeConverter.DateTimeFormat = "yyyy-MM-dd HH:mm:ss"; JsonSerializerSettings serializerSettings = new JsonSerializerSettings(); serializerSettings.Converters.Add(dateTimeConverter); // 清除全部 Formatter(默认有 4 个,分别是:JsonMediaTypeFormatter, XmlMediaTypeFormatter, FormUrlEncodedMediaTypeFormatter, JQueryMvcFormUrlEncodedFormatter) // GlobalConfiguration.Configuration.Formatters.Clear(); // 如果请求 header 中有 accept: text/html 则返回这个新建的 JsonMediaTypeFormatter 数据 var jsonFormatter = new JsonMediaTypeFormatter(); jsonFormatter.SerializerSettings = serializerSettings; // jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html")); jsonFormatter.MediaTypeMappings.Add(new RequestHeaderMapping("accept", "text/html", StringComparison.InvariantCultureIgnoreCase, true, new MediaTypeHeaderValue("text/html"))); GlobalConfiguration.Configuration.Formatters.Insert(0, jsonFormatter); // 请求 url 中如果带有参数 xml=true,则返回 xml 数据 GlobalConfiguration.Configuration.Formatters.XmlFormatter.MediaTypeMappings.Add(new QueryStringMapping("xml", "true", "application/xml")); // 请求 url 中如果带有参数 jsonp=true,则返回支持 jsonp 协议的数据(具体实现参见 MyJsonFormatter.cs) MyJsonFormatter formatter = new MyJsonFormatter(); formatter.MediaTypeMappings.Add(new QueryStringMapping("jsonp", "true", "application/javascript")); GlobalConfiguration.Configuration.Formatters.Add(formatter); } } }
关于项目模版生成的代码的简短说明
<p> 项目模板更新了,原来堆在 Global.asax.cs 中的配置都分出去了 <br /> 在 App_Start 文件夹里自动生成的 BundleConfig.cs 用于对多个 css 或 js 做打包和压缩 <br /> 在 App_Start 文件夹里自动生成的 FilterConfig.cs 用于配置全局的 Action Filter <br /> 在 App_Start 文件夹里自动生成的 RouteConfig.cs 用于配置路由 <br /> 在 App_Start 文件夹里自动生成的 WebApiConfig.cs 用于为 web api 配置路由 </p>
web api 的路由配置
WebApiConfig.cs
/* * web api 的路由配置 */ using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; namespace MVC40 { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // 此配置为默认生成的配置 config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // 由于默认为 web api 生成的路由配置,无 action 配置,所以只能通过 http 方法来匹配 action // 如果需要带 action 的路由则使用以下配置即可 /* routes.MapHttpRoute( name: "ActionApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); */ } } }
3、提供 Web API 服务的 Controller(数据层用 Entity Framework 5.0 来实现)
ProductsController.cs
/* * ASP.NET Web API * * c - POST - 创建 * r - GET - 读取 * u - PUT - 更新 * d - DELETE - 删除 * * 注:win8 的 iis 默认不会安装 asp.net,需要在“程序和功能”中手动添加 */ using MVC40.Models; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Web.Http; namespace MVC40.Controllers { /* * Web API 的 Controller 要从 ApiController 继承 * * 默认:http get 找 controller 的 get(), http post 找 controller 的 post(), http put 找 controller 的 put(), http delete 找 controller 的 delete() */ public class ProductsController : ApiController { // 获取全部 Product 数据:get http://localhost:17612/api/products public IEnumerable<Product> Get() { NorthwindEntities db = new NorthwindEntities(); var products = from p in db.Products orderby p.ProductID descending select p; return products.ToList(); } // 根据 ProductId 获取指定的 Product 数据:get http://localhost:17612/api/products/3 public Product Get(int id) { NorthwindEntities db = new NorthwindEntities(); var product = db.Products.SingleOrDefault(p => p.ProductID == id); return product; } // 新建 Product:post 一个 product 到 http://localhost:17612/api/products public void Post(Product product) { NorthwindEntities db = new NorthwindEntities(); db.Products.Add(product); db.SaveChanges(); } // 更新 Product:put 一个 product 到 http://localhost:17612/api/products public void Put(Product product) { NorthwindEntities db = new NorthwindEntities(); db.Products.Attach(product); var entry = db.Entry(product); entry.State = EntityState.Modified; db.SaveChanges(); } // 根据 ProductId 删除指定的 Product 数据:delete http://localhost:17612/api/products/3 public void Delete(int id) { NorthwindEntities db = new NorthwindEntities(); var product = db.Products.SingleOrDefault(p => p.ProductID == id); db.Products.Remove(product); db.SaveChanges(); } } }
4、调用 Web API 的客户端(用 jQuery 实现)
CRUDDemo.cshtml
@{ Layout = null; } <!DOCTYPE html> <html> <head> <title>演示如何通过 jQuery 调用 asp.net web api 做 crud 操作</title> <script src="../Scripts/jquery-1.7.1.js" type="text/javascript"></script> </head> <body> <table id="tblProducts" border="1"> <tr> <th>Product Id</th> <th>Product Name</th> <th>Unit Price</th> <th>Actions</th> </tr> <tr> <td> <input type="text" id="txtProductId" size="5" disabled /></td> <td> <input type="text" id="txtProductName" /></td> <td> <input type="text" id="txtUnitPrice" /></td> <td> <input type="button" name="btnInsert" value="Insert" /></td> </tr> </table> <script type="text/javascript"> $(document).ready(function () { loadProductsAsync(); }); // 异步获取全部 Product 数据 function loadProductsAsync() { $.getJSON("/api/products?jsonp=true&callback=?", loadProductsCompleted).error(function () { alert('error') }); } // 显示获取到的 Product 数据 function loadProductsCompleted(data) { $("#tblProducts").find("tr:gt(1)").remove(); $.each(data, function (key, val) { var tableRow = '<tr>' + '<td>' + val.ProductID + '</td>' + '<td><input type="text" value="' + val.ProductName + '"/></td>' + '<td><input type="text" value="' + val.UnitPrice + '"/></td>' + '<td><input type="button" name="btnUpdate" value="Update" /> <input type="button" name="btnDelete" value="Delete" /></td>' + '</tr>'; $('#tblProducts').append(tableRow); }); $("input[name='btnInsert']").click(onInsert); $("input[name='btnUpdate']").click(onUpdate); $("input[name='btnDelete']").click(onDelete); } // 新增 Product 数据 function onInsert(evt) { var productName = $("#txtProductName").val(); var unitPrice = $("#txtUnitPrice").val(); // 构建需要 post 的数据,即需要添加的数据 var data = '{"ProductName":"' + productName + '","UnitPrice":' + unitPrice + '}'; $.ajax({ type: 'POST', url: '/api/products/', data: data, contentType: "application/json; charset=utf-8", dataType: 'json', success: function (results) { $("#txtProductName").val(''); $("#txtUnitPrice").val(''); alert('Product Added'); loadProductsAsync(); } }) } // 更新 Product 数据 function onUpdate(evt) { var productId = $(this).parent().parent().children().get(0).innerHTML; var cell = $(this).parent().parent().children().get(1); var productName = $(cell).find('input').val(); cell = $(this).parent().parent().children().get(2); var unitPrice = $(cell).find('input').val(); // 构建需要 put 的数据,即需要更新的数据 var data = '{"ProductID":"' + productId + '","ProductName":"' + productName + '","UnitPrice":' + unitPrice + '}'; $.ajax({ type: 'PUT', url: '/api/products/', data: data, contentType: "application/json; charset=utf-8", dataType: 'json', success: function (results) { alert('Product Updated'); } }) } // 删除指定的 Product 数据 function onDelete(evt) { var productId = $(this).parent().parent().children().get(0).innerHTML; $.ajax({ type: 'DELETE', url: '/api/products/' + productId, contentType: "application/json; charset=utf-8", dataType: 'json', success: function (results) { alert('Product Deleted'); loadProductsAsync(); } }) } </script> </body> </html>
OK
[源码下载]