// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IBlogArticleServices _blogArticleServices, ILoggerFactory loggerFactory) { // 记录所有的访问记录 loggerFactory.AddLog4Net(); // 记录请求与返回数据 app.UseReuestResponseLog(); // signalr app.UseSignalRSendMildd(); // 记录ip请求 app.UseIPLogMildd(); #region Environment if (env.IsDevelopment()) { // 在开发环境中,使用异常页面,这样可以暴露错误堆栈信息,所以不要放在生产环境。 app.UseDeveloperExceptionPage(); //app.Use(async (context, next) => //{ // //这里会多次调用,这里测试一下就行,不要打开注释 // //var blogs =await _blogArticleServices.GetBlogs(); // var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; // Console.WriteLine(processName); // await next(); //}); } else { app.UseExceptionHandler("/Error"); // 在非开发环境中,使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。 // 强制实施 HTTPS 在 ASP.NET Core,配合 app.UseHttpsRedirection //app.UseHsts(); } #endregion #region Swagger app.UseSwagger(); app.UseSwaggerUI(c => { //根据版本名称倒序 遍历展示 var ApiName = Appsettings.app(new string[] { "Startup", "ApiName" }); typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version => { c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}"); }); // 将swagger首页,设置成我们自定义的页面,记得这个字符串的写法:解决方案名.index.html c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.index.html"); //这里是配合MiniProfiler进行性能监控的,《文章:完美基于AOP的接口性能分析》,如果你不需要,可以暂时先注释掉,不影响大局。 c.RoutePrefix = ""; //路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉,如果你想换一个路径,直接写名字即可,比如直接写c.RoutePrefix = "doc"; }); #endregion // ↓↓↓↓↓↓ 注意下边这些中间件的顺序,很重要 ↓↓↓↓↓↓ app.UseCors("LimitRequests"); // 跳转https //app.UseHttpsRedirection(); // 使用静态文件 app.UseStaticFiles(); // 使用cookie app.UseCookiePolicy(); // 返回错误码 app.UseStatusCodePages();//把错误码返回前台,比如是404 // Routing app.UseRouting(); // 这种自定义授权中间件,可以尝试,但不推荐 // app.UseJwtTokenAuth(); // 先开启认证 app.UseAuthentication(); // 然后是授权中间件 app.UseAuthorization(); // 开启异常中间件,要放到最后 //app.UseExceptionHandlerMidd(); app.UseMiniProfiler(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); endpoints.MapHub <ChatHub>("/api2/chatHub"); }); }
// 注意在CreateDefaultBuilder中,添加Autofac服务工厂 public void ConfigureContainer(ContainerBuilder builder) { var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath; //注册要通过反射创建的组件 //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); builder.RegisterType <BlogCacheAOP>(); //可以直接替换其他拦截器 builder.RegisterType <BlogRedisCacheAOP>(); //可以直接替换其他拦截器 builder.RegisterType <BlogLogAOP>(); //这样可以注入第二个 builder.RegisterType <BlogTranAOP>(); // ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※ #region 带有接口层的服务注入 #region Service.dll 注入,有对应接口 //获取项目绝对路径,请注意,这个是实现类的dll文件,不是接口 IService.dll ,注入容器当然是Activatore try { var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); var assemblysServices = Assembly.LoadFrom(servicesDllFile);//直接采用加载文件的方法 ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※ //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已扫描程序集中的类型注册为提供所有其实现的接口。 // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应对应 true 就行。 var cacheType = new List <Type>(); if (Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" }).ObjToBool()) { cacheType.Add(typeof(BlogRedisCacheAOP)); } if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool()) { cacheType.Add(typeof(BlogCacheAOP)); } if (Appsettings.app(new string[] { "AppSettings", "TranAOP", "Enabled" }).ObjToBool()) { cacheType.Add(typeof(BlogTranAOP)); } if (Appsettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool()) { cacheType.Add(typeof(BlogLogAOP)); } builder.RegisterAssemblyTypes(assemblysServices) .AsImplementedInterfaces() .InstancePerLifetimeScope() .EnableInterfaceInterceptors() //引用Autofac.Extras.DynamicProxy; // 如果你想注入两个,就这么写 InterceptedBy(typeof(BlogCacheAOP), typeof(BlogLogAOP)); // 如果想使用Redis缓存,请必须开启 redis 服务,端口号我的是6319,如果不一样还是无效,否则请使用memory缓存 BlogCacheAOP .InterceptedBy(cacheType.ToArray()); //允许将拦截器服务的列表分配给注册。 #endregion #region Repository.dll 注入,有对应接口 var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); } catch (Exception ex) { log.Error("Repository.dll和service.dll 丢失,因为项目解耦了,所以需要先F6编译,再F5运行,请检查并拷贝。\n" + ex.Message); //throw new Exception("※※★※※ 如果你是第一次下载项目,请先对整个解决方案dotnet build(F6编译),然后再对api层 dotnet run(F5执行),\n因为解耦了,如果你是发布的模式,请检查bin文件夹是否存在Repository.dll和service.dll ※※★※※" + ex.Message + "\n" + ex.InnerException); } #endregion #endregion #region 没有接口层的服务层注入 ////因为没有接口层,所以不能实现解耦,只能用 Load 方法。 ////注意如果使用没有接口的服务,并想对其使用 AOP 拦截,就必须设置为虚方法 ////var assemblysServicesNoInterfaces = Assembly.Load("Blog.Core.Services"); ////builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces); #endregion #region 没有接口的单独类 class 注入 ////只能注入该类中的虚方法 builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) .EnableClassInterceptors() .InterceptedBy(typeof(BlogLogAOP)); #endregion }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // 以下code可能与文章中不一样,对代码做了封装,具体查看右侧 Extensions 文件夹. services.AddSingleton(new Appsettings(Configuration)); services.AddSingleton(new LogLock(Env.ContentRootPath)); Permissions.IsUseIds4 = Appsettings.app(new string[] { "Startup", "IdentityServer4", "Enabled" }).ObjToBool(); // 确保从认证中心返回的ClaimType不被更改,不使用Map映射 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddMemoryCacheSetup(); services.AddRedisCacheSetup(); services.AddSqlsugarSetup(); services.AddDbSetup(); services.AddAutoMapperSetup(); services.AddCorsSetup(); services.AddMiniProfilerSetup(); services.AddSwaggerSetup(); services.AddJobSetup(); services.AddHttpContextSetup(); services.AddAppConfigSetup(Env); services.AddHttpApi(); services.AddRedisInitMqSetup(); // 授权+认证 (jwt or ids4) services.AddAuthorizationSetup(); if (Permissions.IsUseIds4) { services.AddAuthentication_Ids4Setup(); } else { services.AddAuthentication_JWTSetup(); } services.AddIpPolicyRateLimitSetup(Configuration); services.AddSignalR().AddNewtonsoftJsonProtocol(); services.AddScoped <UseServiceDIAttribute>(); services.Configure <KestrelServerOptions>(x => x.AllowSynchronousIO = true) .Configure <IISServerOptions>(x => x.AllowSynchronousIO = true); services.AddControllers(o => { // 全局异常过滤 o.Filters.Add(typeof(GlobalExceptionsFilter)); // 全局路由权限公约 //o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention()); // 全局路由前缀,统一修改路由 o.Conventions.Insert(0, new GlobalRoutePrefixFilter(new RouteAttribute(RoutePrefix.Name))); }) //全局配置Json序列化处理 .AddNewtonsoftJson(options => { //忽略循环引用 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; //不使用驼峰样式的key options.SerializerSettings.ContractResolver = new DefaultContractResolver(); //设置时间格式 //options.SerializerSettings.DateFormatString = "yyyy-MM-dd"; //忽略Model中为null的属性 //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; }); _services = services; }
// This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { #region 部分服务注入-netcore自带方法 //缓存注入 services.AddScoped <ICaching, MemoryCaching>(); services.AddSingleton <IMemoryCache>(factory => { var cache = new MemoryCache(new MemoryCacheOptions()); return(cache); }); //Redis注入 services.AddSingleton <IRedisCacheManager, RedisCacheManager>(); //log日志注入 services.AddSingleton <ILoggerHelper, LogHelper>(); #endregion #region 初始化DB services.AddScoped <Blog.Core.Model.Models.DBSeed>(); services.AddScoped <Blog.Core.Model.Models.MyContext>(); #endregion #region Automapper services.AddAutoMapper(typeof(Startup)); #endregion #region CORS //跨域第二种方法,声明策略,记得下边app中配置 services.AddCors(c => { //↓↓↓↓↓↓↓注意正式环境不要使用这种全开放的处理↓↓↓↓↓↓↓↓↓↓ c.AddPolicy("AllRequests", policy => { policy .AllowAnyOrigin() //允许任何源 .AllowAnyMethod() //允许任何方式 .AllowAnyHeader() //允许任何头 .AllowCredentials(); //允许cookie }); //↑↑↑↑↑↑↑注意正式环境不要使用这种全开放的处理↑↑↑↑↑↑↑↑↑↑ //一般采用这种方法 c.AddPolicy("LimitRequests", policy => { policy .WithOrigins("http://127.0.0.1:1818", "http://*****:*****@xxx.com", Url = "https://www.jianshu.com/u/94102b59cc2a" } }); // 按相对路径排序,作者:Alby c.OrderActionsBy(o => o.RelativePath); }); //就是这里 var xmlPath = Path.Combine(basePath, "Blog.Core.xml"); //这个就是刚刚配置的xml文件名 c.IncludeXmlComments(xmlPath, true); //默认的第二个参数是false,这个是controller的注释,记得修改 var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml"); //这个就是Model层的xml文件名 c.IncludeXmlComments(xmlModelPath); #region Token绑定到ConfigureServices //添加header验证信息 //c.OperationFilter<SwaggerHeader>(); // 发行人 var IssuerName = (Configuration.GetSection("Audience"))["Issuer"]; var security = new Dictionary <string, IEnumerable <string> > { { IssuerName, new string[] { } }, }; c.AddSecurityRequirement(security); //方案名称“Blog.Core”可自定义,上下一致即可 c.AddSecurityDefinition(IssuerName, new ApiKeyScheme { Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", Name = "Authorization", //jwt默认的参数名称 In = "header", //jwt默认存放Authorization信息的位置(请求头中) Type = "apiKey" }); #endregion }); #endregion #region MVC + GlobalExceptions //注入全局异常捕获 services.AddMvc(o => { // 全局异常过滤 o.Filters.Add(typeof(GlobalExceptionsFilter)); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) // 取消默认驼峰 .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new DefaultContractResolver(); }); #endregion #region Authorize权限设置三种情况 //使用说明: //如果你只是简单的基于角色授权的,第一步:【1/2 简单角色授权】,第二步:配置【统一认证】,第三步:开启中间件app.UseMiddleware<JwtTokenAuth>()不能验证过期,或者 app.UseAuthentication();可以验证过期时间 //如果你是用的复杂的策略授权,配置权限在数据库,第一步:【3复杂策略授权】,第二步:配置【统一认证】,第三步:开启中间件app.UseAuthentication(); //综上所述,设置权限,必须要三步走,涉及授权策略 + 配置认证 + 开启授权中间件,只不过自定义的中间件不能验证过期时间,所以我都是用官方的。 #region 【1/2、简单角色授权】 #region 1、基于角色的API授权 // 1【授权】、这个很简单,其他什么都不用做, // 无需配置服务,只需要在API层的controller上边,增加特性即可,注意,只能是角色的: // [Authorize(Roles = "Admin")] // 2【认证】、然后在下边的configure里,配置中间件即可:app.UseMiddleware<JwtTokenAuth>();但是这个方法,无法验证过期时间,所以如果需要验证过期时间,还是需要下边的第三种方法,官方认证 #endregion #region 2、基于策略的授权(简单版) // 1【授权】、这个和上边的异曲同工,好处就是不用在controller中,写多个 roles 。 // 然后这么写 [Authorize(Policy = "Admin")] services.AddAuthorization(options => { options.AddPolicy("Client", policy => policy.RequireRole("Client").Build()); options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build()); options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System")); }); // 2【认证】、然后在下边的configure里,配置中间件即可:app.UseMiddleware<JwtTokenAuth>();但是这个方法,无法验证过期时间,所以如果需要验证过期时间,还是需要下边的第三种方法,官方认证 #endregion #endregion #region 【3、复杂策略授权】 #region 参数 //读取配置文件 var audienceConfig = Configuration.GetSection("Audience"); var symmetricKeyAsBase64 = audienceConfig["Secret"]; var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); var signingKey = new SymmetricSecurityKey(keyByteArray); var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); // 如果要数据库动态绑定,这里先留个空,后边处理器里动态赋值 var permission = new List <PermissionItem>(); // 角色与接口的权限要求参数 var permissionRequirement = new PermissionRequirement( "/api/denied", // 拒绝授权的跳转地址(目前无用) permission, ClaimTypes.Role, //基于角色的授权 audienceConfig["Issuer"], //发行人 audienceConfig["Audience"], //听众 signingCredentials, //签名凭据 expiration: TimeSpan.FromSeconds(60 * 5) //接口的过期时间 ); #endregion //【授权】 services.AddAuthorization(options => { options.AddPolicy("Permission", policy => policy.Requirements.Add(permissionRequirement)); }); #endregion #region 【统一认证】 // 令牌验证参数 var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, ValidateIssuer = true, ValidIssuer = audienceConfig["Issuer"], //发行人 ValidateAudience = true, ValidAudience = audienceConfig["Audience"], //订阅人 ValidateLifetime = true, ClockSkew = TimeSpan.Zero, RequireExpirationTime = true, }; //2.1【认证】、core自带官方JWT认证 services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(o => { o.TokenValidationParameters = tokenValidationParameters; o.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { // 如果过期,则把<是否过期>添加到,返回头信息中 if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add("Token-Expired", "true"); } return(Task.CompletedTask); } }; }); //2.2【认证】、IdentityServer4 认证 (暂时忽略) //services.AddAuthentication("Bearer") // .AddIdentityServerAuthentication(options => // { // options.Authority = "http://localhost:5002"; // options.RequireHttpsMetadata = false; // options.ApiName = "blog.core.api"; // }); // 注入权限处理器 services.AddSingleton <IAuthorizationHandler, PermissionHandler>(); services.AddSingleton(permissionRequirement); #endregion #endregion #region AutoFac DI //实例化 AutoFac 容器 var builder = new ContainerBuilder(); //注册要通过反射创建的组件 //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); builder.RegisterType <BlogCacheAOP>(); //可以直接替换其他拦截器 builder.RegisterType <BlogRedisCacheAOP>(); //可以直接替换其他拦截器 builder.RegisterType <BlogLogAOP>(); //这样可以注入第二个 // ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※ #region 带有接口层的服务注入 #region Service.dll 注入,有对应接口 //获取项目绝对路径,请注意,这个是实现类的dll文件,不是接口 IService.dll ,注入容器当然是Activatore try { var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); var assemblysServices = Assembly.LoadFile(servicesDllFile);//直接采用加载文件的方法 ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※ //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已扫描程序集中的类型注册为提供所有其实现的接口。 // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应对应 true 就行。 var cacheType = new List <Type>(); if (Appsettings.app(new string[] { "AppSettings", "RedisCaching", "Enabled" }).ObjToBool()) { cacheType.Add(typeof(BlogRedisCacheAOP)); } if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool()) { cacheType.Add(typeof(BlogCacheAOP)); } if (Appsettings.app(new string[] { "AppSettings", "LogoAOP", "Enabled" }).ObjToBool()) { cacheType.Add(typeof(BlogLogAOP)); } builder.RegisterAssemblyTypes(assemblysServices) .AsImplementedInterfaces() .InstancePerLifetimeScope() .EnableInterfaceInterceptors() //引用Autofac.Extras.DynamicProxy; // 如果你想注入两个,就这么写 InterceptedBy(typeof(BlogCacheAOP), typeof(BlogLogAOP)); // 如果想使用Redis缓存,请必须开启 redis 服务,端口号我的是6319,如果不一样还是无效,否则请使用memory缓存 BlogCacheAOP .InterceptedBy(cacheType.ToArray()); //允许将拦截器服务的列表分配给注册。 #endregion #region Repository.dll 注入,有对应接口 var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); var assemblysRepository = Assembly.LoadFile(repositoryDllFile); builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); } catch (Exception) { throw new Exception("※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,因为解耦了 ※※★※※"); } #endregion #endregion #region 没有接口层的服务层注入 ////因为没有接口层,所以不能实现解耦,只能用 Load 方法。 ////var assemblysServicesNoInterfaces = Assembly.Load("Blog.Core.Services"); ////builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces); #endregion #region 没有接口的单独类 class 注入 ////只能注入该类中的虚方法 builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) .EnableClassInterceptors() .InterceptedBy(typeof(BlogLogAOP)); #endregion //将services填充到Autofac容器生成器中 builder.Populate(services); //使用已进行的组件登记创建新容器 var ApplicationContainer = builder.Build(); #endregion return(new AutofacServiceProvider(ApplicationContainer));//第三方IOC接管 core内置DI容器 }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IBlogArticleServices _blogArticleServices, ILoggerFactory loggerFactory) { #region RecordAllLogs if (Appsettings.app("AppSettings", "Middleware_RecordAllLogs", "Enabled").ObjToBool()) { loggerFactory.AddLog4Net();//记录所有的访问记录 } #endregion #region ReuestResponseLog if (Appsettings.app("AppSettings", "Middleware_RequestResponse", "Enabled").ObjToBool()) { app.UseReuestResponseLog();//记录请求与返回数据 } #endregion #region Environment if (env.IsDevelopment()) { // 在开发环境中,使用异常页面,这样可以暴露错误堆栈信息,所以不要放在生产环境。 app.UseDeveloperExceptionPage(); //app.Use(async (context, next) => //{ // //这里会多次调用,这里测试一下就行,不要打开注释 // //var blogs =await _blogArticleServices.GetBlogs(); // var processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; // Console.WriteLine(processName); // await next(); //}); } else { app.UseExceptionHandler("/Error"); // 在非开发环境中,使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。 // 强制实施 HTTPS 在 ASP.NET Core,配合 app.UseHttpsRedirection //app.UseHsts(); } #endregion #region Swagger app.UseSwagger(); app.UseSwaggerUI(c => { //根据版本名称倒序 遍历展示 typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version => { c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}"); }); // 将swagger首页,设置成我们自定义的页面,记得这个字符串的写法:解决方案名.index.html c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.index.html"); //这里是配合MiniProfiler进行性能监控的,《文章:完美基于AOP的接口性能分析》,如果你不需要,可以暂时先注释掉,不影响大局。 c.RoutePrefix = ""; //路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉,如果你想换一个路径,直接写名字即可,比如直接写c.RoutePrefix = "doc"; }); #endregion #region MiniProfiler app.UseMiniProfiler(); #endregion #region CORS //跨域第二种方法,使用策略,详细策略信息在ConfigureService中 app.UseCors("LimitRequests");//将 CORS 中间件添加到 web 应用程序管线中, 以允许跨域请求。 #region 跨域第一种版本 //跨域第一种版本,请要ConfigureService中配置服务 services.AddCors(); // app.UseCors(options => options.WithOrigins("http://localhost:8021").AllowAnyHeader() //.AllowAnyMethod()); #endregion #endregion // 跳转https //app.UseHttpsRedirection(); // 使用静态文件 app.UseStaticFiles(); // 使用cookie app.UseCookiePolicy(); // 返回错误码 app.UseStatusCodePages();//把错误码返回前台,比如是404 app.UseRouting(); #region 第三步:开启认证中间件 //此授权认证方法已经放弃,请使用下边的官方验证方法。但是如果你还想传User的全局变量,还是可以继续使用中间件,第二种写法//app.UseMiddleware<JwtTokenAuth>(); //app.UseJwtTokenAuth(); //如果你想使用官方认证,必须在上边ConfigureService 中,配置JWT的认证服务 (.AddAuthentication 和 .AddJwtBearer 二者缺一不可) app.UseAuthentication(); #endregion app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); endpoints.MapHub <ChatHub>("/api2/chatHub"); }); }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyContext myContext, ITasksQzServices tasksQzServices, ISchedulerCenter schedulerCenter, IHostApplicationLifetime lifetime) { // Ip限流,尽量放管道外层 app.UseIpLimitMildd(); // 记录请求与返回数据 app.UseReuestResponseLog(); // 用户访问记录(必须放到外层,不然如果遇到异常,会报错,因为不能返回流) app.UseRecordAccessLogsMildd(); // signalr app.UseSignalRSendMildd(); // 记录ip请求 app.UseIPLogMildd(); // 查看注入的所有服务 app.UseAllServicesMildd(_services); if (env.IsDevelopment()) { // 在开发环境中,使用异常页面,这样可以暴露错误堆栈信息,所以不要放在生产环境。 app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); // 在非开发环境中,使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。 // 强制实施 HTTPS 在 ASP.NET Core,配合 app.UseHttpsRedirection //app.UseHsts(); } // 封装Swagger展示 app.UseSwaggerMildd(() => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.Api.index.html")); // ↓↓↓↓↓↓ 注意下边这些中间件的顺序,很重要 ↓↓↓↓↓↓ // CORS跨域 app.UseCors(Appsettings.app(new string[] { "Startup", "Cors", "PolicyName" })); // 跳转https //app.UseHttpsRedirection(); // 使用静态文件 app.UseStaticFiles(); // 使用cookie app.UseCookiePolicy(); // 返回错误码 app.UseStatusCodePages(); // Routing app.UseRouting(); // 这种自定义授权中间件,可以尝试,但不推荐 // app.UseJwtTokenAuth(); // 测试用户,用来通过鉴权 if (Configuration.GetValue <bool>("AppSettings:UseLoadTest")) { app.UseMiddleware <ByPassAuthMidd>(); } // 先开启认证 app.UseAuthentication(); // 然后是授权中间件 app.UseAuthorization(); // 开启异常中间件,要放到最后 //app.UseExceptionHandlerMidd(); // 性能分析 app.UseMiniProfiler(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); endpoints.MapHub <ChatHub>("/api2/chatHub"); }); // 生成种子数据 app.UseSeedDataMildd(myContext, Env.WebRootPath); // 开启QuartzNetJob调度服务 app.UseQuartzJobMildd(tasksQzServices, schedulerCenter); //服务注册 app.UseConsulMildd(Configuration, lifetime); }
// This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { #region 部分服务注入-netcore自带方法 // 缓存注入 services.AddScoped <ICaching, MemoryCaching>(); services.AddSingleton <IMemoryCache>(factory => { var cache = new MemoryCache(new MemoryCacheOptions()); return(cache); }); // Redis注入 services.AddSingleton <IRedisCacheManager, RedisCacheManager>();//这里说下,如果是自己的项目,个人更建议使用单例模式 // log日志注入 services.AddSingleton <ILoggerHelper, LogHelper>(); #endregion #region 依赖注入 ISqlSugarClient // 这里我不是引用了命名空间,因为如果引用命名空间的话,会和Microsoft的一个GetTypeInfo存在二义性,所以就直接这么使用了。 services.AddScoped <SqlSugar.ISqlSugarClient>(o => { return(new SqlSugar.SqlSugarClient(new SqlSugar.ConnectionConfig() { ConnectionString = BaseDBConfig.ConnectionString, //必填, 数据库连接字符串 DbType = (SqlSugar.DbType)BaseDBConfig.DbType, //必填, 数据库类型 IsAutoCloseConnection = true, //默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作 InitKeyType = SqlSugar.InitKeyType.SystemTable //默认SystemTable, 字段信息读取, 如:该属性是不是主键,标识列等等信息 })); }); #endregion #region 初始化DB //services.AddSingleton<Love>(); //反向自动生成数据库--不需要注释掉 services.AddScoped <Blog.Core.Model.See.DBSeed>(); services.AddScoped <Blog.Core.Model.See.MyContext>(); #endregion #region Automapper services.AddAutoMapper(typeof(Startup)); #endregion #region Swagger var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath; services.AddSwaggerGen(c => { //遍历出全部的版本,做文档信息展示 typeof(ApiVersions).GetEnumNames().ToList().ForEach(version => { c.SwaggerDoc(version, new Info { Version = version, Title = $"{ApiName} 接口文档", Description = $"{ApiName} HTTP API ", TermsOfService = "None", Contact = new Swashbuckle.AspNetCore.Swagger.Contact { Name = "Blog.Core", Email = "*****@*****.**", Url = "http://www.baidu.com" } });//就是这里 // 按相对路径排序,作者:Alby c.OrderActionsBy(o => o.RelativePath); }); var xmlPath = Path.Combine(basePath, "Blog.Core.xml"); //这个就是刚刚配置的xml文件名 c.IncludeXmlComments(xmlPath, true); //默认的第二个参数是false,这个是controller的注释,记得修改 var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml");//这个就是Model层的xml文件名 c.IncludeXmlComments(xmlModelPath, true); #region Token绑定到ConfigureServices //添加header验证信息 //c.OperationFilter<SwaggerHeader>(); var security = new Dictionary <string, IEnumerable <string> > { { "Blog.Core", new string[] { } }, }; c.AddSecurityRequirement(security); //方案名称“Blog.Core”可自定义,上下一致即可 c.AddSecurityDefinition("Blog.Core", new ApiKeyScheme { Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", Name = "Authorization", //jwt默认的参数名称 In = "header", //jwt默认存放Authorization信息的位置(请求头中) Type = "apiKey" }); }); #endregion #region Token服务注册 services.AddSingleton <IMemoryCache>(factory => { var cache = new MemoryCache(new MemoryCacheOptions()); return(cache); }); #region 参数 //读取配置文件 var audienceConfig = Configuration.GetSection("Audience"); var symmetricKeyAsBase64 = audienceConfig["Secret"]; var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); var signingKey = new SymmetricSecurityKey(keyByteArray); // 令牌验证参数 var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, ValidateIssuer = true, ValidIssuer = audienceConfig["Issuer"], //发行人 ValidateAudience = true, ValidAudience = audienceConfig["Audience"], //订阅人 ValidateLifetime = true, ClockSkew = TimeSpan.Zero, RequireExpirationTime = true, }; var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); // 如果要数据库动态绑定,这里先留个空,后边处理器里动态赋值 var permission = new List <PermissionItem>(); // 角色与接口的权限要求参数 var permissionRequirement = new PermissionRequirement( "/api/denied", // 拒绝授权的跳转地址(目前无用) permission, ClaimTypes.Role, //基于角色的授权 audienceConfig["Issuer"], //发行人 audienceConfig["Audience"], //听众 signingCredentials, //签名凭据 expiration: TimeSpan.FromSeconds(60 * 60) //接口的过期时间 ); #endregion #region 【第一步:授权】 #region 1、基于角色的API授权 // 1【授权】、这个很简单,其他什么都不用做, 只需要在API层的controller上边,增加特性即可,注意,只能是角色的: // [Authorize(Roles = "Admin,System")] #endregion #region 2、基于策略的授权(简单版和复杂版) services.AddAuthorization(options => { //options.AddPolicy("Client", policy => policy.RequireRole("Client").Build()); //options.AddPolicy("Client1", policy => policy.RequireRole("Client1").Build()); //options.AddPolicy("Client2", policy => policy.RequireRole("Client2").Build()); options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build()); //这个写法是错误的,这个是并列的关系,不是或的关系 //options.AddPolicy("AdminOrClient", policy => policy.RequireRole("Admin,Client").Build()); //这个才是或的关系 options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System")); options.AddPolicy("SystemOrAdminOrOther", policy => policy.RequireRole("Admin", "System", "Other")); options.AddPolicy(Permissions.Name, policy => policy.Requirements.Add(permissionRequirement)); }) // ② 核心之二,必需要配置认证服务,这里是jwtBearer默认认证,比如光有卡没用,得能识别他们 .AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) // ③ 核心之三,针对JWT的配置,比如门禁是如何识别的,是放射卡,还是磁卡 .AddJwtBearer(o => { o.TokenValidationParameters = tokenValidationParameters; }); #endregion #endregion //认证 #region 自定义的认证,自定义认证不够完善,建议使用官方的认证 // services.AddAuthentication(x => // { // x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; // x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; // }) //.AddJwtBearer(o => //{ // o.TokenValidationParameters = new TokenValidationParameters // { // ValidateIssuer = true,//是否验证Issuer // ValidateAudience = true,//是否验证Audience // ValidateIssuerSigningKey = true,//是否验证IssuerSigningKey // ValidIssuer = audienceConfig["Issuer"],//发行人 // ValidAudience = audienceConfig["Audience"],//订阅人 // ValidateLifetime = true,//是否验证超时 当设置exp和nbf时有效 同时启用ClockSkew // IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtHelper.secretKey)), // //注意这是缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间 // ClockSkew = TimeSpan.Zero, // RequireExpirationTime = true // }; //}); #endregion #region 官方认证 暂时注释掉,如果不使用自定义设计可以加上,但不支持定义多个策略 //这个方案上面已经自定义了,这不需要重复定义,重复定义会报错 // services.AddAuthentication("Bearer") //// 添加JwtBearer服务 //.AddJwtBearer(o => //{ // o.TokenValidationParameters = tokenValidationParameters; // o.Events = new JwtBearerEvents // { // OnAuthenticationFailed = context => // { // // 如果过期,则把<是否过期>添加到,返回头信息中 // if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) // { // context.Response.Headers.Add("Token-Expired", "true"); // } // return Task.CompletedTask; // } // }; //}); //2.2【认证】、IdentityServer4 认证 (暂时忽略) //services.AddAuthentication("Bearer") // .AddIdentityServerAuthentication(options => // { // options.Authority = "http://localhost:5002"; // options.RequireHttpsMetadata = false; // options.ApiName = "blog.core.api"; // }); // 注入权限处理器 // 依赖注入,将自定义的授权处理器 匹配给官方授权处理器接口,这样当系统处理授权的时候,就会直接访问我们自定义的授权处理器了。 services.AddSingleton <IAuthorizationHandler, PermissionHandler>(); // 将授权必要类注入生命周期内 services.AddSingleton(permissionRequirement); #endregion #endregion #endregion #region MiniProfiler性能监听 services.AddMiniProfiler(options => { options.RouteBasePath = "/profiler";//注意这个路径要和下边 index.html 脚本配置中的一致, (options.Storage as MemoryCacheStorage).CacheDuration = TimeSpan.FromMinutes(10); }); #endregion #region SignalR 通讯 services.AddSignalR(); #endregion #region CORS 跨域 //跨域第一种方法,先注入服务,声明策略,然后再下边app中配置开启中间件 services.AddCors(c => { //↓↓↓↓↓↓↓注意正式环境不要使用这种全开放的处理↓↓↓↓↓↓↓↓↓↓ c.AddPolicy("AllRequests", policy => { policy .AllowAnyOrigin() //允许任何源 .AllowAnyMethod() //允许任何方式 .AllowAnyHeader() //允许任何头 .AllowCredentials(); //允许cookie }); //↑↑↑↑↑↑↑注意正式环境不要使用这种全开放的处理↑↑↑↑↑↑↑↑↑↑ //一般采用这种方法 c.AddPolicy("LimitRequests", policy => { policy .WithOrigins("http://127.0.0.1:5000", "http://localhost:8080", "http://localhost:8021", "http://localhost:8081", "http://localhost:5000", "http://localhost:20101", "http://127.0.0.1:20101") //支持多个域名端口,注意端口号后不要带/斜杆:比如localhost:8000/,是错的 .AllowAnyHeader() //Ensures that the policy allows any header. .AllowAnyMethod(); //.AllowCredentials(); }); }); // 这是第二种注入跨域服务的方法,这里有歧义,部分读者可能没看懂,请看下边解释 //services.AddCors(); //跨域第一种版本,请要ConfigureService中配置服务 services.AddCors(); // app.UseCors(options => options.WithOrigins("http://localhost:8021").AllowAnyHeader() //.AllowAnyMethod()); #endregion services.AddMvc(o => { o.Filters.Add(typeof(GlobalExceptionsFilter)); }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddSingleton(new Appsettings(Env.ContentRootPath)); #region AutoFac //实例化 AutoFac 容器 var builder = new ContainerBuilder(); //注册要通过反射创建的组件 //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); //var assemblysServices = Assembly.Load("Blog.Core.Services");//要记得!!!这个注入的是实现类层,不是接口层!不是 IServices //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已扫描程序集中的类型注册为提供所有其实现的接口。 //var assemblysRepository = Assembly.Load("Blog.Core.Repository");//模式是 Load(解决方案名) //builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); #region 带有接口层的服务注入 #region Service.dll 注入,有对应接口 try { //注册要通过反射创建的组件 //builder.RegisterType<BlogCacheAOP>();//可以直接替换其他拦截器 //builder.RegisterType<BlogRedisCacheAOP>();//可以直接替换其他拦截器 //builder.RegisterType<BlogLogAOP>();//可以直接替换其他拦截器! //var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;//获取项目路径 //var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); //var assemblysServices = Assembly.LoadFrom(servicesDllFile);//直接采用加载文件的方法 ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※ var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); var assemblysServices = Assembly.LoadFrom(servicesDllFile); //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces(); // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应对应 true 就行。 var cacheType = new List <Type>(); if (Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" }).ObjToBool()) { builder.RegisterType <BlogRedisCacheAOP>();//可以直接替换其他拦截器 cacheType.Add(typeof(BlogRedisCacheAOP)); } if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool()) { builder.RegisterType <BlogCacheAOP>();//可以直接替换其他拦截器 cacheType.Add(typeof(BlogCacheAOP)); } if (Appsettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool()) { builder.RegisterType <BlogLogAOP>();//可以直接替换其他拦截器! cacheType.Add(typeof(BlogLogAOP)); } if (Appsettings.app(new string[] { "AppSettings", "TranAOP", "Enabled" }).ObjToBool()) { builder.RegisterType <BlogTranAOP>(); cacheType.Add(typeof(BlogTranAOP)); } builder.RegisterAssemblyTypes(assemblysServices) .AsImplementedInterfaces() .InstancePerLifetimeScope() .EnableInterfaceInterceptors() //引用Autofac.Extras.DynamicProxy; .InterceptedBy(cacheType.ToArray()); //可以直接替换拦截器 var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces() .InstancePerDependency(); #region 没有接口的单独类 class 注入 ////只能注入该类中的虚方法 //builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) // .EnableClassInterceptors() // .InterceptedBy(typeof(BlogLogAOP)); #endregion } catch (Exception ex) { throw new Exception("※※★※※ 如果你是第一次下载项目,请先对整个解决方案dotnet build(F6编译),然后再对api层 dotnet run(F5执行),\n因为解耦了,如果你是发布的模式,请检查bin文件夹是否存在Repository.dll和service.dll ※※★※※" + ex.Message + "\n" + ex.InnerException); } #endregion #endregion //将services填充到Autofac容器生成器中 builder.Populate(services); //使用已进行的组件登记创建新容器 var ApplicationContainer = builder.Build(); //ApplicationContainer.Resolve<Love>().SayLoveU(); #endregion return(new AutofacServiceProvider(ApplicationContainer));//第三方IOC接管 core内置DI容器 }
// 注意在CreateDefaultBuilder中,添加Autofac服务工厂 public void ConfigureContainer(ContainerBuilder builder) { var basePath = AppContext.BaseDirectory; //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); #region 带有接口层的服务注入 var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); if (!(File.Exists(servicesDllFile) && File.Exists(repositoryDllFile))) { throw new Exception("Repository.dll和service.dll 丢失,因为项目解耦了,所以需要先F6编译,再F5运行,请检查 bin 文件夹,并拷贝。"); } // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应对应 true 就行。 var cacheType = new List <Type>(); if (Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" }).ObjToBool()) { builder.RegisterType <BlogRedisCacheAOP>(); cacheType.Add(typeof(BlogRedisCacheAOP)); } if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool()) { builder.RegisterType <BlogCacheAOP>(); cacheType.Add(typeof(BlogCacheAOP)); } if (Appsettings.app(new string[] { "AppSettings", "TranAOP", "Enabled" }).ObjToBool()) { builder.RegisterType <BlogTranAOP>(); cacheType.Add(typeof(BlogTranAOP)); } if (Appsettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool()) { builder.RegisterType <BlogLogAOP>(); cacheType.Add(typeof(BlogLogAOP)); } #endregion // 获取 Service.dll 程序集服务,并注册 var assemblysServices = Assembly.LoadFrom(servicesDllFile); builder.RegisterAssemblyTypes(assemblysServices) .AsImplementedInterfaces() .InstancePerDependency() .EnableInterfaceInterceptors() //引用Autofac.Extras.DynamicProxy; .InterceptedBy(cacheType.ToArray()); //允许将拦截器服务的列表分配给注册。 // 获取 Repository.dll 程序集服务,并注册 var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); builder.RegisterAssemblyTypes(assemblysRepository) .AsImplementedInterfaces() .InstancePerDependency(); #region 没有接口层的服务层注入 //因为没有接口层,所以不能实现解耦,只能用 Load 方法。 //注意如果使用没有接口的服务,并想对其使用 AOP 拦截,就必须设置为虚方法 //var assemblysServicesNoInterfaces = Assembly.Load("Blog.Core.Services"); //builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces); #endregion #region 没有接口的单独类 class 注入 //只能注入该类中的虚方法,且必须是public builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) .EnableClassInterceptors() .InterceptedBy(cacheType.ToArray()); #endregion //最大的 gayhub 网站 }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { #region ReuestResponseLog if (Appsettings.app("AppSettings", "Middleware_RequestResponse", "Enabled").ObjToBool()) { app.UseReuestResponseLog();//记录请求与返回数据 } #endregion #region Environment if (env.IsDevelopment()) { // 在开发环境中,使用异常页面,这样可以暴露错误堆栈信息,所以不要放在生产环境。 app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); // 在非开发环境中,使用HTTP严格安全传输(or HSTS) 对于保护web安全是非常重要的。 // 强制实施 HTTPS 在 ASP.NET Core,配合 app.UseHttpsRedirection //app.UseHsts(); } #endregion #region Swagger app.UseSwagger(); app.UseSwaggerUI(c => { //根据版本名称倒序 遍历展示 typeof(ApiVersions).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version => { c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{ApiName} {version}"); }); // 将swagger首页,设置成我们自定义的页面,记得这个字符串的写法:解决方案名.index.html c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.index.html"); //这里是配合MiniProfiler进行性能监控的,《文章:完美基于AOP的接口性能分析》,如果你不需要,可以暂时先注释掉,不影响大局。 c.RoutePrefix = ""; //路径配置,设置为空,表示直接在根域名(localhost:8001)访问该文件,注意localhost:8001/swagger是访问不到的,去launchSettings.json把launchUrl去掉 }); #endregion #region MiniProfiler app.UseMiniProfiler(); #endregion #region 第三步:开启认证中间件 //此授权认证方法已经放弃,请使用下边的官方验证方法。但是如果你还想传User的全局变量,还是可以继续使用中间件,第二种写法//app.UseMiddleware<JwtTokenAuth>(); app.UseJwtTokenAuth(); //如果你想使用官方认证,必须在上边ConfigureService 中,配置JWT的认证服务 (.AddAuthentication 和 .AddJwtBearer 二者缺一不可) app.UseAuthentication(); #endregion #region CORS //跨域第二种方法,使用策略,详细策略信息在ConfigureService中 app.UseCors("LimitRequests");//将 CORS 中间件添加到 web 应用程序管线中, 以允许跨域请求。 #region 跨域第一种版本 //跨域第一种版本,请要ConfigureService中配置服务 services.AddCors(); // app.UseCors(options => options.WithOrigins("http://localhost:8021").AllowAnyHeader() //.AllowAnyMethod()); #endregion #endregion // 跳转https //app.UseHttpsRedirection(); // 使用静态文件 app.UseStaticFiles(); // 使用cookie app.UseCookiePolicy(); // 返回错误码 app.UseStatusCodePages();//把错误码返回前台,比如是404 app.UseMvc(); app.UseSignalR(routes => { //这里要说下,为啥地址要写 /api/xxx //因为我前后端分离了,而且使用的是代理模式,所以如果你不用/api/xxx的这个规则的话,会出现跨域问题,毕竟这个不是我的controller的路由,而且自己定义的路由 routes.MapHub <ChatHub>("/api2/chatHub"); }); }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // 以下code可能与文章中不一样,对代码做了封装,具体查看右侧 Extensions 文件夹. services.AddSingleton(new Appsettings(Configuration)); services.AddSingleton(new LogLock(Env.ContentRootPath)); services.AddUiFilesZipSetup(Env); Permissions.IsUseIds4 = Appsettings.app(new string[] { "Startup", "IdentityServer4", "Enabled" }).ObjToBool(); RoutePrefix.Name = Appsettings.app(new string[] { "AppSettings", "SvcName" }).ObjToString(); // 确保从认证中心返回的ClaimType不被更改,不使用Map映射 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddMemoryCacheSetup(); services.AddRedisCacheSetup(); services.AddSqlsugarSetup(); services.AddDbSetup(); services.AddAutoMapperSetup(); services.AddCorsSetup(); services.AddMiniProfilerSetup(); services.AddSwaggerSetup(); services.AddJobSetup(); services.AddHttpContextSetup(); //services.AddAppConfigSetup(Env); services.AddAppTableConfigSetup(Env);//表格打印配置 services.AddHttpApi(); services.AddRedisInitMqSetup(); services.AddRabbitMQSetup(); services.AddKafkaSetup(Configuration); services.AddEventBusSetup(); services.AddNacosSetup(Configuration); // 授权+认证 (jwt or ids4) services.AddAuthorizationSetup(); if (Permissions.IsUseIds4) { services.AddAuthentication_Ids4Setup(); } else { services.AddAuthentication_JWTSetup(); } services.AddIpPolicyRateLimitSetup(Configuration); services.AddSignalR().AddNewtonsoftJsonProtocol(); services.AddScoped <UseServiceDIAttribute>(); services.Configure <KestrelServerOptions>(x => x.AllowSynchronousIO = true) .Configure <IISServerOptions>(x => x.AllowSynchronousIO = true); services.AddDistributedMemoryCache(); services.AddSession(); services.AddHttpPollySetup(); services.AddControllers(o => { // 全局异常过滤 o.Filters.Add(typeof(GlobalExceptionsFilter)); // 全局路由权限公约 //o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention()); // 全局路由前缀,统一修改路由 o.Conventions.Insert(0, new GlobalRoutePrefixFilter(new RouteAttribute(RoutePrefix.Name))); }) // 这种写法也可以 //.AddJsonOptions(options => //{ // options.JsonSerializerOptions.PropertyNamingPolicy = null; //}) //MVC全局配置Json序列化处理 .AddNewtonsoftJson(options => { //忽略循环引用 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; //不使用驼峰样式的key options.SerializerSettings.ContractResolver = new DefaultContractResolver(); //设置时间格式 options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; //忽略Model中为null的属性 //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; //设置本地时间而非UTC时间 options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local; //添加Enum转string options.SerializerSettings.Converters.Add(new StringEnumConverter()); }); services.Replace(ServiceDescriptor.Transient <IControllerActivator, ServiceBasedControllerActivator>()); _services = services; //支持编码大全 例如:支持 System.Text.Encoding.GetEncoding("GB2312") System.Text.Encoding.GetEncoding("GB18030") Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { #region 部分服务注入-netcore自带方法 services.AddSingleton(new Appsettings(Configuration)); services.AddSingleton(new LogLock(Env.ContentRootPath)); services.AddSingleton <ILoggerHelper, LogHelper>(); Permissions.IsUseIds4 = Appsettings.app(new string[] { "Startup", "IdentityServer4", "Enabled" }).ObjToBool(); // Redis注入 services.AddRedisCacheSetup(); services.AddRedisInitMqSetup();//redis 消息队列 services.AddMemoryCacheSetup(); services.AddSqlsugarSetup(); services.AddDbSetup(); services.AddAutoMapperSetup(); services.AddCorsSetup(); services.AddMiniProfilerSetup(); services.AddSwaggerSetup(); services.AddHttpContextSetup(); services.AddAppConfigSetup(); services.AddRabbitMQSetup(); services.AddEventBusSetup(); #endregion // 授权+认证 (jwt or ids4) services.AddAuthorizationSetup(); if (Permissions.IsUseIds4) { services.AddAuthentication_Ids4Setup(); } else { services.AddAuthentication_JWTSetup(); } services.AddIpPolicyRateLimitSetup(Configuration); services.AddSignalR().AddNewtonsoftJsonProtocol(); services.AddScoped <UseServiceDIAttribute>(); //autoface 接口注入必加,否则注入失败报错 services.Configure <KestrelServerOptions>(x => x.AllowSynchronousIO = true) .Configure <IISServerOptions>(x => x.AllowSynchronousIO = true); services.AddControllers(o => { // 全局异常过滤 o.Filters.Add(typeof(GlobalExceptionsFilter)); // 全局路由权限公约 //o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention()); // 全局路由前缀,统一修改路由 o.Conventions.Insert(0, new GlobalRoutePrefixFilter(new RouteAttribute(RoutePrefix.Name))); }) //全局配置Json序列化处理 .AddNewtonsoftJson(options => { //忽略循环引用 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; //不使用驼峰样式的key options.SerializerSettings.ContractResolver = new DefaultContractResolver(); //设置时间格式 //options.SerializerSettings.DateFormatString = "yyyy-MM-dd"; }); _services = services; }
// This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { #region 部分服务注入-netcore自带方法 // 缓存注入 services.AddScoped <ICaching, MemoryCaching>(); services.AddSingleton <IMemoryCache>(factory => { var cache = new MemoryCache(new MemoryCacheOptions()); return(cache); }); // Redis注入 services.AddSingleton <IRedisCacheManager, RedisCacheManager>(); // log日志注入 services.AddSingleton <ILoggerHelper, LogHelper>(); #endregion #region 初始化DB services.AddScoped <Blog.Core.Model.Models.DBSeed>(); services.AddScoped <Blog.Core.Model.Models.MyContext>(); #endregion #region Automapper services.AddAutoMapper(typeof(Startup)); #endregion #region CORS //跨域第二种方法,声明策略,记得下边app中配置 services.AddCors(c => { //↓↓↓↓↓↓↓注意正式环境不要使用这种全开放的处理↓↓↓↓↓↓↓↓↓↓ c.AddPolicy("AllRequests", policy => { policy .AllowAnyOrigin() //允许任何源 .AllowAnyMethod() //允许任何方式 .AllowAnyHeader() //允许任何头 .AllowCredentials(); //允许cookie }); //↑↑↑↑↑↑↑注意正式环境不要使用这种全开放的处理↑↑↑↑↑↑↑↑↑↑ //一般采用这种方法 c.AddPolicy("LimitRequests", policy => { // 支持多个域名端口,注意端口号后不要带/斜杆:比如localhost:8000/,是错的 // 注意,http://127.0.0.1:1818 和 http://localhost:1818 是不一样的,尽量写两个 policy .WithOrigins("http://127.0.0.1:1818", "http://*****:*****@xxx.com", Url = "https://www.jianshu.com/u/94102b59cc2a" } }); // 按相对路径排序,作者:Alby c.OrderActionsBy(o => o.RelativePath); }); //就是这里 var xmlPath = Path.Combine(basePath, "Blog.Core.xml"); //这个就是刚刚配置的xml文件名 c.IncludeXmlComments(xmlPath, true); //默认的第二个参数是false,这个是controller的注释,记得修改 var xmlModelPath = Path.Combine(basePath, "Blog.Core.Model.xml"); //这个就是Model层的xml文件名 c.IncludeXmlComments(xmlModelPath); #region Token绑定到ConfigureServices //添加header验证信息 //c.OperationFilter<SwaggerHeader>(); // 发行人 var IssuerName = (Configuration.GetSection("Audience"))["Issuer"]; var security = new Dictionary <string, IEnumerable <string> > { { IssuerName, new string[] { } }, }; c.AddSecurityRequirement(security); //方案名称“Blog.Core”可自定义,上下一致即可 c.AddSecurityDefinition(IssuerName, new ApiKeyScheme { Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", Name = "Authorization", //jwt默认的参数名称 In = "header", //jwt默认存放Authorization信息的位置(请求头中) Type = "apiKey" }); #endregion }); #endregion #region MVC + GlobalExceptions //注入全局异常捕获 services.AddMvc(o => { // 全局异常过滤 o.Filters.Add(typeof(GlobalExceptionsFilter)); // 全局路由权限公约 o.Conventions.Insert(0, new GlobalRouteAuthorizeConvention()); // 全局路由前缀,统一修改路由 o.Conventions.Insert(0, new GlobalRoutePrefixFilter(new RouteAttribute(RoutePrefix.Name))); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) // 取消默认驼峰 .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new DefaultContractResolver(); }); #endregion #region TimedJob services.AddHostedService <Job1TimedService>(); services.AddHostedService <Job2TimedService>(); #endregion #region Httpcontext // Httpcontext 注入 services.AddSingleton <IHttpContextAccessor, HttpContextAccessor>(); services.AddScoped <IUser, AspNetUser>(); #endregion #region SignalR 通讯 services.AddSignalR(); #endregion #region Authorize 权限认证三步走 //Tips:注释中的中括号【xxx】的内容,与下边region中的模块,是一一匹配的 /* * 如果不想走数据库,仅仅想在代码里配置授权,这里可以按照下边的步骤: * 1步、【1、基于角色的API授权】 * 很简单,只需要在指定的接口上,配置特性即可,比如:[Authorize(Roles = "Admin,System,Others")] * * 但是如果你感觉"Admin,System,Others",这样的字符串太长的话,可以把这个融合到简单策略里 * 具体的配置,看下文的Region模块【2、基于策略的授权(简单版)】 ,然后在接口上,配置特性:[Authorize(Policy = "A_S_O")] * * * 2步、配置Bearer认证服务,具体代码看下文的 region 【第二步:配置认证服务】 * * 3步、开启中间件 */ /* * 如果想要把权限配置到数据库,步骤如下: * 1步、【3复杂策略授权】 * 具体的查看下边 region 内的内容 * * 2步、配置Bearer认证服务,具体代码看下文的 region 【第二步:配置认证服务】 * * 3步、开启中间件 */ //3、综上所述,设置权限,必须要三步走,授权 + 配置认证服务 + 开启授权中间件,只不过自定义的中间件不能验证过期时间,所以我都是用官方的。 #region 【第一步:授权】 #region 1、基于角色的API授权 // 1【授权】、这个很简单,其他什么都不用做, 只需要在API层的controller上边,增加特性即可,注意,只能是角色的: // [Authorize(Roles = "Admin,System")] #endregion #region 2、基于策略的授权(简单版) // 1【授权】、这个和上边的异曲同工,好处就是不用在controller中,写多个 roles 。 // 然后这么写 [Authorize(Policy = "Admin")] services.AddAuthorization(options => { options.AddPolicy("Client", policy => policy.RequireRole("Client").Build()); options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build()); options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System")); options.AddPolicy("A_S_O", policy => policy.RequireRole("Admin", "System", "Others")); }); #endregion #region 【3、复杂策略授权】 #region 参数 //读取配置文件 var audienceConfig = Configuration.GetSection("Audience"); var symmetricKeyAsBase64 = audienceConfig["Secret"]; var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); var signingKey = new SymmetricSecurityKey(keyByteArray); var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); // 如果要数据库动态绑定,这里先留个空,后边处理器里动态赋值 var permission = new List <PermissionItem>(); // 角色与接口的权限要求参数 var permissionRequirement = new PermissionRequirement( "/api/denied", // 拒绝授权的跳转地址(目前无用) permission, ClaimTypes.Role, //基于角色的授权 audienceConfig["Issuer"], //发行人 audienceConfig["Audience"], //听众 signingCredentials, //签名凭据 expiration: TimeSpan.FromSeconds(60 * 60) //接口的过期时间 ); #endregion //【授权】 services.AddAuthorization(options => { options.AddPolicy(Permissions.Name, policy => policy.Requirements.Add(permissionRequirement)); }); #endregion #endregion #region 【第二步:配置认证服务】 // 令牌验证参数 var tokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, ValidateIssuer = true, ValidIssuer = audienceConfig["Issuer"], //发行人 ValidateAudience = true, ValidAudience = audienceConfig["Audience"], //订阅人 ValidateLifetime = true, ClockSkew = TimeSpan.FromSeconds(30), RequireExpirationTime = true, }; //2.1【认证】、core自带官方JWT认证 // 开启Bearer认证 services.AddAuthentication("Bearer") // 添加JwtBearer服务 .AddJwtBearer(o => { o.TokenValidationParameters = tokenValidationParameters; o.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { // 如果过期,则把<是否过期>添加到,返回头信息中 if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) { context.Response.Headers.Add("Token-Expired", "true"); } return(Task.CompletedTask); } }; }); //2.2【认证】、IdentityServer4 认证 (暂时忽略) //services.AddAuthentication("Bearer") // .AddIdentityServerAuthentication(options => // { // options.Authority = "http://localhost:5002"; // options.RequireHttpsMetadata = false; // options.ApiName = "blog.core.api"; // }); // 注入权限处理器 services.AddSingleton <IAuthorizationHandler, PermissionHandler>(); services.AddSingleton(permissionRequirement); #endregion #endregion services.AddSingleton(new Appsettings(Env)); services.AddSingleton(new LogLock(Env)); #region AutoFac DI //实例化 AutoFac 容器 var builder = new ContainerBuilder(); //注册要通过反射创建的组件 //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); builder.RegisterType <BlogCacheAOP>(); //可以直接替换其他拦截器 builder.RegisterType <BlogRedisCacheAOP>(); //可以直接替换其他拦截器 builder.RegisterType <BlogLogAOP>(); //这样可以注入第二个 // ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※ #region 带有接口层的服务注入 #region Service.dll 注入,有对应接口 //获取项目绝对路径,请注意,这个是实现类的dll文件,不是接口 IService.dll ,注入容器当然是Activatore try { var servicesDllFile = Path.Combine(basePath, "Blog.Core.Services.dll"); var assemblysServices = Assembly.LoadFrom(servicesDllFile);//直接采用加载文件的方法 ※※★※※ 如果你是第一次下载项目,请先F6编译,然后再F5执行,※※★※※ //builder.RegisterAssemblyTypes(assemblysServices).AsImplementedInterfaces();//指定已扫描程序集中的类型注册为提供所有其实现的接口。 // AOP 开关,如果想要打开指定的功能,只需要在 appsettigns.json 对应对应 true 就行。 var cacheType = new List <Type>(); if (Appsettings.app(new string[] { "AppSettings", "RedisCachingAOP", "Enabled" }).ObjToBool()) { cacheType.Add(typeof(BlogRedisCacheAOP)); } if (Appsettings.app(new string[] { "AppSettings", "MemoryCachingAOP", "Enabled" }).ObjToBool()) { cacheType.Add(typeof(BlogCacheAOP)); } if (Appsettings.app(new string[] { "AppSettings", "LogAOP", "Enabled" }).ObjToBool()) { cacheType.Add(typeof(BlogLogAOP)); } builder.RegisterAssemblyTypes(assemblysServices) .AsImplementedInterfaces() .InstancePerLifetimeScope() .EnableInterfaceInterceptors() //引用Autofac.Extras.DynamicProxy; // 如果你想注入两个,就这么写 InterceptedBy(typeof(BlogCacheAOP), typeof(BlogLogAOP)); // 如果想使用Redis缓存,请必须开启 redis 服务,端口号我的是6319,如果不一样还是无效,否则请使用memory缓存 BlogCacheAOP .InterceptedBy(cacheType.ToArray()); //允许将拦截器服务的列表分配给注册。 #endregion #region Repository.dll 注入,有对应接口 var repositoryDllFile = Path.Combine(basePath, "Blog.Core.Repository.dll"); var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces(); } catch (Exception ex) { throw new Exception("※※★※※ 如果你是第一次下载项目,请先对整个解决方案dotnet build(F6编译),然后再对api层 dotnet run(F5执行),\n因为解耦了,如果你是发布的模式,请检查bin文件夹是否存在Repository.dll和service.dll ※※★※※" + ex.Message + "\n" + ex.InnerException); } #endregion #endregion #region 没有接口层的服务层注入 ////因为没有接口层,所以不能实现解耦,只能用 Load 方法。 ////注意如果使用没有接口的服务,并想对其使用 AOP 拦截,就必须设置为虚方法 ////var assemblysServicesNoInterfaces = Assembly.Load("Blog.Core.Services"); ////builder.RegisterAssemblyTypes(assemblysServicesNoInterfaces); #endregion #region 没有接口的单独类 class 注入 ////只能注入该类中的虚方法 builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(Love))) .EnableClassInterceptors() .InterceptedBy(typeof(BlogLogAOP)); #endregion //将services填充到Autofac容器生成器中 builder.Populate(services); //使用已进行的组件登记创建新容器 var ApplicationContainer = builder.Build(); #endregion return(new AutofacServiceProvider(ApplicationContainer));//第三方IOC接管 core内置DI容器 }
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { #region 依赖注入 services.AddSingleton(new Appsettings(Configuration)); #endregion services.AddControllers(); #region Swagger var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath; services.AddSwaggerGen(c => { c.SwaggerDoc("V1", new OpenApiInfo { // {ApiName} 定义成全局变量,方便修改 Version = "V1", Title = $"{ApiName} 接口文档——Net 5", Description = $"{ApiName} HTTP API V1", //Contact = new OpenApiContact { Name = ApiName, Email = "*****@*****.**", Url = new Uri("https://www.jianshu.com/u/94102b59cc2a") }, //License = new OpenApiLicense { Name = ApiName, Url = new Uri("https://www.jianshu.com/u/94102b59cc2a") } }); //c.OrderActionsBy(o => o.RelativePath); var xmlPath = Path.Combine(basePath, "blog.core.xml"); //这个就是刚刚配置的xml文件名 c.IncludeXmlComments(xmlPath, true); //默认的第二个参数是false,这个是controller的注释,记得修改 // Jwt Bearer 认证,必须是 oauth2 c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"", Name = "Authorization", //jwt默认的参数名称 In = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中) Type = SecuritySchemeType.ApiKey }); // 开启加权小锁 c.OperationFilter <AddResponseHeadersFilter>(); c.OperationFilter <AppendAuthorizeToSummaryOperationFilter>(); // 在header中添加token,传递到后台 c.OperationFilter <SecurityRequirementsOperationFilter>(); }); #endregion #region 认证 //读取配置文件 var symmetricKeyAsBase64 = Appsettings.app(new string[] { "Audience", "Secret" }); var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); var signingKey = new SymmetricSecurityKey(keyByteArray); var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); services.AddAuthentication(x => { //看这个单词熟悉么?没错,就是上边错误里的那个。 x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; })// 也可以直接写字符串,AddAuthentication("Bearer") .AddJwtBearer(o => { o.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = signingKey, ValidateIssuer = true, ValidIssuer = Appsettings.app(new string[] { "Audience", "Issuer" }), //audienceConfig["Issuer"],//发行人 ValidateAudience = true, ValidAudience = Appsettings.app(new string[] { "Audience", "Audience" }), //audienceConfig["Audience"],//订阅人 ValidateLifetime = true, ClockSkew = TimeSpan.Zero, //这个是缓冲过期时间,也就是说,即使我们配置了过期时间,这里也要考虑进去,过期时间+缓冲,默认好像是7分钟,你可以直接设置为0 RequireExpirationTime = true, }; }); #endregion #region FreeSql string MysqlConnStr = Configuration.GetConnectionString("MySql"); IFreeSql fsql = new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.MySql, MysqlConnStr) .UseLazyLoading(true) //开启延时加载功能 .UseAutoSyncStructure(false) //自动同步实体结构到数据库 .Build(); //请务必定义成 Singleton 单例模式 services.AddSingleton(fsql); //注意: IFreeSql 在项目中应以单例声明,而不是在每次使用的时候创建。 #endregion }