此文章翻译自 NDC { London } 16-20 January 2017 上, Damian Edwards和David Fowler的演讲,如果翻译不周,请大家指出错误。

Logging

  1. 生产环境总是配置一个Logger(比如: Serilog, Application Insights)

  2. 日志作为诊断应用程序问题的入口

  3. 不需要重启应用程序,就能改变日志的级别

    在开发环境应该记录尽可能多的日志,但是生产环境出于性能考虑,应该只记录Warning以上的日志

  4. 如果不想显示太多的信息,可以选择特定的Category

    比如只想调试SQL语句,可以只记录Category为Microsoft.EntityFrameworkCore.*的日志。

  5. ASP.NET按照如下方式记录日志:

    • ANCM(IIS): 将不能启动进程的错误记录到EventLog

      ANCM是指ASP.NET Core Module。按照Damian的说法,当IIS进程无法启动的时候是很崩溃的,此时可以从Windows Event Log中查看具体的错误。

    • ANCM also uses regular IIS failed request tracing

    • 其他未处理的异常将由为logger providers提供的logger记录

Configuration & Options

  1. 随着配置文件的改变而自动重新加载配置

  2. 当配置内容的值更改时,使用IOptionsSnapshot重新加载IOptions对象

    IOptionsSnapshot是ASP.NET Core 1.1提供的新接口,这个接口在配置文件改变时会重新绑定配置对象的值,这样可以在应用程序运行时动态修改配置内容。

  3. 使用User Secrets, 环境变量,Azure KeyVault等方式存储和环境有关的配置内容

    比如每个环境的连接字符串,启动的端口不一致,并且这些配置是很少发生改变的,可以配置在环境变量中。

  4. 应用程序部署在IIS时,可以通过web.config配置环境变量

File Upload

  1. 如果上传的文件需要提供给浏览器访问,将它们放在web root下

  2. 最简单的处理文件上传的方法,是使用MVC里的IFormFile或者IFormFileCollection

    这2个接口可以直接定义为Action的参数,也可以定义在Model里。

    和传统的ASP.NET相比,少了Request.Files。

  3. 也可以使用低级别的API,比如HttpRequest.ReadFormAsync和HttpRequest.Form

  4. 阻止用户上传任何可以在服务器执行的文件

    比较容易疏忽的是.cshtml文件。ASP.NET Core 2.0提供了Razor Page,那么.cshtml文件很可能被制作成一个可以直接访问的Razor Page上传上来。

  5. 大文件会提供buffer,防止服务器内存暴增

  6. 在保存文件之前,检查客户端传递的文件路径

    保证文件上传功能的安全性一直是个斗智斗勇的过程,David演示了一个简单的Hack场景。如果直接使用如下的方式保存文件:

    var path = Path.Combine(env.ContentRoot, file.FileName);

    当客户端传递来的FileName是"\Views\Home\Index.cshtml"时,你的网站就杯具了。

  7. 处理很大的multipart uploads时,不要使用model binding。手动的调用HttpRequest.GetMultipartBoundary()方法。

    这个方法仅在ASP.NET CORE 1.1.0提供

  8. IIS的限制仍然生效

    这里应该说的是maxRequestSize之类的限制

Dependency Injection

  1. 保持你的DI工厂快速响应,不要在构造服务时使用异步方法

    如下代码会导致死锁

    class Program
    {
    static void Main(string[] args)
    {
    DeadlockWithFactory(); Console.WriteLine("hello");
    } private static void DeadlockWithFactory()
    {
    var services = new ServiceCollection();
    services.AddSingleton(s =>
    {
    var b = GetBAsync(s).Result; return new A(b);
    }); services.AddSingleton<B>(); var serviceProvider = services.BuildServiceProvider();
    var a = serviceProvider.GetService<A>(); //1. 此处lock了Container
    } private static async Task<B> GetBAsync(IServiceProvider s)
    {
    await Task.Delay(1000); //2. 此处由于使用了await关键字,离开了主线程,进入了后台线程 return s.GetRequiredService<B>(); //3. 此处后台线程尝试lock Container,导致死锁
    }
    } public class A
    {
    public A(B b)
    { }
    } public class B
    { }
  2. 避免手动的调用GetService

    这里的大概意思是,由于手动调用GetService依赖了IServiceProvider,会使得单元测试变复杂。

    有些场景下,我们不需要在构造函数注入服务,而是想在执行到特定方法的时候才注入。Controller层面有个FromServices特性可以标记在Action的参数上。如果是下层代码,则可以自己定义一个XXXProvider,提供一个ResolveXXX方法,那么只需要在构造函数注入XXXProvider即可。这种方式适用于构造数据库连接,打开文件等。

  3. 实现了IDisposable接口的服务会由Container控制调用它们的Dispose方法,如果在顶层Container中构造出该服务的实例,会导致内存泄漏。

    Container构造的服务,会在Container释放的时候自动释放。如果在ApplicationServices上构造出的实例,在应用停止后才会释放。

  4. Turn on scope validation to make sure you don't have scoped services capturing singletons.

    想像一下这个场景

    var services = new ServiceCollection();
    services.AddSingleton<A>();
    services.AddScoped<B>(); var sp = services.BuildServiceProvider();
    var a = sp.GetRequiredService<A>();

    如果A依赖了B,那么本应该是Scoped的B类型被捕获成了单例。

    在ASP.NET Core 1.1中, BuildServiceProvider方法提供了一个新的参数 validateScopes,上面的代码可以修改成

    var sp = services.BuildServiceProvider(validateScopes: true);
    var a = sp.GetRequiredService<A>(); //如果A依赖B,那么此处会抛出System.InvalidOperationException: Cannot consume scoped service 'B' from singleton 'A'.
  5. 优化依赖注入

    构造函数注入

    public class A(B b) { }
    public class B(C c) { }
    public class C() { }

    等同于

    new A(new B(new C()));

    ServiceProvider注入

    public class A(IServiceProvider sp) { _b = sp.GetService<B>(); }
    public class B(IServiceProvider sp) { _c = sp.GetService<C>(); }
    public class C() { }

    等同于

    new C();
    new B(GetService<C>());
    new A(GetService<B>());

MVC

  1. Protect against mass assignment with separate input/output models

    在现有MVC模式中,我们一般定义一个ViewModel来展示整个页面需要显示的内容,再定义一些Model存储客户端传递的参数。我们要尽量避免这个过程中产生的重复编码。

    public class AdminViewModel
    {
    [Display(Name = "Live Show Embed URL", Description = "URL for embedding the live show")]
    [DataType(DataType.Url)]
    public string LiveShowEmbedUrl { get; set; } [Display(Name = "Live Show HTML", Description = "HTML content for the live show")]
    [DataType(DataType.MultilineText)]
    public string LiveShowHtml { get; set; } [Display(Name = "Next Show Date/time", Description = "Exact date and time of the next live show in Pacific Time")]
    [DateAfterNow(TimeZoneId = "Pacific Standard Time")]
    public DateTime? NextShowDatePst { get; set; } [Display(Name = "Standby Message", Description = "Message to show on home page during show standby")]
    public string AdminMessage { get; set; } public string NextShowDateSuggestionPstAM { get; set; } public string NextShowDateSuggestionPstPM { get; set; } public string SuccessMessage { get; set; } public bool ShowSuccessMessage => !string.IsNullOrEmpty(SuccessMessage); public AppSettings AppSettings { get; set; } public string EnvironmentName { get; set; }
    } [ModelMetadataType(typeof(AdminViewModel))]
    public class AdminInputModel
    {
    public string LiveShowEmbedUrl { get; set; } public string LiveShowHtml { get; set; } public DateTime? NextShowDatePst { get; set; } public string AdminMessage { get; set; }
    }

    在这个例子中,在Get请求时需要返回一个包含了所有展示内容的AdminViewModel,在Post请求时只需要客户端传递部分参数,因此可以定义一个AdminInputModel,同时指定ModelMetadataType(typeof(AdminViewModel)),这样验证的Attribute可以从AdminViewModel中自动复制过来,避免重复编码。

    同时,推荐大家采用类似AutoMapper之类的第三方库解决对象复制、克隆过程中产生的重复编码。

    在上述代码中,我更倾向于以如下的方式定义Model

    public class AdminViewModel
    {
    public AdminInputModel Input { get; set; } public string NextShowDateSuggestionPstAM { get; set; } public string NextShowDateSuggestionPstPM { get; set; } public string SuccessMessage { get; set; } public bool ShowSuccessMessage => !string.IsNullOrEmpty(SuccessMessage); public AppSettings AppSettings { get; set; } public string EnvironmentName { get; set; }
    } [ModelMetadataType(typeof(AdminViewModel))]
    public class AdminInputModel
    {
    [Display(Name = "Live Show Embed URL", Description = "URL for embedding the live show")]
    [DataType(DataType.Url)]
    public string LiveShowEmbedUrl { get; set; } [Display(Name = "Live Show HTML", Description = "HTML content for the live show")]
    [DataType(DataType.MultilineText)]
    public string LiveShowHtml { get; set; } [Display(Name = "Next Show Date/time", Description = "Exact date and time of the next live show in Pacific Time")]
    [DateAfterNow(TimeZoneId = "Pacific Standard Time")]
    public DateTime? NextShowDatePst { get; set; } [Display(Name = "Standby Message", Description = "Message to show on home page during show standby")]
    public string AdminMessage { get; set; }
    }
  2. TagHelpers

    • 正确的处理属性值
    • 属性中带有文件路径时,确保在执行前验证文件是否存在
  3. 在Post-Redirect-Get场景中使用TempData

    当你在一个Post的请求里完成操作,并且需要重定向到另外一个页面展示操作结果时,可以通过URL传参,Cookies, Sessions等技术来实现,也可以使用TempData来实现。

    ASP.NET Core的TempData是基于Cookies的。

Testing your pipeline

  1. 使用TestHost在内存中测试整个管道,而不用访问网络

    [Fact]
    public async Task VerifyResponse()
    {
    var builder = new WebHostBuilder()
    .UseStartup<Startup>(); var server = new TestServer(builder);
    var client = server.CreateClient(); var response = await client.GetAsync("http://something"); Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
    }
  2. 单元测试中也可以使用enviroments来对不同的环境进行测试

    [Fact]
    public async Task VerifyTestEnvironmentResponse()
    {
    var builder = new WebHostBuilder()
    .UseEnvironment("Test")
    .UseStartup<StartupWithEnvironment>(); var server = new TestServer(builder);
    var client = server.CreateClient(); var response = await client.GetAsync("http://something"); Assert.Equal("Test", await response.Content.ReadAsStringAsync());
    }

Per request state

  1. HttpContext.Items

    和以往一样,可以将与会话相关的数据存储在HttpContext.Items

  2. HttpContext.Get/SetFeature

    通过调用HttpContext.Features.Set方法添加自定义的行为

  3. Scoped services

    每个会话开始会构造一个Scope,会话结束会释放该Scope。因此和会话相关的服务最好注册为Scoped。

  4. 在Request Context以外要访问HttpContext时,可以使用IHttpContextAcessor接口。

    public class A
    {
    private HttpContext HttpContext { get; set; } public A(IHttpContextAcessor httpContextAcessor)
    {
    this.HttpContext = httpContextAcessor.HttpContext;
    }
    }

备注

  1. 原文视频:Youtube

  2. 相关源码

Patterns for application development with ASP.NET Core的更多相关文章

  1. Prerender Application Level Middleware - ASP.NET Core Middleware

    In the previous post Use Prerender to improve AngularJS SEO, I have explained different solutions at ...

  2. [转]Session and application state in ASP.NET Core

    本文转自:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state By Rick Anderson and Steve ...

  3. 使用Azure Application Insignhts监控ASP.NET Core应用程序

    Application Insignhts是微软开发的一套监控程序.他可以对线上的应用程序进行全方位的监控,比如监控每秒的请求数,失败的请求,追踪异常,对每个请求进行监控,从http的耗时,到SQL查 ...

  4. Working with Data » 使用Visual Studio开发ASP.NET Core MVC and Entity Framework Core初学者教程

    原文地址:https://docs.asp.net/en/latest/data/ef-mvc/intro.html The Contoso University sample web applica ...

  5. [转]ASP.NET Core 1 Deploy to IIS

    本文转自: http://webmodelling.com/webbits/aspnet/aspnet-deploy-iis.aspx 15 Sep 2016. This tutorial will ...

  6. 翻译 - ASP.NET Core 基本知识 - 配置(Configuration)

    翻译自 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0 ASP ...

  7. ASP.NET Core 1.0 入门——了解一个空项目

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  8. ASP.NET Core 2 学习笔记(二)生命周期

    要了解程序的运行原理,就要先知道程序的进入点及生命周期.以往ASP.NET MVC的启动方式,是继承 HttpApplication 作为网站开始的进入点,而ASP.NET Core 改变了网站的启动 ...

  9. ASP.NET Core 2 学习笔记(十一)Cookies & Session

    基本上HTTP是没有记录状态的协定,但可以通过Cookies将Request来源区分出来,并将部分数据暂存于Cookies及Session,是写网站常用的用户数据暂存方式.本篇将介绍如何在ASP.NE ...

随机推荐

  1. 阶乘运算——ACM

    大数阶乘 时间限制:3000 ms  |  内存限制:65535 KB 难度:3   描述 我们都知道如何计算一个数的阶乘,可是,如果这个数很大呢,我们该如何去计算它并输出它?   输入 输入一个整数 ...

  2. 详解JDBC连接数据库

    一.概念 1. 为了能让程序操作数据库,对数据库中的表进行操作,每一种数据库都会提供一套连接和操作该数据库的驱动,而且每种数据库的驱动都各不相同,例如mysql数据库使用mysql驱动,oracle数 ...

  3. 分享小知识:善用Group By排序

    以下列举了公用表/临时表/聚合函数三个因素为例子(覆盖索引因素除外,有利用此类索引都会以索引顺序)  环境: Microsoft SQL Server 2014 (SP1-GDR) (KB319472 ...

  4. freemodbus移植讲解 ZZ

    一   为什么要移植Freemodbus 为什么要移植Freemodbus,这个问题需要从两个方面来回答.第一,modbus是一个非常好的应用层协议,它很简洁也相对完善.对于还没有接触过modbus的 ...

  5. php+apache+mysql的安装

    1.LAMP的安装顺序问题,现在是默认安装好了Linux系统,我的版本是Ubuntu 12.04.一般来说比较建议的顺序是Mysql Apache 最后安装PHP,在我实践下来 Apache和Mysq ...

  6. dreamweaver破解版下载地址

    dreamweaver CC 2015可以在可视化环境中设计的你网页,非常方便高效完成网页设计.Adobe Dreamweaver CC 2015支持CSS编辑器.和jQuery库代码自动完成功能等等 ...

  7. sublime插件emmet的配置、使用及快捷键Ctrl+E修改成Tab键操作

    一.emmet在sublime中的配置与使用: 1.点击sublime text 3的图标,打开编辑器: 2.按键“ctrl+shift+p”,或者单击菜单->工具->命令面板: 3.打开 ...

  8. JDBC(下)

    1. 预编译sql处理(防止sql注入) -- 创建数据库 CREATE DATABASE jdbc_demo DEFAULT CHARACTER SET utf8;i -- 创建表 USE jdbc ...

  9. Alamofire源码解读系列(八)之安全策略(ServerTrustPolicy)

    本篇主要讲解Alamofire中安全验证代码 前言 作为开发人员,理解HTTPS的原理和应用算是一项基本技能.HTTPS目前来说是非常安全的,但仍然有大量的公司还在使用HTTP.其实HTTPS也并不是 ...

  10. Logistic Regression理论总结

    简述: 1. LR 本质上是对正例负例的对数几率做线性回归,因为对数几率叫做logit,做的操作是线性回归,所以该模型叫做Logistic Regression. 2. LR 的输出可以看做是一种可能 ...