注:本文是【ASP.NET Web API系列教程】的一部分,如果您是第一次看本系列教程,请先看前面的内容。
本文引自:http://www.asp.net/web-api/overview/working-with-http/http-message-handlers
By Mike Wasson | February 13, 2012
作者:Mike Wasson |日期:2012-2-13
A message handler is a class that receives an HTTP request and returns an HTTP response. Message handlers derive from the abstract HttpMessageHandler class.
消息处理器是一个接收HTTP请求并返回HTTP响应的类。消息处理器派生于HttpMessageHandler类。
Typically, a series of message handlers are chained together. The first handler receives an HTTP request, does some processing, and gives the request to the next handler. At some point, the response is created and goes back up the chain. This pattern is called a delegating handler.
典型地,一系列消息处理器被链接在一起。第一个处理器接收HTTP请求、进行一些处理,并将该请求交给下一个处理器。在某个点上,创建响应并沿链返回。这种模式称为委托处理器(delegating handler)(如图5-1所示)。
On the server side, the Web API pipeline uses some built-in message handlers:
在服务器端,Web API管线使用一些内建的消息处理器:
You can add custom handlers to the pipeline. Message handlers are good for cross-cutting concerns that operate at the level of HTTP messages (rather than controller actions). For example, a message handler might:
可以把自定义处理器添加到该管线上。对于在HTTP消息层面上(而不是在控制器动作上)进行操作的交叉关注,消息处理器是很有用的。例如,消息处理器可以:
This diagram shows two custom handlers inserted into the pipeline:
下图显示了插入到管线的两个自定义处理器(见图5-2):
On the client side, HttpClient also uses message handlers. For more information, see HttpClient Message Handlers.
在客户端,HttpClient也使用消息处理器。更多信息参阅“Http消息处理器”(本系列教程的第3.4小节 — 译者注)。
To write a custom message handler, derive from System.Net.Http.DelegatingHandler and override the SendAsync method. This method has the following signature:
要编写一个自定义消息处理器,需要通过System.Net.Http.DelegatingHandler进行派生,并重写SendAsync方法。该方法有以下签名:
Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken);
The method takes an HttpRequestMessage as input and asynchronously returns an HttpResponseMessage. A typical implementation does the following:
该方法以HttpRequestMessage作为输入参数,并异步地返回一个HttpResponseMessage。典型的实现要做以下事:
Here is a trivial example:
以下是一个价值不高的示例:
public class MessageHandler1 : DelegatingHandler { protected async override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { Debug.WriteLine("Process request"); // Call the inner handler. // 调用内部处理器。 var response = await base.SendAsync(request, cancellationToken); Debug.WriteLine("Process response"); return response; } }
The call to base.SendAsync is asynchronous. If the handler does any work after this call, use the await keyword, as shown.
对base.SendAsync的调用是异步的。如果处理器在调用之后有工作要做,需使用await关键字,如上所示。
A delegating handler can also skip the inner handler and directly create the response:
委托处理器也可以跳过内部处理器,并直接创建响应:
public class MessageHandler2 : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { // Create the response. // 创建响应。 var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("Hello!") };
// Note: TaskCompletionSource creates a task that does not contain a delegate. // 注:TaskCompletionSource创建一个不含委托的任务。 var tsc = new TaskCompletionSource<HttpResponseMessage>(); tsc.SetResult(response); // Also sets the task state to "RanToCompletion" // 也将此任务设置成“RanToCompletion(已完成)” return tsc.Task; } }
If a delegating handler creates the response without calling base.SendAsync, the request skips the rest of the pipeline. This can be useful for a handler that validates the request (creating an error response).
如果委托处理器未调用base.SendAsync创建了响应,该请求会跳过管线的其余部分(如图5-3所示)。这对验证请求(创建错误消息)的处理器,可能是有用的。
To add a message handler on the server side, add the handler to the HttpConfiguration.MessageHandlers collection. If you used the "ASP.NET MVC 4 Web Application" template to create the project, you can do this inside the WebApiConfig class:
要在服务器端添加消息处理器,需要将该处理器添加到HttpConfiguration.MessageHandlers集合。如果创建项目用的是“ASP.NET MVC 4 Web应用程序”模板,你可以在WebApiConfig类中做这件事:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.MessageHandlers.Add(new MessageHandler1()); config.MessageHandlers.Add(new MessageHandler2());
// Other code not shown(未列出其它代码)... } }
Message handlers are called in the same order that they appear in MessageHandlers collection. Because they are nested, the response message travels in the other direction. That is, the last handler is the first to get the response message.
消息处理器是按它们在MessageHandlers集合中出现的顺序来调用的。因为它们是内嵌的,响应消息以反方向运行,即,最后一个处理器最先得到响应消息。
Notice that you don't need to set the inner handlers; the Web API framework automatically connects the message handlers.
注意,不需要设置内部处理器;Web API框架会自动地连接这些消息处理器。
If you are self-hosting, create an instance of the HttpSelfHostConfiguration class and add the handlers to the MessageHandlers collection.
如果是“自托管”(本系列教程的第8.1小节 — 译者注)的,需要创建一个HttpSelfHostConfiguration类的实例,并将处事器添加到MessageHandlers集合。
var config = new HttpSelfHostConfiguration("http://localhost"); config.MessageHandlers.Add(new MessageHandler1()); config.MessageHandlers.Add(new MessageHandler2());
Now let's look at some examples of custom message handlers.
现在,让我们看一些自定义消息处理器的例子。
X-HTTP-Method-Override is a non-standard HTTP header. It is designed for clients that cannot send certain HTTP request types, such as PUT or DELETE. Instead, the client sends a POST request and sets the X-HTTP-Method-Override header to the desired method. For example:
X-HTTP-Method-Override是一个非标准的HTTP报头。这是为不能发送某些HTTP请求类型(如PUT或DELETE)的客户端而设计的。因此,客户端可以发送一个POST请求,并把X-HTTP-Method-Override设置为所希望的方法(意即,对于不能发送PUT或DELETE请求的客户端,可以让它发送POST请求,然后用X-HTTP-Method-Override把这个POST请求重写成PUT或DELETE请求 — 译者注)。例如:
X-HTTP-Method-Override: PUT
Here is a message handler that adds support for X-HTTP-Method-Override:
以下是添加了X-HTTP-Method-Override支持的一个消息处事器:
public class MethodOverrideHandler : DelegatingHandler { readonly string[] _methods = { "DELETE", "HEAD", "PUT" }; const string _header = "X-HTTP-Method-Override";
protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { // Check for HTTP POST with the X-HTTP-Method-Override header. // 检查带有X-HTTP-Method-Override报头的HTTP POST。 if (request.Method == HttpMethod.Post && request.Headers.Contains(_header)) { // Check if the header value is in our methods list. // 检查其报头值是否在我们的方法列表中 var method = request.Headers.GetValues(_header).FirstOrDefault(); if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase)) { // Change the request method. // 修改请求方法 request.Method = new HttpMethod(method); } } return base.SendAsync(request, cancellationToken); } }
In the SendAsync method, the handler checks whether the request message is a POST request, and whether it contains the X-HTTP-Method-Override header. If so, it validates the header value, and then modifies the request method. Finally, the handler calls base.SendAsync to pass the message to the next handler.
在SendAsync方法中,处理器检查了该请求消息是否是一个POST请求,以及它是否含有X-HTTP-Method-Override报头。如果是,它会验证报头值,然后修改请求方法。最后,处理器调用base.SendAsync,把消息传递给下一下处事器。
When the request reaches the HttpControllerDispatcher class, HttpControllerDispatcher will route the request based on the updated request method.
当请求到达HttpControllerDispatcher类时,HttpControllerDispatcher会根据这个已被更新的请求方法对该请求进行路由。
Here is a message handler that adds a custom header to every response message:
以下是一个把自定义报头添加到每个响应消息的消息处理器:
// .Net 4.5 public class CustomHeaderHandler : DelegatingHandler { async protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { HttpResponseMessage response = await base.SendAsync(request, cancellationToken); response.Headers.Add("X-Custom-Header", "This is my custom header."); return response; } }
First, the handler calls base.SendAsync to pass the request to the inner message handler. The inner handler returns a response message, but it does so asynchronously using a Task<T> object. The response message is not available until base.SendAsync completes asynchronously.
首先,该处理器调用base.SendAsync把请求传递给内部消息处理器。内部处理器返回一条响应消息,但它是用Task<T>对象异步地做这件事的。在base.SendAsync异步完成之前,响应消息是不可用的。
This example uses the await keyword to perform work asynchronously after SendAsync completes. If you are targeting .NET Framework 4.0, use the Task.ContinueWith method:
这个示例使用了await关键字,以便在SendAsync完成之后异步地执行任务。如果你的目标框架是.NET Framework 4.0,需使用Task.ContinueWith方法:
public class CustomHeaderHandler : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { return base.SendAsync(request, cancellationToken).ContinueWith( (task) => { HttpResponseMessage response = task.Result; response.Headers.Add("X-Custom-Header", "This is my custom header."); return response; } ); } }
Some web services require clients to include an API key in their request. The following example shows how a message handler can check requests for a valid API key:
有些Web服务需要客户端在其请求中包含一个API键。以下示例演示一个消息处理器如何针对一个有效的API键来检查请求:
public class ApiKeyHandler : DelegatingHandler { public string Key { get; set; }
public ApiKeyHandler(string key) { this.Key = key; }
protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { if (!ValidateKey(request)) { var response = new HttpResponseMessage(HttpStatusCode.Forbidden); var tsc = new TaskCompletionSource<HttpResponseMessage>(); tsc.SetResult(response); return tsc.Task; } return base.SendAsync(request, cancellationToken); }
private bool ValidateKey(HttpRequestMessage message) { var query = message.RequestUri.ParseQueryString(); string key = query["key"]; return (key == Key); } }
This handler looks for the API key in the URI query string. (For this example, we assume that the key is a static string. A real implementation would probably use more complex validation.) If the query string contains the key, the handler passes the request to the inner handler.
该处理器在URI查询字符串中查找API键(对此例而言,我们假设键是一个静态字符串。一个真实的实现可能会使用更复杂的验证。)如果查询字符串包含这个键,该处理便把请求传递给内部处理器。
If the request does not have a valid key, the handler creates a response message with status 403, Forbidden. In this case, the handler does not call base.SendAsync, so the inner handler never receives the request, nor does the controller. Therefore, the controller can assume that all incoming requests have a valid API key.
如果请求没有一个有效的键,该处理器便创建一个状态为“403 — 拒绝访问”的响应消息。在这种情况下,该处理器不会调用base.SendAsync,于是,内部处理不会收到这个请求,控制器也就不会收到这个请求了。因此,控制器可以假设所有输入请求都有一个有效的API键。
If the API key applies only to certain controller actions, consider using an action filter instead of a message handler. Action filters run after URI routing is performed.
如果API键仅运用于某些控制器动作,请考虑使用动作过滤器,而不是消息处理器。动作过滤在URI路由完成之后运行。
Handlers in the HttpConfiguration.MessageHandlers collection apply globally.
HttpConfiguration.MessageHandlers集合中的处理器是全局运用的。
Alternatively, you can add a message handler to a specific route when you define the route:
另一种办法是,在定义路由时,对一个特定的路由添加消息处理器:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "Route1", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );
config.Routes.MapHttpRoute( name: "Route2", routeTemplate: "api2/{controller}/{id}", defaults: new { id = RouteParameter.Optional }, constraints: null, handler: new MessageHandler2() // per-route message handler(单路由消息处理器) );
config.MessageHandlers.Add(new MessageHandler1()); // global message handler(全局消息处理器) } }
In this example, if the request URI matches "Route2", the request is dispatched to MessageHandler2. The following diagram shows the pipeline for these two routes:
在这个例子中,如果请求的URI与“Route2”匹配,该请求被分发给MessageHandler2。图5-4展示了这两个路由的管线:
Notice that MessageHandler2 replaces the default HttpControllerDispatcher. In this example, MessageHandler2 creates the response, and requests that match "Route2" never go to a controller. This lets you replace the entire Web API controller mechanism with your own custom endpoint.
注意,MessageHandler2替换了默认的HttpControllerDispatcher。在这个例子中,MessageHandler2创建响应,而与“Route2”匹配的请求不会到达控制器。这让你可以用自己的自定义端点来替换整个Web API控制器机制。
Alternatively, a per-route message handler can delegate to HttpControllerDispatcher, which then dispatches to a controller.
另一种办法是,单路由消息处理器可以委托给HttpControllerDispatcher,然后由它来分发给控制器(如图5-5所示)。
The following code shows how to configure this route:
以下代码演示如何配置这种路由:
// List of delegating handlers. // 委托处理器列表 DelegatingHandler[] handlers = new DelegatingHandler[] { new MessageHandler3() }; // Create a message handler chain with an end-point. // 创建带有端点的消息处理器链 var routeHandlers = HttpClientFactory.CreatePipeline( new HttpControllerDispatcher(config), handlers); config.Routes.MapHttpRoute( name: "Route2", routeTemplate: "api2/{controller}/{id}", defaults: new { id = RouteParameter.Optional }, constraints: null, handler: routeHandlers );