通过扩展让ASP.NET Web API支持W3C的CORS规范
让ASP.NET Web API支持JSONP和W3C的CORS规范是解决“跨域资源共享”的两种途径,在《通过扩展让ASP.NET Web API支持JSONP》中我们实现了前者,并且在《W3C的CORS Specification》一文中我们对W3C的CORS规范进行了详细介绍,现在我们通过一个具体的实例来演示如何利用ASP.NET Web API具有的扩展点来实现针对CORS的支持。
目录
一、ActionFilter OR HttpMessageHandler
二、用于定义CORS资源授权策略的特性——CorsAttribute
三、实施CORS授权检验的HttpMessageHandler——CorsMessageHandler
四、CorsMessageHandler针对简单跨域资源请求的授权检验
五、CorsMessageHandler针对Preflight Request的授权检验
一、ActionFilter OR HttpMessageHandler
通过上面针对W3C的CORS规范的介绍,我们知道跨域资源共享实现的途径就是资源的提供者利用预定义的响应报头表明自己是否将提供的资源授权给了客户端JavaScript程序,而支持CORS的浏览器利用这些响应报头决定是否允许JavaScript程序操作返回的资源。对于ASP .NET Web API来说,如果我们具有一种机制能够根据预定义的资源授权规则自动生成和添加针对CORS的响应报头,那么资源的跨域共享就迎刃而解了。
那么如何利用ASP.NET Web API的扩展实现针对CORS响应报头的自动添加呢?可能有人首先想到的是利用HttpActionFilter在目标Action方法执行之后自动添加CORS响应报头。这种解决方案对于简单跨域资源请求是没有问题的,但是不要忘了:对于非简单跨域资源请求,浏览器会采用“预检(Preflight)”机制。目标Action方法只会在处理真正跨域资源请求的过程中才会执行,但是对于采用“OPTIONS”作为HTTP方法的预检请求,根本找不到匹配的目标Action方法。
为了能够有效地应付浏览器采用的预检机制,我们只能在ASP.NET Web API的消息处理管道级别实现对提供资源的授权检验和对CORS响应报头的添加。我们只需要为此创建一个自定义的HttpMessageHandler即可,不过在此之前我们先来介绍用于定义资源授权策略的CorsAttribute特性。
二、用于定义CORS资源授权策略的特性——CorsAttribute
我们将具有如下定义的CorsAttribute特性直接应用到某个HttpController或者定义其中的某个Action方法上来定义相关的资源授权策略。简单起见,我们的授权策略只考虑请求站点,而忽略请求提供的自定义报头和携带的用户凭证。如下面的代码片断所示,CorsAttribute具有一个只读属性AllowOrigins表示一组被授权站点对应的Uri数组,具体站点列表在构造函数中指定。另一个只读属性ErrorMessage表示在请求没有通过授权检验情况下返回的错误消息。
- 1: [AttributeUsage( AttributeTargets.Class| AttributeTargets.Method)]
- 2: public class CorsAttribute: Attribute
- 3: {
- 4: public Uri[] AllowOrigins { get; private set; }
- 5: public string ErrorMessage { get; private set; }
- 6:
- 7: public CorsAttribute(params string[] allowOrigins)
- 8: {
- 9: this.AllowOrigins = (allowOrigins ?? new string[0]).Select(origin => new Uri(origin)).ToArray();
- 10: }
- 11:
- 12: public bool TryEvaluate(HttpRequestMessage request, out IDictionary<string, string> headers)
- 13: {
- 14: headers = null;
- 15: string origin = request.Headers.GetValues("Origin").First();
- 16: Uri originUri = new Uri(origin);
- 17: if (this.AllowOrigins.Contains(originUri))
- 18: {
- 19: headers = this.GenerateResponseHeaders(request);
- 20: return true;
- 21: }
- 22: this.ErrorMessage = "Cross-origin request denied";
- 23: return false;
- 24: }
- 25:
- 26: private IDictionary<string, string> GenerateResponseHeaders(HttpRequestMessage request)
- 27: {
- 28: //设置响应报头"Access-Control-Allow-Methods"
- 29: string origin = request.Headers.GetValues("Origin").First();
- 30: Dictionary<string, string> headers = new Dictionary<string, string>();
- 31: headers.Add("Access-Control-Allow-Origin", origin);
- 32: if (request.IsPreflightRequest())
- 33: {
- 34: //设置响应报头"Access-Control-Request-Headers"
- 35: //和"Access-Control-Allow-Headers"
- 36: headers.Add("Access-Control-Allow-Methods", "*");
- 37: string requestHeaders = request.Headers.GetValues("Access-Control-Request-Headers").FirstOrDefault();
- 38: if (!string.IsNullOrEmpty(requestHeaders))
- 39: {
- 40: headers.Add("Access-Control-Allow-Headers", requestHeaders);
- 41: }
- 42: }
- 43: return headers;
- 44: }
- 45: }
我们将针对请求的资源授权检查定义在TryEvaluate方法中,其返回至表示请求是否通过了授权检查,输出参数headers通过返回的字典对象表示最终添加的CORS响应报头。在该方法中,我们从指定的HttpRequestMessage对象中提取表示请求站点的“Origin”报头值。如果请求站点没有在通过AllowOrigins属性表示的授权站点内,则意味着请求没有通过授权检查,在此情况下我们会将ErrorMessage属性设置为“Cross-origin request denied”。
在请求成功通过授权检查的情况下,我们调用另一个方法GenerateResponseHeaders根据请求生成我们需要的CORS响应报头。如果当前为简单跨域资源请求,只会返回针对“Access-Control-Allow-Origin”的响应报头,其值为请求站点。对于预检请求来说,我们还需要额外添加针对“Access-Control-Request-Headers”和“Access-Control-Allow-Methods”的响应报头。对于前者,我们直接采用请求的“Access-Control-Request-Headers”报头值,而后者被直接设置为“*”。
在上面的程序中,我们通过调用HttpRequestMessage的扩展方法IsPreflightRequest来判断是否是一个预检请求,该方法定义如下。从给出的代码片断可以看出,我们判断预检请求的条件是:包含报头“Origin”和“Access-Control-Request-Method”的HTTP-OPTIONS请求。
- 1: public static class HttpRequestMessageExtensions
- 2: {
- 3: public static bool IsPreflightRequest(this HttpRequestMessage request)
- 4: {
- 5: return request.Method == HttpMethod.Options &&
- 6: request.Headers.GetValues("Origin").Any() &&
- 7: request.Headers.GetValues("Access-Control-Request-Method").Any();
- 8: }
- 9: }
三、实施CORS授权检验的HttpMessageHandler——CorsMessageHandler
针对跨域资源共享的实现最终体现在具有如下定义的CorsMessageHandler类型上,它直接继承自DelegatingHandler。在实现的SendAsync方法中,CorsMessageHandler利用应用在目标Action方法或者HttpController类型上CorsAttribute来对请求实施授权检验,最终将生成的CORS报头添加到响应报头列表中。
- 1: public class CorsMessageHandler: DelegatingHandler
- 2: {
- 3: protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- 4: {
- 5: //得到描述目标Action的HttpActionDescriptor
- 6: HttpMethod originalMethod = request.Method;
- 7: bool isPreflightRequest = request.IsPreflightRequest();
- 8: if (isPreflightRequest)
- 9: {
- 10: string method = request.Headers.GetValues("Access-Control-Request-Method").First();
- 11: request.Method = new HttpMethod(method);
- 12: }
- 13: HttpConfiguration configuration = request.GetConfiguration();
- 14: HttpControllerDescriptor controllerDescriptor = configuration.Services.GetHttpControllerSelector().SelectController(request);
- 15: HttpControllerContext controllerContext = new HttpControllerContext(request.GetConfiguration(), request.GetRouteData(), request)
- 16: {
- 17: ControllerDescriptor = controllerDescriptor
- 18: };
- 19: HttpActionDescriptor actionDescriptor = configuration.Services.GetActionSelector().SelectAction(controllerContext);
- 20:
- 21: //根据HttpActionDescriptor得到应用的CorsAttribute特性
- 22: CorsAttribute corsAttribute = actionDescriptor.GetCustomAttributes<CorsAttribute>().FirstOrDefault()??
- 23: controllerDescriptor.GetCustomAttributes<CorsAttribute>().FirstOrDefault();
- 24: if(null == corsAttribute)
- 25: {
- 26: return base.SendAsync(request, cancellationToken);
- 27: }
- 28:
- 29: //利用CorsAttribute实施授权并生成响应报头
- 30: IDictionary<string,string> headers;
- 31: request.Method = originalMethod;
- 32: bool authorized = corsAttribute.TryEvaluate(request, out headers);
- 33: HttpResponseMessage response;
- 34: if (isPreflightRequest)
- 35: {
- 36: if (authorized)
- 37: {
- 38: response = new HttpResponseMessage(HttpStatusCode.OK);
- 39: }
- 40: else
- 41: {
- 42: response = request.CreateErrorResponse(HttpStatusCode.BadRequest, corsAttribute.ErrorMessage);
- 43: }
- 44: }
- 45: else
- 46: {
- 47: response = base.SendAsync(request, cancellationToken).Result;
- 48: }
- 49:
- 50: //添加响应报头
- 51: foreach (var item in headers)
- 52: {
- 53: response.Headers.Add(item.Key, item.Value);
- 54: }
- 55: return Task.FromResult<HttpResponseMessage>(response);
- 56: }
- 57: }
具体来说,我们通过注册到当前ServicesContainer上的HttpActionSelector根据请求得到描述目标Action的HttpActionDescriptor对象,为此我们需要根据请求手工生成作为HttpActionSelector的SelectAction方法参数的HttpControllerContext对象。对此有一点需要注意:由于预检请求采用的HTTP方法为“OPTIONS”,我们需要将其替换成代表真正跨域资源请求的HTTP方法,也就是预检请求的“Access-Control-Request-Method”报头值。
在得到描述目标Action的HttpActionDescriptor对象后,我们调用其GetCustomAttributes<T>方法得到应用在Action方法上的CorsAttribute特性。如果这样的特性不存在,在调用同名方法得到应用在HttpController类型上的CorsAttribute特性。
接下来我们调用CorsAttribute的TryEvaluate方法对请求实施资源授权检查并得到一组CORS响应报头,作为参数的HttpRequestMessage对象的HTTP方法应该恢复其原有的值。对于预检请求,在请求通过授权检查之后我们会创建一个状态为“200, OK”的响应,否则会根据错误消息创建创建一个状态为“400, Bad Request”的响应。
对于非预检请求来说(可能是简单跨域资源请求,也可能是继预检请求之后发送的真正的跨域资源请求),我们调用基类的SendAsync方法将请求交付给后续的HttpMessageHandler进行处理并最终得到最终的响应。我们最终将调用CorsAttribute的TryEvaluate方法得到的响应报头逐一添加到响应报头列表中。
四、CorsMessageHandler针对简单跨域资源请求的授权检验
接下来我们通过于一个简单的实例来演示同源策略针对跨域Ajax请求的限制。如图右图所示,我们利用Visual Studio在同一个解决方案中创建了两个Web应用。从项目名称可以看出,WebApi和MvcApp分别为ASP.NET Web API和MVC应用,后者是Web API的调用者。我们直接采用默认的IIS Express作为两个应用的宿主,并且固定了端口号:WebApi和MvcApp的端口号分别为“3721”和“9527”,所以指向两个应用的URI肯定不可能是同源的。我们在WebApi应用中定义了如下一个继承自ApiController的ContactsController类型,它具有的唯一Action方法GetAllContacts返回一组联系人列表。
如下面的代码片断所示,用于获取所有联系人列表的Action方法GetAllContacts返回一个JsonResult<IEnumerable<Contact>>对象,但是该方法上面应用了我们定义的CorsAttribute特性,并将“http://localhost:9527”(客户端ASP.NET MVC应用的站点)设置为允许授权的站点。
- 1: public class ContactsController : ApiController
- 2: {
- 3: [Cors("http://localhost:9527")]
- 4: public IHttpActionResult GetAllContacts()
- 5: {
- 6: Contact[] contacts = new Contact[]
- 7: {
- 8: new Contact{ Name="张三", PhoneNo="123", EmailAddress="zhangsan@gmail.com"},
- 9: new Contact{ Name="李四", PhoneNo="456", EmailAddress="lisi@gmail.com"},
- 10: new Contact{ Name="王五", PhoneNo="789", EmailAddress="wangwu@gmail.com"},
- 11: };
- 12: return Json<IEnumerable<Contact>>(contacts);
- 13: }
- 14: }
在Global.asax中,我们采用如下的方式将一个CorsMessageHandler对象添加到ASP.NET Web API的消息处理管道中。
- 1: public class WebApiApplication : System.Web.HttpApplication
- 2: {
- 3: protected void Application_Start()
- 4: {
- 5: GlobalConfiguration.Configuration.MessageHandlers.Add(new CorsMessageHandler ());
- 6: //其他操作
- 7: }
- 8: }
接下来们在MvcApp应用中定义如下一个HomeController,默认的Action方法Index会将对应的View呈现出来。
- 1: public class HomeController : Controller
- 2: {
- 3: public ActionResult Index()
- 4: {
- 5: return View();
- 6: }
- 7: }
如下所示的是Action方法Index对应View的定义。我们的目的在于:当页面成功加载之后以Ajax请求的形式调用上面定义的Web API获取联系人列表,并将自呈现在页面上。如下面的代码片断所示,Ajax调用和返回数据的呈现是通过调用jQuery的getJSON方法完成的。在此基础上直接调用我们的ASP.NET MVC程序照样会得到如右图所示的结果.
- 1: <html>
- 2: <head>
- 3: <title>联系人列表</title>
- 4: <script type="text/javascript" src="@Url.Content("~/scripts/jquery-1.10.2.js")"></script>
1: 2: </head> 3: <body> 4: <ul id="contacts"></ul> 5: <script type="text/javascript"> 6: $(function () 7: { 8: var url = "http://localhost:3721/api/contacts"; 9: $.getJSON(url, null, function (contacts) { 10: $.each(contacts, function (index, contact) 11: { 12: var html = "<li><ul>"; 13: html += "<li>Name: " + contact.Name + "</li>"; 14: html += "<li>Phone No:" + contact.PhoneNo + "</li>"; 15: html += "<li>Email Address: " + contact.EmailAddress + "</li>"; 16: html += "</ul>"; 17: $("#contacts").append($(html)); 18: }); 19: }); 20: }); 21:</script>
- 5: </body>
- 6: </html>
如果我们利用Fiddler来检测针对Web API调用的Ajax请求,如下所示的请求和响应内容会被捕捉到,我们可以清楚地看到利用CorsMessageHandler添加的“Access-Control-Allow-Origin”报头出现在响应的报头集合中。
- 1: GET http://localhost:3721/api/contacts HTTP/1.1
- 2: Host: localhost:3721
- 3: Connection: keep-alive
- 4: Accept: application/json, text/javascript, */*; q=0.01
- 5: Origin: http://localhost:9527
- 6: User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36
- 7: Referer: http://localhost:9527/
- 8: Accept-Encoding: gzip,deflate,sdch
- 9: Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh-TW;q=0.4
- 10:
- 11: HTTP/1.1 200 OK
- 12: Cache-Control: no-cache
- 13: Pragma: no-cache
- 14: Content-Length: 205
- 15: Content-Type: application/json; charset=utf-8
- 16: Expires: -1
- 17: Server: Microsoft-IIS/8.0
- 18: Access-Control-Allow-Origin: http://localhost:9527
- 19: X-AspNet-Version: 4.0.30319
- 20: X-SourceFiles: =?UTF-8?B?RTpc5oiR55qE6JGX5L2cXEFTUC5ORVQgV2ViIEFQSeahhuaetuaPnmFxOZXcgU2FtcGxlc1xDaGFwdGVyIDE0XFMxNDAzXFdlYkFwaVxhcGlcY29udGFjdHM=?=
- 21: X-Powered-By: ASP.NET
- 22: Date: Wed, 04 Dec 2013 01:50:01 GMT
- 23:
- 24: [{"Name":"张三","PhoneNo":"123","EmailAddress":"zhangsan@gmail.com"},{"Name":"李四","PhoneNo":"456","EmailAddress":"lisi@gmail.com"},{"Name":"王五","PhoneNo":"789","EmailAddress":wangwu@gmail.com}]
五、CorsMessageHandler针对Preflight Request的授权检验
从上面给出的请求和响应内容可以确定Web API的调用采用的是“简单跨域资源请求”,所以并没有采用“预检”机制。如何需要迫使浏览器采用预检机制,就需要了解我们在《W3C的CORS Specification》上面提到的简单跨域资源请求具有的两个条件
- 采用简单HTTP方法(GET、HEAD和POST);
- 不具有非简单请求报头的自定义报头。
只要打破其中任何一个条件就会迫使浏览器采用预检机制,我们选择为请求添加额外的自定义报头。在ASP.NET MVC应用用户调用Web API的View中,针对Ajax请求调用Web API的JavaScript程序被改写成如下的形式:我们在发送Ajax请求之前利用setRequestHeader函数添加了两个名称分别为“'X-Custom-Header1”和“'X-Custom-Header2”的自定义报头。
- 1: <html>
- 2: <head>
- 3: <title>联系人列表</title>
- 4: <script type="text/javascript" src="@Url.Content("~/scripts/jquery-1.10.2.js")"></script>
- 1:
- 2: </head>
- 3: <body>
- 4: <ul id="contacts"></ul>
- 5: <script type="text/javascript">
- 6: $(function ()
- 7: {
- 8: $.ajax({
- 9: url : 'http://localhost:3721/api/contacts',
- 10: type : 'GET',
- 11: success : listContacts,
- 12: beforeSend : setRequestHeader
- 13: });
- 14: });
- 15:
- 16: function listContacts(contacts)
- 17: {
- 18: $.each(contacts, function (index, contact) {
- 19: var html = "<li><ul>";
- 20: html += "<li>Name: " + contact.Name + "</li>";
- 21: html += "<li>Phone No:" + contact.PhoneNo + "</li>";
- 22: html += "<li>Email Address: " + contact.EmailAddress + "</li>";
- 23: html += "</ul>";
- 24: $("#contacts").append($(html));
- 25: });
- 26: }
- 27:
- 28: function setRequestHeader(xmlHttpRequest)
- 29: {
- 30: xmlHttpRequest.setRequestHeader('X-Custom-Header1', 'Foo');
- 31: xmlHttpRequest.setRequestHeader('X-Custom-Header2', 'Bar');
- 32: }
- 33:
- </script>
- 5: </body>
- 6: </html>
再次运行我们的ASP.NET MVC程序,依然会得正确的输出结果,但是针对Web API的调用则会涉及到两次消息交换,分别针对预检请求和真正的跨域资源请求。从下面给出的两次消息交换涉及到的请求和响应内容可以看出:自定义的两个报头名称会出现在采用“OPTIONS”作为HTTP方法的预检请求的“Access-Control-Request-Headers”报头中,利用CorsMessageHandler添加的3个报头(“Access-Control-Allow-Origin”、“Access-Control-Allow-Methods”和“Access-Control-Allow-Headers”)均出现在针对预检请求的响应中。
- 1: OPTIONS http://localhost:3721/api/contacts HTTP/1.1
- 2: Host: localhost:3721
- 3: Connection: keep-alive
- 4: Cache-Control: max-age=0
- 5: Access-Control-Request-Method: GET
- 6: Origin: http://localhost:9527
- 7: User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36
- 8: Access-Control-Request-Headers: accept, x-custom-header1, x-custom-header2
- 9: Accept: */*
- 10: Referer: http://localhost:9527/
- 11: Accept-Encoding: gzip,deflate,sdch
- 12: Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh-TW;q=0.4
- 13:
- 14: HTTP/1.1 200 OK
- 15: Cache-Control: no-cache
- 16: Pragma: no-cache
- 17: Expires: -1
- 18: Server: Microsoft-IIS/8.0
- 19: Access-Control-Allow-Origin: http://localhost:9527
- 20: Access-Control-Allow-Methods: *
- 21: Access-Control-Allow-Headers: accept, x-custom-header1, x-custom-header2
- 22: X-AspNet-Version: 4.0.30319
- 23: X-SourceFiles: =?UTF-8?B??=
- 24: X-Powered-By: ASP.NET
- 25: Date: Wed, 04 Dec 2013 02:11:16 GMT
- 26: Content-Length: 0
- 27:
- 28: --------------------------------------------------------------------------------
- 29: GET http://localhost:3721/api/contacts HTTP/1.1
- 30: Host: localhost:3721
- 31: Connection: keep-alive
- 32: Accept: */*
- 33: X-Custom-Header1: Foo
- 34: Origin: http://localhost:9527
- 35: X-Custom-Header2: Bar
- 36: User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36
- 37: Referer: http://localhost:9527/
- 38: Accept-Encoding: gzip,deflate,sdch
- 39: Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh-TW;q=0.4
- 40:
- 41: HTTP/1.1 200 OK
- 42: Cache-Control: no-cache
- 43: Pragma: no-cache
- 44: Content-Length: 205
- 45: Content-Type: application/json; charset=utf-8
- 46: Expires: -1
- 47: Server: Microsoft-IIS/8.0
- 48: Access-Control-Allow-Origin: http://localhost:9527
- 49: X-AspNet-Version: 4.0.30319
- 50: X-SourceFiles: =?UTF-8?B?RTpc5oiR55qE6JGX5L2cXEFTUC5ORVQgV2ViIEFQSeahhuaetuaPreenmFxOZXcgU2FtcGxlc1xDaGFwdGVyIDE0XF9udGFjdHM=?=
- 51: X-Powered-By: ASP.NET
- 52: Date: Wed, 04 Dec 2013 02:11:16 GMT
- 53:
- 54: [{"Name":"张三","PhoneNo":"123","EmailAddress":"zhangsan@gmail.com"},{"Name":"李四","PhoneNo":"456","EmailAddress":"lisi@gmail.com"},{"Name":"王五","PhoneNo":"789","EmailAddress":wangwu@gmail.com}]
CORS系列文章
[1] 同源策略与JSONP
[2] 利用扩展让ASP.NET Web API支持JSONP
[3] W3C的CORS规范
[4] 利用扩展让ASP.NET Web API支持CORS
[5] ASP.NET Web API自身对CORS的支持: 从实例开始
[6] ASP.NET Web API自身对CORS的支持: CORS授权策略的定义和提供
[7] ASP.NET Web API自身对CORS的支持: CORS授权检验的实施
[8] ASP.NET Web API自身对CORS的支持: CorsMessageHandler
通过扩展让ASP.NET Web API支持W3C的CORS规范的更多相关文章
- 通过扩展让ASP.NET Web API支持W3C的CORS规范(转载)
转载地址:http://www.cnblogs.com/artech/p/cors-4-asp-net-web-api-04.html CORS(Cross-Origin Resource Shari ...
- 通过扩展让ASP.NET Web API支持JSONP
同源策略(Same Origin Policy)的存在导致了"源"自A的脚本只能操作"同源"页面的DOM,"跨源"操作来源于B的页面将会被拒 ...
- (转)通过扩展让ASP.NET Web API支持JSONP
原文地址:http://www.cnblogs.com/artech/p/3460544.html 同源策略(Same Origin Policy)的存在导致了“源”自A的脚本只能操作“同源”页面的D ...
- 通过扩展让ASP.NET Web API支持JSONP -摘自网络
同源策略(Same Origin Policy)的存在导致了“源”自A的脚本只能操作“同源”页面的DOM,“跨源”操作来源于B的页面将会被拒绝.同源策略以及跨域资源共享在大部分情况下针对的是Ajax请 ...
- 通过微软的cors类库,让ASP.NET Web API 支持 CORS
前言:因为公司项目需要搭建一个Web API 的后端,用来传输一些数据以及文件,之前有听过Web API的相关说明,但是真正实现的时候,感觉还是需要挺多知识的,正好今天有空,整理一下这周关于解决COR ...
- 让ASP.NET Web API支持POST纯文本格式(text/plain)的数据
今天在web api中遇到了这样一个问题,虽然api的参数类型是string,但只能接收post body中json格式的string,不能接收原始string. web api是这样定义的: pub ...
- 让ASP.NET Web API支持$format参数的方法
在不使用OData的情况下,也可以让ASP.NET Web API支持$format参数,只要在WebApiConfig里添加如下三行红色粗体代码即可: using System; using Sys ...
- [转]让ASP.NET Web API支持$format参数的方法
本文转自:http://www.cnblogs.com/liuzhendong/p/4228592.html 在不使用OData的情况下,也可以让ASP.NET Web API支持$format参数, ...
- 让ASP.NET Web API支持text/plain内容协商
ASP.NET Web API的内容协商(Content Negotiation)机制的理想情况是这样的:客户端在请求头的Accept字段中指定什么样的MIME类型,Web API服务端就返回对应的M ...
随机推荐
- Tomat简介
Tomcat目录结构bin: 存放各种平台下启动和关闭Tomcat的脚本文件.startup.bat是windows下启动tomcat的文件,shutdown.bat是关闭tomcat的文件.comm ...
- C#改善程序的50种方法
作者: suyan010203 来源: 博客园 发布时间: 2011-07-09 14:47 阅读: 11976 次 推荐: 8 原文链接 [收藏] 从去年找工作以来,都没什么时间写博 ...
- Python爬虫学习(11):Beautiful Soup的使用
之前我们从网页中提取重要信息主要是通过自己编写正则表达式完成的,但是如果你觉得正则表达式很好写的话,那你估计不是地球人了,而且很容易出问题.下边要介绍的Beautiful Soup就可以帮你简化这些操 ...
- 可变参数列表与printf()函数的实现
问题 当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时"道行"不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数 ...
- dedecms 文章页图片改为绝对路径
这几天在网站改版,想把网站做大,想做频道页二级域名,于是在做网站的过程中发现一个问题,dedecms开设二级域名后,在二级域名的文章页无法显示图片,查看源代码后发现问题,由于dedecms文章页中的图 ...
- js中的日期控件My97 DatePicker---那些打酱油的日子
使用WdatePicker插件来渲染日期类型的页面. 以下代码用到的属性有: isShowClear是否显示清空按钮 skin皮肤的样式 readOnly是否只读 maxDate:最大的选择时间 &l ...
- Codeforces Round #345 (Div. 1) A. Watchmen
A. Watchmen time limit per test 3 seconds memory limit per test 256 megabytes input standard input o ...
- 【转】iOS开发 -- Apple Pay
技术博客原地址:http://www.cnblogs.com/dashunzi/p/ApplePay.html#top 原技术博客中有源码和视频,有感兴趣的朋友可以研究一下! 一.什么是Apple P ...
- ssh自动输入密码脚本 切换目录脚本
利用expect的,首先查看expect,命令:which expect #!/usr/bin/expect -f spawn ssh 用户名@ip地址 expect "assword:&q ...
- 在linux中设置静态ip地址
在linux中设置静态ip地址1.在终端中输入:vi /etc/sysconfig/network-scripts/ifcfg-eth0 2.开始编辑,填写ip地址.子网掩码.网关.DNS等[root ...