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 :)
Top comments (0)