public void ConfigureServices(IServiceCollection services) { var beforeCount = services.Count; try { // Global configurations maybe used in many places services.Configure <GlobalOptions>(_config); services.Configure <AdminOptions>(_config.GetSection("Admin")); // Adds a service that can resolve the client URI services.AddClientAppAddressResolver(_config); // Azure Application Insights string instrumentationKey = _config["APPINSIGHTS_INSTRUMENTATIONKEY"]; if (!string.IsNullOrWhiteSpace(instrumentationKey)) { services.AddApplicationInsightsTelemetry(instrumentationKey); } // Register the API services.AddTellmaApi(_config) .AddSingleton <IServiceContextAccessor, WebServiceContextAccessor>() .AddClientAppProxy(_config); // Dependency for the GlobalController and the GlobalFilter services.AddScoped <GlobalSettingsProvider>(); // Add optoinal services if (_opt.EmailEnabled) { services.AddSendGrid(_config); } if (_opt.SmsEnabled) { services.AddTwilio(_config); } if (_opt.AzureBlobStorageEnabled) { // This overrides the SQL implementation added by default services.AddAzureBlobStorage(_config); } // Add the default localization that relies on resource files in /Resources services.AddLocalization(); // Register MVC services .AddControllersWithViews(opt => { // This filter checks version headers (e.g. x-translations-version) supplied by the client and efficiently // sets a response header to 'Fresh' or 'Stale' to prompt the client to refresh its settings if necessary opt.Filters.Add <GlobalFilter>(); // This filters traps any exception in the action execution and turns it into a proper response opt.Filters.Add <ExceptionsFilter>(); }) .ConfigureApiBehaviorOptions(opt => { // Validation is performed at the business service layer, not at the Controller layer // Controllers are very thin, their only purpose is to translate from web world to C# world opt.SuppressModelStateInvalidFilter = true; }) .AddDataAnnotationsLocalization(opt => { // This allows us to have a single RESX file for all classes and namespaces opt.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(Strings)); }) .AddJsonOptions(opt => { JsonUtil.ConfigureOptionsForWeb(opt.JsonSerializerOptions); }); // Setup an embedded instance of identity server in the same domain as the API if it is enabled in the configuration if (_opt.EmbeddedIdentityServerEnabled) { // To support the authentication pages var mvcBuilder = services.AddRazorPages(); services.AddEmbeddedIdentityServer(config: _config, mvcBuilder: mvcBuilder, isDevelopment: _env.IsDevelopment()); // Add services for authenticating API calls against the embedded // Identity server, and helper services for accessing claims services.AddApiAuthWithEmbeddedIdentity(); } else { // Add services for authenticating API calls against an external // OIDC authority, and helper services for accessing claims services.AddApiAuthWithExternalIdentity(_config); } // Configure some custom behavior for API controllers services.Configure <ApiBehaviorOptions>(opt => { // This overrides the default behavior, when there are validation // errors we return a 422 unprocessable entity, instead of the default // 400 bad request, this makes it easier for clients to distinguish // such kinds of errors and handle them in a special way, for example: // by showing them on the relevant fields with a red highlight opt.InvalidModelStateResponseFactory = ctx => new UnprocessableEntityObjectResult(ctx.ModelState); }); // Embedded Client Application if (_opt.EmbeddedClientApplicationEnabled) { services.AddSpaStaticFiles(opt => { // In production, the Angular files will be served from this directory opt.RootPath = "ClientApp/dist"; }); } // Giving access to clients that are hosted on another domain services.AddCors(); // This is a required configuration since ASP.NET Core 2.1 services.AddHttpsRedirection(opt => { opt.HttpsPort = 443; }); // Adds and configures SignalR services.AddSignalRImplementation(_config, _env); // API Versioning services.AddApiVersioning(options => { options.DefaultApiVersion = new ApiVersion(1, 0); options.AssumeDefaultVersionWhenUnspecified = true; options.ReportApiVersions = true; options.ApiVersionReader = new HeaderApiVersionReader("X-Api-Version"); }); } catch (Exception ex) { // Remove all custom services to avoid a DI validation exception while (services.Count > beforeCount) { services.RemoveAt(beforeCount); } // The configuration encountered a fatal error, usually a required yet // missing configuration. Setting this property instructs the middleware // to short-circuit and just return this error in plain text SetStartupError(ex); } }