1. 程式人生 > 實用技巧 >Net Core 重要的技術點

Net Core 重要的技術點

Net Core 重要的技術點

1、中介軟體概念

是處理Http請求和響應的元件(程式碼段,一段處理邏輯),每個元件

  • 選擇是否將請求傳遞給管道中的下一個元件。
  • 可以在呼叫管道中的下一個元件之前和之後執行一些邏輯。

這樣的機制使得HTTP請求能夠很好的被層層處理和控制,並且層次清晰處理起來甚是方便。
最後一個管道或者中斷管道的中介軟體叫終端中介軟體;

請求委託(Request delegates)用於構建請求管道,處理每個HTTP請求

管道就是http請求抵達伺服器到響應結果返回的中間的一系列的處理過程

2、中介軟體常用的方法

中介軟體中定義了Run、Use、Map、MapWhen幾種方法,我們下面一一講解這幾種方法。

1、Run()

Run()方法中只有一個RequestDelegate委託型別的引數,沒有Next引數,所以Run()方法也叫終端中介軟體,不會將請求傳遞給下一個中介軟體,也就是發生了“短路”

// Run方法嚮應用程式的請求管道中新增一個RequestDelegate委託
// 放在管道最後面,終端中介軟體
app.Run(handler: async context => 
{
    await context.Response.WriteAsync(text: "Hello World1\r\n");
});
app.Run(handler: async context =>
{
    await context.Response.WriteAsync(text: "Hello World2\r\n");
});

2、Use()方法

Use方法的引數是一個Func委託,輸入引數是一個RequestDelegate型別的委託,返回引數也是一個RequestDelegate型別的委託,這裡表示呼叫下一個中介軟體

Public   delegate Task RequestDelegate(HttpContext context)

RequestDelegate是一個委託,有一個HttpContext型別的引數,HttPContext表示Http請求上下文,可以獲取請求資訊,返回值是Task型別

// 嚮應用程式的請求管道中新增一個Func委託,這個委託其實就是所謂的中介軟體。
// context引數是HttpContext,表示HTTP請求的上下文物件
// next引數表示管道中的下一個中介軟體委託,如果不呼叫next,則會使管道短路
// 用Use可以將多箇中間件連結在一起
app.Use(async (context, next) =>
{
    await context.Response.WriteAsync(text: "hello Use1\r\n");
    // 呼叫下一個委託
    await next();
});
app.Use(async (context, next) =>
{
    await context.Response.WriteAsync(text: "hello Use2\r\n");
    // 呼叫下一個委託
    await next();
});

3、自定義中介軟體

中介軟體遵循顯示依賴原則,並在其建構函式中暴露所有依賴項。中介軟體能夠利用UseMiddleware擴充套件方法的優勢,直接通過它們的建構函式注入服務。依賴注入服務是自動完成填充的。

ASP.NET Core約定中介軟體類必須包括以下內容:

  1. 具有型別為RequestDelegate引數的公共建構函式。
  2. 必須有名為Invoke或InvokeAsync的公共方法,此方法必須滿足兩個條件:方法返回型別是Task、方法的第一個引數必須是HttpContext型別。

我們自定義一個記錄IP的中介軟體,新建一個類RequestIPMiddleware,程式碼如下:

using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace MiddlewareDemo.Middleware
{
    /// <summary>
    /// 記錄IP地址的中介軟體
    /// </summary>
    public class RequestIPMiddleware
    {
        // 私有欄位
        private readonly RequestDelegate _next;

        /// <summary>
        /// 公共建構函式,引數是RequestDelegate型別
        /// 通過建構函式進行注入,依賴注入服務會自動完成注入
        /// </summary>
        /// <param name="next"></param>
        public RequestIPMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        /// <summary>
        /// Invoke方法
        /// 返回值是Task,引數型別是HttpContext
        /// </summary>
        /// <param name="context">Http上下文</param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context)
        {
            await context.Response.WriteAsync($"User IP:{context.Connection.RemoteIpAddress.ToString()}\r\n");
            // 呼叫管道中的下一個委託
            await _next.Invoke(context);
        }
    }
}

然後建立一個擴充套件方法,對IApplicationBuilder進行擴充套件:

using Microsoft.AspNetCore.Builder;

namespace MiddlewareDemo.Middleware
{
    public static class RequestIPExtensions
    {
        /// <summary>
        /// 擴充套件方法,對IApplicationBuilder進行擴充套件
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public static IApplicationBuilder UseRequestIP(this IApplicationBuilder builder)
        {
            // UseMiddleware<T>
            return builder.UseMiddleware<RequestIPMiddleware>();
        }
    }
}

最後在Startup類的Configure方法中使用自定義中介軟體:

// 使用自定義中介軟體
app.UseRequestIP();

4、中介軟體和過濾器的區別

中介軟體和過濾器都是一種AOP的思想,他們的功能類似,那麼他們有什麼區別呢?

  1. 過濾器更加貼合業務,它關注於應用程式本身,關注的是如何實現業務,比如對輸出結果進行格式化,對請求的ViewModel進行資料校驗,這時就肯定要使用過濾器了。過濾器是MVC的一部分,它可以攔截到你Action上下文的一些資訊,而中介軟體是沒有這個能力的。可以認為過濾器是附加性的一種功能,它只是中介軟體附帶表現出來的特徵。
  2. 中介軟體是管道模型裡重要的組成部分,不可或缺,而過濾器可以沒有。

5、Asp.Net Core異常處理

  1. 使用開發人員異常頁面(The developer exception page)
  2. 配置HTTP錯誤內碼表 Configuring status code pages
  3. 使用MVC過濾器 ExceptionFilter
  4. 自定義異常捕獲中介軟體 Middleware

1、使用開發人員異常頁面(The developer exception page)

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   //判斷是否是開發環境
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/error");
    }

}

2、配置HTTP錯誤內碼表 Configuring status code pages

 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                //開發環境異常處理
                app.UseBrowserLink();
                app.UseDeveloperExceptionPage();
            }
            else
            {
                //生產環境異常處理
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStatusCodePages();//使用HTTP錯誤內碼表
        }

app.UseStatusCodePages支援多種擴充套件方法。其中一個方法接受一個lambda表示式:

app.UseStatusCodePages(async context =>
{
    context.HttpContext.Response.ContentType = "text/plain";
    await context.HttpContext.Response.WriteAsync(
        "Status code page, status code: " + 
        context.HttpContext.Response.StatusCode);
});

還可以跳轉到指定頁面,並附加Response.StatusCode

app.UseStatusCodePagesWithReExecute("/Home/Error/{0}");

3、使用MVC過濾器

    /// <summary>
    /// 自定義全域性異常過濾器
    /// </summary>
    public class GlobalExceptionFilter : IExceptionFilter
    {
       
        readonly ILoggerFactory _loggerFactory;//採用內建日誌記錄
        readonly IHostingEnvironment _env;//環境變數
        public GlobalExceptionFilter(ILoggerFactory loggerFactory, IHostingEnvironment env)
        {
            _loggerFactory = loggerFactory;
            _env = env;
        }

        public void OnException(ExceptionContext context)
        {
            var controller = context.ActionDescriptor;          
            ILog log = LogManager.GetLogger(Startup.Repository.Name, controller.ToString());//初始化Log4net日誌
            #region 記錄到內建日誌
            //var logger = _loggerFactory.CreateLogger(context.Exception.TargetSite.ReflectedType);
            //logger.LogError(new EventId(context.Exception.HResult),
            //context.Exception,
            //context.Exception.Message);
            #endregion
            if (_env.IsDevelopment())
            {
                log.Error(context.Exception.ToString());
                //var JsonMessage = new ErrorResponse("未知錯誤,請重試");
                //JsonMessage.DeveloperMessage = context.Exception;
                //context.Result = new ApplicationErrorResult(JsonMessage);
                //context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                //context.ExceptionHandled = true;
            }
            else
            {
                log.Error(context.Exception.ToString());
                context.ExceptionHandled = true;
                context.Result=new RedirectResult("/home/Error");
            }
        }
        public class ApplicationErrorResult : ObjectResult
        {
            public ApplicationErrorResult(object value) : base(value)
            {
                StatusCode = (int)HttpStatusCode.InternalServerError;
            }
        }
        public class ErrorResponse
        {
            public ErrorResponse(string msg)
            {
                Message = msg;
            }
            public string Message { get; set; }
            public object DeveloperMessage { get; set; }
        }
    }
}

4、四自定義異常捕獲中介軟體 Middleware

   /// <summary>
   /// 自定義異常處理中介軟體
   /// </summary>
   public class ExceptionHandlingMiddleware
   {
       private readonly RequestDelegate _next;

       public ExceptionHandlingMiddleware(RequestDelegate next)
       {
           _next = next;
       }

       public async Task Invoke(HttpContext context)
       {
           try
           {
               await _next(context);
           }
           catch (Exception ex)
           {
               var statusCode = context.Response.StatusCode;
               await HandleExceptionAsync(context, ex.ToString());            
           }        
       }
       private Task HandleExceptionAsync(HttpContext context,  string msg)
       {          
           HandleExceptionHelper hannd = new HandleExceptionHelper();
           hannd.log.Error(msg);//記錄到日誌檔案
           return context.Response.WriteAsync("ERROR");
       }
   }

6、依賴注入

ASP.NET Core的核心是通過一個Server和若干註冊的Middleware構成的管道,不論是管道自身的構建,還是Server和Middleware自身的實現,以及構建在這個管道的應用,都需要相應的服務提供支援,ASP.NET Core自身提供了一個DI容器來實現針對服務的註冊和消費。

DI框架具有兩個核心的功能,即服務的註冊和提供,這兩個功能分別由對應的物件來承載, 它們分別是ServiceCollection和ServiceProvider

1、依賴注入的三種方式

在ASP.Net Core 依賴注入有三種:

  • Transient :每次請求時都會建立,並且永遠不會被共享。
  • Scoped : 在同一個Scope內只初始化一個例項 ,可以理解為( 每一個request級別只建立一個例項,同一個http request會在一個 scope內)
  • Singleton :只會建立一個例項。該例項在需要它的所有元件之間共享。因此總是使用相同的例項。

DI容器跟蹤所有已解析的元件, 元件在其生命週期結束時被釋放和處理:

  • 如果元件具有依賴關係,則它們也會自動釋放和處理。
  • 如果元件實現IDisposable介面,則在元件釋放時自動呼叫Dispose方法。

重要的是要理解,如果將元件A註冊為單例,則它不能依賴於使用Scoped或Transient生命週期註冊的元件。更一般地說:

服務不能依賴於生命週期小於其自身的服務。

通常你希望將應用範圍的配置註冊為單例,資料庫訪問類,比如Entity Framework上下文被推薦以Scoped方式注入,以便可以重用連線。如果要並行執行的話,請記住Entity Framework上下文不能由兩個執行緒共享,如果需要,最好將上下文註冊為Transient,然後每個服務都獲得自己的上下文例項,並且可以並行執行。

建議的做法:

儘可能將您的服務註冊為瞬態服務。 因為設計瞬態服務很簡單。 您通常不用關心多執行緒和記憶體洩漏,並且您知道該服務的壽命很短。
1、請謹慎使用Scoped,因為如果您建立子服務作用域或從非Web應用程式使用這些服務,則可能會非常棘手。
2、謹慎使用singleton ,因為您需要處理多執行緒和潛在的記憶體洩漏問題。
3、在singleton 服務中不要依賴transient 或者scoped 服務,因為如果當一個singleton 服務注入transient服務,這個 transient服務就會變成一個singleton服務,並且如果transient服務不是為支援這種情況而設計的,則可能導致問題。 在這種情況下,ASP.NET Core的預設DI容器已經丟擲異常。

2、DI在ASP.NET Core中的應用

1在Startup類中初始化

ASP.NET Core可以在Startup.cs的 ConfigureService中配置DI,大家看到 IServiceCollection這個引數應該就比較熟悉了。

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<ILoginService<ApplicationUser>,
      EFLoginService>();
    services.AddMvc();
)

  

ASP.NET Core的一些元件已經提供了一些例項的繫結,像AddMvc就是Mvc Middleware在 IServiceCollection上新增的擴充套件方法。

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);
    ...
}

  

2 Controller中使用

一般可以通過建構函式或者屬性來實現注入,但是官方推薦是通過建構函式。這也是所謂的顯式依賴。

private ILoginService<ApplicationUser> _loginService;
public AccountController(
  ILoginService<ApplicationUser> loginService)
{
  _loginService = loginService;
}

  

我們只要在控制器的建構函式裡面寫了這個引數,ServiceProvider就會幫我們注入進來。這一步是在Mvc初始化控制器的時候完成的,我們後面再介紹到Mvc的時候會往細裡講。

3 View中使用

在View中需要用@inject 再宣告一下,起一個別名。

@using MilkStone.Services;
@model MilkStone.Models.AccountViewModel.LoginViewModel
@inject ILoginService<ApplicationUser>  loginService
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head></head>
<body>
  @loginService.GetUserName()
</body>
</html>

  

4 通過 HttpContext來獲取例項

HttpContext下有一個RequestedService同樣可以用來獲取例項物件,不過這種方法一般不推薦。同時要注意GetService<>這是個範型方法,預設如果沒有新增Microsoft.Extension.DependencyInjection的using,是不用呼叫這個方法的。

HttpContext.RequestServices.GetService<ILoginService<ApplicationUser>>();

3、替換其它的Ioc容器

使用Autofac可實現批量注入,Autofac 原來的一個生命週期InstancePerRequest,將不再有效。正如我們前面所說的,整個request的生命週期被ASP.NET Core管理了,所以Autofac的這個將不再有效。我們可以使用 InstancePerLifetimeScope ,同樣是有用的,對應了我們ASP.NET Core DI 裡面的Scoped

這會給我們的初始化帶來一些便利性,我們來看看如何替換Autofac到ASP.NET Core。我們只需要把Startup類裡面的 ConfigureService的 返回值從 void改為 IServiceProvider即可。而返回的則是一個AutoServiceProvider。

public IServiceProvider ConfigureServices(
  IServiceCollection services){
    services.AddMvc();
    // Add other framework services
 
    // Add Autofac
    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterModule<DefaultModule>();
    containerBuilder.Populate(services);
    var container = containerBuilder.Build();
    return new AutofacServiceProvider(container);
}

Autofac 批量注入

//自動註冊介面
builder.RegisterAssemblyTypes(assemblies).Where(b => b.GetInterfaces().
Any(c => c == baseType && b != baseType)).AsImplementedInterfaces(). InstancePerLifetimeScope();

//定義可批量注入的介面  需要繼承
public interface IAutoInject { }

protected void Application_Start()
        {
            var builder = new ContainerBuilder();
            //獲取IAutoInject的Type
            var baseType = typeof(IAutoInject);
            //獲取所有程式集
            var assemblies = System.Web.Compilation.BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToArray();
            //自動註冊介面
            builder.RegisterAssemblyTypes(assemblies).Where(b => b.GetInterfaces().
            Any(c => c == baseType && b != baseType)).AsImplementedInterfaces(). InstancePerLifetimeScope();
            //自動註冊控制器
            builder.RegisterControllers(assemblies);
            var container = builder.Build();
            DependencyResolver.SetResolver(new AutofacDependencyResolver(container));

            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
 

7、提高ASP.NET Web應用效能

參考

https://developer.aliyun.com/article/298431

1、執行環境優化 做負載均衡和伺服器加成

伺服器配置升級 CPU 處理器 磁碟陣列

橫向擴充套件 使用負載均衡、反向代理服務實現伺服器叢集

2、快取應用

快取是一種用空間換取時間的技術,通俗點也就是說把你得到的資料存放在記憶體 中一段時間,在這短時間內伺服器不去讀取資料庫、或是真實的資料來源,而是讀取你存放在記憶體中的資料。 快取是網站效能優化不可缺少的一種資料處理機制,他能有效的緩解資料庫壓力。 ASP.NET 中的快取主要分為:

頁面快取
資料來源快取
自定義資料快取

redis分散式快取

增加快取命中率

全文搜尋使用ES

 快取系統

  快取分為檔案快取、記憶體快取、資料庫快取。在大型Web應用中使用最多且效率最高的是記憶體快取。最常用的記憶體快取工具是Memcachd。使用正確的快取系統可以達到實現以下目標:

  1、使用快取系統可以提高訪問效率,提高伺服器吞吐能力,改善使用者體驗。

  2、減輕對資料庫及儲存集伺服器的訪問壓力。

  3、Memcached伺服器有多臺,避免單點故障,提供高可靠性和可擴充套件性,提高效能。

3、資料庫優化

搭建資料庫叢集 使用主從策略、讀寫分離

1、讀寫分離 主從策略 主從庫,主負責寫,從是隻讀的;搭建叢集

2、分庫分表

3、合理使用索引,避免使用全表掃描

4、搜尋引擎ES(文件和索引結合,快的原因是分詞 like "%word%",es只需要查"word"這個詞包含的文件id ) ELK

5、使用ETL工具(Kettle)

  由於Web前端採用了負載均衡叢集結構提高了服務的有效性和擴充套件性,因此資料庫必須也是高可靠的才能保證整個服務體系的高可靠性,如何構建一個高可靠的、可以提供大規模併發處理的資料庫體系?

  我們可以採用如上圖所示的方案:

  1)使用SQL資料庫,考慮到Web應用的資料庫讀多寫少的特點,我們主要對讀資料庫做了優化,提供專用的讀資料庫和寫資料庫,在應用程式中實現讀操作和寫操作分別訪問不同的資料庫。

  2)使用同步機制實現快速將主庫(寫庫)的資料庫複製到從庫(讀庫)。一個主庫對應多個從庫,主庫資料實時同步到從庫。

  3)寫資料庫有多臺,每臺都可以提供多個應用共同使用,這樣可以解決寫庫的效能瓶頸問題和單點故障問題。

  4)讀資料庫有多臺,通過負載均衡裝置實現負載均衡,從而達到讀資料庫的高效能、高可靠和高可擴充套件性。

  5)資料庫伺服器和應用伺服器分離。

4、程式碼層面

1、資源合理利用,合理釋放資源資料庫連線 的關閉 使用using()

2、避免丟擲異常最小化異常 異常應極少。 相對於其他程式碼流模式,引發和捕獲異常的速度很慢。 因此,不應使用異常來控制正常的程式流。

3、使用非同步 async/await 多個請求過來時,執行緒池分配足夠的執行緒來處理多個請求,提高執行緒池的利用率 !

4、返回多個數據源進行讀取,減少資料庫的連線 比方分頁中返回 所有當前頁資料 和資料總條數 一條sql 返回多個數據源 進行讀取

5、較少裝箱拆箱操作,使用泛型,string 和stringbuilder

8、Redis分散式快取

主從模式:讀寫分離

哨兵模式:

心跳機制(每隔幾分鐘傳送一個固定資訊給服務端,服務端收到後回覆一個固定資訊如果服務端幾分鐘內沒有收到客戶端資訊則視客戶端斷開)+哨兵裁決。主從切換,故障轉移。

Cluster叢集模式:無中心架構

面試題:redis記憶體操作速度快,缺點是受實體記憶體限制。持久化:RDB(定時,二進位制,適合備份),AOF(日誌方式,寫,刪,沒有查詢),快取命中率,通過快取取到資料,不需要去資料庫查詢,預熱可以提高。