日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[上篇]

發布時間:2023/12/4 asp.net 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[上篇] 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

《200行代碼,7個對象——讓你了解ASP.NET Core框架的本質》讓很多讀者對ASP.NET Core管道有了真實的了解。在過去很長一段時間中,有很多人私信給我:能否按照相同的方式分析一下MVC框架的設計與實現原理,希望這篇文章能夠滿足你們的需求,源代碼可以通過原文下載。

01

MVC與路由

整個MVC框架建立在路由中間件上。不論是面向Controller的Model-View-Controller編程模型,還是面向頁面的Razor Pages編程模型,每個請求指向的都一個某個Action,所以MVC框架只需要將每個Action封裝成一個路由終結點(RouteEndpoint),并通過自定義的EndpointDataSource注冊到路由中間件上即可。

被封裝的路由終結點它的請求處理器會幫助我們執行對應的Action,這是一個相對復雜的流程,所以我們創建了一個模擬框架。模擬框架采用真實MVC框架的設計和實現原理,但是會在各個環節進行最大限度地簡化。我們希望讀者朋友們通過這個模擬框架對MVC框架的設計與實現具有一個總體的認識。

02

Action元數據的解析

由于我們需要在應用啟動的時候將所有Action提取出來并封裝成路由終結點,所以我們需要一種“Action發現機制”得到定義在所有Controller類型的Action方法,以及所有Razor Page對應的Action方法,并將它們的元數據提取出來。兩種編程模型的Action元數據都封裝到一個ActionDescriptor對象中。

ActionDescriptor

模擬框架針對Action的描述體現在如下這個ActionDescriptor類型上,它的兩個屬性成員都與路由有關。我們知道面向Controller的MVC模型支持兩種形式的路由,即“約定路由(Conventional Routing)”和“特性路由(Attribute Routing)”。對于前者,我們可以將路由規則定義在Action方法上標注的特性(比如HttpGetAttribute特性)上,后者則體現為針對路由的全局注冊。

public?abstract?class?ActionDescriptor {public?AttributeRouteInfo?AttributeRouteInfo?{?get;?set;?}public?IDictionary<string,?string>?RouteValues?{?get;?set;?} }public?class?AttributeRouteInfo {public?int?Order?{?get;?set;?}public?string?Template?{?get;?set;?} }

我們將通過特性路由提供的原始信息封裝成 一個AttributeRouteInfo對象,它的Template代表路由模板。對于一組給定的路由終結點來說,有可能存在多個終結點的路由模式都與某個請求匹配,所以代表路由終結點的RouteEndpoint類型定義了一個Order屬性,該屬性值越小,代表選擇優先級越高。對于通過特性路由創建的RouteEndpoint對象來說,它的Order屬性來源于對應AttributeRouteInfo對象的同名屬性。

ActionDescriptor的RouteValues屬性與“約定路由”有關。比如我們全局定義了一個模板為“{controller}/{action}/{id?}”的路由({controller}和{action}分別表示Controller和Action的名稱),如果定義在某個Controller類型(比如FooController)的Action方法(比如Bar)上沒有標注任何路由特性,它對應的路由終結點將采用這個約定路由來創建,具體的路由模板將使用真正的Controller和Action名稱(“Foo/Bar/{id?}”)。ActionDescriptor的RouteValues屬性表示某個Action為約定路由參數提供的參數值,這些值會用來替換約定路由模板中相應的路由參數來生成屬于當前Action的路由模板。

我們的模擬框架只提供針對面向Controller的MVC編程模型的支持,針對該模型的Action描述通過如下這個ControllerActionDescriptor類型表示。ControllerActionDescriptor類型繼承自抽象類ActionDescriptor,它的MethodInfo和ControllerType屬性分別表示Action方法和所在的Controller類型。

public?class?ControllerActionDescriptor?:?ActionDescriptor {public?Type?ControllerType?{?get;?set;?}public?MethodInfo?Method?{?get;?set;?} }

IActionDescriptorProvider

當前應用范圍內針對有效Action元數據的解析通過相應的IActionDescriptorProvider對象來完成。如下面的代碼片段所示,IActionDescriptorProvider接口通過唯一的屬性ActionDescriptors來提供用來描述所有有效Action的ActionDescriptor對象。

public?interface?IActionDescriptorProvider {IEnumerable<ActionDescriptor>?ActionDescriptors?{?get;?} }

如下這個ControllerActionDescriptorProvider類型是IActionDescriptorProvider接口針對面向Controller的MVC編程模型的實現。簡單起見,我們在這里作了這么一個假設:所有的Controller類型都定義在當前ASP.NET Core應用所在的項目(程序集)中。基于這個假設,我們在構造函數中注入了代表當前承載環境的IHostEnvironment對象,并利用它得到當前的應用名稱。由于應用名稱同時也是程序集名稱,所以我們得以獲取應用所在的程序集,并從中解析出有效的Controller類型。

public?class?ControllerActionDescriptorProvider?:?IActionDescriptorProvider {private?readonly?Lazy<IEnumerable<ActionDescriptor>>?_accessor;public?IEnumerable<ActionDescriptor>?ActionDescriptors?=>?_accessor.Value;public?ControllerActionDescriptorProvider(IHostEnvironment?environment){_accessor?=?new?Lazy<IEnumerable<ActionDescriptor>>(()?=>?GetActionDescriptors(environment.ApplicationName));}private?IEnumerable<ActionDescriptor>?GetActionDescriptors(string?applicationName){var?assemblyName?=?new?AssemblyName(applicationName);var?assembly?=?Assembly.Load(assemblyName);foreach?(var?type?in?assembly.GetExportedTypes()){if?(type.Name.EndsWith("Controller")){var?controllerName?=?type.Name.Substring(0,type.Name.Length?-?"Controller".Length);foreach?(var?method?in?type.GetMethods()){yield?return?CreateActionDescriptor(method,?type,?controllerName);}}}}private?ControllerActionDescriptor?CreateActionDescriptor(MethodInfo?method,Type?controllerType,?string?controllerName){var?actionName?=?method.Name;if?(actionName.EndsWith("Async")){actionName?=?actionName.Substring(0,?actionName.Length?-?"Async".Length);}var?templateProvider?=?method.GetCustomAttributes().OfType<IRouteTemplateProvider>().FirstOrDefault();if?(templateProvider?!=?null){var?routeInfo?=?new?AttributeRouteInfo{Order?=?templateProvider.Order????0,Template?=?templateProvider.Template};return?new?ControllerActionDescriptor{AttributeRouteInfo?=?routeInfo,ControllerType?=?controllerType,Method?=?method};}return?new?ControllerActionDescriptor{ControllerType?=?controllerType,Method?=?method,RouteValues?=?new?Dictionary<string,?string>{["controller"]?=?controllerName,["action"]?=?actionName}};} }

簡單起見,我們只是將定義在當前應用所在程序集中采用“Controller”后綴命名的類型解析出來,并將定義在它們之中的公共方法作為Action方法(針對Controller和Action方法應該做更為嚴謹的有效性驗證,為了使模擬框架顯得更簡單一點,我們刻意將這些驗證簡化了)。我們根據類型和方法解析出Controller名稱(類型名稱去除“Controller”后綴)和Action名稱(方法名去除“Async”后綴),并進一步為每個Action方法創建出對應的ControllerActionDescriptor對象。

如果Action方法上標注了如下這個IRouteTemplateProvider接口類型的特性(比如HttpGetAttribute類型最終實現了該接口),意味著當前Action方法采用“特性路由”,那么最終創建的ControllerActionDescriptor對象的AttributeRouteInfo屬性將通過這個特性構建出來。如果沒有標注這樣的特性,意味著可能會采用約定路由,所以我們需要將當前Controller和Action名稱填充到RouteValues屬性表示的”必需路由參數值字典”中。

public?interface?IRouteTemplateProvider {string?Name?{?get;?}string?Template?{?get;?}int??Order?{?get;?} }

IActionDescriptorCollectionProvider

ControllerActionDescriptorProvider類型僅僅是IActionDescriptorProvider接口針對面向Controller的MVC編程模型的實現,Razor Pages編程模型中對應的實現類型為PageActionDescriptorProvider。由于同一個應用是可以同時支持這兩種編程模型的,所以這兩個實現類型可能會同時注冊到應用的依賴注入框架中。MVC框架需要獲取兩種編程模型的Action,這一個功能體現在如下這個IActionDescriptorCollectionProvider接口上,描述所有類型Action的ActionDescriptor對象通過它的ActionDescriptors屬性返回。

public?interface?IActionDescriptorCollectionProvider {IReadOnlyList<ActionDescriptor>?ActionDescriptors?{?get;?} }

如下所示的DefaultActionDescriptorCollectionProvider是對IActionDescriptorCollectionProvider接口的默認實現,它直接利用在構造函數中注入的IActionDescriptorProvider對象列表來提供描述Action的ActionDescriptor對象。

public?class?DefaultActionDescriptorCollectionProvider?:?IActionDescriptorCollectionProvider {private?readonly?Lazy<IReadOnlyList<ActionDescriptor>>?_accessor;public?IReadOnlyList<ActionDescriptor>?ActionDescriptors?=>?_accessor.Value;public?DefaultActionDescriptorCollectionProvider(IEnumerable<IActionDescriptorProvider>?providers)=>?_accessor?=?new?Lazy<IReadOnlyList<ActionDescriptor>>(()?=>?providers.SelectMany(it?=>?it.ActionDescriptors).ToList());???? }

03

路由

當描述Action的所有ActionDescriptor對象被解析出來之后,MVC框架需要將它們轉換成表示路由終結點的RoutEndpoint對象。一個RoutEndpoint對象由代表路由模式的RoutePattern對象和代表請求處理器的RequestDelegate對象組成。RoutePattern對象可以直接通過ActionDescriptor對象提供的路由信息構建出來,所以最難解決的是如果創建出用來執行目標Action的RequestDelegate對象。MVC框架中針對Action的執行是通過一個IActionInvoker對象來完成的。

IActionInvoker

MVC框架需要解決的核心問題就是根據請求選擇并執行目標Action,所以用來執行Action的IActionInvoker對象無疑是整個MVC框架最為核心的對象。雖然重要性不容置疑,但是IActionInvoker接口的定義卻極其簡單。如下面的代碼片段所示,IActionInvoker接口只定義了一個唯一的InvokeAsync,這是一個返回類型為Task的無參數方法。

public?interface?IActionInvoker {Task?InvokeAsync(); }

用來執行Action的IActionInvoker對象是根據每個請求上下文動態創建的。具體來說,當路由解析成功并執行匹配終結點的請求處理器時,針對目標Action的上下文對象會被創建出來,一個IActionInvokerFactory對象會被用來創建執行目標Action的IActionInvoker對象。顧名思義,IActionInvokerFactory接口代表創建IActionInvoker對象的工廠,針對IActionInvoker對象的創建體現在如下這個CreateInvoker方法上。

public?interface?IActionInvokerFactory {IActionInvoker?CreateInvoker(ActionContext?actionContext); }

具體的IActionInvokerFactory對象應該創建怎樣的IActionInvoker對象取決于提供的ActionContext上下文。如下面的代碼片段所示,ActionContext對象是對當前HttpContext上下文的封裝,它的ActionDescriptor屬性返回的ActionDescriptor對象是對待執行Action的描述。

public?class?ActionContext {public?ActionDescriptor?ActionDescriptor?{?get;?set;?}public?HttpContext?HttpContext?{?get;?set;?} }

ActionEndpointDataSourceBase

終結點的路由模式可以通過描述Action的ActionDescriptor對象提供的路由信息來創建,它的處理器則可以利用IActionInvokerFactory工廠創建的IActionInvoker對象來完成針對請求的處理,所以我們接下來只需要提供一個自定義的EndpointDataSource類型按照這樣的方式為每個Action創建對應的路由終結點就可以了。考慮到兩種不同編程模型的差異,我們會定義不同的EndpointDataSource派生類,它們都繼承自如下這個抽象的基類ActionEndpointDataSourceBase。

public?abstract?class?ActionEndpointDataSourceBase?:?EndpointDataSource {private?readonly?Lazy<IReadOnlyList<Endpoint>>?_endpointsAccessor;protected?readonly?List<Action<EndpointBuilder>>?Conventions;public?override?IReadOnlyList<Endpoint>?Endpoints?=>?_endpointsAccessor.Value;protected?ActionEndpointDataSourceBase(IActionDescriptorCollectionProvider?provider){Conventions?=?new?List<Action<EndpointBuilder>>();_endpointsAccessor?=?new?Lazy<IReadOnlyList<Endpoint>>(()?=>?CreateEndpoints(provider.ActionDescriptors,?Conventions));}public?override?IChangeToken?GetChangeToken()?=>?NullChangeToken.Instance;protected?abstract?List<Endpoint>?CreateEndpoints(IReadOnlyList<ActionDescriptor>?actions,IReadOnlyList<Action<EndpointBuilder>>?conventions); }???????

MVC框架支持采用全局注冊方式的 “約定理由(Conventional Routing )” ,這里的約定路由規則通過Action<EndpointBuilder>對象的列表來體現,對應著ActionEndpointDataSourceBase類型的Conventions屬性。ActionEndpointDataSourceBase類型的構造函數中注入了一個IActionDescriptorCollectionProvider對象,我們利用它來獲取描述當前應用范圍內所有Action的ActionDescriptor對象。Endpoints屬性返回的路由終結點列表最終是通過抽象方法CreateEndpoints根據提供的ActionDescriptor對象列表和約定路由列表創建的。對于重寫的GetChangeToken方法,我們直接返回如下這個不具有變化監測功能的NullChangeToken對象。

internal?class?NullChangeToken?:?IChangeToken {public?bool?ActiveChangeCallbacks?=>?false;public?bool?HasChanged?=>?false;public?IDisposable?RegisterChangeCallback(Action<object>?callback,?object?state)?=>?new?NullDisposable()?;public?static?readonly?NullChangeToken?Instance?=?new?NullChangeToken();private?class?NullDisposable?:?IDisposable{public?void?Dispose()?{}} }?????????

ControllerActionEndpointDataSource

ControllerActionEndpointDataSource是ActionEndpointDataSourceBase的派生類型,它幫助我們完成基于Controller的MVC編程模式下的路由終結點的創建。不過在正式介紹這個類型之前,我們先來介紹兩個與 “約定路由” 相關的類型。如下這個ConventionalRouteEntry結構表示單個約定路由的注冊項,其中包括路由名稱、路由模式、Data Token和排列位置。我們在上面說過,注冊的約定路由規則最終體現為一個Action<EndpointBuilder>對象的列表,ConventionalRouteEntry的Conventions屬性返回的就是這個列表。

internal?struct?ConventionalRouteEntry {public?string?RouteName;public?RoutePattern?Pattern?{?get;?}public?RouteValueDictionary?DataTokens?{?get;?}public?int?Order?{?get;?}public?IReadOnlyList<Action<EndpointBuilder>>?Conventions?{?get;?}public?ConventionalRouteEntry(string?routeName,?string?pattern,RouteValueDictionary?defaults,?IDictionary<string,?object>?constraints,RouteValueDictionary?dataTokens,?int?order,List<Action<EndpointBuilder>>?conventions){RouteName?=?routeName;DataTokens?=?dataTokens;Order?=?order;Conventions?=?conventions;Pattern?=?RoutePatternFactory.Parse(pattern,?defaults,?constraints);} }??

另一個與約定路由相關的是如下這個ControllerActionEndpointConventionBuilder類型,我們從其明明不難看出該類型用來幫助我們構建約定路由。ControllerActionEndpointConventionBuilder是對一個Action<EndpointBuilder>列表的封裝,它定義的唯一的Add方法僅僅是向該列表中添加一個表示路由約定的Action<EndpointBuilder>對象罷了。

public?class?ControllerActionEndpointConventionBuilder?:?IEndpointConventionBuilder {private?readonly?List<Action<EndpointBuilder>>?_conventions;public?ControllerActionEndpointConventionBuilder(List<Action<EndpointBuilder>>?conventions){_conventions?=?conventions;}public?void?Add(Action<EndpointBuilder>?convention)?=>?_conventions.Add(convention); }??? ???

我們最后來看看ControllerActionEndpointDataSource類型的定義。對于ControllerActionEndpointDataSource對象構建的路由終結點來說,作為請求處理器的RequestDelegate委托對象指向的都是ProcessRequestAsync方法。我們先來看看ProcessRequestAsync方法是如何處理請求的:該方法首先從HttpContext上下文中獲取當前終結點的Endpoint對象,并從其元數據列表中得到預先放置的用來表示目標Action的ActionDescriptor對象。接下來,該方法根據HttpContext上下文和這個ActionDescriptor對象創建出ActionContext上下文。該方法最后從基于請求的依賴注入容器中提取出IActionInvokerFactory工廠,并利用它根據當前ActionContext上下文創建出對應的IActionInvoker對象。請求的處理最終通過執行該IActionInvoker得以完成。

public?class?ControllerActionEndpointDataSource?:?ActionEndpointDataSourceBase {private?readonly?List<ConventionalRouteEntry>?_conventionalRoutes;private?int?_order;private?readonly?RoutePatternTransformer?_routePatternTransformer;private?readonly?RequestDelegate?_requestDelegate;public?ControllerActionEndpointConventionBuilder?DefaultBuilder?{?get;?}public?ControllerActionEndpointDataSource(IActionDescriptorCollectionProvider?provider,RoutePatternTransformer?transformer)?:?base(provider){_conventionalRoutes?=?new?List<ConventionalRouteEntry>();_order?=?0;_routePatternTransformer?=?transformer;_requestDelegate?=?ProcessRequestAsync;DefaultBuilder?=?new?ControllerActionEndpointConventionBuilder(base.Conventions);}public?ControllerActionEndpointConventionBuilder?AddRoute(string?routeName,string?pattern,?RouteValueDictionary?defaults,IDictionary<string,?object>?constraints,?RouteValueDictionary?dataTokens){List<Action<EndpointBuilder>>?conventions?=?new?List<Action<EndpointBuilder>>();order++;conventionalRoutes.Add(new?ConventionalRouteEntry(routeName,?pattern,?defaults,constraints,?dataTokens,?_order,?conventions));return?new?ControllerActionEndpointConventionBuilder(conventions);}protected?override?List<Endpoint>?CreateEndpoints(IReadOnlyList<ActionDescriptor>?actions,IReadOnlyList<Action<EndpointBuilder>>?conventions){var?endpoints?=?new?List<Endpoint>();foreach?(var?action?in?actions){var?attributeInfo?=?action.AttributeRouteInfo;if?(attributeInfo?==?null)?//約定路由{foreach?(var?route?in?_conventionalRoutes){var?pattern?=?_routePatternTransformer.SubstituteRequiredValues(route.Pattern,?action.RouteValues);if?(pattern?!=?null){var?builder?=?new?RouteEndpointBuilder(_requestDelegate,?pattern,?route.Order);builder.Metadata.Add(action);endpoints.Add(builder.Build());}}}else?//特性路由{var?original?=?RoutePatternFactory.Parse(attributeInfo.Template);var?pattern?=?_routePatternTransformer.SubstituteRequiredValues(original,?action.RouteValues);if?(pattern?!=?null){var?builder?=?new?RouteEndpointBuilder(_requestDelegate,?pattern,?attributeInfo.Order);builder.Metadata.Add(action);endpoints.Add(builder.Build());}}}return?endpoints;}private?Task?ProcessRequestAsync(HttpContext?httContext){var?endpoint?=?httContext.GetEndpoint();var?actionDescriptor?=?endpoint.Metadata.GetMetadata<ActionDescriptor>();var?actionContext?=?new?ActionContext{ActionDescriptor?=?actionDescriptor,HttpContext?=?httContext};var?invokerFactory?=?httContext.RequestServices.GetRequiredService<IActionInvokerFactory>();var?invoker?=?invokerFactory.CreateInvoker(actionContext);return?invoker.InvokeAsync();} }

ControllerActionEndpointDataSource定義了一個List<ConventionalRouteEntry類型的字段_conventionalRoutes用來表示存儲添加的約定路由注冊項。的構造函數中除了注入了用于提供Action描述的IActionDescriptorCollectionProvider對象之外,還注入了用于路由模式轉換的RoutePatternTransformer對象。它的_order字段表示為注冊的約定路由指定的位置編號,最終會賦值到表示路由終結點的RouteEndpoint對象的Order屬性。

在實現的CreateEndpoints方法中,ControllerActionEndpointDataSource會便利提供的每個ActionDescriptor對象,如果該對象的AttributeRouteInfo屬性為空,意味著應該采用約定路由,該方法會為每個表示約定路由注冊項的ConventionalRouteEntry對象創建一個路由終結點。具體來說,ControllerActionEndpointDataSource會將當前ActionDescriptor對象RouteValues屬性攜帶的路由參數值(包含Controller和Action名稱等必要信息),并將其作為參數調用RoutePatternTransformer對象的SubstituteRequiredValues方法將全局注冊的原始路由模式(比如“{controller}/{action}/{id?}”)中相應的路由參數替換掉(最終可能變成“Foo/Bar/{id?}”)。SubstituteRequiredValues返回RoutePattern對象將作為最終路由終結點的路由模式。

如果ActionDescriptor對象的AttributeRouteInfo屬性返回一個具體的AttributeRouteInfo對象,意味著應該采用特性路由,支持它會利用這個AttributeRouteInfo對象創建一個新的RoutePattern對象將作為最終路由終結點的路由模式。不論是采用何種路由方式,用來描述當前Action的ActionDescriptor對象都會以元數據的形式添加到路由終結點的元數據集合中(對應于Endpoint類型的Metadata屬性),ProcessRequestAsync方法中從當前終結點提取的ActionDescriptor對象就來源于此。

ControllerActionEndpointDataSource還提供了一個DefaultBuilder屬性,它會返回一個默認的ControllerActionEndpointConventionBuilder對象用來進一步注冊約定路由。約定路由可以直接通過調用AddRoute方法進行注冊,由于該方法使用自增的_order字段作為注冊路由的Order屬性,所以先注冊的路由具有更高的選擇優先級。AddRoute方法同樣返回一個ControllerActionEndpointConventionBuilder對象。

如下定義的針對IEndpointRouteBuilder接口的MapMvcControllers擴展方法幫助我們方便地注冊ControllerActionEndpointDataSource對象。另一個MapMvcControllerRoute擴展方法則在此基礎上提供了約定路由的注冊。這兩個擴展分別模擬的是MapControllers和MapControllerRoute擴展方法的實現,為了避免命名沖突,我們不得不起一個不同的方法名。

public?static?class?EndpointRouteBuilderExtensions {public?static?ControllerActionEndpointConventionBuilder?MapMvcControllers(this?IEndpointRouteBuilder?endpointBuilder){var?endpointDatasource?=?endpointBuilder.ServiceProvider.GetRequiredService<ControllerActionEndpointDataSource>();endpointBuilder.DataSources.Add(endpointDatasource);return?endpointDatasource.DefaultBuilder;}public?static?ControllerActionEndpointConventionBuilder?MapMvcControllerRoute(this?IEndpointRouteBuilder?endpointBuilder,?string?name,?string?pattern,RouteValueDictionary?defaults?=?null,?RouteValueDictionary?constraints?=?null,RouteValueDictionary?dataTokens?=?null){var?endpointDatasource?=?endpointBuilder.ServiceProvider.GetRequiredService<ControllerActionEndpointDataSource>();endpointBuilder.DataSources.Add(endpointDatasource);return?endpointDatasource.AddRoute(name,?pattern,?defaults,?constraints,dataTokens);} }

總結

以上是生活随笔為你收集整理的通过极简模拟框架让你了解ASP.NET Core MVC框架的设计与实现[上篇]的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。