Example #1
0
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddIdentityServer()
            .AddSigningCredential(new X509Certificate2(GetCertificatePath(), "password"))
            .AddTestUsers(InMemoryConfiguration.GetUsers())
            .AddInMemoryClients(InMemoryConfiguration.GetClients())
            .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
            .AddInMemoryApiResources(InMemoryConfiguration.GetApis());

            services.AddMvc();
        }
Example #2
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            #region IdentityServer4+FreeSql
            InMemoryConfiguration.Configuration = this.Configuration;
            services.AddSingleton(Fsql);

            services.AddFreeRepository(filter =>
            {
                filter.Apply <ISoftDeleteAduitEntity>("SoftDelete", a => a.IsDeleted == false);
            }, GetType().Assembly, typeof(AuditBaseRepository <>).Assembly);

            services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
            .AddInMemoryApiResources(InMemoryConfiguration.GetApis())
            .AddInMemoryClients(InMemoryConfiguration.GetClients())
            .AddProfileService <LinCmsProfileService>()
            .AddResourceOwnerValidator <LinCmsResourceOwnerPasswordValidator>();


            #region AddAuthentication\AddIdentityServerAuthentication
            //AddAuthentication()是把验证服务注册到DI, 并配置了Bearer作为默认模式.

            //AddIdentityServerAuthentication()是在DI注册了token验证的处理者.
            //由于是本地运行, 所以就不使用https了, RequireHttpsMetadata = false.如果是生产环境, 一定要使用https.
            //Authority指定Authorization Server的地址.
            //ApiName要和Authorization Server里面配置ApiResource的name一样.
            //和  AddJwtBearer不能同时使用,目前还不理解区别。
            //services
            //    .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
            //    .AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
            //    {
            //        options.RequireHttpsMetadata = false; // for dev env
            //        options.Authority = $"{Configuration["Identity:Protocol"]}://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}"; ;
            //        options.ApiName = Configuration["Service:Name"]; // match with configuration in IdentityServer

            //        options.JwtValidationClockSkew = TimeSpan.FromSeconds(60*5);

            //    });
            #endregion

            #region AddJwtBearer
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                //identityserver4 地址 也就是本项目地址
                options.Authority            = $"{Configuration["Identity:Protocol"]}://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}";
                options.RequireHttpsMetadata = false;
                options.Audience             = Configuration["Service:Name"];

                //options.TokenValidationParameters = new TokenValidationParameters()
                //{
                //    ClockSkew = TimeSpan.Zero   //偏移设置为了0s,用于测试过期策略,完全按照access_token的过期时间策略,默认原本为5分钟
                //};


                //使用Authorize设置为需要登录时,返回json格式数据。
                options.Events = new JwtBearerEvents()
                {
                    OnChallenge = context =>
                    {
                        //此处代码为终止.Net Core默认的返回类型和数据结果,这个很重要哦
                        context.HandleResponse();

                        string msg;
                        ErrorCode errorCode;
                        int statusCode = StatusCodes.Status401Unauthorized;

                        if (context.Error == "invalid_token" &&
                            context.ErrorDescription == "The token is expired")
                        {
                            msg        = "令牌过期";
                            errorCode  = ErrorCode.TokenExpired;
                            statusCode = StatusCodes.Status422UnprocessableEntity;
                        }
                        else if (context.Error == "invalid_token" && context.ErrorDescription.IsNullOrEmpty())
                        {
                            msg       = "令牌失效";
                            errorCode = ErrorCode.TokenInvalidation;
                        }

                        else
                        {
                            msg       = "认证失败,请检查请求头或者重新登陆";
                            errorCode = ErrorCode.TokenInvalidation;
                        }


                        context.Response.ContentType = "application/json";
                        context.Response.StatusCode  = statusCode;
                        context.Response.WriteAsync(new ResultDto(errorCode, msg, context.HttpContext).ToString());

                        return(Task.FromResult(0));
                    }
                };
            });
            #endregion

            #endregion

            services.AddAutoMapper(typeof(Startup).Assembly, typeof(PoemProfile).Assembly);

            services.AddCors(option => option.AddPolicy("cors", policy => policy.AllowAnyHeader().AllowAnyMethod().AllowCredentials().AllowAnyOrigin()));

            #region Mvc
            services.AddMvc(options =>
            {
                options.ValueProviderFactories.Add(new SnakeCaseQueryValueProviderFactory()); //设置SnakeCase形式的QueryString参数
                options.Filters.Add <LinCmsExceptionFilter>();
                options.Filters.Add <LogActionFilterAttribute>();                             // 添加请求方法时的日志记录过滤器
            })
            .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
            .ConfigureApiBehaviorOptions(options =>
            {
                options.SuppressUseValidationProblemDetailsForInvalidModelStateResponses = true;
                //自定义 BadRequest 响应
                options.InvalidModelStateResponseFactory = context =>
                {
                    var problemDetails = new ValidationProblemDetails(context.ModelState);

                    var resultDto = new ResultDto(ErrorCode.ParameterError, problemDetails.Errors, context.HttpContext);

                    return(new BadRequestObjectResult(resultDto)
                    {
                        ContentTypes = { "application/json" }
                    });
                };
            })
            .AddJsonOptions(opt =>
            {
                //opt.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:MM:ss";
                //设置时间戳格式
                opt.SerializerSettings.Converters = new List <JsonConverter>()
                {
                    new LinCmsTimeConverter()
                };
                // 设置下划线方式,首字母是小写
                opt.SerializerSettings.ContractResolver = new DefaultContractResolver()
                {
                    NamingStrategy = new SnakeCaseNamingStrategy()
                    {
                        ProcessDictionaryKeys = true
                    }
                };
            });
            #endregion

            services.AddSingleton <IHttpContextAccessor, HttpContextAccessor>();

            #region Scrutor 与单个单个注册等价,不过可批量注册
            //services.AddScoped<ILogService, LogService>();
            //services.AddScoped<IUserSevice, UserService>();
            services.Scan(scan => scan
                          //加载Startup这个类所在的程序集
                          .FromAssemblyOf <Startup>()
                          // 表示要注册那些类,上面的代码还做了过滤,只留下了以 Service 结尾的类
                          .AddClasses(classes => classes.Where(t => t.Name.EndsWith("Service", StringComparison.OrdinalIgnoreCase)))
                          //表示将类型注册为提供其所有公共接口作为服务
                          .AsImplementedInterfaces()
                          //表示注册的生命周期为 Transient
                          .WithTransientLifetime()
                          // We start out with all types in the assembly of ITransientService
                          .FromAssemblyOf <IScopeDependency>()
                          // AddClasses starts out with all public, non-abstract types in this assembly.
                          // These types are then filtered by the delegate passed to the method.
                          // In this case, we filter out only the classes that are assignable to ITransientService.
                          .AddClasses(classes => classes.AssignableTo <ITransientDependency>())
                          // We then specify what type we want to register these classes as.
                          // In this case, we want to register the types as all of its implemented interfaces.
                          // So if a type implements 3 interfaces; A, B, C, we'd end up with three separate registrations.
                          .AsImplementedInterfaces()
                          // And lastly, we specify the lifetime of these registrations.
                          .WithTransientLifetime()
                          );
            #endregion

            #region Swagger
            //Swagger重写PascalCase,改成SnakeCase模式
            services.TryAddEnumerable(ServiceDescriptor
                                      .Transient <IApiDescriptionProvider, SnakeCaseQueryParametersApiDescriptionProvider>());

            //Register the Swagger generator, defining 1 or more Swagger documents
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new Info()
                {
                    Title = "LinCms", Version = "v1"
                });
                var security = new Dictionary <string, IEnumerable <string> > {
                    { "Bearer", new string[] { } },
                };
                options.AddSecurityRequirement(security);//添加一个必须的全局安全信息,和AddSecurityDefinition方法指定的方案名称要一致,这里是Bearer。
                options.AddSecurityDefinition("Bearer", new ApiKeyScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
                    Name        = "Authorization", //jwt默认的参数名称
                    In          = "header",        //jwt默认存放Authorization信息的位置(请求头中)
                    Type        = "apiKey"
                });

                string basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径)
                string xmlPath  = Path.Combine(basePath, "LinCms.Web.xml");
                options.IncludeXmlComments(xmlPath);

                options.OperationFilter <SwaggerFileHeaderParameter>();
            });
            #endregion

            //将Handler注册到DI系统中
            services.AddScoped <IAuthorizationHandler, PermissionAuthorizationHandler>();

            services.Configure <FormOptions>(options =>
            {
                options.MultipartBodyLengthLimit   = 1024 * 1024 * 2;
                options.MultipartHeadersCountLimit = 10;
            });
        }
Example #3
0
        public void ConfigureServices(IServiceCollection services)
        {
            InMemoryConfiguration.Configuration = this.Configuration;

            services.AddContext();

            services.AddCors();
            services.AddHash();
            services.AddCryptography("lin-cms-dotnetcore-cryptography");

            services.ConfigureApplicationCookie(options =>
            {
                options.LoginPath = "/account/login";
            });
            services.AddSession(options =>
            {
                options.IdleTimeout = TimeSpan.FromSeconds(120);
            });

            services.AddIdentityServer(options => new IdentityServerOptions
            {
                UserInteraction = new UserInteractionOptions
                {
                    LoginUrl  = "/account/login",
                    LogoutUrl = "/account/logout",
                }
            })
#if DEBUG
            .AddDeveloperSigningCredential()
#endif
#if !DEBUG
            .AddSigningCredential(new X509Certificate2(
                                      Path.Combine(AppContext.BaseDirectory, Configuration["Certificates:Path"]),
                                      Configuration["Certificates:Password"])
                                  )
#endif
            .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
            .AddInMemoryApiResources(InMemoryConfiguration.GetApis())
            .AddInMemoryClients(InMemoryConfiguration.GetClients())
            .AddInMemoryApiScopes(InMemoryConfiguration.GetApiScopes())
            .AddProfileService <LinCmsProfileService>()
            .AddResourceOwnerValidator <LinCmsResourceOwnerPasswordValidator>();

            #region Swagger

            //Register the Swagger generator, defining 1 or more Swagger documents
            services.AddSwaggerGen(options =>
            {
                string ApiName = "LinCms.IdentityServer4";
                options.SwaggerDoc("v1", new OpenApiInfo()
                {
                    Title   = ApiName + RuntimeInformation.FrameworkDescription,
                    Version = "v1",
                    Contact = new OpenApiContact {
                        Name = ApiName, Email = "*****@*****.**", Url = new Uri("https://www.cnblogs.com/igeekfan/")
                    },
                    License = new OpenApiLicense {
                        Name = ApiName + " 官方文档", Url = new Uri("https://luoyunchong.github.io/vovo-docs/dotnetcore/lin-cms/dotnetcore-start.html")
                    }
                });
                var security = new OpenApiSecurityRequirement()
                {
                    { new OpenApiSecurityScheme
                      {
                          Reference = new OpenApiReference()
                          {
                              Id   = "Bearer",
                              Type = ReferenceType.SecurityScheme
                          }
                      }, Array.Empty <string>() }
                };
                options.AddSecurityRequirement(security);//添加一个必须的全局安全信息,和AddSecurityDefinition方法指定的方案名称要一致,这里是Bearer。
                options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
                    Name        = "Authorization",          //jwt默认的参数名称
                    In          = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中)
                    Type        = SecuritySchemeType.ApiKey
                });
                try
                {
                    string xmlPath = Path.Combine(AppContext.BaseDirectory, $"{typeof(Startup).Assembly.GetName().Name}.xml");
                    options.IncludeXmlComments(xmlPath, true);
                }
                catch (Exception ex)
                {
                    Log.Logger.Warning(ex.Message);
                }
                options.AddServer(new OpenApiServer()
                {
                    Url         = "",
                    Description = "vvv"
                });
                options.CustomOperationIds(apiDesc =>
                {
                    var controllerAction = apiDesc.ActionDescriptor as ControllerActionDescriptor;
                    return(controllerAction.ControllerName + "-" + controllerAction.ActionName);
                });
            });
            #endregion

            services.AddTransient <IUserRepository, UserRepository>();
            services.AddTransient <IUserIdentityService, UserIdentityService>();
            services.AddTransient <ICurrentUser, CurrentUser>();
            services.AddTransient(typeof(IAuditBaseRepository <>), typeof(AuditBaseRepository <>));
            services.AddTransient(typeof(IAuditBaseRepository <,>), typeof(AuditBaseRepository <,>));
            //services.AddTransient<CustomExceptionMiddleWare>();

            services.AddAutoMapper(typeof(UserProfile).Assembly);

            services.AddControllersWithViews(options =>
            {
                options.Filters.Add <LinCmsExceptionFilter>();
            })
            .AddNewtonsoftJson(opt =>
            {
                //忽略循环引用
                opt.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                //opt.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:MM:ss";
                //设置自定义时间戳格式
                opt.SerializerSettings.Converters = new List <JsonConverter>()
                {
                    new LinCmsTimeConverter()
                };
                // 设置下划线方式,首字母是小写
                //opt.SerializerSettings.ContractResolver = new DefaultContractResolver()
                //{
                //    NamingStrategy = new SnakeCaseNamingStrategy()
                //    {
                //        ProcessDictionaryKeys = true
                //    }
                //};
            })
            .ConfigureApiBehaviorOptions(options =>
            {
                options.SuppressConsumesConstraintForFormFileParameters = true;
                //自定义 BadRequest 响应
                options.InvalidModelStateResponseFactory = context =>
                {
                    var problemDetails = new ValidationProblemDetails(context.ModelState);

                    var resultDto = new UnifyResponseDto(ErrorCode.ParameterError, problemDetails.Errors, context.HttpContext);

                    return(new BadRequestObjectResult(resultDto)
                    {
                        ContentTypes = { "application/json" }
                    });
                };
            });
            services.AddHttpsRedirection(options =>
            {
                options.RedirectStatusCode = StatusCodes.Status308PermanentRedirect;
                options.HttpsPort          = 443;
            });
            services.AddHealthChecks();
        }
Example #4
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            InMemoryConfiguration.Configuration = this.Configuration;

            services.AddSingleton(Fsql);
            services.AddScoped <IUnitOfWork>(sp => sp.GetService <IFreeSql>().CreateUnitOfWork());

            services.AddFreeRepository(filter =>
            {
                filter.Apply <IDeleteAduitEntity>("IsDeleted", a => a.IsDeleted == false);
            }, GetType().Assembly, typeof(AuditBaseRepository <>).Assembly);

            services.AddIdentityServer()
#if DEBUG
            .AddDeveloperSigningCredential()
#endif
#if DEBUG
            .AddSigningCredential(new X509Certificate2(
                                      Path.Combine(AppContext.BaseDirectory, Configuration["Certificates:Path"]),
                                      Configuration["Certificates:Password"])
                                  )
#endif
            .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
            .AddInMemoryApiResources(InMemoryConfiguration.GetApis())
            .AddInMemoryClients(InMemoryConfiguration.GetClients())
            .AddProfileService <LinCmsProfileService>()
            .AddResourceOwnerValidator <LinCmsResourceOwnerPasswordValidator>();

            #region Swagger
            //Swagger重写PascalCase,改成SnakeCase模式
            services.TryAddEnumerable(ServiceDescriptor
                                      .Transient <IApiDescriptionProvider, SnakeCaseQueryParametersApiDescriptionProvider>());

            //Register the Swagger generator, defining 1 or more Swagger documents
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo()
                {
                    Title = "LinCms.IdentityServer4", Version = "v1"
                });
                var security = new OpenApiSecurityRequirement()
                {
                    { new OpenApiSecurityScheme
                      {
                          Reference = new OpenApiReference()
                          {
                              Id   = "Bearer",
                              Type = ReferenceType.SecurityScheme
                          }
                      }, Array.Empty <string>() }
                };
                options.AddSecurityRequirement(security);//添加一个必须的全局安全信息,和AddSecurityDefinition方法指定的方案名称要一致,这里是Bearer。
                options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
                    Name        = "Authorization",          //jwt默认的参数名称
                    In          = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中)
                    Type        = SecuritySchemeType.ApiKey
                });

                string xmlPath = Path.Combine(AppContext.BaseDirectory, $"{typeof(Startup).Assembly.GetName().Name}.xml");
                options.IncludeXmlComments(xmlPath);
            });
            #endregion

            services.AddTransient <ICurrentUser, CurrentUser>();

            services.AddControllers();
        }
Example #5
0
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            #region IdentityServer4+FreeSql
            InMemoryConfiguration.Configuration = this.Configuration;
            services.AddSingleton(Fsql);
            //services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork));
            services.AddScoped <IUnitOfWork>(sp => sp.GetService <IFreeSql>().CreateUnitOfWork());

            services.AddFreeRepository(filter =>
            {
                filter.Apply <IDeleteAduitEntity>("IsDeleted", a => a.IsDeleted == false);
            }, GetType().Assembly, typeof(AuditBaseRepository <>).Assembly);

            services.AddIdentityServer()
#if DEBUG
            .AddDeveloperSigningCredential()
#endif
#if !DEBUG
            .AddSigningCredential(new X509Certificate2(Path.Combine(AppContext.BaseDirectory,
                                                                    Configuration["Certificates:Path"]),
                                                       Configuration["Certificates:Password"]))
#endif
            .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
            .AddInMemoryApiResources(InMemoryConfiguration.GetApis())
            .AddInMemoryClients(InMemoryConfiguration.GetClients())
            .AddProfileService <LinCmsProfileService>()
            .AddResourceOwnerValidator <LinCmsResourceOwnerPasswordValidator>();


            #region AddAuthentication\AddIdentityServerAuthentication
            //AddAuthentication()是把验证服务注册到DI, 并配置了Bearer作为默认模式.

            //AddIdentityServerAuthentication()是在DI注册了token验证的处理者.
            //由于是本地运行, 所以就不使用https了, RequireHttpsMetadata = false.如果是生产环境, 一定要使用https.
            //Authority指定Authorization Server的地址.
            //ApiName要和Authorization Server里面配置ApiResource的name一样.
            //和  AddJwtBearer不能同时使用,目前还不理解区别。
            //services
            //    .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
            //    .AddIdentityServerAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme, options =>
            //    {
            //        options.RequireHttpsMetadata = false; // for dev env
            //        options.Authority = $"{Configuration["Identity:Protocol"]}://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}"; ;
            //        options.ApiName = Configuration["Service:Name"]; // match with configuration in IdentityServer

            //        //options.JwtValidationClockSkew = TimeSpan.FromSeconds(60 * 5);

            //    });
            #endregion

            #region AddJwtBearer
            services.AddAuthentication(opts =>
            {
                opts.DefaultScheme             = CookieAuthenticationDefaults.AuthenticationScheme;
                opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                //opts.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddCookie(options =>
            {
                options.LoginPath  = "/cms/oauth2/signin";
                options.LogoutPath = "/cms/oauth2/signout";
            })
            .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
            {
                //identityserver4 地址 也就是本项目地址
                options.Authority            = $"{Configuration["Identity:Protocol"]}://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}";
                options.RequireHttpsMetadata = false;
                options.Audience             = Configuration["Service:Name"];

                options.TokenValidationParameters = new TokenValidationParameters
                {
                    // The signing key must match!
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey         = new SymmetricSecurityKey(
                        Encoding.ASCII.GetBytes(Configuration["Authentication:JwtBearer:SecurityKey"])),

                    // Validate the JWT Issuer (iss) claim
                    ValidateIssuer = true,
                    ValidIssuer    = Configuration["Authentication:JwtBearer:Issuer"],

                    // Validate the JWT Audience (aud) claim
                    ValidateAudience = true,
                    ValidAudience    = Configuration["Authentication:JwtBearer:Audience"],

                    // Validate the token expiry
                    ValidateLifetime = true,

                    // If you want to allow a certain amount of clock drift, set that here
                    //ClockSkew = TimeSpan.Zero
                };


                //options.TokenValidationParameters = new TokenValidationParameters()
                //{
                //    ClockSkew = TimeSpan.Zero   //偏移设置为了0s,用于测试过期策略,完全按照access_token的过期时间策略,默认原本为5分钟
                //};


                //使用Authorize设置为需要登录时,返回json格式数据。
                options.Events = new JwtBearerEvents()
                {
                    OnAuthenticationFailed = context =>
                    {
                        //Token expired
                        if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                        {
                            context.Response.Headers.Add("Token-Expired", "true");
                        }
                        return(Task.CompletedTask);
                    },
                    OnChallenge = context =>
                    {
                        //此处代码为终止.Net Core默认的返回类型和数据结果,这个很重要哦
                        context.HandleResponse();

                        string msg;
                        ErrorCode errorCode;
                        int statusCode = StatusCodes.Status401Unauthorized;

                        if (context.Error == "invalid_token" &&
                            context.ErrorDescription == "The token is expired")
                        {
                            msg        = "令牌过期";
                            errorCode  = ErrorCode.TokenExpired;
                            statusCode = StatusCodes.Status422UnprocessableEntity;
                        }
                        else if (context.Error == "invalid_token" && context.ErrorDescription.IsNullOrEmpty())
                        {
                            msg       = "令牌失效";
                            errorCode = ErrorCode.TokenInvalidation;
                        }

                        else
                        {
                            msg       = "请先登录";//""认证失败,请检查请求头或者重新登录";
                            errorCode = ErrorCode.AuthenticationFailed;
                        }

                        context.Response.ContentType = "application/json";
                        context.Response.StatusCode  = statusCode;
                        context.Response.WriteAsync(new ResultDto(errorCode, msg, context.HttpContext).ToString());

                        return(Task.FromResult(0));
                    }
                };
            })
            .AddGitHub(options =>
            {
                options.ClientId     = Configuration["Authentication:GitHub:ClientId"];
                options.ClientSecret = Configuration["Authentication:GitHub:ClientSecret"];
                options.Scope.Add("user:email");
                //authenticateResult.Principal.FindFirst(ClaimTypes.Uri)?.Value;  得到GitHub头像
                options.ClaimActions.MapJsonKey(LinConsts.Claims.AvatarUrl, "avatar_url");
                options.ClaimActions.MapJsonKey(LinConsts.Claims.BIO, "bio");
                options.ClaimActions.MapJsonKey(LinConsts.Claims.BlogAddress, "blog");
            });
            #endregion

            #endregion

            services.AddAutoMapper(typeof(UserProfile).Assembly, typeof(PoemProfile).Assembly);

            //services.AddCors(option => option.AddPolicy("cors", policy => policy.AllowAnyHeader().AllowAnyMethod().AllowCredentials().AllowAnyOrigin()));
            services.AddCors();

            #region Mvc
            services.AddControllers(options =>
            {
                options.ValueProviderFactories.Add(new SnakeCaseQueryValueProviderFactory()); //设置SnakeCase形式的QueryString参数
                options.Filters.Add <LinCmsExceptionFilter>();
                options.Filters.Add <LogActionFilterAttribute>();                             // 添加请求方法时的日志记录过滤器
            })
            .AddNewtonsoftJson(opt =>
            {
                //opt.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:MM:ss";
                //设置时间戳格式
                opt.SerializerSettings.Converters = new List <JsonConverter>()
                {
                    new LinCmsTimeConverter()
                };
                // 设置下划线方式,首字母是小写
                opt.SerializerSettings.ContractResolver = new DefaultContractResolver()
                {
                    NamingStrategy = new SnakeCaseNamingStrategy()
                    {
                        ProcessDictionaryKeys = true
                    }
                };
            })
            .ConfigureApiBehaviorOptions(options =>
            {
                options.SuppressConsumesConstraintForFormFileParameters = true; //SuppressUseValidationProblemDetailsForInvalidModelStateResponses;
                //自定义 BadRequest 响应
                options.InvalidModelStateResponseFactory = context =>
                {
                    var problemDetails = new ValidationProblemDetails(context.ModelState);

                    var resultDto = new ResultDto(ErrorCode.ParameterError, problemDetails.Errors, context.HttpContext);

                    return(new BadRequestObjectResult(resultDto)
                    {
                        ContentTypes = { "application/json" }
                    });
                };
            });
            #endregion

            services.AddSingleton <IHttpContextAccessor, HttpContextAccessor>();


            #region Scrutor 与单个单个注册等价,不过可批量注册
            //services.AddScoped<ILogService, LogService>();
            //services.AddScoped<IUserSevice, UserService>();
            services.Scan(scan => scan
                          //加载Startup这个类所在的程序集
                          .FromAssemblyOf <IAppService>()
                          // 表示要注册那些类,上面的代码还做了过滤,只留下了以 Service 结尾的类
                          .AddClasses(classes => classes.Where(t => t.Name != "IFileService" && t.Name.EndsWith("Service", StringComparison.OrdinalIgnoreCase)))
                          .UsingRegistrationStrategy(RegistrationStrategy.Skip)
                          //表示将类型注册为提供其所有公共接口作为服务
                          .AsImplementedInterfaces()
                          //表示注册的生命周期为 Transient
                          .WithTransientLifetime()
                          // We start out with all types in the assembly of ITransientService
                          .FromAssemblyOf <IScopeDependency>()
                          // AddClasses starts out with all public, non-abstract types in this assembly.
                          // These types are then filtered by the delegate passed to the method.
                          // In this case, we filter out only the classes that are assignable to ITransientService.
                          .AddClasses(classes => classes.AssignableTo <ITransientDependency>())
                          // We then specify what type we want to register these classes as.
                          // In this case, we want to register the types as all of its implemented interfaces.
                          // So if a type implements 3 interfaces; A, B, C, we'd end up with three separate registrations.
                          .AsImplementedInterfaces()
                          // And lastly, we specify the lifetime of these registrations.
                          .WithTransientLifetime()
                          );

            string serviceName = Configuration.GetSection("FILE:SERVICE").Value;

            if (serviceName == LinFile.LocalFileService)
            {
                services.AddTransient <IFileService, LocalFileService>();
            }
            else
            {
                services.AddTransient <IFileService, QiniuService>();
            }
            #endregion

            #region Swagger
            //Swagger重写PascalCase,改成SnakeCase模式
            services.TryAddEnumerable(ServiceDescriptor
                                      .Transient <IApiDescriptionProvider, SnakeCaseQueryParametersApiDescriptionProvider>());

            //Register the Swagger generator, defining 1 or more Swagger documents
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo()
                {
                    Title = "LinCms", Version = "v1"
                });
                var security = new OpenApiSecurityRequirement()
                {
                    { new OpenApiSecurityScheme
                      {
                          Reference = new OpenApiReference()
                          {
                              Id   = "Bearer",
                              Type = ReferenceType.SecurityScheme
                          }
                      }, Array.Empty <string>() }
                };
                options.AddSecurityRequirement(security);//添加一个必须的全局安全信息,和AddSecurityDefinition方法指定的方案名称要一致,这里是Bearer。
                options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
                    Name        = "Authorization",          //jwt默认的参数名称
                    In          = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中)
                    Type        = SecuritySchemeType.ApiKey
                });

                string xmlPath = Path.Combine(AppContext.BaseDirectory, $"{typeof(Startup).Assembly.GetName().Name}.xml");
                options.IncludeXmlComments(xmlPath);
            });
            #endregion

            //将Handler注册到DI系统中
            services.AddScoped <IAuthorizationHandler, PermissionAuthorizationHandler>();

            services.Configure <FormOptions>(options =>
            {
                options.MultipartBodyLengthLimit   = 1024 * 1024 * 2;
                options.MultipartHeadersCountLimit = 10;
            });

            IConfigurationSection configurationSection = Configuration.GetSection("ConnectionStrings:Default");
            services.AddCap(x =>
            {
                x.UseMySql(configurationSection.Value);

                x.UseRabbitMQ("localhost");
                x.UseDashboard();
                x.FailedRetryCount        = 5;
                x.FailedThresholdCallback = (type, msg) =>
                {
                    Console.WriteLine(
                        $@"A message of type {type} failed after executing {x.FailedRetryCount} several times, requiring manual troubleshooting. Message name: {msg.GetName()}");
                };
            });
        }
        public void ConfigureServices(IServiceCollection services)
        {
            InMemoryConfiguration.Configuration = this.Configuration;

            services.AddContext();

            services.AddIdentityServer()
#if DEBUG
            .AddDeveloperSigningCredential()
#endif
#if !DEBUG
            .AddSigningCredential(new X509Certificate2(
                                      Path.Combine(AppContext.BaseDirectory, Configuration["Certificates:Path"]),
                                      Configuration["Certificates:Password"])
                                  )
#endif
            .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
            .AddInMemoryApiResources(InMemoryConfiguration.GetApis())
            .AddInMemoryClients(InMemoryConfiguration.GetClients())
            .AddProfileService <LinCmsProfileService>()
            .AddResourceOwnerValidator <LinCmsResourceOwnerPasswordValidator>();

            #region Swagger

            //Register the Swagger generator, defining 1 or more Swagger documents
            services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo()
                {
                    Title = "LinCms.IdentityServer4", Version = "v1"
                });
                var security = new OpenApiSecurityRequirement()
                {
                    { new OpenApiSecurityScheme
                      {
                          Reference = new OpenApiReference()
                          {
                              Id   = "Bearer",
                              Type = ReferenceType.SecurityScheme
                          }
                      }, Array.Empty <string>() }
                };
                options.AddSecurityRequirement(security);//添加一个必须的全局安全信息,和AddSecurityDefinition方法指定的方案名称要一致,这里是Bearer。
                options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 参数结构: \"Authorization: Bearer {token}\"",
                    Name        = "Authorization",          //jwt默认的参数名称
                    In          = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中)
                    Type        = SecuritySchemeType.ApiKey
                });

                string xmlPath = Path.Combine(AppContext.BaseDirectory, $"{typeof(Startup).Assembly.GetName().Name}.xml");
                options.IncludeXmlComments(xmlPath);
            });
            #endregion

            services.AddTransient <IUserRepository, UserRepository>();
            services.AddTransient <IUserIdentityService, UserIdentityService>();
            services.AddTransient <ICurrentUser, CurrentUser>();
            services.AddTransient(typeof(IAuditBaseRepository <>), typeof(AuditBaseRepository <>));
            services.AddTransient(typeof(IAuditBaseRepository <,>), typeof(AuditBaseRepository <,>));
            services.AddTransient <CustomExceptionMiddleWare>();

            services.AddCors();
            services.AddAutoMapper(typeof(UserProfile).Assembly);

            services.AddControllers(options =>
            {
                //options.Filters.Add<LinCmsExceptionFilter>();
            })
            .AddNewtonsoftJson(opt =>
            {
                //opt.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:MM:ss";
                //设置自定义时间戳格式
                opt.SerializerSettings.Converters = new List <JsonConverter>()
                {
                    new LinCmsTimeConverter()
                };
                // 设置下划线方式,首字母是小写
                opt.SerializerSettings.ContractResolver = new DefaultContractResolver()
                {
                    NamingStrategy = new SnakeCaseNamingStrategy()
                    {
                        ProcessDictionaryKeys = true
                    }
                };
            })
            .ConfigureApiBehaviorOptions(options =>
            {
                options.SuppressConsumesConstraintForFormFileParameters = true;
                //自定义 BadRequest 响应
                options.InvalidModelStateResponseFactory = context =>
                {
                    var problemDetails = new ValidationProblemDetails(context.ModelState);

                    var resultDto = new UnifyResponseDto(ErrorCode.ParameterError, problemDetails.Errors, context.HttpContext);

                    return(new BadRequestObjectResult(resultDto)
                    {
                        ContentTypes = { "application/json" }
                    });
                };
            });
            services.AddHealthChecks();
        }