重庆做网站letide,网站开发公司 logo,推广策略,it外包公司 能去吗.NET 6 迁移到 Minimal APIIntro上次写了一篇 Minimal API Todo Sample#xff0c;有些童鞋觉得 Minimal API 有些鸡肋#xff0c;有一些功能的支持都不太好#xff0c;但是其实 Host 之前支持的功能 Minimal API 大部分都是支持的#xff0c;上次的 Todo Sample 完全没有使… .NET 6 迁移到 Minimal APIIntro上次写了一篇 Minimal API Todo Sample有些童鞋觉得 Minimal API 有些鸡肋有一些功能的支持都不太好但是其实 Host 之前支持的功能 Minimal API 大部分都是支持的上次的 Todo Sample 完全没有使用 Controller 来使用 API但也是可以使用 Controller 的这一点从新的项目模板就能看的出来New Template使用 dotnet new webapi -n Net6TestApi 新的 ASP.NET Core Web API 模板项目结构如下创建新的项目结构如下主要变化的结构如下默认启用了可空引用类型Nullableenable/Nullable和隐式命名空间引用ImplicitUsingsenable/ImplicitUsings(可以参考项目文件的变化)Program.cs和之前项目的相比新的项目模板没有了 Startup服务都在 Program.cs 中注册Program 使用了 C# 9 中引入的顶级应用程序以及依赖 C# 10 带来的 Global Usings 的隐式命名空间引用WeatherForecast/WeatherForecastController 使用 C# 10 的 File Scoped Namespace 新特性以及上述的隐式命名空间引用namespace Net6TestApi;public class WeatherForecast
{public DateTime Date { get; set; }public int TemperatureC { get; set; }public int TemperatureF 32 (int)(TemperatureC / 0.5556);public string? Summary { get; set; }
}如果想和之前的模板对比一下可以使用 dotnet new webapi -o Net5TestApi -f net5.0 可以创建 .NET 5.0 的一个 API因为 .NET 5.0 默认不支持 C# 10 新特性所以还是之前的项目模板Migration上面是一个模板的变化对于已有的项目如何做项目升级呢以之前的一个 TodoApp 为例升级到 .NET 6 之后向 Minimal API 做迁移的一个示例修改之前的代码是这样的Program.cs比默认模板多了 Runtime metrics 的注册和数据库和默认用户的初始化using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Prometheus.DotNetRuntime;
using SparkTodo.API;
using SparkTodo.Models;DotNetRuntimeStatsBuilder.Customize().WithContentionStats().WithGcStats().WithThreadPoolStats().StartCollecting();var host Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webHostBuilder {webHostBuilder.UseStartupStartup();}).ConfigureLogging(loggingBuilder {loggingBuilder.AddJsonConsole();}).Build();using (var serviceScope host.Services.CreateScope())
{var dbContext serviceScope.ServiceProvider.GetRequiredServiceSparkTodoDbContext();await dbContext.Database.EnsureCreatedAsync();//init Database,you can add your init data herevar userManager serviceScope.ServiceProvider.GetRequiredServiceUserManagerUserAccount();var email weihanlioutlook.com;if (await userManager.FindByEmailAsync(email) null){await userManager.CreateAsync(new UserAccount{UserName email,Email email}, Test1234);}
}await host.RunAsync();Startup 代码如下using System;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Prometheus;
using SparkTodo.API.Services;
using SparkTodo.API.Swagger;
using SparkTodo.DataAccess;
using Swashbuckle.AspNetCore.SwaggerGen;namespace SparkTodo.API
{/// summary/// StartUp/// /summarypublic class Startup{public Startup(IConfiguration configuration){Configuration configuration.ReplacePlaceholders();}public IConfiguration Configuration { get; }public void ConfigureServices(IServiceCollection services){// Add framework services.services.AddDbContextPoolSparkTodo.Models.SparkTodoDbContext(options options.UseInMemoryDatabase(SparkTodo));//services.AddIdentitySparkTodo.Models.UserAccount, SparkTodo.Models.UserRole(options {options.Password.RequireLowercase false;options.Password.RequireUppercase false;options.Password.RequireNonAlphanumeric false;options.Password.RequiredUniqueChars 0;options.User.RequireUniqueEmail true;}).AddEntityFrameworkStoresSparkTodo.Models.SparkTodoDbContext().AddDefaultTokenProviders();// Add JWT token validationvar secretKey Configuration.GetAppSetting(SecretKey);var signingKey new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes(secretKey));var tokenAudience Configuration.GetAppSetting(TokenAudience);var tokenIssuer Configuration.GetAppSetting(TokenIssuer);services.ConfigureJWT.TokenOptions(options {options.Audience tokenAudience;options.Issuer tokenIssuer;options.ValidFor TimeSpan.FromHours(2);options.SigningCredentials new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);});services.AddAuthentication(options {options.DefaultScheme JwtBearerDefaults.AuthenticationScheme;options.DefaultAuthenticateScheme JwtBearerDefaults.AuthenticationScheme;options.DefaultForbidScheme JwtBearerDefaults.AuthenticationScheme;options.DefaultChallengeScheme JwtBearerDefaults.AuthenticationScheme;}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options {options.TokenValidationParameters new TokenValidationParameters{// The signing key must match!ValidateIssuerSigningKey true,IssuerSigningKey signingKey,// Validate the JWT Issuer (iss) claimValidateIssuer true,ValidIssuer tokenIssuer,// Validate the JWT Audience (aud) claimValidateAudience true,ValidAudience tokenAudience,// Validate the token expiryValidateLifetime true,// If you want to allow a certain amount of clock drift, set that here:ClockSkew System.TimeSpan.FromMinutes(2)};});// Add MvcFrameworkservices.AddControllers();// Add api version// https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspxservices.AddApiVersioning(options {options.AssumeDefaultVersionWhenUnspecified true;options.DefaultApiVersion ApiVersion.Default;options.ReportApiVersions true;});// swagger// https://stackoverflow.com/questions/58197244/swaggerui-with-netcore-3-0-bearer-token-authorizationservices.AddSwaggerGen(option {option.SwaggerDoc(spark todo, new OpenApiInfo{Version v1,Title SparkTodo API,Description API for SparkTodo,Contact new OpenApiContact() { Name WeihanLi, Email weihanlioutlook.com }});option.SwaggerDoc(v1, new OpenApiInfo { Version v1, Title API V1 });option.SwaggerDoc(v2, new OpenApiInfo { Version v2, Title API V2 });option.DocInclusionPredicate((docName, apiDesc) {var versions apiDesc.CustomAttributes().OfTypeApiVersionAttribute().SelectMany(attr attr.Versions);return versions.Any(v $v{v} docName);});option.OperationFilterRemoveVersionParameterOperationFilter();option.DocumentFilterSetVersionInPathDocumentFilter();// include document fileoption.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, ${typeof(Startup).Assembly.GetName().Name}.xml), true);option.AddSecurityDefinition(Bearer, new OpenApiSecurityScheme(){Description Please enter into field the word Bearer followed by a space and the JWT value,Name Authorization,In ParameterLocation.Header,Type SecuritySchemeType.ApiKey,});option.AddSecurityRequirement(new OpenApiSecurityRequirement{{ new OpenApiSecurityScheme{Reference new OpenApiReference(){Id Bearer,Type ReferenceType.SecurityScheme}}, Array.Emptystring() }});});services.AddHealthChecks();// Add application services.services.AddSingletonITokenGenerator, TokenGenerator();//Repositoryservices.RegisterAssemblyTypesAsImplementedInterfaces(t t.Name.EndsWith(Repository),ServiceLifetime.Scoped, typeof(IUserAccountRepository).Assembly);}public void Configure(IApplicationBuilder app){// Disable claimType transform, see details here https://stackoverflow.com/questions/39141310/jwttoken-claim-name-jwttokentypes-subject-resolved-to-claimtypes-nameidentifieJwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();// Emit dotnet runtime version to response headerapp.Use(async (context, next) {context.Response.Headers[DotNetVersion] System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;await next();});//Enable middleware to serve generated Swagger as a JSON endpoint.app.UseSwagger();//Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpointapp.UseSwaggerUI(option {option.SwaggerEndpoint(/swagger/v2/swagger.json, V2 Docs);option.SwaggerEndpoint(/swagger/v1/swagger.json, V1 Docs);option.RoutePrefix string.Empty;option.DocumentTitle SparkTodo API;});app.UseRouting();app.UseCors(builder{builder.AllowAnyHeader().AllowAnyMethod().AllowCredentials().SetIsOriginAllowed(_true);});app.UseHttpMetrics();app.UseAuthentication();app.UseAuthorization();app.UseEndpoints(endpoints {endpoints.MapHealthChecks(/health);endpoints.MapMetrics();endpoints.MapControllers();});}}
}使用 Minimal API 改造后是下面这样的DotNetRuntimeStatsBuilder.Customize().WithContentionStats().WithGcStats().WithThreadPoolStats().StartCollecting();var builder WebApplication.CreateBuilder(args);
builder.Logging.AddJsonConsole();// Add framework services.
builder.Services.AddDbContextPoolSparkTodo.Models.SparkTodoDbContext(options options.UseInMemoryDatabase(SparkTodo));
//
builder.Services.AddIdentitySparkTodo.Models.UserAccount, SparkTodo.Models.UserRole(options
{options.Password.RequireLowercase false;options.Password.RequireUppercase false;options.Password.RequireNonAlphanumeric false;options.Password.RequiredUniqueChars 0;options.User.RequireUniqueEmail true;
}).AddEntityFrameworkStoresSparkTodo.Models.SparkTodoDbContext().AddDefaultTokenProviders();// Add JWT token validation
var secretKey builder.Configuration.GetAppSetting(SecretKey);
var signingKey new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes(secretKey));var tokenAudience builder.Configuration.GetAppSetting(TokenAudience);
var tokenIssuer builder.Configuration.GetAppSetting(TokenIssuer);
builder.Services.ConfigureSparkTodo.API.JWT.TokenOptions(options
{options.Audience tokenAudience;options.Issuer tokenIssuer;options.ValidFor TimeSpan.FromHours(2);options.SigningCredentials new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
});builder.Services.AddAuthentication(options
{options.DefaultScheme JwtBearerDefaults.AuthenticationScheme;options.DefaultAuthenticateScheme JwtBearerDefaults.AuthenticationScheme;options.DefaultForbidScheme JwtBearerDefaults.AuthenticationScheme;options.DefaultChallengeScheme JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options {options.TokenValidationParameters new TokenValidationParameters{// The signing key must match!ValidateIssuerSigningKey true,IssuerSigningKey signingKey,// Validate the JWT Issuer (iss) claimValidateIssuer true,ValidIssuer tokenIssuer,// Validate the JWT Audience (aud) claimValidateAudience true,ValidAudience tokenAudience,// Validate the token expiryValidateLifetime true,// If you want to allow a certain amount of clock drift, set that here:ClockSkew System.TimeSpan.FromMinutes(2)};});// Add MvcFramework
builder.Services.AddControllers();
// Add api version
// https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx
builder.Services.AddApiVersioning(options
{options.AssumeDefaultVersionWhenUnspecified true;options.DefaultApiVersion ApiVersion.Default;options.ReportApiVersions true;
});
// swagger
// https://stackoverflow.com/questions/58197244/swaggerui-with-netcore-3-0-bearer-token-authorization
builder.Services.AddSwaggerGen(option
{option.SwaggerDoc(spark todo, new OpenApiInfo{Version v1,Title SparkTodo API,Description API for SparkTodo,Contact new OpenApiContact() { Name WeihanLi, Email weihanlioutlook.com }});option.SwaggerDoc(v1, new OpenApiInfo { Version v1, Title API V1 });option.SwaggerDoc(v2, new OpenApiInfo { Version v2, Title API V2 });option.DocInclusionPredicate((docName, apiDesc) {var versions apiDesc.CustomAttributes().OfTypeApiVersionAttribute().SelectMany(attr attr.Versions);return versions.Any(v $v{v} docName);});option.OperationFilterRemoveVersionParameterOperationFilter();option.DocumentFilterSetVersionInPathDocumentFilter();// include document fileoption.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, ${Assembly.GetExecutingAssembly().GetName().Name}.xml), true);option.AddSecurityDefinition(Bearer, new OpenApiSecurityScheme(){Description Please enter into field the word Bearer followed by a space and the JWT value,Name Authorization,In ParameterLocation.Header,Type SecuritySchemeType.ApiKey,});option.AddSecurityRequirement(new OpenApiSecurityRequirement{{ new OpenApiSecurityScheme{Reference new OpenApiReference(){Id Bearer,Type ReferenceType.SecurityScheme}}, Array.Emptystring() }});
});
builder.Services.AddHealthChecks();
// Add application services.
builder.Services.AddSingletonITokenGenerator, TokenGenerator();
//Repository
builder.Services.RegisterAssemblyTypesAsImplementedInterfaces(t t.Name.EndsWith(Repository),ServiceLifetime.Scoped, typeof(IUserAccountRepository).Assembly);var app builder.Build();// Disable claimType transform, see details here https://stackoverflow.com/questions/39141310/jwttoken-claim-name-jwttokentypes-subject-resolved-to-claimtypes-nameidentifie
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();// Emit dotnet runtime version to response header
app.Use(async (context, next)
{context.Response.Headers[DotNetVersion] System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;await next();
});//Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger();
//Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint
app.UseSwaggerUI(option
{option.SwaggerEndpoint(/swagger/v2/swagger.json, V2 Docs);option.SwaggerEndpoint(/swagger/v1/swagger.json, V1 Docs);option.RoutePrefix string.Empty;option.DocumentTitle SparkTodo API;
});app.UseRouting();
app.UseCors(builder
{builder.AllowAnyHeader().AllowAnyMethod().AllowCredentials().SetIsOriginAllowed(_ true);
});app.UseHttpMetrics();app.UseAuthentication();
app.UseAuthorization();app.MapHealthChecks(/health);
app.MapMetrics();
app.MapControllers();using (var serviceScope app.Services.CreateScope())
{var dbContext serviceScope.ServiceProvider.GetRequiredServiceSparkTodoDbContext();await dbContext.Database.EnsureCreatedAsync();//init Database,you can add your init data herevar userManager serviceScope.ServiceProvider.GetRequiredServiceUserManagerUserAccount();var email weihanlioutlook.com;if (await userManager.FindByEmailAsync(email) null){await userManager.CreateAsync(new UserAccount{UserName email,Email email}, Test1234);}
}
await app.RunAsync();改造方法原来 Program 里的 Host.CreateDefaultBuilder(args) 使用新的 var builder WebApplication.CreateBuilder(args); 来代替原来 Program 里的 ConfigureLogging 使用 builder.Logging 来配置 builder.Logging.AddJsonConsole();原来 Program 里的 ConfigureAppConfiguration 使用 builder.Configuration.AddXxx 来配置 builder.Configuration.AddJsonFile();原来 Startup 里的服务注册使用 builder.Services 来注册原来 Startup 里的配置是从构造器注入的需要使用配置的话用 builder.Configuration 来代替原来 Startup 里中间件的配置通过 var app builder.Build(); 构建出来的 WebApplication 来注册原来 Program 里的 host.Run/host.RunAsync 需要改成 app.Run/app.RunAsyncMoreMinimal API 会有一些限制比如不能通过 builder.WebHost.UseStartupStartup() 通过 Startup 来注册服务和中间件的配置的不能通过 builder.Host.UseEnvironment/builder.Host.UseContentRoot/builder.WebHost.UseContentRoot/builder.WebHost.UseEnvironment/builder.WebHost.UseSetting 来配置 host 的一些配置现在的 WebApplication 实现了 IEndpointRouteBuilder可以不用 UseEndpoints 来注册比如可以直接使用 app.MapController() 代替 app.UseEndpoints(endpoints endpoints.MapController())更多可以参考 David 总结的一个迁移指南 https://gist.github.com/davidfowl/0e0372c3c1d895c3ce195ba983b1e03dMinimal API 结合了原来的 Startup不再有 Startup但是原来的应用也可以不必迁移到 Minimal API根据自己的需要进行选择Referenceshttps://github.com/WeihanLi/SparkTodo/commit/d3e327405c0f151e89378e9c01acde4648a7812fhttps://github.com/WeihanLi/SparkTodohttps://gist.github.com/davidfowl/0e0372c3c1d895c3ce195ba983b1e03d