原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore
发布于:2017年3月
环境:ASP.NET Core 1.1

欢迎阅读剖析ASP.NET Core源代码系列第二部分。细心的读者可能发现文章标题发生了变化,去掉了“MVC”。虽然我最感兴趣的是MVC的实现,但随着剖析的深入,不可避免的会涉及到ASP.NET Core 核心框架的内容,比如 IRouter。因此,适当扩大研究范围是必要的,“剖析ASP.NET Core”对本系列来说更加贴切。

在Part 1,我们了解了AddMvcCore扩展方法,本文我们将分析AddMvc扩展方法。我们继续使用rel/1.1.2版本的ASP.NET Core MVC。未来代码可能会发生变化,请使用相同版本。我们的起点仍是MvcSandbox项目,Startup.cs中的ConfigureServices方法如下:

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}

AddMvc扩展方法的实现:

public static IMvcBuilder AddMvc(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
} var builder = services.AddMvcCore(); builder.AddApiExplorer();
builder.AddAuthorization(); AddDefaultFrameworkParts(builder.PartManager); // Order added affects options setup order // Default framework order
builder.AddFormatterMappings();
builder.AddViews();
builder.AddRazorViewEngine();
builder.AddCacheTagHelper(); // +1 order
builder.AddDataAnnotations(); // +1 order // +10 order
builder.AddJsonFormatters(); builder.AddCors(); return new MvcBuilder(builder.Services, builder.PartManager);
}

上面代码首先调用我们在Part 1中分析过的AddMvcCore扩展方法,执行相同的配置和服务注册,包括创建了一个ApplicationPartManager。AddMvcCore的返回类型是一个MvcCoreBuilder,AddMvc使用一个变量保存(builder)。正如我在Part 1中提到的,它(builder)提供了对AddMvcCore生成的IServiceCollection和ApplicationPartManager的访问。

AddMvc调用AddApiExplorer扩展方法,向builder的IServiceCollection添加两个服务注册:ApiDescriptionGroupCollectionProvider和DefaultApiDescriptionProvider。

public static IMvcCoreBuilder AddApiExplorer(this IMvcCoreBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
} AddApiExplorerServices(builder.Services);
return builder;
} // Internal for testing.
internal static void AddApiExplorerServices(IServiceCollection services)
{
services.TryAddSingleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>();
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApiDescriptionProvider, DefaultApiDescriptionProvider>());
}

接下来调用AddAuthorization扩展方法:

public static IMvcCoreBuilder AddAuthorization(this IMvcCoreBuilder builder)
{
AddAuthorizationServices(builder.Services);
return builder;
} internal static void AddAuthorizationServices(IServiceCollection services)
{
services.AddAuthorization(); services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, AuthorizationApplicationModelProvider>());
}

首先它调用了Microsoft.AspNetCore.Authorization程序集中的AddAuthorization扩展方法。这里不深入介绍,但它添加了启用授权(authorization)的核心服务。之后,AuthorizationApplicationModelProvider被添加到ServicesCollection中。

接下来返回AddMvc主方法,它调用静态私有方法AddDefaultFrameworkParts添加TagHelpers和Razor AssemblyParts。

private static void AddDefaultFrameworkParts(ApplicationPartManager partManager)
{
var mvcTagHelpersAssembly = typeof(InputTagHelper).GetTypeInfo().Assembly;
if(!partManager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == mvcTagHelpersAssembly))
{
partManager.ApplicationParts.Add(new AssemblyPart(mvcTagHelpersAssembly));
} var mvcRazorAssembly = typeof(UrlResolutionTagHelper).GetTypeInfo().Assembly;
if(!partManager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == mvcRazorAssembly))
{
partManager.ApplicationParts.Add(new AssemblyPart(mvcRazorAssembly));
}
}

本方法首先获得Microsoft.AspNetCore.Mvc.TagHelpers中的InputTagHelper类的封装(Assembly),然后检查ApplicationPartManager中的ApplicationParts列表是否包含匹配的程序集,如果不存在则添加。然后使用同样方法处理Razor,使用的是Microsoft.AspNetCore.Mvc.Razor中的UrlResolutionTagHelper,同样执行如果不存在则添加操作。

再次返回AddMvc主方法,更多的项目通过扩展方法添加到ServiceCollection中。AddMvc调用AddFormatterMappings注册FormatFilter类。

接下来调用AddViews。首先使用AddDataAnnotations扩展方法添加DataAnnotations服务。接下来把ViewComponentFeatureProvider添加到ApplicationPartManager.FeatureProviders。最后注册了一些基于视图的服务,如singletons。这个类非常大,这里我不贴出所有代码。关键部分如下:

public static IMvcCoreBuilder AddViews(this IMvcCoreBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
} builder.AddDataAnnotations();
AddViewComponentApplicationPartsProviders(builder.PartManager);
AddViewServices(builder.Services);
return builder;
} private static void AddViewComponentApplicationPartsProviders(ApplicationPartManager manager)
{
if (!manager.FeatureProviders.OfType<ViewComponentFeatureProvider>().Any())
{
manager.FeatureProviders.Add(new ViewComponentFeatureProvider());
}
}

接下来AddMvc调用AddRazorViewEngine扩展方法:

public static IMvcCoreBuilder AddRazorViewEngine(this IMvcCoreBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
} builder.AddViews(); // AddMvc主方法中执行过 AddViews()
AddRazorViewEngineFeatureProviders(builder);
AddRazorViewEngineServices(builder.Services);
return builder;
} private static void AddRazorViewEngineFeatureProviders(IMvcCoreBuilder builder)
{
if (!builder.PartManager.FeatureProviders.OfType<TagHelperFeatureProvider>().Any())
{
builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider());
} if (!builder.PartManager.FeatureProviders.OfType<MetadataReferenceFeatureProvider>().Any())
{
builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider());
} if (!builder.PartManager.FeatureProviders.OfType<ViewsFeatureProvider>().Any())
{
builder.PartManager.FeatureProviders.Add(new ViewsFeatureProvider());
}
}

这里再次调用AddViews引起了我的好奇。在AddRazorViewEngine之前调用AddViews似乎有些多余,因为AddRazorViewEngine会为我们注册所有服务。这样操作虽然是安全的,因为这些扩展方法都使用 TryAdd… 形式,能够避免服务的重复注册,但看到这样的重复调用还是觉得有些奇怪。为满足我的好奇,我在Mvc GitHub上开启了一个问题,求证是一个错误还是有意设计。我得到了微软Ryan Nowak非常迅速的回复,确认这是一个设计决定,使依赖关系更加明确和容易看出。

AddRazorViewEngine 添加了三个feature providers到ApplicationPartManager.FeatureProviders:TagHelperFeatureProvider,MetadataReferenceFeatureProvider和ViewsFeatureProvider。这些是实现razor视图功能必要的服务。

AdddMvc然后调用另一个扩展方法来为Cache Tag Helper功能添加服务。这是一个非常简单的扩展方法,仅注册了5个所需的服务。然后返回AddMvc,再次调用AddDataAnnotations。AddViews此前已调用过此方法,这与前面提到的设计决定相同。

AddMvc然后调用AddJsonFormatters扩展方法,将几个项目添加到ServicesCollection。

最后被调用扩展方法是Microsoft.AspNetCore.Cors中的AddCors,添加Cors相关的服务。

随着服务注册的完成,AddMvc创建了一个新的MvcBuilder,将当前的ServicesCollection和ApplicationPartManager作为属性存储。就像我们在第一篇文章中看到的MvcBuilder一样,MvcBuilder被描述为“允许细粒度的配置MVC服务”(allowing fine grained configurations of MVC services)。

AddMvc执行完成后,services collection共有148个注册服务,比AddMvcCore方法多了86个服务。ApplicationPartManager中有3个ApplicationParts和5个FeatureProviders。

小结

以上就是我对AddMvc的分析。相比前面对ApplicationPartManager的铺垫、创建分析,显得不那么让人兴奋,我们主要是调用扩展方法扩展服务。如你所见,许多服务是针对使用Views的Web 应用程序。如果是单纯的API应用程序,你可能不需要这些服务。在我开发过的API项目中,就使用AddMvcCore,同时通过builder的扩展方法添加额外几个我们需要的服务。下面是我在实际运用中的示例代码:

services.AddMvcCore(options =>
{
options.UseHtmlEncodeModelBinding();
})
.AddJsonFormatters(options =>
{
options.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.AddApiExplorer()
.AddAuthorization(options =>
{
options.DefaultPolicy =
new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireAssertion(ctx => ClaimsHelper.IsAdminUser(ctx.User.Claims.ToList()))
.Build(); options.AddPolicy(SecurityPolicyNames.DoesNotRequireAdminUser,
policy => policy.RequireAuthenticatedUser());
});

下篇文章我将讲解Startup.cs中的Configure方法调用的UseMvc到底做了些什么。

剖析ASP.NET Core(Part 2)- AddMvc(译)的更多相关文章

  1. 剖析ASP.NET Core MVC(Part 1)- AddMvcCore(译)

    原文:https://www.stevejgordon.co.uk/asp-net-core-mvc-anatomy-addmvccore发布于:2017年3月环境:ASP.NET Core 1.1 ...

  2. 剖析ASP.NET Core(Part 4)- 调用MVC中间件(译)

    原文:https://www.stevejgordon.co.uk/invoking-mvc-middleware-asp-net-core-anatomy-part-4 发布于:2017年5月环境: ...

  3. 剖析ASP.NET Core(Part 3)- UseMvc(译)

    原文:https://www.stevejgordon.co.uk/asp-net-core-anatomy-part-3-addmvc 发布于:2017年4月环境:ASP.NET Core 1.1 ...

  4. [译]ASP.NET Core 2.0 系列文章目录

    基础篇 [译]ASP.NET Core 2.0 中间件 [译]ASP.NET Core 2.0 带初始参数的中间件 [译]ASP.NET Core 2.0 依赖注入 [译]ASP.NET Core 2 ...

  5. .Net Core 学习 (1) - ASP.NET Core 总览

    什么是ASP.NET 1.0 开源 - GitHub 跨平台 - 支持Windows, Mac, Linux 从底层进行了优化 - 使用最小开销的模块化组件 - 给与了开发人员很大的灵活性 为什么要使 ...

  6. [ASP.NET Core 2.0 前方速报]Core 2.0.3 已经支持引用第三方程序集了

    发现问题 在将 FineUIMvc(支持ASP.NET MVC 5.2.3)升级到 ASP.NET Core 2.0 的过程中,我们发现一个奇怪的现象: 通过项目引用 FineUICore 工程一切正 ...

  7. 《ASP.NET Core项目开发实战入门》带你走进ASP.NET Core开发

    <ASP.NET Core项目开发实战入门>从基础到实际项目开发部署带你走进ASP.NET Core开发. ASP.NET Core项目开发实战入门是基于ASP.NET Core 3.1 ...

  8. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  9. ASP.NET Core 运行原理剖析

    1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...

随机推荐

  1. NOIP 2012 Day1

    tags: NOIP 模拟 倍增 高精 Python categories: 信息学竞赛 总结 Luogu P1079 Vigenère 密码 Solution 表示并不是很懂其他人发的题解. 我是这 ...

  2. mysql大法

    mysql大法 MySQL 安装方式 1.rpm(yum) 2.源码包 3.通用二进制 企业中版本选择 5.6 5.7 选择 GA 6个月到1年之间的------------------------- ...

  3. 使用GitLab进行落地项目的管理,并且自动更新、重启、回滚

      Gitlab 清空项目历史commit,节省空间 http://blog.csdn.net/dounine/article/details/77840416?locationNum=6&f ...

  4. 微信小程序获取用户信息“授权失败”场景的处理

    很多的时候我们在处理小程序功能的时候需要用户获取用户信息,但是呢为了信息安全,用户不授权导致授权失败场景:但是小程序第二次不在启动授权信息弹层,为了用户体验,可以用以下方式处理: function i ...

  5. jmeter+Jenkins 持续集成中发送邮件报错:MessagingException message: Exception reading response

    已经配置好了发送邮件的相关信息,但是执行完脚本出现报错:MessagingException message: Exception reading response 1.查看Jenkins本次构建的控 ...

  6. 查看linux版本及lsb_release安装及一些想法

    https://blog.csdn.net/darkdragonking/article/details/61194308

  7. 四十八 常用内建模块 HTMLParser

    如果我们要编写一个搜索引擎,第一步是用爬虫把目标网站的页面抓下来,第二步就是解析该HTML页面,看看里面的内容到底是新闻.图片还是视频. 假设第一步已经完成了,第二步应该如何解析HTML呢? HTML ...

  8. 《深入理解Android2》读书笔记(二)

    接之前那篇<深入理解Android2>读书笔记(一) 下面几篇来分别详细分析 Binder类作为服务端的Bn的代表,BinderProxy类作为客户端的Bp的代表,BinderIntern ...

  9. Spring Cloud Feign 总结

    Spring Cloud中, 服务又该如何调用 ? 各个服务以HTTP接口形式暴露 , 各个服务底层以HTTP Client的方式进行互相访问. SpringCloud开发中,Feign是最方便,最为 ...

  10. 解决PHPExcel列超过26的问题

    $column = PHPExcel_Cell::stringFromColumnIndex(index);//index对应的就是列,从0开始 $objPHPExcel->getActiveS ...