【ASP.NET Core 沉思录】CreateWebHostBuilder 是一个 Convension

失踪人口回归。去年六月份开始,我开始翻译一千多页的《CSharp 7 in a Nutshell》到现在为止终于告一段落。我又回归了表世界。从这次开始我希望展开一个全新的主题。我叫它 ASP.NET Core 沉思录(多么高大上的名字,自我陶醉~)。今天是第一个主题。CreateWebHostBuilder 是一个 Convension。

太长不读

对于 WebApplicationFactory<T> 而言,默认情况下会采取如下假定:

  • Startup 所在的程序集应当就是应用程序入口(Main)所在的程序集;(官方工程模板的坑)

  • 应用程序入口所在的类(Program),里面会包含整个创建和配置 IWebHostBuilder 的过程;

  • 创建和配置 IWebHostBuilder 的过程是由应用程序入口所在类的 CreateWebHostBuilder 方法完成的。

在满足上述假定的情况下,无需额外代码,Web 应用的执行和测试将共享相同的逻辑。如若不然,则测试失败。如果无法满足上述三种条件还可以通过集成 WebApplicationFactory<T> 并重写 CreateWebHostBuilder 方法来解决。

以上约束仅仅限定于 WebApplicationFactory<T>,若直接在测试中使用 TestServer 则没有这种限制。

WebApplicationFactory<T>T 并不是 TStartup,而是应用程序入口所在的程序集中的任意类型。

娓娓道来

如果我们使用 dotnet 命令行创建一个 ASP.NET Core MVC/WebAPI 的工程。那么它的启动代码大概是这样的:

public static class Program
{
    public static void Main(string[] args)
    
{
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args)
    
{
        // Modified a little bit for the sake of illustration
        return new WebHostBuilder()
            .UseKestrel()
            .ConfigureLogging(lb =>
            {
                lb.SetMinimumLevel(LogLevel.Debug).AddConsole();
            })
            .UseStartup<Startup>();
    }
}

有没有小伙伴好奇,为什么需要一个 CreateWebHostBuilder 方法?从直观上看,它是创建并完成基本的 IWebHostBuilder 配置的方法。这个方法应在测试中进行复用以确保测试和应用程序中的 IWebHostBuilder 配置几乎相同,例如:

[Fact]
public async Task should_get_response_text()
{
    IWebHostBuilder webHostBuilder = Program.CreateWebHostBuilder(Array.Empty<string>());

    using (var testServer = new TestServer(webHostBuilder))
    using (HttpClient client = testServer.CreateClient())
    {
        HttpResponseMessage response = await client.GetAsync("/message");

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.Equal("Hello"await response.Content.ReadAsStringAsync());
    }
}

这个测试是可以顺利通过的。但是我们认为将 Program.CreateWebHostBuilder 暴露并不是一个好的感觉。我们更希望把这个配置过程分离。例如分离到一个类中:

public class WebHostBuilderConfigurator
{
    public IWebHostBuilder Configure(IWebHostBuilder webHostBuilder)
    
{
        return webHostBuilder
            .UseKestrel()
            .ConfigureLogging(lb =>
            {
                lb.SetMinimumLevel(LogLevel.Debug).AddConsole();
            })
            .UseStartup<Startup>();
    }
}

这样,Program 仅仅包含整个应用程序的入口,CreateWebHostBuilder 方法就被删掉了:

public static void Main(string[] args)
{
    var webHostBuilder = new WebHostBuilder();
    new WebHostBuilderConfigurator().Configure(webHostBuilder).Build().Run();
}

测试也就变成了:

[Fact]
public async Task should_get_response_text()
{
    IWebHostBuilder webHostBuilder = new WebHostBuilderConfigurator().Configure(new WebHostBuilder());

    using (var testServer = new TestServer(webHostBuilder))
    using (HttpClient client = testServer.CreateClient())
    {
        HttpResponseMessage response = await client.GetAsync("/message");

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.Equal("Hello"await response.Content.ReadAsStringAsync());
    }
}

看起来不错,测试也通过了真是可喜可贺。现在我们准备使用更加完善的 WebApplicationFactory<T> 代替 TestServer 进行测试:

[Fact]
public async Task should_get_response_text_using_web_app_factory()
{
    using (var factory = new WebApplicationFactory<Startup>().WithWebHostBuilder(
        wb => new WebHostBuilderConfigurator().Configure(wb)))
    using (HttpClient client = factory.CreateClient())
    {
        HttpResponseMessage response = await client.GetAsync("/message");

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.Equal("Hello"await response.Content.ReadAsStringAsync());
    }
}

看起来不错,但是发现测试运行的时候却失败了。并伴有诡异的异常信息:

System.InvalidOperationException : No method 'public static IWebHostBuilder CreateWebHostBuilder(string[] args)' found on 'WebApp.Program'. Alternatively, WebApplicationFactory`1 can be extended and 'protected virtual IWebHostBuilder CreateWebHostBuilder()' can be overridden to provide your own IWebHostBuilder instance.

哦,真神奇,它怎么找到 WebApp.Program 的?我只告诉了它 Startup 而并没有提供任何 Program 类型的信息啊?而这个时候,如果我们老老实实的恢复 WebApp.Program 类中的 CreateWebHostBuilder 方法,那么测试就顺利通过了。

这是为什么呢?原来让测试环境尽可能的 Match 执行环境是我们共同的心愿,WebApplicationFactory 希望能够自动的帮我们解决这个问题,于是它做了如下的假定:

  • Startup 所在的程序集应当就是应用程序入口(Main)所在的程序集;

  • 应用程序入口所在的类(Program),里面会包含整个创建和配置 IWebHostBuilder 的过程;

  • 创建和配置 IWebHostBuilder 的过程是由应用程序入口所在类的 CreateWebHostBuilder 方法完成的。

只要符合这三个假定,那么你尽可不费吹灰之力就达到了产品测试配置一致的目的。而如果不符合这个假定将让测试在默认状态下执行失败。具体的代码请参考 这里 和 这里。从 WebHostFactoryResolver 里面可以看出,除了 CreateWebHostBuilder 方法之外,BuildWebHost 也是一个 Convension,只不过主要是为了向前兼容的目的。

在真实的项目中,很可能是不满足这三个条件的,那么怎么办呢?还好我们可以通过集成 WebApplicationFactory<T> 并重写 CreateWebHostBuilder 方法来解决这个问题:

public class MyWebApplicationFactory : WebApplicationFactory<Startup>
{
    protected override IWebHostBuilder CreateWebHostBuilder()
    
{
        var webHostBuilder = new WebHostBuilder();
        new WebHostBuilderConfigurator().Configure(webHostBuilder);
        return webHostBuilder;
    }
}

并相应的将测试更改为:

[Fact]
public async Task should_get_response_text_using_web_app_factory()
{
    using (var factory = new MyWebApplicationFactory())
    using (HttpClient client = factory.CreateClient())
    {
        HttpResponseMessage response = await client.GetAsync("/message");

        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.Equal("Hello"await response.Content.ReadAsStringAsync());
    }
}

就可以了。

最后,需要提醒的是 WebApplicationFactory<T>TTEntryPoint ,是入口所在的程序集的类型。虽然平常大家都喜欢写 Startup

总结

请飞到文章开头~ :-D


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316815.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

对弈(nim-k游戏博弈)

problem AliceAliceAlice 和 BobBobBob 又在玩游戏。 AliceAliceAlice 和 BobBobBob 在一个 1n1\times n1n 的网格图上玩游戏&#xff0c;网格图的 nnn 个格子中&#xff0c;有 kkk 个格子内被各放了一个棋子&#xff0c;其中 kkk 是一个偶数。 从左到右&#xff0c;这 kkk 个…

Codeforces Round #722 (Div. 2)

Codeforces Round #722 (Div. 2) 题号题目知识点AEshag Loves Big Arrays&#xff08;题解略&#xff09;贪心BSifid and Strange Subsequences思维CParsa’s Humongous Tree树形dpDKavi on Pairing Duty思维推公式ETrees of Tranquillity思维线段树FIt’s a bird! No, it’s …

P3226 [HNOI2012]集合选数(状压、构造)

解析 做法闻所未闻的神仙题。 题目可以看成求这张特殊图的合法独立集数目。 这张图有一个特点&#xff1a;链长是 O(log⁡n)O(\log n)O(logn) 级别的&#xff0c;且每个点的度数比较少。 考虑构造如下矩阵&#xff1a; 1 3 9 12 ... 2 6 18 54 ... 4 ... 8 ...这个矩阵其实…

.Netcore 2.0 Ocelot Api网关教程(6)- 配置管理

本文介绍Ocelot中的配置管理&#xff0c;配置管理允许在Api网关运行时动态通过Http Api查看/修改当前配置。由于该功能权限很高&#xff0c;所以需要授权才能进行相关操作。有两种方式来认证&#xff0c;外部Identity Server或内部Identity Server。1、外部Identity Server修改…

CF 1529B. Sifid and Strange Subsequences

CF 1529B. Sifid and Strange Subsequences 题意&#xff1a; 给你n个数&#xff0c;让你从这n个数中找m个数&#xff0c;保证这m个数中任意两个数的差的绝对值大于等于这m个数中最大值。求一个最大的m。 题解&#xff1a; 这个m个数中最多只能有一个正数。因为任意两个正数…

最短路径(虚树+期望)

problem 给定一棵 nnn 个结点的无根树&#xff0c;每条边的边权均为 111 。 树上标记有 mmm 个互不相同的关键点&#xff0c;小 A 会在这 mmm 个点中等概率随机地选择 kkk 个不同的点放上小饼干。 你想知道&#xff0c;经过有小饼干的 kkk 个点的最短路径长度的期望是多少。…

AT2000 [AGC002F] Leftmost Ball(dp、组合数学)

解析 如果之前有些卡住的题可以说是奇淫技巧的话&#xff0c;这道题的思路只能说太经典了。 感觉其实也就是紫的难度吧&#xff0c;没做出来有些可惜。 还是有写畏黑情绪&#xff0c;见到黑题本能的感觉做不出来。 首先是一个比较自然的题意转化&#xff1a;有 k-1 个 1-n 的…

CF 1529E. Trees of Tranquillity

CF 1529E. Trees of Tranquillity 文章目录题意&#xff1a;题解&#xff1a;代码&#xff1a;线段树代码&#xff1a;利用set实现题意&#xff1a; 有A1&#xff0c;A2两棵树&#xff0c;根是1&#xff0c;编号都是1~n&#xff0c;先制作图A3&#xff0c;如果两个点的x和y同时…

【学习笔记】最大权闭合子图和最大密度子图(最小割的模型应用)

最大权闭合子图和最大密度子图最大权闭合子图contentexercise最大密度子图contentexerciseUpd&#xff1a;最大权闭合子图易懂证明最大权闭合子图 content 先作出以下声明&#xff1a; c(u,v):c(u,v):c(u,v): 边 (u,v)(u,v)(u,v) 的容量。 f(u,v):f(u,v):f(u,v): 边 (u,v)(u,…

Docker最全教程之使用Docker搭建Java开发环境(十八)

前言Java是一门面向对象的优秀编程语言&#xff0c;市场占有率极高&#xff0c;但是在容器化实践过程中&#xff0c;发现官方支持并不友好&#xff0c;同时与其他编程语言的基础镜像相比&#xff08;具体见各语言镜像比较&#xff09;&#xff0c;确实是非常臃肿。本篇仅作探索…

AT2705 [AGC019F] Yes or No(组合数学)

解析 Atcoder的题超小的码量总让人做不出来的时候感到很不甘心… 但这题确实挺难的&#xff0c;主要还是魔术一样的奇淫技巧。 大力推式子那个阴间方法我直接选择弃疗。 一个很显然的结论是&#xff1a;肯定回答当前剩的比较多的选项。 pia一张洛谷的图&#xff1a; &#…

Coding Contest HDU - 5988

Coding Contest HDU - 5988 题意&#xff1a; 有n个点&#xff0c;m个边&#xff0c;每个点有人数和食物数&#xff0c;每个人都要吃一份食物&#xff0c;如果该点的食物不够&#xff0c;他们就要去其他点&#xff0c;每个边最多只能走c次&#xff0c;每次有人走一条路&#…

ASP.NET Core 项目简单实现身份验证及鉴权

环境VS 2017ASP.NET Core 2.2目标以相对简单优雅的方式实现用户身份验证和鉴权&#xff0c;解决以下两个问题&#xff1a;无状态的身份验证服务&#xff0c;使用请求头附加访问令牌&#xff0c;几乎适用于手机、网页、桌面应用等所有客户端基于功能点的权限访问控制&#xff0c…

AT4352 [ARC101C] Ribbons on Tree

解析 其实想到了断边按连通块容斥的做法。 但不知道为啥觉得没前途弃了… 悲。 考虑容斥&#xff0c;设 f[x][i] 表示 x 子树内与 x 所连的联通块有 i 个节点的方案数&#xff0c;第三维朴素是记录连通块个数&#xff0c;但其实只需要记一个 0/1 表示奇偶性即可。 然后题解奥…

【无码专区7】括号序列(思维)

因为只有std&#xff0c;没有自我实现&#xff0c;所以是无码专区 主要是为了训练思维能力 solution才是dls正解&#xff0c;但是因为只有潦草几句&#xff0c;所以大部分会有我自己基于正解上面的算法实现过程&#xff0c;可能选择的算法跟std中dls的实现不太一样。 std可能…

2016ICPC青岛

2016ICPC青岛 题号题目难度知识点ARelic Discovery签到贪心BPocket Cube快铜大模拟CPocky签到数论&#xff0c;推公式DLucky CoinsEFibonacciFLambda CalculusGCoding Contest银牌题网络流HPatternITravel BrochureJCliquesKFinding HotelsLTower AttackMGenerator and Monito…

ML.NET 发布0.11版本:.NET中的机器学习,为TensorFlow和ONNX添加了新功能

微软发布了其最新版本的机器学习框架&#xff1a;ML.NET 0.11带来了新功能和突破性变化。新版本的机器学习开源框架为TensorFlow和ONNX添加了新功能&#xff0c;但也包括一些重大变化, 这也是发布RC版本之前的最后一个预览版&#xff0c;这个月底将发布0.12版本&#xff0c;也就…

CF1267G Game Relics(期望、背包)

解析 有些遗憾的一个题。 几乎已经做出来了&#xff0c;但最后把买的代价看成平均数确实没有想到。 还有那个背包我觉得直接做会炸精度&#xff0c;就开始各种玩泥巴。 悲。 比较显然的结论是最优解必然是先抽抽抽然后再买买买。 剩 i 个宝物的时候抽出一个新宝物的期望代价比…

铺地毯(矩形的交+前后缀矩形交)

铺地毯problemsolutioncodeproblem 给定矩阵的长宽 P,QP,QP,Q&#xff0c;矩阵从下往上从左往后编号增加&#xff0c;(0,0)∼(P,Q)(0,0)\sim (P,Q)(0,0)∼(P,Q)。 给定 nnn 张长宽平行于坐标轴的矩形地毯&#xff0c;左下角和右上角的坐标。 求被至少 n−1n-1n−1 张地毯覆盖…

AT2366 [AGC012F] Prefix Median(dp)

解析 确实精妙的一道题。 卡在最后一步dp转化上了&#xff0c;这个转化也确实是本题的难点。 当一个计数难以下手的时候&#xff0c;先想想如何判合法&#xff1f; 不难想到一个较为显然的贪心&#xff1a;假如和上一个一样&#xff0c;就填一个当前的最大值和最小值&#xf…