在新出的MVC4中,增加了WebAPI,用于提供REST风格的WebService,新生成的WebAPI项目和典型的MVC项目一样,包含主要的Models、Views、Controllers等文件夹和Global.asax文件。Views对于WebAPI来说没有太大的用途,Models中的Model主要用于保存Service和Client交互的对象,这些对象默认情况下会被转换为Json格式的数据迚行传输,Controllers中的Controller对应于WebService来说是一个Resource,用于提供服务。和普通的MVC一样,Global.asax用于配置路由规则。 
环境准备 
     建议使用VS2012以上版本创建WebAPI,如果是使用VS2010,需要安装VS2010 SP1升级包,MVC4升级包,打开VS2012创建如下: 
第一步:新建ASP.NET Web应用程序

第二步:建议WebAPI

新生成的WebAPI项目和典型的MVC项目一样,包含主要的Models,Views,Controllers等文件夹和Global.asax文件

注意:再次强调Views对于WebAPI来说没有太大的用途,Models中的Model主要用于保存Service和Client交互的对象,这些对象默认情况下会被转换为Json格式的数据进行传输,Controllers中的Controller对应于WebService来说是一个Resource,用于提供服务。和普通的MVC一样,Global.asax用于配置路由规则

(二)Models 
和WCF中的数据契约形成鲜明对比的是,MVC WebAPI中的Model就是简单的POCO,没有任何别的东西,如,你可以创建如下的Model

  1. public class UserModel
  2. {
  3. public int Id { get; set; }
  4. public string UserName { get; set; }
  5. public string PassWord { get; set; }
  6. }

注意:Model必须提供public的属性,用于json或xml反序列化时的赋值 
(三)Controllers 
MVC WebAPI中的Controllers和普通MVC的Controllers类似,不过不再继承于Controller,而改为继承API的ApiController,一个Controller可以包含多个Action,这些Action响应请求的方法与Global中配置的路由规则有关,在后面结束Global时统一说明 
 
(四)Global 
默认情况下,模板自带了两个路由规则,分别对应于WebAPI和普通MVC的Web请求,默认的WebAPI路由规则如下

  1. routes.MapHttpRoute(
  2. name: "DefaultApi",
  3. routeTemplate: "api/{controller}/{id}",
  4. defaults: new { id = RouteParameter.Optional }
  5. );

可以看到,默认路由使用的固定的api作为Uri的先导,按照微软官方的说法,用于区分普通Web请求和WebService的请求路径: 
可以看到,默认的路由规则只指向了Controller,没有指向具体的Action,因为默认情况下,对于Controller中的Action的匹配是和Action的方法名相关联的:具体来说,如果使用上面的路由规则,对应下面的Controller:

  1. public class UserController : ApiController
  2. {
  3. public List<UserModel> allModeList = new List<UserModel>() {
  4. new UserModel(){ Id=,UserName="zhang", PassWord=""},
  5. new UserModel(){ Id=,UserName="lishi", PassWord=""},
  6. new UserModel(){ Id=,UserName="wang", PassWord=""}
  7. };
  8. //Get api/User/
  9. public IEnumerable<UserModel> GetAll()
  10. {
  11. return allModeList;
  12. }
  13. //Get api/User/1
  14. public IEnumerable<UserModel> GetOne(int id) {
  15. return allModeList.FindAll((m) => { return m.Id == id; });
  16. }
  17. //POST api/User/
  18. public bool PostNew(UserModel user)
  19. {
  20. try
  21. {
  22. allModeList.Add(user);
  23. return true;
  24. }
  25. catch {
  26. return false;
  27. }
  28. }
  29. //Delete api/User/
  30. public int DeleteAll()
  31. {
  32. return allModeList.RemoveAll((mode) => { return true; });
  33. }
  34. //Delete api/User/1
  35. public int DeleteOne(int id) {
  36. return allModeList.RemoveAll((m) => { return m.Id == id; });
  37. }
  38. //Put api/User
  39. public int PutOne(int id, UserModel user)
  40. {
  41. List<UserModel> upDataList = allModeList.FindAll((mode) => { return mode.Id == id; });
  42. foreach (var mode in upDataList)
  43. {
  44. mode.PassWord = user.PassWord;
  45. mode.UserName = user.UserName;
  46. }
  47. return upDataList.Count;
  48. }
  49. }

则,会有下面的对应关系:

URL HttpMethod 对应的Action名
/api/User GET GetALL
/api/User/1 GET GetOne
/api/User POST PostNew
/api/User/1 DELETE DeleteOne
/api/User DELETE DeleteALL
/api/User PUT PutOne

客户端JS调用

  1. function getAll() {
  2. $.ajax({
  3. url: "api/User/",
  4. type: 'GET',
  5. success: function (data) {
  6. document.getElementById("modes").innerHTML = "";
  7. $.each(data, function (key, val) {
  8. var str = val.UserName + ': ' + val.PassWord;
  9. $('<li/>', { html: str }).appendTo($('#modes'));
  10. });
  11. }
  12. }).fail(
  13. function (xhr, textStatus, err) {
  14. alert('Error: ' + err);
  15. });
  16.    }
  17. function find() {
  18. $.ajax({
  19. url: "api/User/1" ,
  20. type: 'GET',
  21. success: function (data) {
  22. document.getElementById("modes").innerHTML = "";
  23. $.each(data, function (key, val) {
  24. var str = val.UserName + ': ' + val.PassWord;
  25. $('<li/>', { html: str }).appendTo($('#modes'));
  26. });
  27. }
  28. }).fail(
  29. function (xhr, textStatus, err) {
  30. alert('Error: ' + err);
  31. });
  32. }
  33.  
  34. function add() {
  35.  
  36. $.ajax({
  37. url: "api/User/",
  38. type: "POST",
  39. dataType: "json",
  40. data: { "Id":4,"UserName": "admin", "PassWord": "666666"},
  41. success: function (data) {
  42. getAll();
  43. }
  44. }).fail(
  45. function (xhr, textStatus, err) {
  46. alert('Error: ' + err);
  47. });
  48.  
  49. }
  50.  
  51. function removeUser() {
  52. $.ajax({
  53. url: "api/User/3",
  54. type: 'DELETE',
  55. success: function (data) {
  56. document.getElementById("modes").innerHTML = "";
  57. getAll();
  58. }
  59. }).fail(
  60. function (xhr, textStatus, err) {
  61. alert('Error: ' + err);
  62. });
  63. }
  64.  
  65. function removeAll() {
  66. $.ajax({
  67. url: "api/User/",
  68. type: 'DELETE',
  69. success: function (data) {
  70. document.getElementById("modes").innerHTML = "";
  71. getAll();
  72. }
  73. }).fail(
  74. function (xhr, textStatus, err) {
  75. alert('Error: ' + err);
  76. });
  77. }
  78. function udpate() {
  79. $.ajax({
  80. url: "api/User/1",
  81. type: 'PUT',
  82. dataType: "json",
  83. data: { Id: 1, "UserName": "admin", "PassWord": "666666" },
  84. success: function (data) {
  85. document.getElementById("modes").innerHTML = "";
  86. getAll();
  87. }
  88. }).fail(
  89. function (xhr, textStatus, err) {
  90. alert('Error: ' + err);
  91. });
  92. }

这样就实现了最基本的CRUD操作。 
扩展需求 
问题1:我想按照用户名称(UserName)进行查询,怎么办? 
办法:第一步:在UserController类中加一个方法名称叫:GetUserByName,如下所示:

  1. public UserModel GetUserByName(string userName) {
  2. return allModeList.Find((m) => { return m.UserName.Equals(userName); });
  3. }

第二步:在客户端index.cshtml中调用

  1. function getUserByName() {
  2. $.ajax({
  3. url: "api/User/zhang",
  4. type: 'GET',
  5. success: function (data) {
  6. document.getElementById("modes").innerHTML = "";
  7. var str = data.UserName + ': ' + data.PassWord;
  8. $('<li/>', { html: str }).appendTo($('#modes'));
  9. }
  10. }).fail(
  11. function (xhr, textStatus, err) {
  12. alert('Error: ' + err);
  13. });
  14.    }

如果URL是: url: "api/User/zhang",将会报错:Bad Request 
原因是他会自动调用我们的GetOne(int id) 这个方法,类型转换出错 
解决办法: 
改变URL为: url: "api/User/?userName=zhang", 
问题2:我想按用户名称(UserName) 和用户密码(PassWord)一起来进行查询,怎么办? 
解决办法 
第一步:UserController类中,可以重载一个GetUserByName的方法,如下所示:

  1. public UserModel GetUserByName(string userName) {
  2. return allModeList.Find((m) => { return m.UserName.Equals(userName); });
  3. }

第二步:客户端调用:

  1. function getUserByName() {
  2. $.ajax({
  3. url: "api/User/?userName=zhang&passWord=123", //这里尤其需要注意
  4. type: 'GET',
  5. success: function (data) {
  6. document.getElementById("modes").innerHTML = "";
  7. var str = data.UserName + ': ' + data.PassWord;
  8. $('<li/>', { html: str }).appendTo($('#modes'));
  9. }
  10. }).fail(
  11. function (xhr, textStatus, err) {
  12. alert('Error: ' + err);
  13. });
  14.    }

路由规则扩展 
和普通的MVC一样,MVC WebAPI支持自定义的路由规则,如:在上面的操作中,路由规则使用 
"api/{controller}/{id}" 
则限定了使用GET方式利用URL来传值时,controller后面的接收参数名为id,但是在Controller中,如果GetOne方法的接收参数名为key,是不会被匹配的,这是只需要新增一个新的路由规则,或修改原先的路由规则为: 
"api/{controller}/{key}",如下所示:

  1. config.Routes.MapHttpRoute(
  2. name: "DefaultApi",
  3. routeTemplate: "api/{controller}/{key}",
  4. defaults: new { key = RouteParameter.Optional }
  5. );

当然,可以对路由进行更深的扩展,如:扩展成和普通MVC一样的路由: 
"api/{controller}/{action}/{id}" 
这样,就要求同时使用Action和HTTP方法进行匹配当然,根据微软的说法,这种使用是不被推荐的,因为这不符合大家对WebService的一般认知: 
使用Attribute声明HTTP方法

  1.        [HttpGet]
  2. public IEnumerable<TestUseMode> FindAll()
  3. [HttpGet]
  4. public IEnumerable<TestUseMode> FindByKey(string key)
  5. [HttpPost]
  6. public bool Add(TestUseMode mode)
  7. [HttpDelete]
  8. public int RemoveByKey(string key)
  9. [HttpDelete]
  10. public int RemoveAll()
  11. [HttpPut]
  12. public int UpdateByKey(string key, string value)
  13. [NonAction]
  14. public string GetPrivateData()

当然,我只列出了方法名,而不是这些方法真的没有方法体...方法体是不变的,NoAction表示这个方法是不接收请求的,即使以GET开头。如果感觉常规的GET,POST,DELETE,PUT不够用,还可以使用AcceptVerbs的方式来声明HTTP方法,如:

  1. [AcceptVerbs("MKCOL", "HEAD")]
  2. public int UpdateByKey(string key, string value)
  3. {
  4. List<TestUseMode> upDataList = allModeList.FindAll((mode) => { if (mode.ModeKey == key) return true; return false; });
  5. foreach(var mode in upDataList)
  6. {
  7. mode.ModeValue = value;
  8. }
  9. return upDataList.Count;
  10. }

附:什么是REST风格? 参考:什么是REST风格 
http://hi.baidu.com/yankaiwei/item/1f0b37dd922d53ef3cc2cb69 
第二部分:综合示例:应用ASP.NET MVC4+WebAPI+FluentData开发Web应用 
第一步:创建数据库 
NorthWind数据库的Customers表

  1. Create DataBase NorthWind
  2. Go
  3. Use NorthWind
  4. Go
  5. CREATE TABLE [dbo].[Customers](
  6. [CustomerID] [nchar](5) NOT NULL,
  7. [CompanyName] [nvarchar](40) NOT NULL,
  8. [ContactName] [nvarchar](30) NULL,
  9. [ContactTitle] [nvarchar](30) NULL,
  10. [Address] [nvarchar](60) NULL,
  11. [City] [nvarchar](15) NULL,
  12. [Region] [nvarchar](15) NULL,
  13. [PostalCode] [nvarchar](10) NULL,
  14. [Country] [nvarchar](15) NULL,
  15. [Phone] [nvarchar](24) NULL,
  16. [Fax] [nvarchar](24) NULL,
  17. CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED
  18. (
  19. [CustomerID] ASC
  20. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
  21. ) ON [PRIMARY]
  22.  
  23. GO

第二步:创建 FluentData.Entity层,创建Customer实体类

  1. namespace FluentData.Entity
  2. {
  3. public class Customer
  4. {
  5. public string CustomerID { get; set; }
  6. public string CompanyName { get; set; }
  7. public string ContactName { get; set; }
  8. public string ContactTitle { get; set; }
  9. public string Address { get; set; }
  10. public string City { get; set; }
  11. public string Region { get; set; }
  12. public string PostalCode { get; set; }
  13. public string Country { get; set; }
  14. public string Phone { get; set; }
  15. public string Fax { get; set; }
  16. }
  17. }

第三步:利用FluentData做数据的持久化 
首先引入FluentData.cs (见附件) 
其次:创建DBHelper类,代码如下:

  1. public class DBHelper
  2. {
  3. public static IDbContext Context() {
  4. //return new DbContext().ConnectionString("server=127.0.0.1;uid=sa;pwd=sa;database=TestDB", new SqlServerProvider());
  5. return new DbContext().ConnectionStringName("connString", new SqlServerProvider());
  6. }
  7. }

然后不要忘记修改ASP.NET MVC层所在的Web.config,加入数据库连结字符串:

  1. <connectionStrings>
  2. <add name="connString" connectionString="server=127.0.0.1;database=Northwind;uid=sa;pwd=sa;"/>
  3. </connectionStrings>

第三步:创建 CustomerService数据持久化类,代码如下:

  1. public class CustomerService
  2. {
  3. private IDbContext context = DBHelper.Context();
  4. public Customer Select(string customerId){
  5.  
  6. return context.Select<Customer>("*").From("Customers").Where("CustomerID=@0").Parameters(customerId)
  7. .QuerySingle();
  8.  
  9. }
  10. public List<Customer> SelectAll() {
  11. return context.Select<Customer>("*").From("Customers").QueryMany();
  12. }
  13.  
  14. public List<Customer> SelectAll(string sortExpression) {
  15. if (String.IsNullOrEmpty(sortExpression)) return null;
  16. return context.Select<Customer>("*").From("Customers").OrderBy(sortExpression).QueryMany();
  17. }
  18.  
  19. public List<Customer> SelectAll(int currentPageIndex,int maxRows, string sortExpression)
  20. {
  21. var select = context.Select<Customer>("*").From("Customers");
  22. if (maxRows > ) {
  23. if (currentPageIndex == ) currentPageIndex = ;
  24. select.Paging(currentPageIndex, maxRows);
  25. }
  26. if (!string.IsNullOrEmpty(sortExpression)) {
  27. select.OrderBy(sortExpression);
  28. }
  29.  
  30. return select.QueryMany();
  31. }
  32.  
  33. public int CountAll() {
  34. return context.Sql("select count(*) from Customers").QuerySingle<int>();
  35. }
  36.  
  37. public int Insert(Customer customer) {
  38. return context.Insert<Customer>("Customers", customer).Execute();
  39. }
  40.  
  41. public int Update(Customer customer) {
  42. return context.Update<Customer>("Customers", customer).Where("CustomerID", customer.CustomerID).Execute();
  43. }
  44.  
  45. public int Delete(string customerId) {
  46. return context.Delete("Customers").Where("CustomerID", customerId).Execute();
  47. }
  48.  
  49. public int Delete(Customer customer)
  50. {
  51. return this.Delete(customer.CustomerID);
  52. }
  53. }

第四步:Web API,创建CustomerController 
注意要引用:FluentData.Entity及FluentData.DAL 程序集

  1. public class CustomerController : ApiController
  2. {
  3. private CustomerService customerService = new CustomerService();
  4. //Select All
  5. public IEnumerable<Customer> Get()
  6. {
  7. return customerService.SelectAll();
  8. }
  9.  
  10. //Select By Id
  11. public Customer Get(string id)
  12. {
  13. return customerService.Select(id);
  14. }
  15.  
  16. //Insert
  17. public void Post(Customer customer)
  18. {
  19. customerService.Insert(customer);
  20. }
  21.  
  22. //Update
  23. public void Put(string id, Customer obj)
  24. {
  25. customerService.Update(obj);
  26. }
  27.  
  28. //Delete
  29. public void Delete(string id)
  30. {
  31. customerService.Delete(id);
  32. }
  33. }

第五步:View层代码

  1. namespace MyWebApI.Controllers
  2. {
  3. public class HomeController : Controller
  4. {
  5. public ActionResult Index()
  6. {
  7. return View();
  8. }
  9.  
  10. public ActionResult Test() {
  11. return View();
  12. }
  13.  
  14. public ActionResult CustomerManager() {
  15. return View();
  16. }
  17. }
  18. }

然后创建View

  1. <table id="customerTable" border="1" cellpadding="3" style="width:700px">
  2. <tr>
  3. <th>Customer ID</th>
  4. <th>Company Name</th>
  5. <th>Contact Name</th>
  6. <th>Country</th>
  7. <th>Actions</th>
  8. </tr>
  9. <tr>
  10. <td><input type="text" id="txtCustomerId" style="width:100px" size="5"/></td>
  11. <td><input type="text" id="txtCompanyName" style="width:150px" /></td>
  12. <td><input type="text" id="txtContactName" style="width:150px"/></td>
  13. <td><input type="text" id="txtCountry" style="width:150px"/></td>
  14. <td><input type="button" name="btnInsert" value="Insert" style="width:150px"/></td>
  15. </tr>
  16. </table>
  17.  
  18. <script type="text/javascript">
  19. $(function () {
  20. $.getJSON("api/Customer", LoadCustomers);
  21. });
  22.  
  23. function LoadCustomers(data) {
  24. $("#customerTable").find("tr:gt(1)").remove();
  25. $.each(data, function (key, val) {
  26. var tableRow = '<tr>' +
  27. '<td>' + val.CustomerID + '</td>' +
  28. '<td><input type="text" value="' + val.CompanyName + '" /></td>' +
  29. '<td><input type="text" value="' + val.ContactName + '" /></td>' +
  30. '<td><input type="text" value="' + val.Country + '" /></td>' +
  31. '<td><input type="button" name="btnUpdate" value="修改" /> <input type="button" name="btnDelete" value="删除" /></td>' +
  32. '</tr>';
  33. $('#customerTable').append(tableRow);
  34. });
  35.  
  36. $("input[name='btnInsert']").click(OnInsert);
  37. $("input[name='btnUpdate']").click(OnUpdate);
  38. $("input[name='btnDelete']").click(OnDelete);
  39. }
  40.  
  41. function OnInsert(evt) {
  42. var customerId = $("#txtCustomerId").val();
  43. var companyName = $("#txtCompanyName").val();
  44. var contactName = $("#txtContactName").val();
  45. var country = $("#txtCountry").val();
  46. var data = '{"CustomerID":"' + customerId + '","CompanyName":"' + companyName + '","ContactName":"' + contactName + '","Country":"' + country + '"}';
  47.  
  48. $.ajax({
  49. type: 'POST',
  50. url: '/api/Customer/',
  51. data: data,
  52. contentType: "application/json; charset=utf-8",
  53. dataType: 'json',
  54. success: function (results) {
  55. $("#txtCustomerId").val('');
  56. $("#txtCompanyName").val('');
  57. $("#txtContactName").val('');
  58. $("#txtCountry").val('');
  59. $.getJSON("api/customers" + new Date().getTime(), LoadCustomers);
  60. alert('添加成功!');
  61. }
  62. }).fail(
  63. function (xhr, textStatus, err) {
  64. alert('添加失败,原因如下: ' + err);
  65. });
  66. }
  67. function OnUpdate(evt) {
  68.  
  69. var input;
  70. var customerId = $(this).parent().parent().children().get(0).innerHTML;
  71.  
  72. input = $($(this).parent().parent().children().get(1)).find("input");
  73. //input.removeAttr("disabled");
  74. var companyName = input.val();
  75.  
  76. input = $($(this).parent().parent().children().get(2)).find("input");
  77. //input.removeAttr("disabled");
  78. var contactName = input.val();
  79.  
  80. input = $($(this).parent().parent().children().get(3)).find("input");
  81. //input.removeAttr("disabled");
  82. var country = input.val();
  83.  
  84. var data = '{"CustomerID":"' + customerId + '","CompanyName":"' + companyName + '","ContactName":"' + contactName + '","Country":"' + country + '"}';
  85.  
  86. $.ajax({
  87. type: 'PUT',
  88. url: '/api/Customer/' + customerId,
  89. data: data,
  90. contentType: "application/json; charset=utf-8",
  91. dataType: 'json',
  92. success: function (results) {
  93. $.getJSON("api/Customer" + new Date().getTime(), LoadCustomers);
  94. alert('修改成功 !');
  95. }
  96. }).fail(
  97. function (xhr, textStatus, err) {
  98. alert('修改失败,原因如下: ' + err);
  99. });
  100. }
  101. function OnDelete(evt) {
  102. var customerId = $(this).parent().parent().children().get(0).innerHTML;
  103. //var data = '{"id":"' + customerId + '"}';
  104. //var row = $(this).parent().parent();
  105.  
  106. $.ajax({
  107. type: 'DELETE',
  108. url: '/api/Customer/' + customerId,
  109. contentType: "application/json; charset=utf-8",
  110. dataType: 'json',
  111. success: function (results) {
  112. $.getJSON("api/Customer?"+new Date().getTime(), LoadCustomers);
  113. alert('成功删除!');
  114. }
  115. }).fail(
  116. function (xhr, textStatus, err) {
  117. alert('删除失败,原因如下: ' + err);
  118. });
  119.  
  120. }
  121. </script>

第三部分:Web API高级部分 
在第一部分和大家一起学习了建立基本的WebAPI应用,第二部分写了一个综合示例,立刻就有人想到了一些问题:1.客户端和WebService/WebAPI之间文件传输2.客户端或者服务端的安全控制要解决这些问题,要了解一下WebAPI的基本工作方式。 
(一)WebAPI中工作的Class 
在MVC中大家都知道,获取Request和Response使用HttpRequest和HttpResponse两个类,在WebAPI中使用两外两个类:HttpRequestMessage 和HttpResponseMessage,分别用于封装Requset和Response。除了这两个类之外,还有一个常见的抽象 类:HttpMessageHandler,用于过滤和加工HttpRequestMessage和HttpResponseMessage 
 
(二)解决第一个问题:客户端和WebService之间文件传输其 实第一个问题之所以被提出来应该是和客户端有关,如果客户端的请求是我们手写提交的,比如使用HttpClient封装的请求,则要传递文件之前,我们一 般会进行一次序列化,转化为二进制数组之类的,在网络上传输。这样的话,在Controller中的Action参数里,我们只需要接收这个二进制数组类 型的对象就可以了。但是如果客户端是Web Form呢,比如我们提交一个Form到指定的Controller的Action中,这个Action要接收什么类型的参数呢?或者我们问另外一个问题,如果我将Web Form提交到一个WebAPI的Action中 ,我要怎么去取出这个表单中的数据呢?其 实我们应该想到:我们的Action设置的参数之所以能够被赋值,是因为WebAPI的架构中在调用Action时将HTTP请求中的数据解析出来分别赋 值给Action中的参数,如果真是这样的话,我们只需要在Action中获取到HTTP请求,然后直接获取请求里面的数据,就能解决上面的问题。这 种想法是正确的,只不过,此时的HTTP请求已经不是最原始的HTTP Request,而是已经被转化成了HttpRequestMessage,在Action中,我们可以直接调用base.Requet来得到这个 HttpRequestMessage实例,通过这个实例我们就可以随心所欲的取出HTTP请求中想要的数据 
 
2.1从RequestMessage中获取普通表单数据 
这里的普通表单是指不包含File的表单,也就是说表单的enctype值不是multipart/form-data,这时,表单的数据默认情况下是以Json来传递的如下页面

  1. <form name="form" action="~/api/File/Register" method="post">
  2. <input type="text" name="userName" />
  3. <br />
  4. <input type="text" name="passWord" />
  5. <br />
  6. <input type="submit" value="Submit" />
  7. </form>

ApiContoller:

  1. public class FileController : ApiController
  2. {
  3. [HttpPost]
  4. public async Task Register()
  5. {
  6. HttpContent content = Request.Content;
  7. var jsonValue = await content.ReadAsStringAsync();
  8. Console.WriteLine(jsonValue);
  9. }
  10. }

执行:userName字段输入admin,passWord字段输入123456 
传递到服务器端输出的是:userName=admin&passWord=12345 
2.2从RequestMessage中获取multipart表单数据将view页面改写为:

  1. <form name="form" action="~/api/File/SubmitFile" method="post" enctype="multipart/form-data" >
  2. <input type="text" name="userName2" />
  3. <br />
  4. <input type="text" name="passWord2" />
  5. <br />
  6. <input type="file" name="file" id="upFile" />
  7. <br />
  8. <input type="submit" value="Submit" />
  9. </form>

ApiController代码:

  1. public async Task<string> SubmitFile()
  2. {
  3. // 检查是否是 multipart/form-data
  4. if (!Request.Content.IsMimeMultipartContent("form-data"))
  5. throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
  6. var path = HttpContext.Current.Server.MapPath("~/File");
  7. // 设置上传目录
  8. var provider = new MultipartFormDataStreamProvider(path);
  9. // 接收数据,并保存文件
  10. var bodyparts = await Request.Content.ReadAsMultipartAsync(provider);
  11. var file = provider.FileData[];//provider.FormData
  12. string orfilename = file.Headers.ContentDisposition.FileName.TrimStart('"').TrimEnd('"');
  13. FileInfo fileinfo = new FileInfo(file.LocalFileName);
  14. String ymd = DateTime.Now.ToString("yyyyMMdd", System.Globalization.DateTimeFormatInfo.InvariantInfo);
  15. String newFileName = DateTime.Now.ToString("yyyyMMddHHmmss_ffff", System.Globalization.DateTimeFormatInfo.InvariantInfo);
  16. string fileExt = orfilename.Substring(orfilename.LastIndexOf('.'));
  17. fileinfo.CopyTo(Path.Combine(path, newFileName + fileExt), true);
  18. fileinfo.Delete();
  19. string result = "";
  20. // 获取表单数据
  21. result += "formData userName: " + bodyparts.FormData["userName1"];
  22. result += "<br />";
  23. // 获取文件数据
  24. result += "fileData headers: " + bodyparts.FileData[].Headers; // 上传文件相关的头信息
  25. result += "<br />";
  26. result += "fileData localFileName: " + bodyparts.FileData[].LocalFileName; // 文件在服务端的保存地址,需要的话自行 rename 或 move
  27. return result;
  28. }

还有一种简单的方式

  1. public string Post()
  2.          {
  3. HttpPostedFile file = HttpContext.Current.Request.Files[];
  4. string strPath = "D:\\MyProjects\\StudySolution\\RestDemo\\Upload\\test2.rar" ;
  5. file.SaveAs(strPath);
  6. string result = "";
  7. return result;
  8. }

注:上述的文件上传代码涉及到async、Task、await 这些关键字是 asp.net mvc 中异步编程的知识点,在这里暂不做过多解释,不了解的同学可以去先了解一下这块的内容,后期在我的系列主题文章中也还会有这块知识点的讲解,敬请关注! 
解决第二个问题:客户端或者服务端的安全控制 
WebAPI的工作方式:HTTP的请求最先是被传递到HOST中的,如果WebAPI是被寄宿在IIS上的,这个HOST就是IIS上,HOST是没有能力也没有必要进行请求的处理的,请求通过HOST被转发给了HttPServer此时已经进入WebAPI的处理加工范围,HttpServer是 System.Net.HTTP中的一个类,通过HttpServer,请求被封装成了WebAPI中的请求承载 类:HttpRequestMessage,这个封装后的请求可以经过一系列自定义的Handler来处理,这些handler串联成一个 pipeline,最后请求会被传递给HttpControlDispather,这个类通过对路由表的检索来确定请求将被转发到的具体的 Controller中的Action。 
   由此我们早就可以看出,想要解决第二个问题,可以直接在Handler PipeLine中进行,这种AOP风格的过滤器(拦截器)在REST的Webservice的安全验证中应用很广,一般大家比较乐于在HTTP头或者在 HTTP请求的URL中加上身份验证字段进行身份验证,下面举一个在Http头中添加身份验证信息的小例子: 
3.1客户端客户端的customhandler用于将身份验证信息添加入报头

  1. class RequestCheckHandler : DelegatingHandler
  2. {
  3. protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
  4. {
  5. request.Headers.Add("keyword", "ibeifeng");
  6. return base.SendAsync(request, cancellationToken);
  7. }
  8. }

注:1.customhandler继承自DelegatingHandler类,上面已经说过,WebAPI的客户端和服务端被设计为相互对应的两套结构,所以不论是在客户端还是服务端,customhandler都是继承自DelegatingHandler类2.DelegatingHandler的sendAsync方法便是处理请求和接受请求时会被调用的方法,该方法返回值是HttPResponseMessage,接收的值为HttpRequestMessage,符合我们的一般认知3.方法的最后,调用base.SendAsync是将Request继续向该pipeline的其他customHandler传递,并获取其返回值。由于该方法不包含Response的处理逻辑,只需直接将上一个CustomHandler的返回值直接返回 
 
客户端主程序:

  1. static void Main(string[] args)
  2. {
  3. HttpClient client = new HttpClient(new RequestCheckHandler() { InnerHandler = new HttpClientHandler() });
  4. HttpResponseMessage response = client.GetAsync("http://localhost:47673/api/File/GetUserInfo?userName=admin").Result;
  5. response.Content.ReadAsStringAsync().ContinueWith((str) => { Console.WriteLine(str.Result); });
  6. }

客户端的主程序创建了一个HttpClient,HttpClient可以接受一个参数,该参数就是CustomHandler,此处我们嵌入了我们定义的 RequestUpHandler,用于对Request报头进行嵌入身份验证码的处理,CustomHandler通过InnerHandler属性嵌 入其内置的下一个CustomHandler,此处,由于没有下一个CustomerHandler,我们直接嵌入HttpClientHandler用 于将HttpRequestMessage转化为HTTP 请求、将HTTP响应转化为HttpResponseMessage 
 
3.2服务端服务端的customHandler用于解析HTTP报头中的身份认证码

  1. public class SecurityHandler : DelegatingHandler
  2. {
  3.    protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken
  4.     cancellationToken)
  5. {
  6. int matchHeaderCount = request.Headers.Count((item) =>
  7. {
  8. if ("keyword".Equals(item.Key))
  9. {
  10. foreach (var str in item.Value)
  11. {
  12. if ("ibeifeng".Equals(str))
  13. {
  14. return true;
  15. }
  16. }
  17. }
  18. return false;
  19. });
  20. if (matchHeaderCount>)
  21. {
  22. return base.SendAsync(request, cancellationToken);
  23. }
  24. return Task.Factory.StartNew<HttpResponseMessage>(() => { return new HttpResponseMessage(HttpStatusCode.Forbidden); });
  25. }
  26. }

另: 
FileController中加一个测试方法:

  1.         [HttpGet]
  2. public string GetUserInfo(string userName)
  3. {
  4. if (userName == "admin")
  5. {
  6. return "success";
  7. }
  8. else {
  9. return "failed";
  10. }
  11. }

注:代码的处理逻辑很简单:如果身份验证码匹配成功,则通过base.SendAsync继续将请求向下传递,否则返回直接中断请求的传递,直接返回一个响应码为403的响应,指示没有权限。注意由于SendAsync的返回值需要封装在Task之中,所以需要使用Task.Factory.StartNew将返回值包含在Task中将customHandler注入到HOST中本例中WebAPI HOST在IIS上,所以我们只需将我们定义的CustomHandler在Application_Start中定义即可

  1. protected void Application_Start()
  2. {
  3. //省略其他逻辑代码
  4. GlobalConfiguration.Configuration.MessageHandlers.Add(new SecurityHandler ());
  5. }

由于WebAPI Host在IIS上,所以HttpServer和HttpControllerDispatcher不用我们手工处理 
在加上上面的处理后,如果没有身份验证码的请求,会得到如下的响应 
 
 
总结 
1.使用WebAPI的目的 
  当你遇到以下这些情况的时候,就可以考虑使用Web API了。 
  a. 需要Web Service但是不需要SOAP 
  b. 需要在已有的WCF服务基础上建立non-soap-based http服务 
  c. 只想发布一些简单的Http服务,不想使用相对复杂的WCF配置 
  d. 发布的服务可能会被带宽受限的设备访问 
  e. 希望使用开源框架,关键时候可以自己调试或者自定义一下框架 
2.使用WebAPI的几种方式 与注意事项 
3.使用WebAPI实现文件上传 
4.如何加强WebAPI的安全性

实例快速上手 -ASP.NET 4.5新特性WebAPI从入门到精通的更多相关文章

  1. ASP.NET 4.5新特性WebAPI从入门到精通

    在新出的MVC4中,增加了WebAPI,用于提供REST风格的WebService,新生成的WebAPI项目和典型的MVC项目一样,包含主要的Models.Views.Controllers等文件夹和 ...

  2. JDK 8 新特性,从入门到精通

    default关键字 在jdk1.8以前接口里面是只能有抽象方法,不能有任何方法的实现的. 在jdk1.8里面打破了这个规定,引入了新的关键字:default,使用default修饰方法,可以在接口里 ...

  3. 微软下一代站点开发框架:ASP.NET MVC 6 新特性揭秘

     国内第一个<微软下一代站点开发框架:ASP.NET MVC 6 新特性揭秘 >课程 微软特邀讲师 徐雷!周六晚8点YY预定:id=28447" href="htt ...

  4. WebAPI调用笔记 ASP.NET CORE 学习之自定义异常处理 MySQL数据库查询优化建议 .NET操作XML文件之泛型集合的序列化与反序列化 Asp.Net Core 轻松学-多线程之Task快速上手 Asp.Net Core 轻松学-多线程之Task(补充)

    WebAPI调用笔记   前言 即时通信项目中初次调用OA接口遇到了一些问题,因为本人从业后几乎一直做CS端项目,一个简单的WebAPI调用居然浪费了不少时间,特此记录. 接口描述 首先说明一下,基于 ...

  5. 实例快速上手UDP和TCP的使用

    TCP和UDP两个协议在Java通信编程中是如何被使用的 UDP协议与TCP协议之间的区别不再分析,主要是分析一下这两个协议在Java通信编程中是如何被使用的.首先介绍TCP,对于TCP,Java语言 ...

  6. ASP.NET 5系列教程 (一):领读新特性

    近期微软发布了ASP.NET 5.0,本次发布的新特性需求源于大量用户的反馈和需求,例如灵活的跨平台运行时和自主部署能力使ASP.NET应用不再受限于IIS.Cloud-ready环境配置降低了云端部 ...

  7. asp.net core新特性(1):TagHelper

    进步,才是人应该有的现象.-- 雨果 今天开始,我就来说说asp.net core的新特性,今天就说说TagHelper标签助手.虽然学习.net,最有帮助的就是microsoft的官方说明文档了,里 ...

  8. JS 4 新特性:混合属性(mixins)

    Ext JS4的新特征1:混合属性(mixins) 组合是Extjs4的新特性,可用于实现多继承的情况.该属性会以同步方式加载类文件,并实例化该类(译者推理其内部使用Ext.create方法).直接上 ...

  9. ES2018新特性(译文)

    原文链接:css-tricks.com 第9版ECMAScript标准于2018年6月发布,正式名称为ECMAScript 2018(简称ES2018).从ES2016开始,ECMAScript规范的 ...

随机推荐

  1. IQD文件模板以及cs7g.ini信息

    COGNOS QUERY STRUCTURE,1,1 DATABASE, cognos TITLE,test BEGIN SQL { select time, city_id, city_name, ...

  2. HttpURLConnection 文件上传限制

    一.      问题 最近在Android程序里上传向.Net服务器上传大文件的时候,发现了一个问题.当上传大文件的时候会爆出OutOfMemoryError,上传小文件则没有这种情况. 二.     ...

  3. centos安装lamp环境

    通过yum安装,需要联网且为su账号 yum -y install httpd php mysql mysql-server php-mysql 设置开启启动mysql,httpd     /sbin ...

  4. 复星昆仲杨光:VR行业四大痛点

    2016年,可是说是VR产业的爆发之年,从公司层面到资本层面都对VR产业给予了厚望,期望其能够在移动互联网之后带来革命性的突破,掀起新一轮技术主导的商业革命.而创业者们已经跃跃欲试,期望在资本的支持下 ...

  5. 通过inflate获取布局,设置layoutparams无效

    给ll——addtiem当设置layoutparams无效时,试着修改上一个布局的属性

  6. python基础整理笔记(七)

    一. python的类属性与实例属性的注意点 class TestAtt(): aaa = 10 def main(): # case 1 obj1 = TestAtt() obj2 = TestAt ...

  7. leetcode Insertion Sort List

    题目:Sort a linked list using insertion sort. 代码: /** * Definition for singly-linked list. * struct Li ...

  8. springmvc和struts2的差别

    springmvc和struts2的区别相关内容   1.spring mvc的入口是servlet,而struts2是filter filter功能:用户可以改变一个request和修改一个resp ...

  9. 二进制求最大公约数&&输出二进制

    Divided Land Time Limit: 8000/4000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Tot ...

  10. linux-13基础命令之-touch,mkdir

    1.  touch 命令 用于创建空白文件与修改文件时间,格式:touch[选项][文件]: linux 下文件时间有三种 @1.更改时间(mtime):内容修改时间: @2.更改权限(ctime): ...