A way to configure an asp.net core site
Tags: webdev, aspnetcore, csharp
Published: 2024-05-21 03:26:43

I found myself coding a lot of the same code when creating and configuring ASP.NET Core sites, so I made this system to gather all the config code in one class and simplify adding and removing features.

I create one class per feature and all classes derive from AppFeatureBase.cs. All classes must set a value indicating what feature they represent.

Sorry, but I can't get to mark the code, so it's a little hard to read.

In Program.cs I add features by creating new features classes.

**AppFeatureBase.cs**:
`public interface IAppFeatureBase
{
	Features Type { get; }
}

public abstract class AppFeatureBase : IAppFeatureBase
{
	private readonly Features _type;

	protected AppFeatureBase(Features type)
	{
		_type = type;
	}

	public Features Type => _type;
}`

Some types implementing _IAppFeatureBase_:
public interface IDefaultRoute
{
	string Route { get; }
}

public sealed class DefaultRoute : AppFeatureBase, IAppFeatureBase, IDefaultRoute
{
	private readonly string _route;

	public DefaultRoute(string controller, string action) : base(Features.DefaultRoute)
	{
		_route = CoreStatics.GetBaseRoute(controller, action);
	}

	public string Route => _route;
}
`
`public interface ISSL
{
	int Port { get; }
}

public sealed class SSL : AppFeatureBase, IAppFeatureBase, ISSL
{
	private readonly int _port = 443;

	public SSL() : base(Features.SSL)
	{
	}

	public SSL(int port) : base(Features.SSL)
	{
		_port = port;
	}

	public int Port => _port;
}`

**AssetsConfig.cs**:
`public static class AssetsConfig
{
    public static void AddAssets(WebApplicationBuilder builder, params IAppFeatureBase[] features) => AddAssets(builder, null, features);

    public static void AddAssets(WebApplicationBuilder builder, Action<MvcOptions>? mvcOptions, params IAppFeatureBase[] features)
    {
        if (builder.Configuration.GetSection(AssetsStatics.ConfigSectionName).Exists())
        {
            IAssetsConfig cnf = new Assets.AssetsConfig(builder.Configuration);
            builder.Configuration.Bind(AssetsStatics.ConfigSectionName, cnf);

            _ = builder.Services
                .AddOptions()
                .Configure<IConfiguration>(builder.Configuration)
                .AddSingleton(cnf);
        }
        else
        {
            throw new ConfigSectionNotFoundException();
        }

        _ = builder.Services
            .AddHttpContextAccessor()
            .AddDbContext<AssetsDbContext>(opt => opt.UseSqlServer(AssetsStatics.ConnectionString))
            .AddDbContext<AssetsClientDbContext>(opt => opt.UseSqlServer(AssetsStatics.ClientConnectionString))
            .AddTransient<ISiteHelper, SiteHelper>()
            .AddTransient<IAssetsDbContext, AssetsDbContext>()
            .AddTransient<IAssetsClientDbContext, AssetsClientDbContext>()
            .AddTransient<IDbContextAccessor, DbContextAccessor>()
            .AddTransient<IActionContextAccessor, ActionContextAccessor>()
            .AddTransient<IAssetsConfigAccessor, AssetsConfigAccessor>()
            .AddTransient<IUtilityHelper, UtilityHelper>()
            .AddTransient<IUrlHelperFactory, UrlHelperFactory>()
            .AddTransient<IRepoFactory, RepoFactory>()
            .AddTransient<IExpressionFactory, ExpressionFactory>()
            .AddTransient<ITagHelperRepo, TagHelperRepo>()
            .AddTransient<ISiteHelper, SiteHelper>()
            .Configure<CookiePolicyOptions>(opt =>
            {
                opt.MinimumSameSitePolicy = SameSiteMode.Lax;
            })
            .AddSession(opt =>
            {
                opt.IdleTimeout = TimeSpan.FromMinutes(CoreStatics.SessionTimeout);
            });

        if (features.Exists(Features.Secure))
        {
            _ = builder.Services
                .AddTransient<ISecureAccessor, SecureAccessor>();
        }

        if (features.Exists(Features.FileUpload))
        {
            IFileUpload fu = (FileUpload)features.Single(feat => feat.Type == Features.FileUpload);
            _ = builder.Services
                .AddSingleton(new PhysicalFileProvider(fu.FolderPath));
        }

        if (features.Exists(Features.Logging))
        {
            _ = builder.Services
                .AddTransient<IAssetsLogging, AssetsLogging>();
        }

        if (features.Exists(Features.SSL))
        {
            SSL ssl = (SSL)features.Single(feat => feat.Type == Features.SSL);

            _ = builder.Services
               .AddHttpsRedirection(conf =>
               {
                   conf.HttpsPort = ssl.Port;
               });
        }

        if (features.Exists(Features.Service))
        {
            foreach (Service feat in features.Where(feat => feat.Type == Features.Service).Cast<Service>())
            {
                switch (feat.Scope)
                {
                    case ServiceScopes.Singleton:
                        builder.Services
                            .AddSingleton(feat.Interface, feat.Implementation);
                        break;

                    case ServiceScopes.Scoped:
                        builder.Services
                            .AddScoped(feat.Interface, feat.Implementation);
                        break;

                    case ServiceScopes.Transient:
                        builder.Services
                            .AddTransient(feat.Interface, feat.Implementation);
                        break;
                }
            }
        }

        if (features.Exists(Features.Blazor))
        {
            _ = builder.Services
                .AddServerSideBlazor();
        }

        if (features.Exists(Features.Content))
        {
            foreach (ApplicationFeatures.Content cnt in features.Where(ft => ft.Type == Features.Content))
            {
                switch (cnt.Feature)
                {
                    case ContentFeature.Local:
                        _ = builder.Services
                            .AddTransient<ILocalizationAccessor, LocalizationAccessor>();
                        break;

                    case ContentFeature.Value:
                        _ = builder.Services
                            .AddTransient<IContentAccessor, ContentAccessor>();
                        break;

                    case ContentFeature.All:
                        _ = builder.Services
                            .AddTransient<ILocalizationAccessor, LocalizationAccessor>()
                            .AddTransient<IContentAccessor, ContentAccessor>();
                        break;
                }
            }
        }

        _ = builder.Services
            .AddDistributedMemoryCache()
            .AddSession(opt =>
            {
                opt.Cookie.Name = CoreStatics.SessionCookieName;
                opt.Cookie.HttpOnly = false;
            })
            .AddControllersWithViews(mvcOptions);

        if (features.Exists(Features.Minify))
        {
            Minify mn = (Minify)features.Single(ft => ft.Type == Features.Minify);

            switch (mn.Target)
            {
                case MinifyTargets.Production:
                    if (builder.Environment.IsProduction())
                    {
                        AddToServices();
                    }
                    break;

                case MinifyTargets.Both:
                    AddToServices();
                    break;
            }

            void AddToServices()
            {
                _ = builder.Services
                    .AddWebMarkupMin(opt =>
                    {
                        opt.AllowCompressionInDevelopmentEnvironment = true;
                        opt.AllowMinificationInDevelopmentEnvironment = true;
                    })
                    .AddHtmlMinification(opt =>
                    {
                        opt.MinificationSettings.RemoveRedundantAttributes = true;
                        opt.MinificationSettings.RemoveHttpsProtocolFromAttributes = true;
                        opt.MinificationSettings.RemoveHttpsProtocolFromAttributes = true;
                    });
            }
        }

        if (features.Exists(Features.GoogleAuth))
        {
            GoogleConfigSection config = new();
            builder.Configuration.GetSection("Google").Bind(config);

            _ = builder.Services.AddAuthentication().AddGoogle(opt =>
            {
                opt.ClientId = config.ClientId;
                opt.ClientSecret = config.ClientSecret;
            });
        }

        WebApplication app = builder.Build();

        if (features.Exists(Features.Debug))
        {
            Debug db = (Debug)features.Single(feat => feat.Type == Features.Debug);
            db.Environment = app.Environment;

            if (db.Environment.IsDevelopment() || db.IgnoreEnvironment)
            {
                _ = app
                    .UseDeveloperExceptionPage();
            }
        }

        if (features.Exists(Features.SSL))
        {
            _ = app
                .UseHttpsRedirection();
        }

        if (features.Exists(Features.GDPR))
        {
            GDPR feat = (GDPR)features.Single(ft => ft.Type == Features.GDPR);

            _ = app.Map("/AssetsAPI/SetGDPR", async context =>
            {
                context.Response.Cookies.Append(CoreStatics.GDPRCookieName, CoreStatics.GDPRCookieName);
            });
        }

        if (features.Exists(Features.Minify))
        {
            Minify mn = (Minify)features.Single(ft => ft.Type == Features.Minify);

            switch (mn.Target)
            {
                case MinifyTargets.Production:
                    if (builder.Environment.IsProduction())
                    {
                        _ = app.UseWebMarkupMin();
                    }
                    break;

                case MinifyTargets.Both:
                    _ = app.UseWebMarkupMin();
                    break;
            }
        }

        _ = app
            .UseStaticFiles()
            .UseRouting()
            .UseSession()
            .UseCookiePolicy(new CookiePolicyOptions
            {
                MinimumSameSitePolicy = SameSiteMode.Lax
            });

        if (features.Exists(Features.Content))
        {
            app
                .UseMiddleware<SetCulture>();
        }

        if (features.Exists(Features.DefaultRoute) && !features.Exists(Features.Blazor))
        {
            DefaultRoute route = (DefaultRoute)features.Single(feat => feat.Type == Features.DefaultRoute);

            _ = app.UseEndpoints(end =>
            {
                _ = end.MapControllerRoute(name: "default", pattern: route.Route);
            });
        }
        else if (features.Exists(Features.Blazor))
        {
            IBlazor blzr = (Blazor)features.Single(ft => ft.Type == Features.Blazor);

            _ = app.UseEndpoints(endpoints =>
            {
                _ = endpoints.MapControllerRoute(
                    name: "default",
                    pattern: blzr.Pattern);

                _ = endpoints.MapBlazorHub();
            });
        }
        else
        {
            _ = app.UseEndpoints(opt => opt.MapDefaultControllerRoute());
        }

        app.Run();
    }
}`

**And this is how it looks in Program.cs**:
`WebApplicationBuilder bld = WebApplication.CreateBuilder(args);

AssetsConfig.AddAssets(bld,
    new GDPR("LE", "Index"),
    new DefaultRoute("LE", "Index"),
    new Service(typeof(IDataRepo), typeof(DataRepo)),
    new Service(typeof(IPictureListRepo), typeof(PictureListRepo)),
    new Service(typeof(ILESiteHelper), typeof(LESiteHelper)),
    new Minify(MinifyTargets.Production));`
	
I have a framework called Assets which all my sites derives from, so this way I can easily add new features to Assets and add them to sites without having to think about adding all services in correct order.

Hope you like it :)