快速入门系列--WebAPI--04在老版本MVC4下的调整

WebAPI是建立在MVC和WCF的基础上的,原来微软老是喜欢封装的很多,这次终于愿意将http编程模型的相关细节暴露给我们了。在之前的介绍中,基本上都基于.NET 4.5之后版本,其System.Net.Http程序集非常的丰富,而老版本的则相对较弱。在WebAPI v1.0(和ASP.NET MVC4在一起的版本)很多的类和接口并不存在,同时对Task异步编程(ApiController默认提供异步执行方法)的支持还有一些欠缺(缺少不少方便的扩展方法),在使用时会有一些需要注意的地方,由于一些老的项目用的.NET 4.0的程序集,无法升级和使用一些新的dll,因而部分功能需要自己来考虑,本文旨在将自己遇到的一些困难分享给大家。

  • 路由设置

在Global.asax文件中需要注意WebApi的路由要先于MVC的路由进行注册,不然会出现路由无效的情况。

前端路由地址的提供,使用@Url.HttpRouteUrl("AddedApi", new { controller = "SMSCenterApi", action = "MassTexting" })来生成路由,与MVC的方式有一些差异,需要注意。

  • 参数绑定

包括ModelBinder和MediaTypeFormatter两种方式,与MVC不同(MVC均使用ModelBinder进行绑定)。前者包括针对数组、集合、字典、简单和复杂类型的绑定器,后者其实就是一个序列化器,默认包括3中:Json.NET的json序列化器(用的最多);DataContractSerializer和XMLSerializer用于序列化XML;最后一种解码表单URL,编码主体数据。这些格式化器均在System.Net.Http.Formatting命名空间中。

相关的特性包括:ModelBindingAttribute,默认绑定逻辑;FormUriAttribute,只从Uri获取值;FromBodyAtrribute,使用MediaTypeFormatter媒体格式化器,也是我们在WebAPi最常用的,再次提醒一下,一定要提供contentType哦,比如"application/json"。

Tip:模型绑定常见问题,WebAPI的格式化器Formatter需要提供相应的contentType才会起作用,返回值通过dataType设置(默认为XML),一定不能忘记内容协商,需要注意内容协商,附上一个ajax调用的例子,我在这也吃了很大的亏,默认formatter其实做了很多事情哦。

这儿强烈提醒的是dataType表示返回值类型,contentType为请求体的类型,熊二你个二货,内容协商是必须的,不然别人哪知道怎么做!此外,这个的dataType='json'最终反应到http请求体中为Accept: application/json,

这个对于你使用过滤器拦截并新建httpMessageResponse的HttpContent时非常有用,最后的例子会涉及这部分内容。

  • 过滤请求

过去我们常常将一些验证逻辑和异常处理逻辑放在Controller中,极大的增加了Controller的复杂性,完全可以通过面向切面(AOP)来处理,在.NET 4.0提供的相关基类和接口如下所示:

异步接口和同步基类

用途

IAuthenticationFilter AuthorizationFilterAttribute

认证过滤器可以在参数绑定发生以前运行,它们计划过滤没有正确认证且请求争议操作的请求 认证过滤器先于操作过滤器运行,应用场景为验证客户身份,例如去Cookie或HttpHead中获取相关验证信息

IActionFilter ActionFilterAttribute

操作过滤器在参数绑定时发生,并封装API操作方法调用之后运行,允许在调度操作之前,完成执行之后拦截。操作过滤器的目标时允许开发人员增加和替换操作的输入值和输出结果。如果说自定义绑定器或格式化器是用于扩展正常状态下解析数据的话,那么过滤器可以用在一些特殊情况下

IExceptionFilter ExceptionFilterAttribute

当调用操作抛出异常时,就会调用异常过滤器,可以检查异常,并采取一些操作,例如记录日志、提供新的响应对象来处理异常等

Tip: 在MVC4中,推荐使用同步基类,在以后的版本中推荐使用异步接口对应用程序进行扩展。

此外,需要注意过滤器的使用范围,包括:全局,在FilterConfig中添加;类级别过滤器,通过添加特性的方式;方法级别过滤器。

默认提供AuthorizeAttribute完成基础验证,AllowAnonymousAttribute提供匿名验证的情况。此外还提供一个关于OData的第三方解决方案,包括可以自动支持OData查询语法的QueryableAttribute(如$top和$filter等)。

  • 其他小知识点

WebAPI的托管,包括通过System.Web.Http.WebHost.dll的IIS托管,配置对象为GlobalConfiguration;自托管的配置,通过Mocrosoft.AspNet.WebApi.Selfhost。

可以通过HttpConfiguration.Service获取IApiExplorer服务,即全领域搜索可用服务。

通过ITraceWriter来跟踪应用程序,可以很方便的和ETW、Log4net、ELMAH等跟踪服务集成。

  • 简单示例程序,包括过滤器的使用,JQuery的调用,请求的简易验签

Controller:

 1 public class SMSCenterApiController : ApiController
 2 {
 3 [HttpPost]
 4 [CheckPermissionFilter]
 5 [ApiExceptionFilter]
 6 public WebApiResult MassTexting([FromBody]SMSCenterViewModel model)
 7 {
 8 var result = new WebApiResult { Status = WebApiResultStatus.Fail, Message = string.Empty };
 9 
10 int sendedNum = SMSCenterBL.Instance.MassTexting(model.SMSContent, model.PhoneList, "xionger"); 
11 result.Status = WebApiResultStatus.Success;
12 result.Message = sendedNum.ToString();
13 return result;
14 }

Jquery调用:

 1 jQuery.ajax({
 2 type: 'POST',
 3 url: url,
 4 contentType: "application/json",
 5 dataType: 'json',
 6 data: postData,
 7 beforeSend: function (request) {
 8 request.setRequestHeader("smsToken", smsToken);
 9 },
10 success: function (data) {
11 if (data.Succ == 1) {
12 var msg = "发送结束。成功{0} --- 共{1}";
13 msg = msg.format(data.Count, data.Count);
14 alert(msg);
15 }
16 else {
17 alert("发送失败。");
18 }
19 },
20 error: function (data) {
21 alert("发送失败。");
22 }
23 });

CheckPremissionFilter:

 1 public class CheckPermissionFilterAttribute : AuthorizationFilterAttribute
 2 {
 3 #region 验证权限
 4 /// <summary>
 5 /// 验证权限
 6 /// </summary>
 7 public override void OnAuthorization(HttpActionContext actionContext)
 8 {
 9 var token = GetHttpToken(actionContext.Request.Headers);
10 if (SMSTokenHelper.CheckToken(token))
11 {
12 base.OnAuthorization(actionContext);
13 }
14 else
15 {
16 throw new BizException("当前请求没有访问权限!");
17 }
18 }
19 #endregion
20 
21 #region 辅助方法
22 /// <summary>
23 /// 获得请求头中的token信息
24 /// </summary>
25 private string GetHttpToken(HttpRequestHeaders headers)
26 {
27 IEnumerable<string> tokenCollection;
28 if (headers.TryGetValues(ConfigHelper.SMSCENTER_TOKEN_NAME, out tokenCollection))
29 {
30 var token = tokenCollection.FirstOrDefault();
31 return token;
32 }
33 return null;
34 }
35 #endregion
36 }

SMSTokenHelper:

  1 internal class SMSTokenHelper
  2 {
  3 public static string CreateToken(string eid)
  4 {
  5 //1.使用eid,moduleID,当前时间构建认证对象
  6 var token = new SMSToken()
  7 {
  8 EID = eid,
  9 ModuleID = ConfigHelper.SMSCENTER_MODULE_ID,
 10 CurrentTime = DateTime.Now.ToString()
 11 };
 12  
 13 //2.转化为Json字符串
 14 var tokenString = JsonConvert.SerializeObject(token);
 15 //3.将json字符串加密
 16 var encryptToken = DESHelper.DESEncrypt(tokenString);
 17 return encryptToken;
 18 }
 19 
 20 public static bool CheckToken(string token)
 21 {
 22 try
 23 {
 24 //1.解密
 25 var tokenString = DESHelper.DESDecrypt(token);
 26 //2.反序列化为对象
 27 var smstoken = JsonConvert.DeserializeObject<SMSToken>(tokenString);
 28 //3.验证结果
 29 if (ConfigHelper.SMSCENTER_MODULE_ID == smstoken.ModuleID)
 30 {
 31 return true;
 32 }
 33 }
 34 catch { }
 35 return false;
 36 }
 37 
 38 private class SMSToken
 39 {
 40 public string EID { get; set; }
 41 public string ModuleID { get; set; }
 42 public string CurrentTime { get; set; }
 43 }
 44 
 45 #region 辅助类
 46 /// <summary>
 47 /// DES加密解密
 48 /// </summary>
 49 private class DESHelper
 50 {
 51 /// <summary>
 52 /// 获取密钥
 53 /// </summary>
 54 private static string Key
 55 {
 56 get { return @"P@+#wG+Z"; }
 57 }
 58 
 59 /// <summary>
 60 /// 获取向量
 61 /// </summary>
 62 private static string IV
 63 {
 64 get { return @"L%n67}G/Mk@k%:~Y"; }
 65 }
 66  
 67 /// <summary>
 68 /// DES加密
 69 /// </summary>
 70 /// <param name="plainStr">明文字符串</param>
 71 /// <returns>密文</returns>
 72 public static string DESEncrypt(string plainStr)
 73 {
 74 byte[] bKey = Encoding.UTF8.GetBytes(Key);
 75 byte[] bIV = Encoding.UTF8.GetBytes(IV);
 76 byte[] byteArray = Encoding.UTF8.GetBytes(plainStr);
 77 
 78 string encrypt = null;
 79 DESCryptoServiceProvider des = new DESCryptoServiceProvider();
 80 try
 81 {
 82 using (MemoryStream mStream = new MemoryStream())
 83 {
 84 using (CryptoStream cStream = new CryptoStream(mStream, des.CreateEncryptor(bKey, bIV), CryptoStreamMode.Write))
 85 {
 86 cStream.Write(byteArray, 0, byteArray.Length);
 87 cStream.FlushFinalBlock();
 88 encrypt = Convert.ToBase64String(mStream.ToArray());
 89 }
 90 }
 91 }
 92 catch { }
 93 des.Clear();
 94 return encrypt;
 95 }
 96 
 97 /// <summary>
 98 /// DES解密
 99 /// </summary>
100 /// <param name="encryptStr">密文字符串</param>
101 /// <returns>明文</returns>
102 public static string DESDecrypt(string encryptStr)
103 {
104 byte[] bKey = Encoding.UTF8.GetBytes(Key);
105 byte[] bIV = Encoding.UTF8.GetBytes(IV);
106 byte[] byteArray = Convert.FromBase64String(encryptStr);
107 
108 string decrypt = null;
109 DESCryptoServiceProvider des = new DESCryptoServiceProvider();
110 try
111 {
112 using (MemoryStream mStream = new MemoryStream())
113 {
114 using (CryptoStream cStream = new CryptoStream(mStream, des.CreateDecryptor(bKey, bIV), CryptoStreamMode.Write))
115 {
116 cStream.Write(byteArray, 0, byteArray.Length);
117 cStream.FlushFinalBlock();
118 decrypt = Encoding.UTF8.GetString(mStream.ToArray());
119 }
120 }
121 }
122 catch { }
123 des.Clear();
124 return decrypt;
125 }
126 }
127 #endregion
128 }
129 }

Tip: DES加密部分借鉴博主IT合伙人文章http://www.cnblogs.com/IT-haidong/p/4856848.html

最后,补充一个在MVC4.0下的自定义ModerBinder,非常的简单,但可以帮助实现json数据的绑定,简化使用。当然使用JQuery的form.serialize(),将数据转化为form提交,然后应用默认的绑定器也是ok的。以前一直form提交也没有认真去想想form的区别,其实form是用"&"符号来连接数据的。

 1 [AttributeUsage(AttributeTargets.Parameter)]
 2 public class FromBodyAttribute : CustomModelBinderAttribute
 3 {
 4 private static readonly ILog _logger = LogManager.GetLogger(typeof(FromBodyAttribute));
 5  
 6 public override IModelBinder GetBinder()
 7 {
 8 return new JsonModelBinder();
 9 }
10 
11 public class JsonModelBinder : IModelBinder
12 {
13 public object BindModel(ControllerContext controllerContext,
14 ModelBindingContext bindingContext)
15 {
16 if (!controllerContext.HttpContext.Request.ContentType.ToLower().Contains("json"))
17 {
18 return null;
19 }
20 try
21 {
22 var jsonString = GetJsonString(controllerContext.HttpContext.Request.InputStream);
23 var result = JsonConvert.DeserializeObject(jsonString, bindingContext.ModelType);
24 return result;
25 }
26 catch(Exception ex)
27 {
28 _logger.Warn(ex.Message, ex);
29 }
30 return null;
31 }
32 
33 private string GetJsonString(Stream stream)
34 {
35 var jsonString = string.Empty;
36 using (var sr = new StreamReader(stream))
37 {
38 stream.Position = 0;
39 jsonString = sr.ReadToEnd();
40 }
41 return jsonString;
42 }
43 }
44 }

此外,WebAPI学习系列目录如下,欢迎您的阅读!

快速入门系列--WebAPI--01基础

快速入门系列--WebAPI--02进阶

快速入门系列--WebAPI--03框架你值得拥有

快速入门系列--WebAPI--04在老版本MVC4下的调整

参考资料:

  1. (美)加洛韦. ASP.NET MVC 4高级编程(第4版)[M]. 北京:清华大学出版社, 2012.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android 研究

OKHttp源码解析(八)--中阶之连接与请求前奏

在http请求中,对于请求速度提升和降低延迟,keepalive在网络连接发挥着重大作用。

39820
来自专栏分布式系统进阶

ReplicaManager源码解析1-消息同步线程管理

基本上就是作三件事: 构造FetchRequest, 同步发送FetchRequest并接收FetchResponse, 处理FetchResponse, 这三...

17920
来自专栏圣杰的专栏

ASP.NET Core 中断请求了解一下(翻译)

假设有一个耗时的Action,在浏览器发出请求返回响应之前,如果刷新了页面,对于浏览器(客户端)来说前一个请求就会被终止。而对于服务端来说,又是怎样呢?前一个请...

15330
来自专栏Star先生的专栏

从源码中分析 Hadoop 的 RPC 机制

RPC是Remote Procedure Call(远程过程调用)的简称,这一机制都要面对两个问题:对象调用方式余与序列/反序列化机制。本文给大家介绍从源码中分...

77100
来自专栏Urahara Blog

Joomla V3.7.0 核心组件SQL注入漏洞分析

20840
来自专栏熊二哥

快速入门系列--MVC--05行为

    Action执行包含内容比较多,主要有同步/异步Action的概念和执行过程,Authorationfilter, ActionFiltor, Resu...

20170
来自专栏高性能服务器开发

redis网络通信模块源码分析(下)

这里注册可写事件AE_WRITABLE的回调函数是sendReplyToClient。也就是说,当下一次某个触发可写事件时,调用的就是sendReplyToCl...

41840
来自专栏腾讯IVWEB团队的专栏

动手写 js 沙箱

市面上现在流行两种沙箱模式,一种是使用iframe,还有一种是直接在页面上使用new Function + eval进行执行。殊途同归,主要还是防止一些Hack...

90400
来自专栏大内老A

WCF后续之旅(10): 通过WCF Extension实现以对象池的方式创建Service Instance

我们知道WCF有3种典型的对service instance进行实例化的方式,他们分别与WCF的三种InstanceContextMode相匹配,他们分别是Pe...

22180
来自专栏Rindew的iOS技术分享

苏宁一面

18740

扫码关注云+社区

领取腾讯云代金券