In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)05/31 Report--
Most people do not understand the knowledge points of this article ".net Core 3.1 Web API basics", so the editor summarizes the following content, detailed content, clear steps, and has a certain reference value. I hope you can get something after reading this article. Let's take a look at this ".net Core 3.1 Web API basics" article.
I. Preface
With the rise of front-end separation and micro-services in recent years, Net Core also seems to be in full swing, from the release of the first version in 16 to the 3.1 LTS version at the end of 19, as well as the replacement of the .NET 5 LTS Core to be released, and cross-platform applications are also supported in deployment and development tools. Has been paying attention to .net Core, but does not involve much practical application, after some study and understanding, so share it. This article mainly takes .Net Core WebAPI as an example to describe the basic application and matters needing attention of .net Core. For developers who want to build interface applications through WebAPI, they should be able to provide a system outline and understanding, and at the same time communicate with more .net Core developers, explore errors, strengthen the understanding of knowledge, and help more people.
Second, Swagger debug Web API
Development environment: Visual Studio 2019
In order to solve the problems of the front and rear end suffering from the inconsistency between the interface document and the reality, and the time-consuming and laborious maintenance and updating of the document, swagger arises at the historic moment, and also solves the problem of interface testing. Without saying much, explain the application steps directly.
Create a new ASP.NET Core Web API application with a version of .ASP.NET Core 3.1
Install the package through Nuget: Swashbuckle.AspNetCore, current sample version 5.5.0
Add the following injection code to the ConfigureServices method of the Startup class:
Services.AddSwaggerGen (c = > {c.SwaggerDoc ("v1", new OpenApiInfo {Title = "My API", Version = "v1", Description = "API document description" Contact = new OpenApiContact {Email = "5007032@qq.com", Name = "Test Project", / / Url = new Uri ("http://t.abc.com/")}" License = new OpenApiLicense {Name = "BROOKE license", / / Url = new Uri ("http://t.abc.com/")}}) })
The Configure method of the Startup class adds the following code:
/ / configure Swagger app.UseSwagger (); app.UseSwaggerUI (c = > {c.SwaggerEndpoint ("/ swagger/v1/swagger.json", "My API V1"); c.RoutePrefix = "api"; / / if left empty, the access path is the root domain name / index.html, which means access directly from the root domain name. If you want to change the path, just write the name directly, for example, c.RoutePrefix = "swagger"; then the access path is the root domain name / swagger/index.html})
Ctrl+F5 enters browsing and changes the path to: http://localhost:***/api/index.html according to the above configuration to see the Swagger page:
However, it is not over yet. We cannot see the comments on the relevant APIs. We can continue to adjust the code by configuring the XML file as follows. For more information, please see the bold section:
Services.AddSwaggerGen (c = > {c.SwaggerDoc ("v1", new OpenApiInfo {Title = "My API", Version = "v1", Description = "API document description" Contact = new OpenApiContact {Email = "5007032@qq.com", Name = "Test Project", / / Url = new Uri ("http://t.abc.com/")}" License = new OpenApiLicense {Name = "BROOKE license", / / Url = new Uri ("http://t.abc.com/")}}) Var xmlFile = $"{Assembly.GetExecutingAssembly () .GetName () .Name} .xml"; var xmlPath = Path.Combine (AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments (xmlPath);})
The above code generates the XML file name that matches the Web API project through reflection, the AppContext.BaseDirectory attribute is used to construct the path of the XML file, and some descriptions of the configuration parameters within the OpenApiInfo for the document are not explained too much here.
Then right-click the Web API project, properties, build, configure the output path of the XML document, and cancel unnecessary XML comment warnings (add 1591):
In this way, after we add comments to the relevant code such as class method properties in the form of a three-slash (/ /), refresh the Swagger page and see the comment description.
If you do not want to export the XML file to a directory under debug, for example, you want to put it in the root directory of the project (but do not change it to the absolute path to disk), you can adjust the relevant code as follows, and you can change the name of the xml file to what you want:
Var basePath = Path.GetDirectoryName (typeof (Program) .Assembly.location); / / get the directory where the application resides var xmlPath = Path.Combine (basePath, "CoreAPI_Demo.xml"); c.IncludeXmlComments (xmlPath, true)
At the same time, adjust the path of the XML document file generated by the project to:.\ CoreAPI_Demo\ CoreAPI_Demo.xml
4. Hide related interfaces
For interfaces that do not want to be exposed to Swagger, we can add to the relevant Controller or Action headers: [ApiExplorerSettings (IgnoreApi = true)]
5. Adjust the system default output path
After the project starts, the default weatherforecast will be accessed. If you want to change it to another path, such as directly accessing the Swagger document after opening it, adjust the launchSettings.json file in the Properties directory and change the launchUrl value to api (RoutePrefix value of the above configuration):
{"$schema": "http://json.schemastore.org/launchsettings.json"," iisSettings ": {" windowsAuthentication ": false," anonymousAuthentication ": true," iisExpress ": {" applicationUrl ":" http://localhost:7864", "sslPort": 0}}, "profiles": {"IISExpress": {"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"} 3. Configuration file
Take reading the appsettings.json file as an example, of course, you also define a .json file with other names to read in the same way, which is similar to the Web.config file. For example, the contents of the defined appsettings.json file are as follows:
{"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": "*"}
1. Basic reading of configuration files
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 (); / / read method-var ConnString = Configuration ["ConnString"]; var MySQLConnection = Configuration.GetSection ("ConnectionStrings") ["MySQLConnection"]; var UploadPath = Configuration.GetSection ("SystemConfig") ["UploadPath"] Var LogDefault = Configuration.GetSection ("Logging"). GetSection ("LogLevel") ["Default"]; / / read mode 2: var ConnString2 = Configuration ["ConnString"]; var MySQLConnection2 = Configuration ["ConnectionStrings:MySQLConnection"]; var UploadPath3 = Configuration ["SystemConfig:UploadPath"]; var LogDefault2 = Configuration ["Logging:LogLevel:Default"] }}
The above describes two ways to read configuration information. If you want to use it in Controller, similarly, inject and call as follows:
Public class ValuesController: ControllerBase {private IConfiguration _ configuration; public ValuesController (IConfiguration configuration) {_ configuration = configuration;} / / GET: api/ [HttpGet] public IEnumerable 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"};}}
2. Read the configuration file to the custom object
Taking the SystemConfig node as an example, the classes are defined as follows:
Public class SystemConfig {public string UploadPath {get; set;} public string Domain {get; set;}}
The adjustment code is as follows:
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");}}
Then the injection call is made within the Controller:
[Route ("api/ [controller] / [action]")] [ApiController] public class ValuesController: ControllerBase {private SystemConfig _ sysConfig; public ValuesController (IOptions sysConfig) {_ sysConfig = sysConfig.Value;} [HttpGet] public IEnumerable GetSetting () {var UploadPath = _ sysConfig.UploadPath Var Domain = _ sysConfig.Domain; return new string [] {"value1", "value2"};}}
3. Bind to static class to read
The relevant static classes are defined as follows:
Public static class MySettings {public static SystemConfig Setting {get; set;} = new SystemConfig ();}
Adjust the Startup class constructor as follows:
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) / / bind static configuration class}
Next, such as directly using: MySettings.Setting.UploadPath can be called.
IV. File upload
The API generally requires file upload. Compared with webapi uploading files through byte array objects and other complex methods under the .netframework framework, .Net Core WebApi has changed a lot. It defines a new IFormFile object to receive uploaded files and directly upload the Controller code:
Back-end code
[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 () / Note: the parameter files object can also be replaced by: var files = Request.Form.Files; to get if (files.Count $(function () {}))
The above upload is carried out by building FormData and ajaxSubmit. You need to pay attention to the setting of contentType and processData parameters. In addition, if you are allowed to upload more than one file at a time, set the multipart attribute.
Before you can access a static file under wwwroot, you must register under the Configure method of the Startup class:
Public void Configure (IApplicationBuilder app) {app.UseStaticFiles (); / / used to access files under wwwroot}
Start the project and test the upload through the access path: http://localhost:***/index.html. After success, you will see the uploaded file in the Files directory under wwwroot.
Unified WebApi data return format
Define a unified return format
In order to facilitate the use of the agreed data format at the front and back end, we usually define a unified data return, including success, return status, specific data, and so on. For ease of explanation, define a data return class as follows:
Public class ApiResult {public bool IsSuccess {get; set;} public string Message {get; set;} public string Code {get; set;} public Dictionary Data {get; set;} = new Dictionary ();}
In this way, we encapsulate each action interface operation in ApiResult format for return. An example of a new ProductController is as follows:
[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 = $"goods-{index}", Price = rd.Next (9999)}); result.IsSuccess = true; return result;}}
Produces: define how the data is returned, and mark each Controller with [Produces ("application/json")], which means that the data is output in json mode.
ApiController: make sure that each Controller has an ApiController logo. Usually, we define a base class such as BaseController, which inherits from ControllerBase and marks it with the [ApiController] logo. The newly created controller inherits from this class.
Route: route access method. If you don't like RESTful, add Action, that is, [Route ("api/ [controller] / [action]")]
HTTP request: combined with the previously configured Swagger, we must ensure that each Action has a specific request method, that is, it must be one of HttpGet, HttpPost, HttpPut, HttpDelete. In general, we can use HttpGet and HttpPost.
In this way, the completion of the data return of the unity.
Solve T time format
.net Core Web Api is returned with hump-like name in lowercase by default, but T format time is returned when encountered with data of DateTime type. To solve T time format, define a time format conversion class as follows:
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"));}}
Then adjust the services.AddControllers code in the ConfigureServices of the Startup class as follows:
Services.AddControllers () .AddJsonOptions (configure = > {configure.JsonSerializerOptions.Converters.Add (new DatetimeJsonConverter ());}); VI. Model verification
Model verification already exists in ASP.NET MVC and is used in the same way. Refers to the parameter verification of the data submitted to the interface, including required items, data format, character length, range, and so on. In general, we will define the object submitted by POST as an entity class to receive, for example, define a registration class as follows:
Public class RegisterEntity {/ Mobile number / / [Display (Name = "Mobile number")] [Required (ErrorMessage = "{0} cannot be empty")] [StringLength (11, ErrorMessage = "{0} maximum {1} characters")] public string Mobile {get; set } / CAPTCHA / / [Display (Name = "CAPTCHA")] [Required (ErrorMessage = "{0} cannot be empty")] [StringLength (6, ErrorMessage = "{0} maximum {1} characters")] public string Code {get; set } / password / / [Display (Name = "password")] [Required (ErrorMessage = "{0} cannot be empty")] [StringLength (16, ErrorMessage = "{0} maximum {1} characters")] public string Pwd {get; set;}}
The name of the Display logo prompt field. Required indicates required, and StringLength limits the length of the field. Of course, there are other built-in features. For more information, please refer to the official documentation. List some common verification features as follows:
[CreditCard]: verify that the attribute has a credit card format. JQuery is required to validate other methods.
[Compare]: verify that the two attributes in the model match.
[EmailAddress]: verify that the property has an e-mail format.
[Phone]: verify that the attribute has a phone number format.
[Range]: verify that the property value is within the specified range.
[RegularExpression]: verifies that the property value matches the specified regular expression.
[Required]: verify that the field is not null. For more information about the behavior of this property, see [Required] attribute.
[StringLength]: verify that the string property value does not exceed the specified length limit.
[Url]: verify that the property is in URL format.
[Remote]: validate input on the client by calling the action method on the server.
The above illustrates the basic use of model verification, in this way, combined with the T4 template
So how does the above model validation work in Web API? Add the following code to the ConfigureServices of the Startup class:
/ / Model parameter verification 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});};})
Add the registration sample Action code:
/ register / / [HttpPost] public async Task Register (RegisterEntity model) {ApiResult result = new ApiResult (); var _ code = CacheHelper.GetCache (model.Mobile) If (_ code = = null) {result.Message = "CAPTCHA expired or does not exist"; return result;} if (! model.Code.Equals (_ code.ToString () {result.Message = "CAPTCHA error"; return result } / * * related logic codes * * / return result;}
In this way, by configuring ApiBehaviorOptions, reading the first message of the verification error message and returning it, the verification of the request parameters by the Action in Web API is completed, and the return of the error message Message can also be slightly encapsulated here.
7. Use of logs
Although .net Core WebApi has its own log management functions, it may not be easy to meet our needs. It usually uses third-party logging frameworks, such as NLog and Log4Net, to briefly introduce the use of NLog logging components.
The use of NLog
① installs the package through NuGet: NLog.Web.AspNetCore, current project version 4.9.2
Create a new NLog.config file in the root directory of the ② project. For other detailed configurations of key NLog.config, please refer to the official documentation. Here is a brief configuration as follows.
③ adjusts the Program.cs file as follows
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 (); / / dependency injection Nlog;}
It is also possible to omit the exception code configuration in the Main function, and UseNLog under CreateHostBuilder is required.
Controller is called through injection as follows:
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 ("Test a log"); 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 ();}
After testing locally, you can see the log files generated in the logs directory under debug.
VIII. Dependency injection
Using .net Core is necessary to deal with dependency injection, which is also one of the design ideas of .net Core, about what dependency injection (DI) is and why it is used. Instead of going into detail here, let's take a look at a simple example of dependency injection.
Public interface IProductRepository {IEnumerable GetAll ();} public class ProductRepository: IProductRepository {public IEnumerable GetAll () {}}
Register with the Startup class:
Public void ConfigureServices (IServiceCollection services) {services.AddScoped ();}
Request the IProductRepository service and call the GetAll method:
Public class ProductController: ControllerBase {private readonly IProductRepository _ productRepository; public ProductController (IProductRepository productRepository) {_ productRepository = productRepository;} public IEnumerable Get () {return _ productRepository.GetAll ();}
The IProductRepository interface is implemented by using the DI pattern. In fact, there have been many examples of injection calls through constructors.
Life cycle
Services.AddScoped (); services.AddTransient (); services.AddSingleton ()
Transient: a new instance is created for each request
Scoped: create an instance in each scope generation cycle
Singleton: singleton mode in which only one instance is created throughout the application lifecycle
Here, the corresponding lifecycle services need to be injected according to the requirements of the specific business logic scenario.
In practical applications, we will have many services that need to register in ConfigureServices. It is obviously cumbersome to write one by one, and it is easy to forget missing writes. Generally speaking, we may think of batch injection using reflection and injection through extension, such as:
Public static class AppServiceExtensions {/ 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);}} public void ConfigureServices (IServiceCollection services) {services.AddAppServices () / / bulk Registration Service}
Admittedly, this with the system's own DI injection can meet our batch injection requirements. But there are also more options to help us simplify DI registration, such as choosing other third-party components: Scrutor, Autofac...
1. The use of Scrutor
Scrutor is an extension library based on Microsoft injection components. A simple example is as follows:
Services.Scan (scan = > scan .FromAssemblyOf () .AddClasses (classes = > classes.Where (s = > s.Name.EndsWith ("Repository") | | s.Name.EndsWith ("Service") .AsImplementedInterfaces () .WithTransientLifetim e ())
The above code batch registers the interface service ending in Repository and Service through Scan, and its life cycle is Transient, which is equivalent to the above-mentioned batch registration service in reflection mode.
For other uses of Scrutor, you can refer to the official documentation, which is only an introduction.
2 、 Autofac
In general, the use of MS's own DI or the use of Scrutor, can meet the actual needs, if there are higher application requirements, such as requiring attribute injection, or even take over or replace MS's own DI, then you can choose Autofac, on the specific use of Autofac, will not be detailed here.
IX. Caching
MemoryCache usage
According to the official statement, developers need to use the cache reasonably and limit the cache size, and the Core runtime does not limit the cache size according to the content pressure. For the mode of use, it is still registered first, and then the controller calls:
Public void ConfigureServices (IServiceCollection services) {services.AddMemoryCache (); / / caching middleware} 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;}}
The above code caches a time and sets the sliding expiration time (the expiration time after the last access) to 3 seconds; if you need to set the absolute expiration time, change SetSlidingExpiration to SetAbsoluteExpiration. Browse refresh and will be updated every 3 seconds.
An encapsulated Cache class is attached as follows:
Public class CacheHelper {public static IMemoryCache _ memoryCache = new MemoryCache (new MemoryCacheOptions ()); / absolute cache expiration time / Cache key / / cached value / minute absolute expiration public static void SetChache (string key, object value, int minute) {if (value = = null) return _ memoryCache.Set (key, value, new MemoryCacheEntryOptions () .SetAbsoluteExpiration (TimeSpan.FromMinutes (minute) } / cache expires relatively. Minute expires after the last visit / Cache key / cached value / slides the expiration minute public static void SetChacheSliding (string key, object value, int minute) {if (value = = null) return _ memoryCache.Set (key, value, new MemoryCacheEntryOptions () .SetSlidingExpiration (TimeSpan.FromMinutes (minute);} / sets the cache, which will always be stored in memory if it is not emptied actively. / Cache key value / the value assigned to Cache [key] public static void SetChache (string key, object value) {_ memoryCache.Set (key, value) } / clear cache / Cache key public static void RemoveCache (string key) {_ memoryCache.Remove (key) } / public static object GetCache (string key) {/ / return _ memoryCache.Get (key) according to the key value; if (key! = null & & _ memoryCache.TryGetValue (key, out object val)) {return val } else {return default }} / return generic object / public static T GetCache (string key) {if (key! = null & & _ memoryCache.TryGetValue (key, out T val)) {return val } else {return default;}} 10. Exception handling
Define exception handling middleware
Here, the global exception is captured and logged, and returned to the interface caller in a unified json format. It is said that middleware is mentioned before exception handling. I will not repeat what is middleware here. The basic structure of a middleware is as follows:
Public class CustomMiddleware {private readonly RequestDelegate _ next; public CustomMiddleware (RequestDelegate next) {_ next = next;} public async Task Invoke (HttpContext httpContext) {await _ next (httpContext);}}
Let's define our own global exception handling middleware with the following code:
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);}} / add middleware / public static class CustomExceptionMiddlewareExtensions {public static IApplicationBuilder UseCustomExceptionMiddleware (this IApplicationBuilder builder) {return builder.UseMiddleware ();}}
Then add the above extended middleware to the Configure method of the Startup class, as shown in the bold section:
Public void Configure (IApplicationBuilder app, IWebHostEnvironment env) {if (env.IsDevelopment ()) {app.UseDeveloperExceptionPage ();} / / Global exception handling app.UseCustomExceptionMiddleware ();}
In the HandleExceptionAsync method, in order to facilitate development and testing, the system error is returned to the interface caller, and a fixed error Message message can be returned in the actual production environment.
Handling of abnormal status codes
With regard to the http status code, it is common, such as the normal return of 200,403,404,502 and so on. Because the system sometimes does not always return 200success, WebApi also has to deal with the abnormal status code that is not 200. so that the interface caller can receive it correctly, such as the following JWT authentication. When the authentication token expires or has no authority, the system will actually return 401,403. However, the API does not provide valid and acceptable returns. Therefore, some common exception status codes are listed here and provided to the interface caller in 200. add the code to the Configure method of the Startup class as follows:
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 = > not logged in, 403 = > access denied, 404 = > not found, _ = > unknown error,} Context.HttpContext.Response.StatusCode = StatusCodes.Status200OK; await context.HttpContext.Response.WriteAsync (new {isSuccess = false, code, message});})
The code is very simple, here we use the system's own exception handling middleware UseStatusCodePages, of course, you can also customize the filter to handle exceptions, but not recommended, simple, efficient and direct is needed.
With regard to the exception handling middleware of .NET Core, there are other middleware, such as UseExceptionHandler, UseStatusCodePagesWithRedirects, etc., different middleware has its own applicable environment, some may be more suitable for MVC or other application scenarios, just find the right one.
Digression: you can also encapsulate the operation of UseStatusCodePages handling exception status codes into the above-mentioned global exception handling middleware.
Application Security and JWT Authentication
I will not repeat what JWT is here. In practical applications, for the security of some interfaces, such as interface resources that need authentication, for Web API, token tokens are generally used for authentication, and the server side is combined with cache to achieve.
Then why choose JWT certification? The reasons are as follows: the server is not saved, stateless, suitable for mobile, suitable for distribution, standardization, and so on. The use of JWT is as follows:
Install the package through NuGget: Microsoft.AspNetCore.Authentication.JwtBearer, current sample version 3.1.5
ConfigureServices is injected. It is named Bearer by default. You can also change it to another name here and keep it consistent. Pay attention to the bold part. The code is as follows:
Appsettings.json add JWT configuration node (see [configuration file] above), and add JWT-related authentication classes:
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;}}
Read the JWT configuration by binding the static class mentioned above and inject it:
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) / bind static configuration class configuration.GetSection ("JwtTokenConfig") .bind (JwtSetting.Setting); / / Ibid} 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 authentication injection 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}
Add JWT authentication support to Swagger. After completion, the lock identification will appear on the Swagger page. After obtaining token, enter Value (in Bearer token form) to log in to Authorize. For more information on Swagger configuration JWT, please see the bold section:
Services.AddSwaggerGen (c = > {c.SwaggerDoc ("v1", new OpenApiInfo {Title = "My API", Version = "v1", Description = "API document description" Contact = new OpenApiContact {Email = "5007032@qq.com", Name = "Test Project", / / Url = new Uri ("http://t.abc.com/")}" License = new OpenApiLicense {Name = "BROOKE license", / / Url = new Uri ("http://t.abc.com/")}}) / / set the xml document comment path for Swagger JSON and UI / / var basePath = Path.GetDirectoryName (typeof (Program) .Assembly.location); / / get the directory where the application is located (not affected by the working directory) / / 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 authentication Swagger authorization c.AddSecurityDefinition ("Bearer", new OpenApiSecurityScheme {Description = "JWT authorization (data will be transferred in the request header header) directly enter Bearer {token} (with a space in the middle)", 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})
Add Configure registration to the Starup class. Note that it needs to be placed in app.UseAuthorization (); before:
Public void Configure (IApplicationBuilder app, IWebHostEnvironment env) {app.UseAuthentication (); / / jwt authentication app.UseAuthorization ();}
In this way, the JWT is basically configured, and then authentication, login and authorization are implemented. The simulation operations are as follows:
[HttpPost] public async Task Login (LoginEntity model) {ApiResult result = new ApiResult (); / / verify username and password var userInfo = await _ memberService.CheckUserAndPwd (model.User, model.Pwd); if (userInfo = = null) {result.Message = "incorrect username or password" 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)) / / generate Token string jwtToken = new JwtSecurityTokenHandler () .WriteToken (token); / / update the last login time await _ memberService.UpdateLastLoginTime (userInfo.MemberID); result.IsSuccess= 1; result.ResultData ["token"] = jwtToken; result.Message = "authorization successful!" ; return result;}
The above code simulates the login operation (login with account password, and sets the validity period for one day after success), generates a token and returns it. The front-end caller gets the token and stores it in a way such as localstorage. When calling the authorization API, add the token to header (Bearer token) to make the API request. Next, mark the Controller or Action that requires identity authorization with the Authorize logo:
[Authorize] [Route ("api/ [controller] / [action]")] public class UserController: ControllerBase {}
If you want to add role-based authorization, you can restrict actions as follows:
[Authorize (Roles = "user")] [Route ("api/ [controller] / [action]")] public class UserController: ControllerBase {} / / multiple roles can also be separated by commas [Authorize (Roles = "Administrator,Finance")] [Route ("api/ [controller] / [action]")] public class UserController: ControllerBase {}
Different role information can be configured through login settings ClaimTypes.Role. Of course, this is only a simple example to illustrate the application of role services. Complex ones can be dynamically configured by registering policy services and combining with the database.
In this way, a simple work based on JWT authentication and authorization is completed.
12. Cross-domain
The separation of front and rear ends will involve cross-domain problems. Simple cross-domain operations are supported as follows:
Add extended support
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 [] {"}) / / .AllowAnyMethod () / / .AllowAnyHeader () / / .AllowCredentials () / /});}}
Add related registrations for the Startup class as follows:
Public void ConfigureServices (IServiceCollection services) {services.ConfigureCors ();} public void Configure (IApplicationBuilder app, IWebHostEnvironment env) {app.UseCors ("CorsPolicy"); / / Cross-domain}
In this way, a simple cross-domain operation is completed, and you can also restrict the source and mode of the request address by setting WithOrigins, WithMethods, and so on.
The above is about the content of this article on "what are the basics of .net Core 3.1 Web API". I believe we all have a certain understanding. I hope the content shared by the editor will be helpful to you. If you want to know more about the relevant knowledge, please follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.