using System; using System.Collections.Generic; using System.IO; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Oqtane.Extensions; using Oqtane.Infrastructure; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Security; using Oqtane.Shared; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Logging; using Oqtane.Components; using Oqtane.UI; using OqtaneSSR.Extensions; using Microsoft.AspNetCore.Components.Authorization; using Oqtane.Providers; using Microsoft.AspNetCore.Cors.Infrastructure; namespace Oqtane { public class Startup { private readonly bool _useSwagger; private readonly IWebHostEnvironment _env; private readonly string[] _installedCultures; private string _configureServicesErrors; public IConfigurationRoot Configuration { get; } public Startup(IWebHostEnvironment env, ILocalizationManager localizationManager) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", false, true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true, true) .AddEnvironmentVariables(); Configuration = builder.Build(); _installedCultures = localizationManager.GetInstalledCultures(); //add possibility to switch off swagger on production. _useSwagger = Configuration.GetSection("UseSwagger").Value != "false"; AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(env.ContentRootPath, "Data")); _env = env; } // 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) { // process forwarded headers on load balancers and proxy servers services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; }); // register localization services services.AddLocalization(options => options.ResourcesPath = "Resources"); services.AddOptions>().Bind(Configuration.GetSection(SettingKeys.AvailableDatabasesSection)); services.Configure(opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(10)); // increase from default of 5 seconds // register scoped core services services.AddScoped() .AddOqtaneServerScopedServices(); services.AddSingleton(); // setup HttpClient for server side in a client side compatible fashion ( with auth cookie ) services.AddHttpClients(); // register singleton scoped core services services.AddSingleton(Configuration) .AddOqtaneSingletonServices(); // install any modules or themes ( this needs to occur BEFORE the assemblies are loaded into the app domain ) _configureServicesErrors += InstallationManager.InstallPackages(_env.WebRootPath, _env.ContentRootPath); // register transient scoped core services services.AddOqtaneTransientServices(); // load the external assemblies into the app domain, install services services.AddOqtane(_installedCultures); services.AddOqtaneDbContext(); services.AddAntiforgery(options => { options.HeaderName = Constants.AntiForgeryTokenHeaderName; options.Cookie.Name = Constants.AntiForgeryTokenCookieName; options.Cookie.SameSite = SameSiteMode.Strict; options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; }); services.AddIdentityCore(options => { }) .AddEntityFrameworkStores() .AddSignInManager() .AddDefaultTokenProviders() .AddClaimsPrincipalFactory>(); // role claims services.ConfigureOqtaneIdentityOptions(Configuration); services.AddCascadingAuthenticationState(); services.AddScoped(); services.AddAuthorization(); services.AddAuthentication(options => { options.DefaultScheme = Constants.AuthenticationScheme; }) .AddCookie(Constants.AuthenticationScheme) .AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { }) .AddOAuth(AuthenticationProviderTypes.OAuth2, options => { }); services.ConfigureOqtaneCookieOptions(); services.ConfigureOqtaneAuthenticationOptions(Configuration); services.AddOqtaneSiteOptions() .WithSiteIdentity() .WithSiteAuthentication(); services.AddCors(options => { options.AddPolicy(Constants.MauiCorsPolicy, policy => { // allow .NET MAUI client cross origin calls policy.WithOrigins("https://0.0.0.0", "http://0.0.0.0", "app://0.0.0.0") .AllowAnyHeader().AllowAnyMethod().AllowCredentials(); }); }); services.AddMvc(options => { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); }) .AddOqtaneApplicationParts() // register any Controllers from custom modules .ConfigureOqtaneMvc(); // any additional configuration from IStartup classes services.AddRazorPages(); services.AddRazorComponents() .AddInteractiveServerComponents(options => { if (_env.IsDevelopment()) { options.DetailedErrors = true; } }).AddHubOptions(options => { options.MaximumReceiveMessageSize = null; // no limit (for large amnounts of data ie. textarea components) }) .AddInteractiveWebAssemblyComponents(); services.AddSwaggerGen(options => { options.CustomSchemaIds(type => type.ToString()); // Handle SchemaId already used for different type }); services.TryAddSwagger(_useSwagger); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ISyncManager sync, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ILogger logger) { if (!string.IsNullOrEmpty(_configureServicesErrors)) { logger.LogError(_configureServicesErrors); } ServiceActivator.Configure(app.ApplicationServices); if (env.IsDevelopment()) { app.UseWebAssemblyDebugging(); app.UseForwardedHeaders(); } else { app.UseForwardedHeaders(); app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } // execute any IServerStartup logic app.ConfigureOqtaneAssemblies(env); // allow oqtane localization middleware app.UseOqtaneLocalization(); app.UseHttpsRedirection(); app.UseStaticFiles(new StaticFileOptions { ServeUnknownFileTypes = true, OnPrepareResponse = (ctx) => { var policy = corsPolicyProvider.GetPolicyAsync(ctx.Context, Constants.MauiCorsPolicy) .ConfigureAwait(false).GetAwaiter().GetResult(); corsService.ApplyResult(corsService.EvaluatePolicy(ctx.Context, policy), ctx.Context.Response); } }); app.UseExceptionMiddleWare(); app.UseTenantResolution(); app.UseJwtAuthorization(); app.UseRouting(); app.UseCors(); app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); if (_useSwagger) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/" + Constants.Version + "/swagger.json", Constants.PackageId + " " + Constants.Version); }); } app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapRazorPages(); }); app.UseEndpoints(endpoints => { endpoints.MapRazorComponents() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(SiteRouter).Assembly); }); // simulate the fallback routing approach of traditional Blazor - allowing the custom SiteRouter to handle all routing concerns app.UseEndpoints(endpoints => { endpoints.MapFallback(); }); // create a global sync event to identify server application startup sync.AddSyncEvent(-1, -1, EntityNames.Host, -1, SyncEventActions.Reload); } } }