返璞歸真 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
[源碼下載]