一、前言
为什么要搭建.Net Core/.NET5/.NET6/.NETX等平台,跨平台!!!而且比.Net 更容易搭建,速度也更快,所有的包均有Nuget提供,不再像以前的单纯引入组件。
1、.net core 框架性能测试
http://www.techempower.com/benchmarks/ 我们可以通过这个web框架性能测试来看看 aspcore 的性能
2、.net core 执行过程
3、中间件执行过程
启动的时候先执行该中间件类的构造函数,然后一路 Next() ;下去,返回的时候,正好是反向的,执行的是该类的逻辑部分:
4、AOP切面
二、创建Core项目
1、创建过程
具体的创建过程直接看官网文档即可Get started with ASP.NET Core
2、注意事项
开启.net开发前,需要先下载相应的SDK和Runtime。可以根据需要官网下载
- SDK 和 RunTime 的区别:SDK 是用来开发 NetCore 的,内部捆绑了 Runtime 运行时;但是如果只想运行 NetCore 项目的话,只需要在服务器中安装 Runtime 运行时即可。
- 判断安装成功:cmd中直接运行dotnet,如果有结果说明安装成功。
- Https问题:如下图,启用了https安全访问协议,如果启用的话,每次接口都需要是https:xxx,如果是测试可以不用勾选。有很多小伙伴勾选了这个,但是还是一直使用 http 协议去访问,导致找不到响应的接口地址。如果你的项目已经创建好了,每次访问都是HTTPS的,但是你不想这么做,可以在 launthSettings.json 文件中,把sslPort 端口号改成0即可。
三、项目整体结构分析
1、Program.cs
using HYEfficiency.Extensions; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; namespace HYEfficiencyCore { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseSeriLog() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); } }
这个Program是程序的入口,Main方法里面的内容主要是用来配置和运行程序的。因为我们的web程序需要一个宿主,所以 BuildWebHost这个方法就创建了一个WebHostBuilder。而且我们还需要Web Server,asp.net core 自带了两种http servers,一个是WebListener, 它只能用于windows系统; 另一个是kestrel, 它是跨平台的。kestrel是默认的web server,就是通过UseKestrel()这个方法来启用的。但是我们开发的时候使用的是IIS Express,调用UseIISIntegration()这个方法是启用IIS Express, 它作为Kestrel的Reverse Proxy server来用。
如果在windows服务器上部署的话, 就应该使用IIS作为Kestrel的反向代理服务器来管理和代理请求。如果在linux上的话, 可以使用apache, nginx等等的作为kestrel的proxy server。当然也可以单独使用kestrel作为web 服务器,但是使用iis作为reverse proxy还是有很多有优点的: 例如IIS可以过滤请求,管理证书,程序崩溃时自动重启等。
UseStartup<Startup>(), 这句话表示在程序启动的时候, 我们会调用Startup这个类,Build()完之后返回一个实现了IWebHost接口的实例(WebHostBuilder),然后调用Run()就会运行Web程序,并且阻止这个调用的线程,直到程序关闭。
关于Program.cs具体可以参考.NET Core项目解读 启动原理(program.cs和startup.cs)
2、Startup.cs
using HYEfficiency.Extensions; using HYEfficiency.Helpers; using HYEfficiency.Models; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.IdentityModel.Tokens.Jwt; namespace HYEfficiencyCore { 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(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } } }
ConfigureServices方法是用来把services注册到容器中去,并配置这些services。这个容器是用来进行dependency injection的,所有注入的services在以后写代码的时候,都可以将它们注入(inject)进去。例如上面的Configure方法的参数, app, env, loggerFactory都是注入进去的services。
Configure方法是asp.net core程序用来具体指定如何处理每个http请求的,例如我们可以让这个程序知道我使用mvc来处理http请求,那就调用app.UseMvc()这个方法就行. 但是目前, 所有的http请求都会导致返回"Hello World!"。
看一看我们项目的最后,Configure方法是如何配置的:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { // 在开发环境中,使用异常页面,这样可以暴露错误堆栈信息,所以不要放在生产环境。 app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); // 在非开发环境中,使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。 // 强制实施 HTTPS 在 ASP.NET Core,配合 app.UseHttpsRedirection //app.UseHsts(); } #region Swagger app.UseSwagger(); app.UseSwaggerUI(c => { //之前是写死的 //c.SwaggerEndpoint("/swagger/v1/swagger.json", "ApiHelp V1"); //c.RoutePrefix = "";//路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉 //根据版本名称倒序 遍历展示 typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version => {
// 注意这个 ApiName 和 要和上边 ConfigureServices 中配置swagger的name要大小写一致,具体查看我的blog.core源码 c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}"); }); }); #endregion #region Authen //app.UseMiddleware<JwtTokenAuth>();//注意此授权方法已经放弃,请使用下边的官方验证方法。但是如果你还想传User的全局变量,还是可以继续使用中间件 app.UseAuthentication(); #endregion #region CORS //跨域第二种方法,使用策略,详细策略信息在ConfigureService中 app.UseCors("LimitRequests");//将 CORS 中间件添加到 web 应用程序管线中, 以允许跨域请求。 //跨域第一种版本,请要ConfigureService中配置服务 services.AddCors(); // app.UseCors(options => options.WithOrigins("http://localhost:8021").AllowAnyHeader() //.AllowAnyMethod()); #endregion // 跳转https app.UseHttpsRedirection(); // 使用静态文件 app.UseStaticFiles(); // 使用cookie app.UseCookiePolicy(); // 返回错误码 app.UseStatusCodePages();//把错误码返回前台,比如是404 app.UseMvc(); }
3、appsettings.json
4、wwwroot
5、launchSettings.json
四、核心知识点
1、Routing路由
路由有两种方式:Convention-based (按约定),attribute-based(基于路由属性配置的)。其中convention-based (基于约定的) 主要用于MVC (返回View或者Razor Page那种的)。Web api 推荐使用attribute-based。这种基于属性配置的路由可以配置Controller或者Action级别, uri会根据Http method然后被匹配到一个controller里具体的action上。
常用的Http Method有:
- Get,查询 HttpGet '/api/product', '/api/product/1'
- POST,创建 HttpPost, '/api/product'
- PUT 整体修改更新 HttpPut, '/api/product/1'
- PATCH 部分更新 HttpPatch, '/api/product/1'
- DELETE 删除,HttpDelete, '/api/product/1
还有一个Route属性(attribute)也可以用于Controller层,它可以控制action级的URI前缀。
namespace Api.Controllers { //[Route("api/product")] [Route("api/[controller]")] public class ProductController: Controller { [HttpGet] public JsonResult GetProducts() { return new JsonResult(new List<Product> { new Product { Id = 1, Name = "牛奶", Price = 2.5f }, new Product { Id = 2, Name = "面包", Price = 4.5f } }); } } }
使用[Route("api/[controller]")],它使得整个Controller下面所有action的uri前缀变成了"/api/product", 其中[controller]表示XxxController.cs中的Xxx(其实是小写)。也可以具体指定, [Route("api/product")],这样做的好处是,如果ProductController重构以后改名了,只要不改Route里面的内容,那么请求的地址不会发生变化。然后在GetProducts方法上面,写上HttpGet,也可以写HttpGet(),它里面还可以加参数,例如: HttpGet("all"), 那么这个Action的请求的地址就变成了 "/api/product/All"。
2、内容协商 Content Negotiation
如果 web api提供了多种内容格式,那么可以通过Accept Header来选择最好的内容返回格式: 例如:application/json, application/xml等等。如果设定的格式在web api里面没有,那么web api就会使用默认的格式。asp.net core 默认提供的是json格式,也可以配置xml等格式。目前只考虑 Output formatter,就是返回的内容格式。如果想输出xml格式,就配置这里:
3、Validation 验证
针对上面的Post方法, 如果请求没有Body,参数product就会是null,这个我们已经判断了;如果body里面的数据所包含的属性在product中不存在,那么这个属性就会被忽略。但是如果body数据的属性有问题,比如说name没有填写, 或者name太长,那么在执行action方法的时候就会报错,这时候框架会自动抛出500异常,表示是服务器的错误,这是不对的。这种错误是由客户端引起的,所以需要返回400 Bad Request错误。验证Model/实体,asp.net core 内置可以使用 Data Annotations进行。
using System; using System.ComponentModel.DataAnnotations; namespace CoreBackend.Api.Dtos { public class ProductCreation { [Display(Name = "产品名称")] [Required(ErrorMessage = "{0}是必填项")] // [MinLength(2, ErrorMessage = "{0}的最小长度是{1}")] // [MaxLength(10, ErrorMessage = "{0}的长度不可以超过{1}")]
[StringLength(10, MinimumLength = 2, ErrorMessage = "{0}的长度应该不小于{2}, 不大于{1}")] public string Name { get; set; } [Display(Name = "价格")] [Range(0, Double.MaxValue, ErrorMessage = "{0}的值必须大于{1}")] public float Price { get; set; } } }
这些Data Annotation (理解为用于验证的注解),可以在System.ComponentModel.DataAnnotation找到,例如[Required]表示必填, [MinLength]表示最小长度, [StringLength]可以同时验证最小和最大长度,[Range]表示数值的范围等等很多。[Display(Name="xxx")]的用处是给属性起一个比较友好的名字。其他的验证注解都有一个属性叫做 ErrorMessage (string), 表示如果验证失败,就会把ErrorMessage的内容添加到错误结果里面去。这个ErrorMessage可以使用参数,{0}表示Display的Name属性,{1}表示当前注解的第一个变量,{2}表示当前注解的第二个变量。在Controller里面添加验证逻辑:
[HttpPost] public IActionResult Post([FromBody] ProductCreation product) { if (product == null) { return BadRequest(); } if (!ModelState.IsValid) { return BadRequest(ModelState); } var maxId = ProductService.Max(x => x.Id); var newProduct = new Product { Id = ++maxId, Name = product.Name, Price = product.Price }; ProductService.Add(newProduct); return CreatedAtRoute("GetProduct", new { id = newProduct.Id }, newProduct); }