1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        一文了解.Net Core 3.1 Web API基礎(chǔ)知識(shí)

        共 30187字,需瀏覽 61分鐘

         ·

        2020-09-27 20:26

        6cc722e25eafa738a0d48b8b76ef9b3f.webp


        目錄

        • 一、前言

        • 二、Swagger調(diào)試Web API

        • 三、配置文件

          • 1、配置文件的基本讀取

          • 2、讀取配置文件到自定義對(duì)象

          • 3、綁定到靜態(tài)類方式讀取

        • 四、文件上傳

          • 后端代碼

          • 前端調(diào)用

        • 五、統(tǒng)一WebApi數(shù)據(jù)返回格式

          • 定義統(tǒng)一返回格式

          • 解決T時(shí)間格式

        • 六、模型驗(yàn)證

        • 七、日志使用

          • NLog的使用

        • 八、依賴注入

          • 生命周期  

          • 1、Scrutor的使用

          • 2、Autofac

        • 九、緩存

          • ?MemoryCache使用

        • 十、異常處理

          • 定義異常處理中間件

          • 異常狀態(tài)碼的處理

        • 十一、應(yīng)用安全與JWT認(rèn)證

        • 十二、跨域

        ?


        回到頂部

        一、前言

          隨著近幾年前后端分離、微服務(wù)等模式的興起,.Net Core也似有如火如荼之勢(shì) ,自16年發(fā)布第一個(gè)版本到19年底的3.1 LTS版本,以及將發(fā)布的.NET 5,.NET Core一路更迭,在部署和開發(fā)工具上也都支持了跨平臺(tái)應(yīng)用。一直對(duì).Net Core有所關(guān)注,但未涉及太多實(shí)際應(yīng)用,經(jīng)過一番學(xué)習(xí)和了解后,于是分享出來。本文主要以.Net Core Web API為例,講述.Net Core的基本應(yīng)用及注意事項(xiàng),對(duì)于想通過WebAPI搭建接口應(yīng)用的開發(fā)者,應(yīng)該能提供一個(gè)系統(tǒng)的輪廓和認(rèn)識(shí),同時(shí)和更多的.Net Core開發(fā)者交流互動(dòng),探本勘誤,加強(qiáng)對(duì)知識(shí)的理解,并幫助更多的人。本文以貼近基本的實(shí)際操作為主,部分概念或基礎(chǔ)步驟不再贅述,文中如有疏漏,還望不吝斧正。

        回到頂部

        二、Swagger調(diào)試Web API

        開發(fā)環(huán)境:Visual Studio 2019

        為解決前后端苦于接口文檔與實(shí)際不一致、維護(hù)和更新文檔的耗時(shí)費(fèi)力等問題,swagger應(yīng)運(yùn)而生,同時(shí)也解決了接口測(cè)試問題。話不多說,直接說明應(yīng)用步驟。

        1. 新建一個(gè)ASP.NET Core Web API應(yīng)用程序,版本選擇.ASP.NET Core 3.1;

        2. 通過Nuget安裝包:Swashbuckle.AspNetCore,當(dāng)前示例版本5.5.0;

        3. 在Startup類的ConfigureServices方法內(nèi)添加以下注入代碼:

          5890c6f087d9222cd64c7e37d6f06b7a.webp

          services.AddSwaggerGen(c =>
          {
          c.SwaggerDoc(
          "v1", new OpenApiInfo
          {
          Title
          = "My API",
          Version
          = "v1",
          Description
          = "API文檔描述",
          Contact
          = new OpenApiContact
          {
          Email
          = "[email protected]",
          Name
          = "測(cè)試項(xiàng)目",
          //Url = new Uri("http://t.abc.com/")
          },
          License
          = new OpenApiLicense
          {
          Name
          = "BROOKE許可證",
          //Url = new Uri("http://t.abc.com/")
          }
          });

          });

          5890c6f087d9222cd64c7e37d6f06b7a.webp

          Startup類的Configure方法添加如下代碼:

          5890c6f087d9222cd64c7e37d6f06b7a.webp

          //配置Swagger
          app.UseSwagger();
          app.UseSwaggerUI(c
          =>
          {
          c.SwaggerEndpoint(
          "/swagger/v1/swagger.json", "My API V1");
          c.RoutePrefix = "api";// 如果設(shè)為空,訪問路徑就是根域名/index.html,設(shè)置為空,表示直接在根域名訪問;想換一個(gè)路徑,直接寫名字即可,比如直接寫c.RoutePrefix = "swagger"; 則訪問路徑為 根域名/swagger/index.html

          });

          5890c6f087d9222cd64c7e37d6f06b7a.webp

          Ctrl+F5進(jìn)入瀏覽,按上述配置修改路徑為:http://localhost:***/api/index.html,即可看到Swagger頁(yè)面:

          9ccaeff21359d52d68b7d995b175aab2.webp

          然而到這里還沒完,相關(guān)接口的注釋說明我們看不到,通過配置XML文件的方式繼續(xù)調(diào)整代碼如下,新增代碼見加粗部分:

          5890c6f087d9222cd64c7e37d6f06b7a.webp

          services.AddSwaggerGen(c =>
          {
          c.SwaggerDoc(
          "v1", new OpenApiInfo
          {
          Title
          = "My API",
          Version
          = "v1",
          Description
          = "API文檔描述",
          Contact
          = new OpenApiContact
          {
          Email
          = "[email protected]",
          Name
          = "測(cè)試項(xiàng)目",
          //Url = new Uri("http://t.abc.com/")
          },
          License
          = new OpenApiLicense
          {
          Name
          = "BROOKE許可證",
          //Url = new Uri("http://t.abc.com/")
          }
          });

          var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
          var xmlPath =
          Path.Combine(AppContext.BaseDirectory, xmlFile);
          c.IncludeXmlComments(xmlPath);

          });

          5890c6f087d9222cd64c7e37d6f06b7a.webp

          上述代碼通過反射生成與Web API項(xiàng)目相匹配的XML文件名,AppContext.BaseDirectory屬性用于構(gòu)造 XML 文件的路徑,關(guān)于OpenApiInfo內(nèi)的配置參數(shù)用于文檔的一些描述,在此不作過多說明。
          然后右鍵Web API項(xiàng)目、屬性、生成,配置XML文檔的輸出路徑,以及取消不必要的XML注釋警告提醒(增加1591):

          e5bf06cc09cb65c9dddfc6c218c60703.webp

          這樣,我們以三斜杠(///)方式給類方法屬性等相關(guān)代碼添加注釋后,刷新Swagger頁(yè)面,即可看到注釋說明。
          如果不想將XML文件輸出為debug下的目錄,譬如想要放在項(xiàng)目根目錄(但不要修改成磁盤絕對(duì)路徑),可調(diào)整相關(guān)代碼如下,xml文件的名字也可以改成自己想要的:

          var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//獲取應(yīng)用程序所在目錄
          var xmlPath = Path.Combine(basePath, "CoreAPI_Demo.xml");
          c.IncludeXmlComments(xmlPath,
          true);

          同時(shí),調(diào)整項(xiàng)目生成的XML文檔文件路徑為:..\CoreAPI_Demo\CoreAPI_Demo.xml

        4. 隱藏相關(guān)接口
          對(duì)于不想暴漏給Swagger展示的接口,我們可以給相關(guān)Controller或Action頭加上:[ApiExplorerSettings(IgnoreApi = true)]

        5. 調(diào)整系統(tǒng)默認(rèn)輸出路徑
          項(xiàng)目啟動(dòng)后,默認(rèn)會(huì)訪問自帶的weatherforecast,如果想調(diào)整為其他路徑,譬如打開后直接訪問Swagger文檔,那么調(diào)整Properties目錄下的launchSettings.json文件,修改launchUrl值為api(前述配置的RoutePrefix值):

          5890c6f087d9222cd64c7e37d6f06b7a.webp

          {
          "$schema": "http://json.schemastore.org/launchsettings.json",
          "iisSettings": {
          "windowsAuthentication": false,
          "anonymousAuthentication": true,
          "iisExpress": {
          "applicationUrl": "http://localhost:7864",
          "sslPort": 0
          }
          },
          "profiles": {
          "IIS Express": {
          "commandName": "IISExpress",
          "launchBrowser": true,
          "launchUrl": "api",
          "environmentVariables": {
          "ASPNETCORE_ENVIRONMENT": "Development"
          }
          },
          "CoreApi_Demo": {
          "commandName": "Project",
          "launchBrowser": true,
          "launchUrl": "api",
          "applicationUrl": "http://localhost:5000",
          "environmentVariables": {
          "ASPNETCORE_ENVIRONMENT": "Development"
          }
          }
          }
          }

          5890c6f087d9222cd64c7e37d6f06b7a.webp

          ?

        回到頂部

        三、配置文件

        以讀取appsettings.json文件為例,當(dāng)然你也定義其他名稱的.json文件進(jìn)行讀取,讀取方式一致,該文件類似于Web.config文件。為方便示例,定義appsettings.json文件內(nèi)容如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        {
        "ConnString": "Data Source=(local);Initial Catalog=Demo;Persist Security Info=True;User ID=DemoUser;Password=123456;MultipleActiveResultSets=True;",
        "ConnectionStrings": {
        "MySQLConnection": "server=127.0.0.1;database=mydemo;uid=root;pwd=123456;charset=utf8;SslMode=None;"
        },
        "SystemConfig": {
        "UploadFile": "/Files",
        "Domain": "http://localhost:7864"
        },
        "JwtTokenConfig": {
        "Secret": "fcbfc8df1ee52ba127ab",
        "Issuer": "abc.com",
        "Audience": "Brooke.WebApi",
        "AccessExpiration": 30,
        "RefreshExpiration": 60
        },
        "Logging": {
        "LogLevel": {
        "Default": "Information",
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information"
        }
        },
        "AllowedHosts": "*"
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        ?


        1、配置文件的基本讀取

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class Startup
        {
        public Startup(IConfiguration configuration)
        {
        Configuration
        = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
        services.AddControllers();

        //讀取方式一
        var ConnString = Configuration["ConnString"];
        var MySQLConnection = Configuration.GetSection("ConnectionStrings")["MySQLConnection"];
        var UploadPath = Configuration.GetSection("SystemConfig")["UploadPath"];
        var LogDefault = Configuration.GetSection("Logging").GetSection("LogLevel")["Default"];

        //讀取方式二
        var ConnString2 = Configuration["ConnString"];
        var MySQLConnection2 = Configuration["ConnectionStrings:MySQLConnection"];
        var UploadPath2 = Configuration["SystemConfig:UploadPath"];
        var LogDefault2 = Configuration["Logging:LogLevel:Default"];

        }

        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        以上介紹了2種讀取配置信息的方式,如果要在Controller內(nèi)使用,類似地,進(jìn)行注入并調(diào)用如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class ValuesController : ControllerBase
        {
        private IConfiguration _configuration;

        public ValuesController(IConfiguration configuration)
        {
        _configuration
        = configuration;
        }

        // GET: api/
        [HttpGet]
        public IEnumerable<string> Get()
        {
        var ConnString = _configuration["ConnString"];
        var MySQLConnection = _configuration.GetSection("ConnectionStrings")["MySQLConnection"];
        var UploadPath = _configuration.GetSection("SystemConfig")["UploadPath"];
        var LogDefault = _configuration.GetSection("Logging").GetSection("LogLevel")["Default"];
        return new string[] { "value1", "value2" };
        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        ?


        2、讀取配置文件到自定義對(duì)象

        以SystemConfig節(jié)點(diǎn)為例,定義類如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class SystemConfig
        {
        public string UploadPath { get; set; }
        public string Domain { get; set; }

        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        調(diào)整代碼如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class Startup
        {
        public Startup(IConfiguration configuration)
        {
        Configuration
        = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
        services.AddControllers();

        services.Configure
        (Configuration.GetSection("SystemConfig"));
        }

        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        ?然后Controller內(nèi)進(jìn)行注入調(diào)用:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        [Route("api/[controller]/[action]")]
        [ApiController]
        public class ValuesController : ControllerBase
        {
        private SystemConfig _sysConfig;
        public ValuesController(IOptions sysConfig)
        {
        _sysConfig
        = sysConfig.Value;
        }

        [HttpGet]
        public IEnumerable<string> GetSetting()
        {
        var UploadPath = _sysConfig.UploadPath;
        var Domain = _sysConfig.Domain;
        return new string[] { "value1", "value2" };
        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp


        3、綁定到靜態(tài)類方式讀取

        定義相關(guān)靜態(tài)類如下:

          public static class MySettings
        {
        public static SystemConfig Setting { get; set; } = new SystemConfig();
        }

        調(diào)整Startup類構(gòu)造函數(shù)如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {
        var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile(
        "appsettings.json", optional: true, reloadOnChange: true);

        Configuration
        = builder.Build();
        //Configuration = configuration;

        configuration.GetSection(
        "SystemConfig").Bind(MySettings.Setting);
        //綁定靜態(tài)配置類
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        接下來,諸如直接使用:MySettings.Setting.UploadPath 即可調(diào)用。

        ?

        回到頂部

        四、文件上傳

        接口一般少不了文件上傳,相比.net framework框架下webapi通過byte數(shù)組對(duì)象等復(fù)雜方式進(jìn)行文件上傳,.Net Core WebApi有了很大變化,其定義了新的IFormFile對(duì)象來接收上傳文件,直接上Controller代碼:?


        后端代碼

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        [Route("api/[controller]/[action]")]
        [ApiController]
        public class UploadController : ControllerBase
        {
        private readonly IWebHostEnvironment _env;

        public UploadController(IWebHostEnvironment env)
        {
        _env
        = env;
        }

        public ApiResult UploadFile(List files)
        {
        ApiResult result
        = new ApiResult();

               //注:參數(shù)files對(duì)象去也可以通過換成:var files = Request.Form.Files;來獲取

        if (files.Count <= 0)
        {
        result.Message
        = "上傳文件不能為空";
        return result;
        }

        #region 上傳

        List
        <string> filenames = new List<string>();

        var webRootPath = _env.WebRootPath;
        var rootFolder = MySettings.Setting.UploadPath;

        var physicalPath = $"{webRootPath}/{rootFolder}/";

        if (!Directory.Exists(physicalPath))
        {
        Directory.CreateDirectory(physicalPath);
        }

        foreach (var file in files)
        {
        var fileExtension = Path.GetExtension(file.FileName);//獲取文件格式,拓展名

        var saveName = $"{rootFolder}/{Path.GetRandomFileName()}{fileExtension}";
        filenames.Add(saveName);
        //相對(duì)路徑

        var fileName = webRootPath + saveName;

        using FileStream fs = System.IO.File.Create(fileName);
        file.CopyTo(fs);
        fs.Flush();

        }
        #endregion


        result.IsSuccess
        = true;
        result.Data[
        "files"] = filenames;

        return result;
        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp


        前端調(diào)用

        接下來通過前端調(diào)用上述上傳接口,在項(xiàng)目根目錄新建wwwroot目錄(.net core webapi內(nèi)置目錄 ),添加相關(guān)js文件包,然后新建一個(gè)index.html文件,內(nèi)容如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp




        "utf-8" />







        "myform" method="post" action="/api/upload/uploadfile" enctype="multipart/form-data">
        "file" id="files" name="files" multiple />


        "button" value="FormData Upload" onclick="AjaxUploadfile();" />


        "button" value="ajaxSubmit Upload" onclick="AjaxUploadfile2();" />


        "filePanel">





        5890c6f087d9222cd64c7e37d6f06b7a.webp

        上述通過構(gòu)建FormData和ajaxSubmit兩種方式進(jìn)行上傳,需要注意的是contentType和processData兩個(gè)參數(shù)的設(shè)置;另外允許一次上傳多個(gè)文件,需設(shè)置multipart屬性。


        在訪問wwwroot下的靜態(tài)文件之前,必須先在Startup類的Configure方法下進(jìn)行注冊(cè):

        public void Configure(IApplicationBuilder app)
        {
        app.UseStaticFiles();
        //用于訪問wwwroot下的文件
        }

        ?啟動(dòng)項(xiàng)目,通過訪問路徑:http://localhost:***/index.html,進(jìn)行上傳測(cè)試,成功后,將在wwwroot下的Files目錄下看到上傳的文件。

        ?

        回到頂部

        五、統(tǒng)一WebApi數(shù)據(jù)返回格式


        定義統(tǒng)一返回格式

        為了方便前后端使用約定好的數(shù)據(jù)格式,通常我們會(huì)定義統(tǒng)一的數(shù)據(jù)返回,其包括是否成功、返回狀態(tài)、具體數(shù)據(jù)等;為便于說明,定義一個(gè)數(shù)據(jù)返回類如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class ApiResult
        {
        public bool IsSuccess { get; set; }
        public string Message { get; set; }
        public string Code { get; set; }
        public Dictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        ?

        這樣,我們將每一個(gè)action接口操作封裝為ApiResult格式進(jìn)行返回。新建一個(gè)ProductController示例如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        [Produces("application/json")]
        [Route(
        "api/[controller]")]
        [ApiController]
        public class ProductController : ControllerBase
        {
        [HttpGet]
        public ApiResult Get()
        {
        var result = new ApiResult();

        var rd = new Random();

        result.Data[
        "dataList"] = Enumerable.Range(1, 5).Select(index => new
        {
        Name
        = $"商品-{index}",
        Price
        = rd.Next(100, 9999)
        });

        result.IsSuccess
        = true;
        return result;
        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        ?

        • Produces:定義數(shù)據(jù)返回的方式,給每個(gè)Controller打上[Produces("application/json")]標(biāo)識(shí),即表示以json方式進(jìn)行數(shù)據(jù)輸出。

        • ApiController:確保每個(gè)Controller有ApiController標(biāo)識(shí),通常,我們會(huì)定義一個(gè)基類如:BaseController,其繼承自ControllerBase,并將其打上[ApiController]標(biāo)識(shí),新建的controller都繼承該類;

        • Route:路由訪問方式,如不喜歡RESTful方式,可加上Action,即:[Route("api/[controller]/[action]")];

        • HTTP 請(qǐng)求:結(jié)合前面配置的Swagger,必須確保每個(gè)Action都有具體的請(qǐng)求方式,即必須是HttpGet、HttpPost、HttpPut、HttpDelete中的一種,通常情況下,我們使用HttpGet、HttpPost足以。

          如此,即完成的數(shù)據(jù)返回的統(tǒng)一。


        解決T時(shí)間格式

          .Net Core Web Api默認(rèn)以首字母小寫的類駝峰式命名返回,但遇到DateTime類型的數(shù)據(jù),會(huì)返回T格式時(shí)間,如要解決T時(shí)間格式,定義一個(gè)時(shí)間格式轉(zhuǎn)換類如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class DatetimeJsonConverter : JsonConverter
        {
        public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
        if (reader.TokenType == JsonTokenType.String)
        {
        if (DateTime.TryParse(reader.GetString(), out DateTime date))
        return date;
        }
        return reader.GetDateTime();
        }

        public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        {
        writer.WriteStringValue(value.ToString(
        "yyyy-MM-dd HH:mm:ss"));
        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        然后在Startup類的ConfigureServices中調(diào)整services.AddControllers代碼如下:

        services.AddControllers()
        .AddJsonOptions(configure
        =>
        {
        configure.JsonSerializerOptions.Converters.Add(
        new DatetimeJsonConverter());
        });

        ?

        回到頂部

        六、模型驗(yàn)證

        模型驗(yàn)證在ASP.NET MVC已存在,使用方式基本一致。指對(duì)向接口提交過來的數(shù)據(jù)進(jìn)行參數(shù)校驗(yàn),包括必填項(xiàng)、數(shù)據(jù)格式、字符長(zhǎng)度、范圍等等。一般的,我們會(huì)將POST提交過來的對(duì)象定義為一個(gè)實(shí)體類進(jìn)行接收,譬如定義一個(gè)注冊(cè)類如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

            public class RegisterEntity
        {
        ///
        /// 手機(jī)號(hào)
        ///

        [Display(Name = "手機(jī)號(hào)")]
        [Required(ErrorMessage
        = "{0}不能為空")]
        [StringLength(
        11, ErrorMessage = "{0}最多{1}個(gè)字符")]
        public string Mobile { get; set; }

        ///
        /// 驗(yàn)證碼
        ///

        [Display(Name = "驗(yàn)證碼")]
        [Required(ErrorMessage
        = "{0}不能為空")]
        [StringLength(
        6, ErrorMessage = "{0}最多{1}個(gè)字符")]
        public string Code { get; set; }

        ///
        /// 密碼
        ///

        [Display(Name = "密碼")]
        [Required(ErrorMessage
        = "{0}不能為空")]
        [StringLength(
        16, ErrorMessage = "{0}最多{1}個(gè)字符")]
        public string Pwd { get; set; }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        ?

          Display標(biāo)識(shí)提示字段的名稱,Required表示必填,StringLength限制字段的長(zhǎng)度,當(dāng)然還有其他一些內(nèi)置特性,具體可參考官方文檔,列舉一些常見的驗(yàn)證特性如下:

        • [CreditCard]:驗(yàn)證屬性是否具有信用卡格式。需要 JQuery 驗(yàn)證其他方法。

        • [Compare]:驗(yàn)證模型中的兩個(gè)屬性是否匹配。

        • [EmailAddress]:驗(yàn)證屬性是否具有電子郵件格式。

        • [Phone]:驗(yàn)證屬性是否具有電話號(hào)碼格式。

        • [Range]:驗(yàn)證屬性值是否在指定的范圍內(nèi)。

        • [RegularExpression]:驗(yàn)證屬性值是否與指定的正則表達(dá)式匹配。

        • [Required]:驗(yàn)證字段是否不為 null。有關(guān)此屬性的行為的詳細(xì)信息,請(qǐng)參閱 [Required] 特性。

        • [StringLength]:驗(yàn)證字符串屬性值是否不超過指定長(zhǎng)度限制。

        • [Url]:驗(yàn)證屬性是否具有 URL 格式。

        • [Remote]:通過在服務(wù)器上調(diào)用操作方法來驗(yàn)證客戶端上的輸入。

          上述說明了基本的模型驗(yàn)證使用方法,以這種方式,同時(shí)結(jié)合T4模板,通過表對(duì)象生成模型驗(yàn)證實(shí)體,省卻了在action中編寫大量驗(yàn)證代碼的工作。當(dāng)然,一些必要的較為復(fù)雜的驗(yàn)證,或結(jié)合數(shù)據(jù)庫(kù)操作的驗(yàn)證,則單獨(dú)寫到action或其他應(yīng)用模塊中。

          那么上述模型驗(yàn)證在Web API中是怎么工作的呢?在Startup類的ConfigureServices添加如下代碼:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        //模型參數(shù)驗(yàn)證
        services.Configure(options =>
        {
        options.InvalidModelStateResponseFactory
        = (context) =>
        {
        var error = context.ModelState.FirstOrDefault().Value;
        var message = error.Errors.FirstOrDefault(p => !string.IsNullOrWhiteSpace(p.ErrorMessage))?.ErrorMessage;

        return new JsonResult(new ApiResult { Message = message });
        };
        });

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        ?

          添加注冊(cè)示例Action代碼:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        /// 
        /// 注冊(cè)
        ///

        ///
        ///
        [HttpPost]
        public async Task Register(RegisterEntity model)
        {
        ApiResult result
        = new ApiResult();

        var _code = CacheHelper.GetCache(model.Mobile);
        if (_code == null)
        {
        result.Message
        = "驗(yàn)證碼過期或不存在";
        return result;
        }
        if (!model.Code.Equals(_code.ToString()))
        {
        result.Message
        = "驗(yàn)證碼錯(cuò)誤";
        return result;
        }

        /**
        相關(guān)邏輯代碼
        *
        */
        return result;
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        ?

          如此,通過配置ApiBehaviorOptions的方式,并讀取驗(yàn)證錯(cuò)誤信息的第一條信息并返回,即完成了Web API中Action對(duì)請(qǐng)求參數(shù)的驗(yàn)證工作,關(guān)于錯(cuò)誤信息Message的返回,也可略作封裝,在此略。

        ?

        回到頂部

        七、日志使用

        雖然.Net Core WebApi有自帶的日志管理功能,但不一定能較容易地滿足我們的需求,通常會(huì)采用第三方日志框架,典型的如:NLog、Log4Net,簡(jiǎn)單介紹NLog日志組件的使用;


        NLog的使用

        ① 通過NuGet安裝包:NLog.Web.AspNetCore,當(dāng)前項(xiàng)目版本4.9.2;

        ② 項(xiàng)目根目錄新建一個(gè)NLog.config文件,關(guān)鍵NLog.config的其他詳細(xì)配置,可參考官方文檔,這里作簡(jiǎn)要配置如下;

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        "1.0" encoding="utf-8"?>
        "http://www.nlog-project.org/schemas/NLog.xsd"
        xmlns:xsi
        ="http://www.w3.org/2001/XMLSchema-instance"
        autoReload
        ="true"
        throwExceptions
        ="false"
        internalLogLevel
        ="Off"
        internalLogFile
        ="NlogRecords.log">


        "NLog.Web.AspNetCore" />


        "log_file" xsi:type="File" fileName="${basedir}/logs/${shortdate}.log"
        layout
        ="${longdate} | ${level:uppercase=false} | ${message} ${onexception:${exception:format=tostring} ${newline} ${stacktrace} ${newline}" />




        "Microsoft.*" final="true" />

        "*" minlevel="Trace" writeTo="log_file" />




        5890c6f087d9222cd64c7e37d6f06b7a.webp

        ?

        ③ 調(diào)整Program.cs文件如下;

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class Program
        {
        public static void Main(string[] args)
        {
        //CreateHostBuilder(args).Build().Run();
               
        var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
        try
        {
        logger.Debug(
        "init main");
        CreateHostBuilder(args).Build().Run();
        }
        catch (Exception exception)
        {
        //NLog: catch setup errors
        logger.Error(exception, "Stopped program because of exception");
        throw;
        }
        finally
        {
        // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
        NLog.LogManager.Shutdown();
        }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder
        =>
        {
        webBuilder.UseStartup
        ();
        }).ConfigureLogging(logging
        => {
        logging.ClearProviders();
        logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
        }).UseNLog();
        //依賴注入Nlog;
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        其中Main函數(shù)里的捕獲異常代碼配置省略也是可以的,CreateHostBuilder下的UseNLog為必設(shè)項(xiàng)。

        Controller通過注入調(diào)用如下:

        ?

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class WeatherForecastController : ControllerBase
        {
        private static readonly string[] Summaries = new[]
        {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger _logger;

        public WeatherForecastController(ILogger logger)
        {
        _logger
        = logger;
        }

        [HttpGet]
        public IEnumerable Get()
        {
        _logger.LogInformation(
        "測(cè)試一條日志");

        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
        Date
        = DateTime.Now.AddDays(index),
        TemperatureC
        = rng.Next(-20, 55),
        Summary
        = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        本地測(cè)試后,即可在debug下看到logs目錄下生成的日志文件。?

        回到頂部

        八、依賴注入

        使用.Net Core少不了和依賴注入打交道,這也是.Net Core的設(shè)計(jì)思想之一,關(guān)于什么是依賴注入(DI),以及為什么要使用依賴注入,這里不再贅述,先來看一個(gè)簡(jiǎn)單示例的依賴注入。

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public interface IProductRepository
        {
        IEnumerable GetAll();
        }

        public class ProductRepository : IProductRepository
        {
        public IEnumerable GetAll()
        {

        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        Startup類進(jìn)行注冊(cè):

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

        請(qǐng)求 IProductRepository 服務(wù)并用于調(diào)用 GetAll 方法:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class ProductController : ControllerBase
        {
        private readonly IProductRepository _productRepository;
           public ProductController(IProductRepository productRepository)
        {
        _productRepository = productRepository;
        }

        public IEnumerable Get()
        {
        return _productRepository.GetAll();
        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        通過使用DI模式,來實(shí)現(xiàn)IProductRepository 接口。其實(shí)前述已多次出現(xiàn)通過構(gòu)造函數(shù)進(jìn)行注入調(diào)用的示例。


        生命周期  

        services.AddScoped();
        services.AddTransient
        ();
        services.AddSingleton
        ();
        • Transient:每一次請(qǐng)求都會(huì)創(chuàng)建一個(gè)新實(shí)例;

        • Scoped:每個(gè)作用域生成周期內(nèi)創(chuàng)建一個(gè)實(shí)例;

        • Singleton:?jiǎn)卫J?,整個(gè)應(yīng)用程序生命周期內(nèi)只創(chuàng)建一個(gè)實(shí)例;

        這里,需要根據(jù)具體的業(yè)務(wù)邏輯場(chǎng)景需求選擇注入相應(yīng)的生命周期服務(wù)。

        實(shí)際應(yīng)用中,我們會(huì)有很多個(gè)服務(wù)需要注冊(cè)到ConfigureServices內(nèi),一個(gè)個(gè)寫入顯然繁瑣,而且容易忘記漏寫,一般地,我們可能會(huì)想到利用反射進(jìn)行批量注入,并通過擴(kuò)展的方式進(jìn)行注入,譬如:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public static class AppServiceExtensions
        {
        ///
        /// 注冊(cè)應(yīng)用程序域中的服務(wù)
        ///

        ///
        public static void AddAppServices(this IServiceCollection services)
        {
        var ts = System.Reflection.Assembly.Load("CoreAPI.Data").GetTypes().Where(s => s.Name.EndsWith("Repository") || s.Name.EndsWith("Service")).ToArray();
        foreach (var item in ts.Where(s => !s.IsInterface))
        {
        var interfaceType = item.GetInterfaces();
        foreach (var typeArray in interfaceType)
        {
        services.AddTransient(typeArray, item);
        }
        }
        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public void ConfigureServices(IServiceCollection services)
        {
        services.AddAppServices();
        //批量注冊(cè)服務(wù)
        }

        ?

        誠(chéng)然,這樣配合系統(tǒng)自帶DI注入是能完成我們的批量注入需求的。但其實(shí)也有更多選擇,來幫我們簡(jiǎn)化DI注冊(cè),譬如選擇其他第三方組件:Scrutor、Autofac…


        1、Scrutor的使用

        Scrutor是基于微軟注入組件的一個(gè)擴(kuò)展庫(kù),簡(jiǎn)單示例如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        services.Scan(scan => scan
        .FromAssemblyOf
        ()
        .AddClasses(classes
        => classes.Where(s => s.Name.EndsWith("Repository") || s.Name.EndsWith("Service")))
        .AsImplementedInterfaces()
        .WithTransientLifetime()
        );

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        以上代碼通過Scan方式批量注冊(cè)了以Repository、Service結(jié)尾的接口服務(wù),其生命周期為Transient,該方式等同于前述的以反射方式的批量注冊(cè)服務(wù)。

        關(guān)于Scrutor的其他用法,大家可以參見官方文檔,這里只做下引子。


        2、Autofac

        一般情況下,使用MS自帶的DI或采用Scrutor,即可滿足實(shí)際需要,如果有更高的應(yīng)用需求,如要求屬性注入、甚至接管或取代MS自帶的DI,那么你可以選擇Autofac,關(guān)于Autofac的具體使用,在此不作詳敘。

        ?

        回到頂部

        九、緩存


        ?MemoryCache使用

        按官方說明,開發(fā)人員需合理說用緩存,以及限制緩存大小,Core運(yùn)行時(shí)不會(huì)根據(jù)內(nèi)容壓力限制緩存大小。對(duì)于使用方式,依舊還是先行注冊(cè),然后控制器調(diào)用:

        public void ConfigureServices(IServiceCollection services)
        {
        services.AddMemoryCache();
        //緩存中間件
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class ProductController : ControllerBase
        {
        private IMemoryCache _cache;

        public ProductController(IMemoryCache memoryCache)
        {
        _cache
        = memoryCache;
        }

        [HttpGet]
        public DateTime GetTime()
        {
        string key = "_timeKey";

        // Look for cache key.
        if (!_cache.TryGetValue(key, out DateTime cacheEntry))
        {
        // Key not in cache, so get data.
        cacheEntry = DateTime.Now;

        // Set cache options.
        var cacheEntryOptions = new MemoryCacheEntryOptions()
        // Keep in cache for this time, reset time if accessed.
        .SetSlidingExpiration(TimeSpan.FromSeconds(3));

        // Save data in cache.
        _cache.Set(key, cacheEntry, cacheEntryOptions);
        }

        return cacheEntry;
        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        上述代碼緩存了一個(gè)時(shí)間,并設(shè)置了滑動(dòng)過期時(shí)間(指最后一次訪問后的過期時(shí)間)為3秒;如果需要設(shè)置絕對(duì)過期時(shí)間,將SetSlidingExpiration 改為SetAbsoluteExpiration即可。瀏覽刷新,每3秒后時(shí)間將更新。

        附一個(gè)封裝好的Cache類如下:

        376ab2f7087a7031d00240c0aa024c6a.webp?View Code

        ?

        回到頂部

        十、異常處理


        定義異常處理中間件

        這里主要針對(duì)全局異常進(jìn)行捕獲處理并記錄日志,并以統(tǒng)一的json格式返回給接口調(diào)用者;說異常處理前先提下中間件,關(guān)于什么是中間件,在此不在贅述,一個(gè)中間件其基本的結(jié)構(gòu)如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public class CustomMiddleware
        {
        private readonly RequestDelegate _next;

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

        public async Task Invoke(HttpContext httpContext)
        {
        await _next(httpContext);
        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        下面我們定義自己的全局異常處理中間件,代碼如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

            public class CustomExceptionMiddleware
        {
        private readonly RequestDelegate _next;
        private readonly ILogger _logger;

        public CustomExceptionMiddleware(RequestDelegate next, ILogger logger)
        {
        _next
        = next;
        _logger
        = logger;
        }

        public async Task Invoke(HttpContext httpContext)
        {
        try
        {
        await _next(httpContext);
        }
        catch (Exception ex)
        {
        _logger.LogError(ex,
        "Unhandled exception...");
        await HandleExceptionAsync(httpContext, ex);
        }
        }

        private Task HandleExceptionAsync(HttpContext httpContext, Exception ex)
        {
        var result = JsonConvert.SerializeObject(new { isSuccess = false, message = ex.Message });
        httpContext.Response.ContentType
        = "application/json;charset=utf-8";
        return httpContext.Response.WriteAsync(result);
        }
        }

        ///
        /// 以擴(kuò)展方式添加中間件
        ///

        public static class CustomExceptionMiddlewareExtensions
        {
        public static IApplicationBuilder UseCustomExceptionMiddleware(this IApplicationBuilder builder)
        {
        return builder.UseMiddleware();
        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        然后在Startup類的Configure方法里添加上述擴(kuò)展的中間件,見加粗部分:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
        if (env.IsDevelopment())
        {
        app.UseDeveloperExceptionPage();
        }

        //全局異常處理
        app.UseCustomExceptionMiddleware();
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        ?在HandleExceptionAsync方法中,為方便開發(fā)和測(cè)試,這里將系統(tǒng)的錯(cuò)誤返回給了接口調(diào)用者,實(shí)際生產(chǎn)環(huán)境中可統(tǒng)一返回固定的錯(cuò)誤Message消息。


        異常狀態(tài)碼的處理

        關(guān)于http狀態(tài)碼,常見的如正常返回的200,其他401、403、404、502等等等等,因?yàn)橄到y(tǒng)有時(shí)候并不總是返回200成功,對(duì)于返回非200的異常狀態(tài)碼,WebApi也要做到相應(yīng)的處理,以便接口調(diào)用者能正確接收,譬如緊接下來的JWT認(rèn)證,當(dāng)認(rèn)證令牌過期或沒有權(quán)限時(shí),系統(tǒng)實(shí)際會(huì)返回401、403,但接口并不提供有效的可接收的返回,因此,這里列舉一些常見的異常狀態(tài)碼,并以200方式提供給接口調(diào)用者,在Startup類的Configure方法里添加代碼如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        app.UseStatusCodePages(async context =>
        {
        //context.HttpContext.Response.ContentType = "text/plain";
        context.HttpContext.Response.ContentType = "application/json;charset=utf-8";

        int code = context.HttpContext.Response.StatusCode;
        string message =
        code
        switch
        {
        401 => "未登錄",
        403 => "訪問拒絕",
        404 => "未找到",
        _
        => "未知錯(cuò)誤",
        };

        context.HttpContext.Response.StatusCode
        = StatusCodes.Status200OK;
        await context.HttpContext.Response.WriteAsync(Newtonsoft.Json.JsonConvert.SerializeObject(new
        {
        isSuccess
        = false,
        code,
        message
        }));

        });

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        代碼很簡(jiǎn)單,這里使用系統(tǒng)自帶的異常處理中間件UseStatusCodePages,當(dāng)然,你還可以自定義過濾器處理異常,不過不推薦,簡(jiǎn)單高效直接才是需要的。

        關(guān)于.NET Core的異常處理中間件,還有其他諸如 UseExceptionHandler、UseStatusCodePagesWithRedirects等等,不同的中間件有其適用的環(huán)境,有的可能更適用于MVC或其他應(yīng)用場(chǎng)景上,找到合適的即可。

        題外話:大家也可以將UseStatusCodePages處理異常狀態(tài)碼的操作封裝到前述的全局異常處理中間件中。

        ?

        回到頂部

        十一、應(yīng)用安全與JWT認(rèn)證

        關(guān)于什么是JWT,在此不作贅述。實(shí)際應(yīng)用中,為了部分接口的安全性,譬如需要身份認(rèn)證才能訪問的接口資源,對(duì)于Web API而言,一般會(huì)采用token令牌進(jìn)行認(rèn)證,服務(wù)端結(jié)合緩存來實(shí)現(xiàn)。

        那為什么要選擇JWT認(rèn)證呢?原因無外乎以下:服務(wù)端不進(jìn)行保存、無狀態(tài)、適合移動(dòng)端、適合分布式、標(biāo)準(zhǔn)化等等。關(guān)于JWT的使用如下:

        通過NuGget安裝包:Microsoft.AspNetCore.Authentication.JwtBearer,當(dāng)前示例版本3.1.5;

        ConfigureServices進(jìn)行注入,默認(rèn)以Bearer命名,這里你也可以改成其他名字,保持前后一致即可,注意加粗部分,代碼如下:?

        appsettings.json添加JWT配置節(jié)點(diǎn)(見前述【配置文件】),添加JWT相關(guān)認(rèn)證類:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public static class JwtSetting
        {
        public static JwtConfig Setting { get; set; } = new JwtConfig();
        }

        public class JwtConfig
        {
        public string Secret { get; set; }
        public string Issuer { get; set; }
        public string Audience { get; set; }
        public int AccessExpiration { get; set; }
        public int RefreshExpiration { get; set; }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        采用前述綁定靜態(tài)類的方式讀取JWT配置,并進(jìn)行注入:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {
        //Configuration = configuration;

        var builder = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddJsonFile(
        "appsettings.json", optional: true, reloadOnChange: true);

        Configuration
        = builder.Build();

        configuration.GetSection(
        "SystemConfig").Bind(MySettings.Setting);//綁定靜態(tài)配置類
        configuration.GetSection("JwtTokenConfig").Bind(JwtSetting.Setting);//同上

        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

        #region JWT認(rèn)證注入

        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        services.AddAuthentication(
        "Bearer")
        .AddJwtBearer(
        "Bearer", options =>
        {
        options.RequireHttpsMetadata
        = false;

        options.TokenValidationParameters
        = new TokenValidationParameters
        {
        ValidateIssuer
        = true,
        ValidateAudience
        = true,
        ValidateLifetime
        = true,
        ValidateIssuerSigningKey
        = true,
        ValidIssuer
        = JwtSetting.Setting.Issuer,
        ValidAudience
        = JwtSetting.Setting.Audience,
        IssuerSigningKey
        = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(JwtSetting.Setting.Secret))
        };
        });

        #endregion

        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        給Swagger添加JWT認(rèn)證支持,完成后,Swagger頁(yè)面會(huì)出現(xiàn)鎖的標(biāo)識(shí),獲取token后填入Value(Bearer token形式)項(xiàng)進(jìn)行Authorize登錄即可,Swagger配置JWT見加粗部分:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        services.AddSwaggerGen(c =>
        {
        c.SwaggerDoc(
        "v1", new OpenApiInfo
        {
        Title
        = "My API",
        Version
        = "v1",
        Description
        = "API文檔描述",
        Contact
        = new OpenApiContact
        {
        Email
        = "[email protected]",
        Name
        = "測(cè)試項(xiàng)目",
        //Url = new Uri("http://t.abc.com/")
        },
        License
        = new OpenApiLicense
        {
        Name
        = "BROOKE許可證",
        //Url = new Uri("http://t.abc.com/")
        }
        });


        // 為 Swagger JSON and UI設(shè)置xml文檔注釋路徑
        //var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//獲取應(yīng)用程序所在目錄(不受工作目錄影響)
        //var xmlPath = Path.Combine(basePath, "CoreAPI_Demo.xml");
        //c.IncludeXmlComments(xmlPath, true);

        var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
        var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
        c.IncludeXmlComments(xmlPath);

        #region JWT認(rèn)證Swagger授權(quán)

        c.AddSecurityDefinition(
        "Bearer", new OpenApiSecurityScheme
        {
        Description
        = "JWT授權(quán)(數(shù)據(jù)將在請(qǐng)求頭header中進(jìn)行傳輸) 直接在下框中輸入Bearer {token}(中間是空格)",
        Name
        = "Authorization",
        In
        = ParameterLocation.Header,
        Type
        = SecuritySchemeType.ApiKey,
        BearerFormat
        = "JWT",
        Scheme
        = "Bearer"
        });

        c.AddSecurityRequirement(
        new OpenApiSecurityRequirement()
        {
        {
        new OpenApiSecurityScheme
        {
        Reference
        = new OpenApiReference {
        Type
        = ReferenceType.SecurityScheme,
        Id
        = "Bearer"
        }
        },
        new string[] { }
        }
        });

        #endregion

        });

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        Starup類添加Configure注冊(cè),注意,需放到 app.UseAuthorization();前面:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

        app.UseAuthentication();
        //jwt認(rèn)證

        app.UseAuthorization();

        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        這樣,JWT就基本配置完畢,接下來實(shí)施認(rèn)證登錄和授權(quán),模擬操作如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        [HttpPost]
        public async Task Login(LoginEntity model)
        {
        ApiResult result
        = new ApiResult();

        //驗(yàn)證用戶名和密碼
        var userInfo = await _memberService.CheckUserAndPwd(model.User, model.Pwd);
        if (userInfo == null)
        {
        result.Message
        = "用戶名或密碼不正確";
        return result;
        }
        var claims = new Claim[]
        {
        new Claim(ClaimTypes.Name,model.User),
        new Claim(ClaimTypes.Role,"User"),
        new Claim(JwtRegisteredClaimNames.Sub,userInfo.MemberID.ToString()),

        };

        var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(JwtSetting.Setting.Secret));
        var expires = DateTime.Now.AddDays(1);
        var token = new JwtSecurityToken(
        issuer: JwtSetting.Setting.Issuer,
        audience: JwtSetting.Setting.Audience,
        claims: claims,
        notBefore: DateTime.Now,
        expires: expires,
        signingCredentials:
        new SigningCredentials(key, SecurityAlgorithms.HmacSha256));

        //生成Token
        string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);

        //更新最后登錄時(shí)間
        await _memberService.UpdateLastLoginTime(userInfo.MemberID);

        result.IsSuccess
        = 1;
        result.ResultData[
        "token"] = jwtToken;
        result.Message
        = "授權(quán)成功!";
        return result;

        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        上述代碼模擬登錄操作(賬號(hào)密碼登錄,成功后設(shè)置有效期一天),生成token并返回,前端調(diào)用者拿到token后以諸如localstorage方式進(jìn)行存儲(chǔ),調(diào)取授權(quán)接口時(shí),添加該token到header(Bearer token)進(jìn)行接口請(qǐng)求。接下來,給需要身份授權(quán)的Controller或Action打上Authorize標(biāo)識(shí):

        [Authorize]
        [Route(
        "api/[controller]/[action]")]
        public class UserController : ControllerBase
        {
        }

        如果要添加基于角色的授權(quán),可限制操作如下:

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        [Authorize(Roles = "user")]
        [Route(
        "api/[controller]/[action]")]
        public class UserController : ControllerBase
        {
        }

        //多個(gè)角色也可以逗號(hào)分隔
        [Authorize(Roles = "Administrator,Finance")]
        [Route(
        "api/[controller]/[action]")]
        public class UserController : ControllerBase
        {
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        不同的角色信息,可通過登錄設(shè)置ClaimTypes.Role進(jìn)行配置;當(dāng)然,這里只是簡(jiǎn)單的示例說明角色服務(wù)的應(yīng)用,復(fù)雜的可通過注冊(cè)策略服務(wù),并結(jié)合數(shù)據(jù)庫(kù)進(jìn)行動(dòng)態(tài)配置。

        這樣,一個(gè)簡(jiǎn)單的基于JWT認(rèn)證授權(quán)的工作就完成了。

        ?

        回到頂部

        十二、跨域

        ?前后端分離,會(huì)涉及到跨域問題,簡(jiǎn)單的支持跨域操作如下:

        添加擴(kuò)展支持

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        public static class CrosExtensions
        {
        public static void ConfigureCors(this IServiceCollection services)
        {


        services.AddCors(options
        => options.AddPolicy("CorsPolicy",
        builder
        =>
        {
        builder.AllowAnyMethod()
        .SetIsOriginAllowed(_
        => true)
        .AllowAnyHeader()
        .AllowCredentials();
        }));

        //services.AddCors(options => options.AddPolicy("CorsPolicy",
        //builder =>
        //{
        // builder.WithOrigins(new string[] { "http://localhost:13210" })
        // .AllowAnyMethod()
        // .AllowAnyHeader()
        // .AllowCredentials();
        //}));


        }
        }

        5890c6f087d9222cd64c7e37d6f06b7a.webp

        Startup類添加相關(guān)注冊(cè)如下:

        public void ConfigureServices(IServiceCollection services)
        {
        services.ConfigureCors();

        }

        ?

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {

        app.UseCors(
        "CorsPolicy");//跨域
        }

        這樣,一個(gè)簡(jiǎn)單跨域操作就完成了,你也可以通過設(shè)置WithOrigins、WithMethods等方法限制請(qǐng)求地址來源和請(qǐng)求方式。

        ?

        至此,全篇結(jié)束,本篇涉及到的源碼地址:https://github.com/Brooke181/CoreAPI_Demo

        下一篇介紹Dapper在.NET Core中的使用,謝謝支持!


        往期精彩回顧



        【推薦】.NET Core開發(fā)實(shí)戰(zhàn)視頻課程?★★★

        .NET Core實(shí)戰(zhàn)項(xiàng)目之CMS 第一章 入門篇-開篇及總體規(guī)劃

        【.NET Core微服務(wù)實(shí)戰(zhàn)-統(tǒng)一身份認(rèn)證】開篇及目錄索引

        Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

        .NET Core中的一個(gè)接口多種實(shí)現(xiàn)的依賴注入與動(dòng)態(tài)選擇看這篇就夠了

        10個(gè)小技巧助您寫出高性能的ASP.NET Core代碼

        用abp vNext快速開發(fā)Quartz.NET定時(shí)任務(wù)管理界面

        在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實(shí)現(xiàn)作業(yè)調(diào)度

        現(xiàn)身說法:實(shí)際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

        關(guān)于C#異步編程你應(yīng)該了解的幾點(diǎn)建議

        C#異步編程看這篇就夠了


        瀏覽 56
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            肥逼网站 | 深夜福利国产 | 激情五月天网 | 女技师三级做爰按摩 | 国产91精品久久久久久第1集 | 天天色狠狠伊人 | 久久久久久夜精品精品免费 | 麻豆A片 国产精品﹣色哟哟入口 | AV京东热| 小草回家永不迷路2024ty66 |