A way to configure an asp.net core site

RMAG news

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 🙂