Cors是个比较热的技术,这在蒋金楠的博客里也有体现,Cors简单来说就是“跨域资源访问”的意思,这种访问我们指的是Ajax实现的异步访问,形象点说就是,一个A网站公开一些接口方法,对于B网站和C网站可以通过发Xmlhttprequest请求来调用A网站的方法,对于xmlhttprequest封装比较好的插件如jquery的$.ajax,它可以让开发者很容易的编写AJAX异步请求,无论是Get,Post,Put,Delete请求都可以发送。

Cors并不是什么新的技术,它只是对HTTP请求头进行了一个加工,还有我们的Cors架构里,对jsonp也有封装,让开发者在使用jsonp访问里,编写的代码量更少,更直观,呵呵。(Jsonp和Json没什么关系,它是从一个URI返回一个Script响应块,所以,JSONP本身是和域名没关系的,而传统上的JSON是走xmlhttprequest的,它在默认情况下,是不能跨域访问的)

做在后

一  下面先说一下,对jsonp的封装

1 注册jsonp类型,在global.asax里Application_Start方法中



GlobalConfiguration.Configuration.Formatters.Insert(0, new EntityFrameworks.Web.Core.JsonpMediaTypeFormatter());


2 编写JsonpMediaTypeFormatter这个类型中实现了对jsonp请求的响应,并在响应流中添加指定信息,如callback方法名。



在WebApi中实现Cors访问_封装

/// <summary>
/// 对jsonp响应流的封装
/// </summary>
public class JsonpMediaTypeFormatter : JsonMediaTypeFormatter
{
public string Callback { get; private set; }
public JsonpMediaTypeFormatter(string callback = null)
{
this.Callback = callback;
}
public override Task WriteToStreamAsync(
Type type,
object value,
Stream writeStream,
HttpContent content,
TransportContext transportContext)
{
if (string.IsNullOrEmpty(this.Callback))
{
return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
}
try
{
this.WriteToStream(type, value, writeStream, content);
return Task.FromResult<AsyncVoid>(new AsyncVoid());
}
catch (Exception exception)
{
TaskCompletionSource<AsyncVoid> source = new TaskCompletionSource<AsyncVoid>();
source.SetException(exception);
return source.Task;
}
}
private void WriteToStream(
Type type,
object value,
Stream writeStream,
HttpContent content)
{
JsonSerializer serializer = JsonSerializer.Create(this.SerializerSettings);
using (StreamWriter streamWriter = new StreamWriter(writeStream, this.SupportedEncodings.First()))
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(streamWriter) { CloseOutput = false })
{
jsonTextWriter.WriteRaw(this.Callback + "(");
serializer.Serialize(jsonTextWriter, value);
jsonTextWriter.WriteRaw(")");
}
}
public override MediaTypeFormatter GetPerRequestFormatterInstance(
Type type,
HttpRequestMessage request,
MediaTypeHeaderValue mediaType)
{
if (request.Method != HttpMethod.Get)
{
return this;
}
string callback;
if (request.GetQueryNameValuePairs().ToDictionary(pair => pair.Key,
pair => pair.Value).TryGetValue("callback", out callback))
{
return new JsonpMediaTypeFormatter(callback);
}
return this;
}
[StructLayout(LayoutKind.Sequential, Size = 1)]
private struct AsyncVoid
{
}

}


在WebApi中实现Cors访问_封装


 

二  对指定域名实现友好的跨域资源访问

1 在global.asax中注册这个HttpHandler,使它对HTTP的处理进行二次加工,它可以有同步和异步两个版本,本例中实现异步方式实现



//对指定URI的网站进行跨域资源的共享
GlobalConfiguration.Configuration.MessageHandlers.Add(new EntityFrameworks.Web.Core.Handlers.CorsMessageHandler());


下面是MessageHandlers原代码,实现对HTTP请求的二次处理



在WebApi中实现Cors访问_封装

/// <summary>
/// 跨域资源访问的HTTP处理程序
/// </summary>
public class CorsMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//得到描述目标Action的HttpActionDescriptor
HttpMethod originalMethod = request.Method;
bool isPreflightRequest = request.IsPreflightRequest();
if (isPreflightRequest)
{
string method = request.Headers.GetValues("Access-Control-Request-Method").First();
request.Method = new HttpMethod(method);
}

HttpConfiguration configuration = request.GetConfiguration();
HttpControllerDescriptor controllerDescriptor = configuration.Services.GetHttpControllerSelector().SelectController(request);
HttpControllerContext controllerContext = new HttpControllerContext(request.GetConfiguration(), request.GetRouteData(), request)
{
ControllerDescriptor = controllerDescriptor
};
HttpActionDescriptor actionDescriptor = configuration.Services.GetActionSelector().SelectAction(controllerContext);
//根据HttpActionDescriptor得到应用的CorsAttribute特性
CorsAttribute corsAttribute = actionDescriptor.GetCustomAttributes<CorsAttribute>().FirstOrDefault() ??
controllerDescriptor.GetCustomAttributes<CorsAttribute>().FirstOrDefault();
if (null == corsAttribute)
{
return base.SendAsync(request, cancellationToken);
}
//利用CorsAttribute实施授权并生成响应报头
IDictionary<string, string> headers;
request.Method = originalMethod;
bool authorized = corsAttribute.TryEvaluate(request, out headers);
HttpResponseMessage response;
if (isPreflightRequest)
{
if (authorized)
{
response = new HttpResponseMessage(HttpStatusCode.OK);
}
else
{
response = request.CreateErrorResponse(HttpStatusCode.BadRequest, corsAttribute.ErrorMessage);
}
}
else
{
response = base.SendAsync(request, cancellationToken).Result;
}

//添加响应报头
if (headers != null && headers.Any())
foreach (var item in headers)
response.Headers.Add(item.Key, item.Value);

return Task.FromResult<HttpResponseMessage>(response);
}
}


在WebApi中实现Cors访问_封装


2 添加Cors特性,以便处理可以跨域访问的域名,如B网站和C网站



在WebApi中实现Cors访问_封装

/// <summary>
/// Cors特性
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class CorsAttribute : Attribute
{
public Uri[] AllowOrigins { get; private set; }
public string ErrorMessage { get; private set; }
public CorsAttribute(params string[] allowOrigins)
{
this.AllowOrigins = (allowOrigins ?? new string[0]).Select(origin => new Uri(origin)).ToArray();
}
public bool TryEvaluate(HttpRequestMessage request, out IDictionary<string, string> headers)
{
headers = null;
string origin = null;
try
{
origin = request.Headers.GetValues("Origin").FirstOrDefault();
}
catch (Exception)
{
this.ErrorMessage = "Cross-origin request denied";
return false;
}
Uri originUri = new Uri(origin);
if (this.AllowOrigins.Contains(originUri))
{
headers = this.GenerateResponseHeaders(request);
return true;
}

this.ErrorMessage = "Cross-origin request denied";
return false;
}

private IDictionary<string, string> GenerateResponseHeaders(HttpRequestMessage request)
{

//设置响应头"Access-Control-Allow-Methods"

string origin = request.Headers.GetValues("Origin").First();

Dictionary<string, string> headers = new Dictionary<string, string>();

headers.Add("Access-Control-Allow-Origin", origin);

if (request.IsPreflightRequest())
{
//设置响应头"Access-Control-Request-Headers"
//和"Access-Control-Allow-Headers"
headers.Add("Access-Control-Allow-Methods", "*");

string requestHeaders = request.Headers.GetValues("Access-Control-Request-Headers").FirstOrDefault();

if (!string.IsNullOrEmpty(requestHeaders))
{
headers.Add("Access-Control-Allow-Headers", requestHeaders);
}
}
return headers;
}
}

/// <summary>
/// HttpRequestMessage扩展方法
/// </summary>
public static class HttpRequestMessageExtensions
{
public static bool IsPreflightRequest(this HttpRequestMessage request)
{
return request.Method == HttpMethod.Options
&& request.Headers.GetValues("Origin").Any()
&& request.Headers.GetValues("Access-Control-Request-Method").Any();
}
}


在WebApi中实现Cors访问_封装


3 下面是为指定的API类型添加指定域名访问的特性



[CorsAttribute("http://localhost:11879/", "http://localhost:5008/")]/*需要加在类上*/
public class ValuesController : ApiController
{
//代码省略
}


下面看一下實例的結果:

在WebApi中实现Cors访问_json_07

上图中分别使用了jsonp和json两种方法,看一下它们的响应结果

在WebApi中实现Cors访问_json_08

在WebApi中实现Cors访问_json_09

CORS实际上是在服务端的响应头上添加的标准的Access-Control-Allow-Origin的信息,它是一种跨域资源访问的标准

在WebApi中实现Cors访问_封装_10

可以看到,jsonp实现上是一种远程JS方法的调用,客户端发起一个HTTP请求,这通过callback参数(一串随机数)来区别多个客户端,每个客户端的请求callback都是不同的,它们由服务器端处理数据,再通过callback随机数去为指定客户端返回数据。