public static IServiceCollection ConfigureDefaultForApi <T, TSharedResources>( this IServiceCollection services, StartupConfigureOptions options) { if (options.Swagger.ApiInfo == null) { throw new ArgumentNullException(nameof(options.Swagger.ApiInfo)); } var configuredCorsMethods = new[] { HttpMethod.Get.Method, HttpMethod.Head.Method, HttpMethod.Post.Method, HttpMethod.Put.Method, HttpMethod.Patch.Method, HttpMethod.Delete.Method, HttpMethod.Options.Method }.Union(options.Cors.Methods ?? Array.Empty <string>()).Distinct().ToArray(); var configuredCorsHeaders = new[] { HeaderNames.Accept, HeaderNames.ContentType, HeaderNames.Origin, HeaderNames.Authorization, HeaderNames.IfMatch, ExtractFilteringRequestExtension.HeaderName, AddSortingExtension.HeaderName, AddPaginationExtension.HeaderName }.Union(options.Cors.Headers ?? Array.Empty <string>()).Distinct().ToArray(); var configuredCorsExposedHeaders = new[] { HeaderNames.Location, ExtractFilteringRequestExtension.HeaderName, AddSortingExtension.HeaderName, AddPaginationExtension.HeaderName, options.Server.VersionHeaderName, AddCorrelationIdToResponseMiddleware.HeaderName, AddHttpSecurityHeadersMiddleware.PoweredByHeaderName, AddHttpSecurityHeadersMiddleware.ContentTypeOptionsHeaderName, AddHttpSecurityHeadersMiddleware.FrameOptionsHeaderName, AddHttpSecurityHeadersMiddleware.XssProtectionHeaderName, AddVersionHeaderMiddleware.HeaderName }.Union(options.Cors.ExposedHeaders ?? Array.Empty <string>()).Distinct().ToArray(); services.AddSingleton(options); services.TryAddEnumerable(ServiceDescriptor.Transient <IApiControllerSpecification, ApiControllerSpec>()); services .AddHttpContextAccessor() .ConfigureOptions <ProblemDetailsSetup>() .AddProblemDetails(cfg => { foreach (var header in configuredCorsExposedHeaders) { if (!cfg.AllowedHeaderNames.Contains(header)) { cfg.AllowedHeaderNames.Add(header); } } options.MiddlewareHooks.ConfigureProblemDetails?.Invoke(cfg); }); var mvcBuilder = services .AddMvcCore(cfg => { cfg.RespectBrowserAcceptHeader = false; cfg.ReturnHttpNotAcceptable = true; cfg.Filters.Add(new LoggingFilterFactory(options.Server.MethodsToLog)); // This got removed in .NET Core 3.0, we need to determine the impact //cfg.Filters.Add(new CorsAuthorizationFilterFactory(StartupHelpers.AllowSpecificOrigin)); cfg.Filters.Add <OperationCancelledExceptionFilter>(); cfg.Filters.Add(new DataDogTracingFilter()); cfg.EnableEndpointRouting = false; options.MiddlewareHooks.ConfigureMvcCore?.Invoke(cfg); }); options.MiddlewareHooks.AfterMvcCore?.Invoke(mvcBuilder); mvcBuilder .SetCompatibilityVersion(CompatibilityVersion.Version_3_0) .AddDataAnnotationsLocalization(options.MiddlewareHooks.DataAnnotationsLocalization) .AddFluentValidation( options.MiddlewareHooks.FluentValidation ?? (fv => fv.RegisterValidatorsFromAssemblyContaining <T>())) .AddCors(cfg => { cfg.AddPolicy(StartupHelpers.AllowAnyOrigin, corsPolicy => corsPolicy .AllowAnyOrigin() .WithMethods(configuredCorsMethods) .WithHeaders(configuredCorsHeaders) .WithExposedHeaders(configuredCorsExposedHeaders) .SetPreflightMaxAge(TimeSpan.FromSeconds(60 * 15))); cfg.AddPolicy(StartupHelpers.AllowSpecificOrigin, corsPolicy => corsPolicy .WithOrigins(options.Cors.Origins ?? new string[0]) .WithMethods(configuredCorsMethods) .WithHeaders(configuredCorsHeaders) .WithExposedHeaders(configuredCorsExposedHeaders) .SetPreflightMaxAge(TimeSpan.FromSeconds(60 * 15)) .AllowCredentials()); options.MiddlewareHooks.ConfigureCors?.Invoke(cfg); }) .AddControllersAsServices() .AddAuthorization(options.MiddlewareHooks.Authorization) .AddNewtonsoftJson( options.MiddlewareHooks.ConfigureJsonOptions ?? (cfg => cfg.SerializerSettings.ConfigureDefaultForApi())) .AddXmlDataContractSerializerFormatters() .AddFormatterMappings(options.MiddlewareHooks.ConfigureFormatterMappings) .AddApiExplorer(); options.MiddlewareHooks.AfterMvc?.Invoke(mvcBuilder); var healthChecksBuilder = services.AddHealthChecks(); options.MiddlewareHooks.AfterHealthChecks?.Invoke(healthChecksBuilder); services .AddLocalization(cfg => cfg.ResourcesPath = "Resources") .AddSingleton <IStringLocalizerFactory, SharedStringLocalizerFactory <TSharedResources> >() .AddSingleton <ResourceManagerStringLocalizerFactory, ResourceManagerStringLocalizerFactory>() .Configure <RequestLocalizationOptions>(opts => { const string fallbackCulture = "en-GB"; var defaultRequestCulture = new RequestCulture(options.Localization.DefaultCulture ?? new CultureInfo(fallbackCulture)); var supportedCulturesOrDefault = options.Localization.SupportedCultures ?? new[] { new CultureInfo(fallbackCulture) }; opts.DefaultRequestCulture = defaultRequestCulture; opts.SupportedCultures = supportedCulturesOrDefault; opts.SupportedUICultures = supportedCulturesOrDefault; opts.FallBackToParentCultures = true; opts.FallBackToParentUICultures = true; }) .AddVersionedApiExplorer(cfg => { cfg.GroupNameFormat = "'v'VVV"; cfg.SubstituteApiVersionInUrl = true; }) .AddApiVersioning(cfg => { cfg.ReportApiVersions = true; cfg.ErrorResponses = new ProblemDetailsResponseProvider(); }) .AddSwagger <T>(new SwaggerOptions { ApiInfoFunc = options.Swagger.ApiInfo, XmlCommentPaths = options.Swagger.XmlCommentPaths ?? new string[0], AdditionalHeaderOperationFilters = options.Swagger.AdditionalHeaderOperationFilters, Servers = options.Swagger.Servers, CustomSortFunc = options.Swagger.CustomSortFunc, MiddlewareHooks = { AfterSwaggerGen = options.Swagger.MiddlewareHooks.AfterSwaggerGen } }) .AddResponseCompression(cfg => { cfg.EnableForHttps = true; cfg.Providers.Add <BrotliCompressionProvider>(); cfg.Providers.Add <GzipCompressionProvider>(); cfg.MimeTypes = new[] { // General "text/plain", "text/csv", // Static files "text/css", "application/javascript", // MVC "text/html", "application/xml", "text/xml", "application/json", "text/json", "application/ld+json", "application/atom+xml", // Fonts "application/font-woff", "font/otf", "application/vnd.ms-fontobject" }; }) .Configure <GzipCompressionProviderOptions>(cfg => cfg.Level = CompressionLevel.Fastest) .Configure <BrotliCompressionProviderOptions>(cfg => cfg.Level = CompressionLevel.Fastest) .Configure <KestrelServerOptions>(serverOptions => serverOptions.AllowSynchronousIO = true); ValidatorOptions.DisplayNameResolver = (type, member, expression) => member != null ? GlobalStringLocalizer.Instance.GetLocalizer <TSharedResources>().GetString(() => member.Name) : null; return(services); }
public static IServiceCollection ConfigureDefaultForApi <T>( this IServiceCollection services, StartupConfigureOptions options) => services.ConfigureDefaultForApi <T, DefaultResources>(options);