Asp.net Boilerplate - Implement setting manager with database - asp.net-core

I've been building an asp.net core website, using the asp.net boilerplate template. As of now, I've been storing all of the settings in the appsettings.json file. As the application gets bigger, I'm thinking I should start storing some settings via ABP's SettingProvider and ISettingStore.
My question is, does anyone have, or know of, a sample application that show's how to implement ISettingStore and storing the settings in the database?
The only post I could find so far is this, but the link hikalkan supplies is broken.
Thanks for any help,
Joe

ABP stores settings on memory with default values. When you insert a new setting value into database, then it reads from database and overrides the default value. So basically when database has no settings then it means all the settings are on default values. Setting values are stored in AbpSettings table.
To start using settings mechanism. Create your own SettingProvider inherited from SettingProvider. Initialize it in your module (eg:
ModuleZeroSampleProjectApplicationModule).
As SettingProvider is automatically registed to dependency injection; You can inject ISettingManager wherever you want.
public class MySettingProvider : SettingProvider
{
public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context)
{
return new[]
{
new SettingDefinition(
"SmtpServerAddress",
"127.0.0.1"
),
new SettingDefinition(
"PassiveUsersCanNotLogin",
"true",
scopes: SettingScopes.Application | SettingScopes.Tenant
),
new SettingDefinition(
"SiteColorPreference",
"red",
scopes: SettingScopes.User,
isVisibleToClients: true
)
};
}
}
In application services and controllers you don't need to inject ISettingManager
(because there's already property injected) and you can directly use SettingManager property. Forexample :
//Getting a boolean value (async call)
var value1 = await SettingManager.GetSettingValueAsync<bool>("PassiveUsersCanNotLogin");
And for the other classes (like Domain Services) can inject ISettingManager
public class UserEmailer : ITransientDependency
{
private readonly ISettingManager _settingManager;
public UserEmailer(ISettingManager settingManager)
{
_settingManager = settingManager;
}
[UnitOfWork]
public virtual async Task TestMethod()
{
var settingValue = _settingManager.GetSettingValueForUser("SmtpServerAddress", tenantAdmin.TenantId, tenantAdmin.Id);
}
}
Note: To modify a setting you can use these methods in SettingManager ChangeSettingForApplicationAsync, ChangeSettingForTenantAsync and ChangeSettingForUserAsync

Related

Access registered service from Program.cs in ASP.NET Core 6

In my Program.cs file I'm registering two repositories on builder.Services that will be used via dependency injection. However, I now have a case where I need to call one to configure the other. So essentially I need to do this:
builder.Services.AddFirstService();
var tempProvider = builder.Services.BuildServiceProvider();
var injected = tempProvider.GetRequiredService<IFirstService>();
var password = await injected.GetPasswordAsync();
builder.Services.AddSecondService(password);
That of course gives a warning about creating duplicate services. Is there a way to properly inject the first service so that I can then use it in another call?
The second service isn't something that can be modified to know about the first service.
The password is runtime data. This means you should design that component as such that it doesn't require runtime data during creation but instead allow to fetch the password lazily after it was created.
In case the component can't be changed—for instance because it comes from a third-party library—it's best to hide its use behind an abstraction. This also makes sure you adhere to the Dependency Inversion Principle, which states that:
high level modules should own the abstractions they depend on.
For instance, inside your application layer, you can create an abstraction such as the following:
public interface IApplicationTailoredAbstraction
{
Task DoSomething();
}
Application code that originally depended on the third-party component can now start to take a dependency on IApplicationTailoredAbstraction instead.
Inside your application's Composition Root, you can now create an Apapter that adapts from IApplicationTailoredAbstraction to the third-party component:
public class AppTailoredToThirdPartyComponentAdapter
: IApplicationTailoredAbstraction
{
public AppTailoredToThirdPartyComponentAdapter(IFirstService firstService) ...
// Optionally cache the password (depending on your needs)
private string password;
public async Task DoSomething()
{
if (password is null)
{
password = await firstService.GetPasswordAsync();
}
// Create the third-party library component
var service = new SecondService(password);
// Invoke its method(s)
await service.Run();
}
}
The registration of your components can now be done as follows:
builder.Services.AddFirstService();
builder.AddSingleton<IApplicationTailoredAbstraction,
AppTailoredToThirdPartyComponentAdapter>();

Dependency Injection Access While Configuring Service Registrations in asp.net Core (3+)

I have cases, where I want to configure services based on objects which are registered in the dependency injection container.
For example I have the following registration for WS Federation:
authenticationBuilder.AddWsFederation((options) =>{
options.MetadataAddress = "...";
options.Wtrealm = "...";
options.[...]=...
});
My goal in the above case is to use a configuration object, which is available via the DI container to configure the WsFederation-middleware.
It looks to me that IPostConfigureOptions<> is the way to go, but until now, I have not found a way to accomplish this.
How can this be done, or is it not possible?
See https://andrewlock.net/simplifying-dependency-injection-for-iconfigureoptions-with-the-configureoptions-helper/ for the I(Post)ConfigureOptions<T> way, but I find that way too cumbersome.
I generally use this pattern:
// Get my custom config section
var fooSettingsSection = configuration.GetSection("Foo");
// Parse it to my custom section's settings class
var fooSettings = fooSettingsSection.Get<FooSettings>()
?? throw new ArgumentException("Foo not configured");
// Register it for services who ask for an IOptions<FooSettings>
services.Configure<FooSettings>(fooSettings);
// Use the settings instance
services.AddSomeOtherService(options => {
ServiceFoo = fooSettings.ServiceFoo;
})
A little more explicit, but you have all your configuration and DI code in one place.
Of course this bypasses the I(Post)ConfigureOptions<T> entirely, so if there's other code that uses those interfaces to modify the FooSettings afterwards, my code won't notice it as it's reading directly from the configuration file. Given I control FooSettings and its users, that's no problem for me.
This should be the approach if you do want to use that interface:
First, register your custom config section that you want to pull the settings from:
var fooSettingsSection = configuration.GetSection("Foo");
services.Configure<FooSettings>(fooSettingsSection);
Then, create an options configurer:
public class ConfigureWSFedFromFooSettingsOptions
: IPostConfigureOptions<Microsoft.AspNetCore.Authentication.WsFederation.WsFederationOptions>
{
private readonly FooSettings _fooSettings;
public ConfigureWSFedFromFooSettingsOptions(IOptions<FooSettings> fooSettings)
{
_fooSettings = fooSettings.Value;
}
public void Configure(WsFederationOptions options)
{
options.MetadataAddress = _fooSettings.WsFedMetadataAddress;
options.Wtrealm = _fooSettings.WsFedWtRealm;
}
}
And finally link the stuff together:
services.AddTransient<IPostConfigureOptions<WsFederationOptions>, ConfigureWSFedFromFooSettingsOptions>();
The configurer will get your IOptions<FooSettings> injected, instantiated from the appsettings, and then be used to further configure the WsFederationOptions.

ABP IO Code sample for run multiple databases for multi-tenancy

Please notice that I am talking about ABP.io, not the Boilerplate framework.
The in-build free module Tenant-Management is developed to work with multiple tenants and a unique database. however, the documentation says that the framework has a built-in friendly way to use the multiple database approach, including:
new dbContext
database migration and seeding
Connection String service
I am new in ABP IO, and I want a sample that employs the framework elements to implement a single database for every tenant.
I get started by overriding the tenant create sync method of the tenant management module as follows.
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(ITenantAppService), typeof(TenantAppService), typeof(ExtendedTenantManagementAppService))]
public class ExtendedTenantManagementAppService : TenantAppService
{
public ExtendedTenantManagementAppService(ITenantRepository tenantRepository,
ITenantManager tenantManager,
IDataSeeder dataSeeder) : base(tenantRepository, tenantManager, dataSeeder)
{
LocalizationResource = typeof(WorkspacesManagerResource);
ObjectMapperContext = typeof(WorkspacesManagerApplicationModule);
}
public override async Task<TenantDto> CreateAsync(TenantCreateDto input)
{
var tenant = await TenantManager.CreateAsync(input.Name);
input.MapExtraPropertiesTo(tenant);
await TenantRepository.InsertAsync(tenant);
await CurrentUnitOfWork.SaveChangesAsync();
using (CurrentTenant.Change(tenant.Id, tenant.Name))
{
//TODO: Handle database creation?
// create database
// migrate
// seed with essential data
await DataSeeder.SeedAsync(
new DataSeedContext(tenant.Id)
.WithProperty("AdminEmail", input.AdminEmailAddress)
.WithProperty("AdminPassword", input.AdminPassword)
);
}
return ObjectMapper.Map<Tenant, TenantDto>(tenant);
}
}
Any code sample?

How to retrieve RouteValues in ActionSelector in ASP.NET Core

I've created a GitHub repo to better understand the problem here. I have two actions on two different controllers bound to the same route.
http://localhost/sameControllerRoute/{identifier}/values
[Route("sameControllerRoute")]
public class FirstController : Controller
{
public FirstController()
{
// different EF Core DataContext than SecondController and possibly other dependencies than SecondController
}
[HttpGet("{identifier}/values")]
public IActionResult Values(string identifier, DateTime from, DateTime to) // other parameters than SecondController/Values
{
return this.Ok("Was in FirstController");
}
}
[Route("sameControllerRoute")]
public class SecondController : Controller
{
public SecondController()
{
// different EF Core DataContext than FirstController and possibly other dependencies than FirstController
}
[HttpGet("{identifier}/values")]
public IActionResult Values(string identifier, int number, string somethingElse) // other parameters than FirstController/Values
{
return this.Ok("Was in SecondController");
}
}
Since there are two matching routes, the default ActionSelector fails with:
'[...] AmbiguousActionException: Multiple actions matched. [...]'
which is comprehensible.
So I thought I can implement my own ActionSelector. In there I would implement the logic that resolves the issue of multiple routes via same logic depending on the 'identifier' route value (line 27 in code)
If 'identifier' value is a --> then FirstController
If 'identifier' value is b --> then SecondController
and so on...
protected override IReadOnlyList<ActionDescriptor> SelectBestActions(IReadOnlyList<ActionDescriptor> actions)
{
if (actions.HasLessThan(2)) return base.SelectBestActions(actions); // works like base implementation
foreach (var action in actions)
{
if (action.Parameters.Any(p => p.Name == "identifier"))
{
/*** get value of identifier from route (launchSettings this would result in 'someIdentifier') ***/
// call logic that decides whether value of identifier matches the controller
// if yes
return new List<ActionDescriptor>(new[] { action }).AsReadOnly();
// else
// keep going
}
}
return base.SelectBestActions(actions); // fail in all other cases with AmbiguousActionException
}
But I haven't found a good solution to get access to the route values in ActionSelector. Which is comprehensible as well because ModelBinding hasn't kicked in yet since MVC is still trying to figure out the Route.
A dirty solution could be to get hold of IHttpContextAccessor and regex somehow against the path.
But I'm still hoping you could provide a better idea to retrieve the route values even though ModelBinding hasn't happend yet in the request pipeline.
Not sure that you need to use ActionSelector at all for your scenario. Accordingly, to provided code, your controllers works with different types of resources (and so they expect different query parameters). As so, it is better to use different routing templates. Something like this for example:
FirstController: /sameControllerRoute/resourceA/{identifier}/values
SecondController: /sameControllerRoute/resourceB/{identifier}/values
In the scope of REST, when we are talking about /sameControllerRoute/{identifier}/values route template, we expect that different identifier means the same resource type, but different resource name. And so, as API consumers, we expect that all of the following requests are supported
/sameControllerRoute/a/values?from=20160101&to=20170202
/sameControllerRoute/b/values?from=20160101&to=20170202
/sameControllerRoute/a/values?number=1&somethingElse=someData
/sameControllerRoute/b/values?number=1&somethingElse=someData
That is not true in your case
I ended up implementing the proposed solution by the ASP.NET team. This was to implement an IActionConstrain as shown here:
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
namespace ActionConstraintSample.Web
{
public class CountrySpecificAttribute : Attribute, IActionConstraint
{
private readonly string _countryCode;
public CountrySpecificAttribute(string countryCode)
{
_countryCode = countryCode;
}
public int Order
{
get
{
return 0;
}
}
public bool Accept(ActionConstraintContext context)
{
return string.Equals(
context.RouteContext.RouteData.Values["country"].ToString(),
_countryCode,
StringComparison.OrdinalIgnoreCase);
}
}
}
https://github.com/aspnet/Entropy/blob/dev/samples/Mvc.ActionConstraintSample.Web/CountrySpecificAttribute.cs

Can i use System.Runtime.Caching.MemoryCache in cloud web role project?

I would like to use System.Runtime.Caching.MemoryCache in Web role project which contains WCF Service.
Can anybody please let me know that whether we can use System.Runtime.Caching.MemoryCache in Cloud web role project?
If yes please let me know Memory and other constraints.
Yes you can.
You should add the reference to System.Runtime.Caching to the Web Role project, then use something like the code below (it is doing almost nothing and is not a best practice, for sure).
Just tried it with ASP.NET MVC in the Cloud Web Role with the Azure Emulator and it works.
Regarding limits - there are two CacheMemoryLimit and PhysicalMemoryLimit properties you can use for retrieve the needed values. It shows the limit in bytes. I do not know if there are any limits beyond these in terms of in-memory cache in Azure Cloud Services.
private static object _lock = new Object();
private static MemoryCache _cache = new MemoryCache("ThisIsMyCache");
public static object GetItem(string key)
{
lock (_lock)
{
var item = _cache.Get(key);
if (item == null)
{
item = InitiaizeItem(key);
_cache.Set(key, item, new CacheItemPolicy());
}
return item;
}
}
private static object InitiaizeItem(string key)
{
return new { Value = key };
}