本文版權歸博客園和作者吳雙本人共同所有 轉載和爬蟲請註明原文地址 www.cnblogs.com/tdws 一.寫在前面 適配器模式(Adapter) 可用來在現有介面和不相容的類之間進行適配。有助於避免大規模改寫現有客戶代碼,其工作機制是對現有類的介面進行包裝,這樣客戶程式就能使用這個並非為其量身 ...
本文版權歸博客園和作者吳雙本人共同所有 轉載和爬蟲請註明原文地址 www.cnblogs.com/tdws
一.寫在前面
適配器模式(Adapter)
可用來在現有介面和不相容的類之間進行適配。有助於避免大規模改寫現有客戶代碼,其工作機制是對現有類的介面進行包裝,這樣客戶程式就能使用這個並非為其量身打造的類而又無需為此大動手術。 ----《JS設計模式》
將一個類的介面,轉換成客戶期望的另一個介面。適配器讓原本介面不相容的類可以合作無間。
----《Head First設計模式》
這兩本書中對適配器模式定義如此,適配器模式在多種設計模式當中屬於比較容易理解的一種,其目的或者說可以解決的問題是新功能/新類型,不受原有類型/方法/功能的相容,有了適配器這種巧妙地經驗,我們可以保證對修改封閉,對拓展開放。而達到此目的,正需要面向介面,並保持職責的單一性。也許對C#開發者來說,見的最多的就是SqlDataAdapter。
二.認識UseWebApi
本文所涉及OWIN,.NetFramework,Webapi 開源源碼下載地址為:
https://github.com/aspnet/AspNetKatana
https://github.com/ASP-NET-MVC/aspnetwebstack
https://github.com/dotnet/corefx
熟悉OWIN體系的小伙伴們,一定都在Startup.cs中見過也使用過app.UseWebApi吧。app是IAppBuilder的對象
Startup.cs是OWIN katana實現的啟動類,剛說的UseWebApi顧名思義,就是將WebApi作為一個OWIN中間件放在整個處理流程中。app是IAppBuilder的對象,其創建由IAppBuilderFactory負責。IAppBuilder定義了三個方法,分別為Build,New和Use. 這三個方法分別負責什麼呢?
Build,返回OWIN管道入口點的一個實例,由 Microsoft.Owin.Host.SystemWeb中的Init方法調用。其返回實例將被轉換為AppFun類型,AppFun( using AppFunc = Func<IDictionary<string, object>, Task>;)是什麼呢?它是OWIN伺服器與應用程式交互的應用程式委托,我們看到這個方法在OWIN.Host中調用,應該就能大概猜到個所以然。
New,用於返回一個AppBuilder實例,由IAppBuilderFactory調用並返回。
Use,就是我們在OWIN體系中,經常使用到的方法,我們可以定義自己的OWIN中間件,按照其定義規範,並Use到處理管道中,比如用戶操作日誌中間件,用戶身份校驗中間件等。
說到這裡,我們應該很清晰的瞭解到WebApi是OWIN的一個中間件而已了吧。舉個慄子:
1 public partial class Startup 2 { 3 4 public void Configuration(IAppBuilder app) 5 { 6 // This must happen FIRST otherwise CORS will not work. 7 // 引入OWin.Cors 解決跨域訪問問題 8 app.UseCors(CorsOptions.AllowAll); 9 10 GlobalConfiguration.Configure(WebApiConfig.Register); 11 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 12 13 ConfigureAuth(app); 14 15 app.Use<UserContextMiddleware>(); 16 app.Use<UserOperationMiddleware>(); 17 app.UseWebApi(GlobalConfiguration.Configuration); 18 } 19 }
三.UseWebApi的實現
看到這裡你一定會問,為什麼IAppBuilder中沒有定義UseWebapi方法呢,UseWebapi的實現在System.Web.Http.Owin的WebApiAppBuilderExtensions.cs中,UseWebApi是一個C# this拓展方法,和你所想到的答案並無差。在其實現中,調用了 builder.Use(typeof(HttpMessageHandlerAdapter), options);
到這裡,一定要啰嗦幾句不要怪我,Adapter的實現步驟:為了使一個類或者一個功能,相容已有類/介面,那麼
1.被適配器實現目標客戶的介面或抽象方法,以便參數的傳入
2.所實現介面/抽象類的方法中調用目標客戶的方法
HttpMessageHandlerAdapter 這個主角終於出現了,對Adapter模式瞭解後的小伙伴們也一定能想得到,既然是HttpMessageHandler的Adapter,那麼 在其類中 一定定義了一個private的欄位,並且類型為HttpMessageHandler,你也一定能想得到這個Adapter繼承了OwinMiddleware這個抽象類型並且實現其Invoke抽象方法,在HttpMessageHandlerAdapter的一個方法中一定調用了HttpMessageHandler的方法。那麼通過源碼我們瞭解到HttpMessageHandler的欄位名為_messageHandler。(是不是和上面所說的Adapter實現方式類似呢,實現方式可能概括的不好,建議參閱更多文章和範例)
Asp.Net Webapi的消息處理管道是由HttpMessageHandler的委托鏈所組成的處理管道
HttpMessageHandler抽象類當中頂一個一個唯一的抽象方法用於實現,其入參為HttpRequestMessage,其出參為HttpResponseMessage。
1 protected internal abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
DelegatingHandler實現了HttpMessageHandler,其構造函數中傳入HttpMessageHandler,並由同類對象innerHandler構成委托鏈。
推薦一篇MS文檔 https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/http-message-handlers,有興趣可以稍微參照下。
1 protected DelegatingHandler(HttpMessageHandler innerHandler) 2 { 3 InnerHandler = innerHandler; 4 } 5 6 protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 7 { 8 if (request == null) 9 { 10 throw new ArgumentNullException(nameof(request), SR.net_http_handler_norequest); 11 } 12 SetOperationStarted(); 13 return _innerHandler.SendAsync(request, cancellationToken); 14 }
中間啰嗦了一串,為了說明HttpMessageHandler的作用,這樣我們能進一步理解,為什麼要有HttpMessageHandlerAdapter的存在,併在Use (WebApi中間件)的時候,將該類型傳入。
在HttpMessageHandlerAdapter構造函數中,_messageHandler被包裝為HttpMessageInvoker類型,這個類型的目的是提供一個專門的類,用於調用SendAsync方法。
剛纔我們已經瞭解到HttpMessageHandlerAdapter實現了OWinMiddleware, 那麼我們從源碼中瞭解下,在其實現的抽象方法Invoke中,做了什麼事情:其調用同類下的InvokeCore方法,InvokeCore中Create了HttpRequestMessage,並將其對象作為SendAsync的入參,最後得到HttpResponseMessage對象。
四.寫在最後
就是這樣,一次通過源碼的閱讀,再次對Adapter的理解,HttpMessageHandlerAdapter最終通過對OwinMiddleware的實現,在Invoke中通過HttpMessageInvoker調用執行SendAsync,丟入HttpRequestMessage,拿到ResponseMessage.最終附上HttpMessageHandlerAdapter源碼,更多源碼,還是從第二段的連接中下載吧。
1 // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Collections.Generic; 4 using System.Diagnostics; 5 using System.Diagnostics.CodeAnalysis; 6 using System.Diagnostics.Contracts; 7 using System.IO; 8 using System.Net; 9 using System.Net.Http; 10 using System.Net.Http.Headers; 11 using System.Runtime.ExceptionServices; 12 using System.Security.Principal; 13 using System.Threading; 14 using System.Threading.Tasks; 15 using System.Web.Http.Controllers; 16 using System.Web.Http.ExceptionHandling; 17 using System.Web.Http.Hosting; 18 using System.Web.Http.Owin.ExceptionHandling; 19 using System.Web.Http.Owin.Properties; 20 using Microsoft.Owin; 21 22 namespace System.Web.Http.Owin 23 { 24 /// <summary> 25 /// Represents an OWIN component that submits requests to an <see cref="HttpMessageHandler"/> when invoked. 26 /// </summary> 27 public class HttpMessageHandlerAdapter : OwinMiddleware, IDisposable 28 { 29 private readonly HttpMessageHandler _messageHandler; 30 private readonly HttpMessageInvoker _messageInvoker; 31 private readonly IHostBufferPolicySelector _bufferPolicySelector; 32 private readonly IExceptionLogger _exceptionLogger; 33 private readonly IExceptionHandler _exceptionHandler; 34 private readonly CancellationToken _appDisposing; 35 36 private bool _disposed; 37 38 /// <summary>Initializes a new instance of the <see cref="HttpMessageHandlerAdapter"/> class.</summary> 39 /// <param name="next">The next component in the pipeline.</param> 40 /// <param name="options">The options to configure this adapter.</param> 41 public HttpMessageHandlerAdapter(OwinMiddleware next, HttpMessageHandlerOptions options) 42 : base(next) 43 { 44 if (options == null) 45 { 46 throw new ArgumentNullException("options"); 47 } 48 49 _messageHandler = options.MessageHandler; 50 51 if (_messageHandler == null) 52 { 53 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull, 54 typeof(HttpMessageHandlerOptions).Name, "MessageHandler"), "options"); 55 } 56 57 _messageInvoker = new HttpMessageInvoker(_messageHandler); 58 _bufferPolicySelector = options.BufferPolicySelector; 59 60 if (_bufferPolicySelector == null) 61 { 62 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull, 63 typeof(HttpMessageHandlerOptions).Name, "BufferPolicySelector"), "options"); 64 } 65 66 _exceptionLogger = options.ExceptionLogger; 67 68 if (_exceptionLogger == null) 69 { 70 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull, 71 typeof(HttpMessageHandlerOptions).Name, "ExceptionLogger"), "options"); 72 } 73 74 _exceptionHandler = options.ExceptionHandler; 75 76 if (_exceptionHandler == null) 77 { 78 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull, 79 typeof(HttpMessageHandlerOptions).Name, "ExceptionHandler"), "options"); 80 } 81 82 _appDisposing = options.AppDisposing; 83 84 if (_appDisposing.CanBeCanceled) 85 { 86 _appDisposing.Register(OnAppDisposing); 87 } 88 } 89 90 /// <summary>Initializes a new instance of the <see cref="HttpMessageHandlerAdapter"/> class.</summary> 91 /// <param name="next">The next component in the pipeline.</param> 92 /// <param name="messageHandler">The <see cref="HttpMessageHandler"/> to submit requests to.</param> 93 /// <param name="bufferPolicySelector"> 94 /// The <see cref="IHostBufferPolicySelector"/> that determines whether or not to buffer requests and 95 /// responses. 96 /// </param> 97 /// <remarks> 98 /// This constructor is retained for backwards compatibility. The constructor taking 99 /// <see cref="HttpMessageHandlerOptions"/> should be used instead. 100 /// </remarks> 101 [Obsolete("Use the HttpMessageHandlerAdapter(OwinMiddleware, HttpMessageHandlerOptions) constructor instead.")] 102 public HttpMessageHandlerAdapter(OwinMiddleware next, HttpMessageHandler messageHandler, 103 IHostBufferPolicySelector bufferPolicySelector) 104 : this(next, CreateOptions(messageHandler, bufferPolicySelector)) 105 { 106 } 107 108 /// <summary>Gets the <see cref="HttpMessageHandler"/> to submit requests to.</summary> 109 public HttpMessageHandler MessageHandler 110 { 111 get { return _messageHandler; } 112 } 113 114 /// <summary> 115 /// Gets the <see cref="IHostBufferPolicySelector"/> that determines whether or not to buffer requests and 116 /// responses. 117 /// </summary> 118 public IHostBufferPolicySelector BufferPolicySelector 119 { 120 get { return _bufferPolicySelector; } 121 } 122 123 /// <summary>Gets the <see cref="IExceptionLogger"/> to use to log unhandled exceptions.</summary> 124 public IExceptionLogger ExceptionLogger 125 { 126 get { return _exceptionLogger; } 127 } 128 129 /// <summary>Gets the <see cref="IExceptionHandler"/> to use to process unhandled exceptions.</summary> 130 public IExceptionHandler ExceptionHandler 131 { 132 get { return _exceptionHandler; } 133 } 134 135 /// <summary>Gets the <see cref="CancellationToken"/> that triggers cleanup of this component.</summary> 136 public CancellationToken AppDisposing 137 { 138 get { return _appDisposing; } 139 } 140 141 /// <inheritdoc /> 142 public override Task Invoke(IOwinContext context) 143 { 144 if (context == null) 145 { 146 throw new ArgumentNullException("context"); 147 } 148 149 IOwinRequest owinRequest = context.Request; 150 IOwinResponse owinResponse = context.Response; 151 152 if (owinRequest == null) 153 { 154 throw Error.InvalidOperation(OwinResources.OwinContext_NullRequest); 155 } 156 if (owinResponse == null) 157 { 158 throw Error.InvalidOperation(OwinResources.OwinContext_NullResponse); 159 } 160 161 return InvokeCore(context, owinRequest, owinResponse); 162 } 163 164 private async Task InvokeCore(IOwinContext context, IOwinRequest owinRequest, IOwinResponse owinResponse) 165 { 166 CancellationToken cancellationToken = owinRequest.CallCancelled; 167 HttpContent requestContent; 168 169 bool bufferInput = _bufferPolicySelector.UseBufferedInputStream(hostContext: context); 170 171 if (!bufferInput) 172 { 173 owinRequest.DisableBuffering(); 174 } 175 176 if (!owinRequest.Body.CanSeek && bufferInput) 177 { 178 requestContent = await CreateBufferedRequestContentAsync(owinRequest, cancellationToken); 179 } 180 else 181 { 182 requestContent = CreateStreamedRequestContent(owinRequest); 183 } 184 185 HttpRequestMessage request = CreateRequestMessage(owinRequest, requestContent); 186 MapRequestProperties(request, context); 187 188 SetPrincipal(owinRequest.User); 189 190 HttpResponseMessage response = null; 191 bool callNext; 192 193 try 194 { 195 response = await _messageInvoker.SendAsync(request, cancellationToken); 196 197 // Handle null responses 198 if (response == null) 199 { 200 throw Error.InvalidOperation(OwinResources.SendAsync_ReturnedNull); 201 } 202 203 // Handle soft 404s where no route matched - call the next component 204 if (IsSoftNotFound(request, response)) 205 { 206 callNext = true; 207 } 208 else 209 { 210 callNext = false; 211 212 // Compute Content-Length before calling UseBufferedOutputStream because the default implementation 213 // accesses that header and we want to catch any exceptions calling TryComputeLength here. 214 215 if (response.Content == null 216 || await ComputeContentLengthAsync(request, response, owinResponse, cancellationToken)) 217 { 218 bool bufferOutput = _bufferPolicySelector.UseBufferedOutputStream(response); 219 220 if (!bufferOutput) 221 { 222 owinResponse.DisableBuffering(); 223 } 224 else if (response.Content != null) 225 { 226 response = await BufferResponseContentAsync(request, response, cancellationToken); 227 } 228 229 if (await PrepareHeadersAsync(request, response, owinResponse, cancellationToken)) 230 { 231 await SendResponseMessageAsync(request, response, owinResponse, cancellationToken); 232 } 233 } 234 } 235 } 236 finally 237 { 238 request.DisposeRequestResources(); 239 request.Dispose(); 240 if (response != null) 241 { 242 response.Dispose(); 243 } 244 } 245 246 // Call the next component if no route matched 247 if (callNext && Next != null) 248 { 249 await Next.Invoke(context); 250 } 251 } 252 253 private static HttpContent CreateStreamedRequestContent(IOwinRequest owinRequest) 254 { 255 // Note that we must NOT dispose owinRequest.Body in this case. Disposing it would close the input 256 // stream and prevent cascaded components from accessing it. The server MUST handle any necessary 257 // cleanup upon request completion. NonOwnedStream prevents StreamContent (or its callers including 258 // HttpRequestMessage) from calling Close or Dispose on owinRequest.Body. 259 return new StreamContent(new NonOwnedStream(owinRequest.Body)); 260 } 261 262 private static async Task<HttpContent> CreateBufferedRequestContentAsync(IOwinRequest owinRequest, 263 CancellationToken cancellationToken) 264 { 265 // We need to replace the request body with a buffered stream so that other components can read the stream. 266 // For this stream to be useful, it must NOT be diposed along with the request. Streams created by 267 // StreamContent do get disposed along with the request, so use MemoryStream to buffer separately. 268 MemoryStream buffer; 269 int? contentLength = owinRequest.GetContentLength(); 270 271 if (!contentLength.HasValue) 272 { 273 buffer = new MemoryStream(); 274 } 275 else 276 { 277 buffer = new MemoryStream(contentLength.Value); 278 } 279 280 cancellationToken.ThrowIfCancellationRequested(); 281 282 using (StreamContent copier = new StreamContent(owinRequest.Body)) 283 { 284 await copier.CopyToAsync(buffer); 285 } 286 287 // Provide the non-disposing, buffered stream to later OWIN components (set to the stream's beginning). 288 buffer.Position = 0; 289 owinRequest.Body = buffer; 290 291 // For MemoryStream, Length is guaranteed to be an int. 292 return new ByteArrayContent(buffer.GetBuffer(), 0, (int)buffer.Length); 293 } 294 295 private static HttpRequestMessage CreateRequestMessage(IOwinRequest owinRequest, HttpContent requestContent) 296 {