.Net - services.Configure bind to already instanciated entity - c#

I've created the following extension to simplify the app configurations in our many projects
public static TAppSettings AddAppSettings<TAppSettings>(this IServiceCollection services,
IConfiguration configuration, string sectionName = BaseAppSettings.DefaultSectionName,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TAppSettings : BaseAppSettings
{
var appSettingsSection = configuration.GetSection(sectionName);
var appSettings = appSettingsSection.Get<TAppSettings>();
if (appSettings == null) throw new NullReferenceException(nameof(appSettings));
services.Configure<TAppSettings>(appSettingsSection);
services.Add(new ServiceDescriptor(typeof(TAppSettings),serviceProvider =>appSettings , lifetime));
return appSettings;
}
Which allows me to call it like
services.AddAppSettings<AppSettings>(context.Configuration);
Is there anyway to bind an already defined object instance,like one with some default values?
I've tried the following code, but any value inside IOptions are empty
public static TAppSettings AddAppSettings<TAppSettings>(this IServiceCollection services, IConfiguration configuration,
TAppSettings appSettings,
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TAppSettings : BaseAppSettings
{
if (appSettings == null) throw new NullReferenceException(nameof(appSettings));
services.Configure<TAppSettings>(options=>options=appSettings);
return appSettings;
}
Update
I know that is unusual but imagine that i've an application that doesn't uses appsettings.json. I want to set some values to my configurations(I know it's possible to set the default values in the class), but imagine that don't want to set some default values there, because they can change from app to app that doesn't uses appsettings.
but i still want to inject IOptions;
Any ideas?

Given that you want to register an object (representing some settings) as IOptions<T>, you just need to wrap that instance with an IOptions<T>.
Options.Create<T> does that.
Creates a wrapper around an instance of TOptions to return itself as an IOptions<TOptions>.
public static class Extenions
{
public static TAppSettings AddAppSettings<TAppSettings>(
this IServiceCollection services, TAppSettings appSettings,
) where TAppSettings : class
{
services.AddSingleton(typeof(IOptions<TAppSettings>), Options.Create(appSettings));
return appSettings;
}
}
The registration exists of creation an AppSettings instance, setting some properties and calling that extension method.
The lifetime of such an existing instance will always need to be a singleton, since you'll be creating that instance only once.
var appSettings = new AppSettings();
// Set some properties on appSettings.
services.AddAppSettings(appSettings);
Now your other classes can have an IOptions<AppSettings> instance injected.

this line
services.Configure<TAppSettings>(options=>options=appSettings);
Won't work
You need to construct an Action like
public static void AddAppSettings<TAppSettings>(this IServiceCollection services,TAppSettings appSettings)
where TAppSettings : class
{
if (appSettings == null) throw new NullReferenceException(nameof(appSettings));
ParameterExpression paraexpression = Expression.Parameter(appSettings.GetType(), "x");
foreach (var prop in appSettings.GetType().GetProperties())
{
if (prop.PropertyType.Name=="String")
{
ConstantExpression val = Expression.Constant(prop.GetValue(appSettings));
MemberExpression memberexpression = Expression.PropertyOrField(paraexpression, prop.Name);
BinaryExpression assign = Expression.Assign(memberexpression, val);
Expression<Action<TAppSettings>> exp = Expression.Lambda<Action<TAppSettings>>(assign, new ParameterExpression[] { paraexpression });
services.Configure<TAppSettings>(exp.Compile());
}
}
}

Based on #pfx answer
I've improved a little bit adding also the IOptionsMonitor
Create a AppSettingsExtensions.cs class:
public static class AppSettingsExtensions
{
public static TAppSettings AddAppSettings<TAppSettings>(this IServiceCollection services,
IConfiguration configuration, string sectionName = "AppSettings",
ServiceLifetime lifetime = ServiceLifetime.Singleton)
where TAppSettings : class
{
var appSettingsSection = configuration.GetSection(sectionName);
var appSettings = appSettingsSection.Get<TAppSettings>();
if (appSettings == null) throw new NullReferenceException(nameof(appSettings));
services.Configure<TAppSettings>(appSettingsSection);
services.Add(new ServiceDescriptor(typeof(TAppSettings), serviceProvider => appSettings, lifetime));
return appSettings;
}
public static TAppSettings AddAppSettings<TAppSettings>(this IServiceCollection services, IConfiguration configuration,
TAppSettings appSettings,
Func<IEnumerable<IOptionsChangeTokenSource<TAppSettings>>> optionsChangeTokenSourceBuilder = null,
ServiceLifetime lifetime = ServiceLifetime.Singleton)
where TAppSettings : class
{
if (appSettings == null) throw new NullReferenceException(nameof(appSettings));
var appSettingsConfigureOptions = configuration.CreateAppSettingsConfigureOptions<TAppSettings>();
appSettingsConfigureOptions.Configure(appSettings);
var appSettingsOptions = Options.Create(appSettings);
var appSettingsMonitor = AddAppSettingsMonitor(services, configuration, appSettingsConfigureOptions);
services.Add(appSettingsOptions,lifetime);
services.Add(appSettingsMonitor, lifetime);
return appSettings;
}
private static IOptionsMonitor<TAppSettings> AddAppSettingsMonitor<TAppSettings>(IServiceCollection services,
IConfiguration configuration, ConfigureFromConfigurationOptions<TAppSettings> appSettingsConfigureOptions)
where TAppSettings : class
{
var appSettingsOptionsFactory = services.CreateAppSettingsOptionsFactory(appSettingsConfigureOptions);
var appSettingsMonitor = new OptionsMonitor<TAppSettings>(appSettingsOptionsFactory,
configuration.CreateAppSettingsOptionsChangeTokenSources<TAppSettings>(), new OptionsCache<TAppSettings>());
return appSettingsMonitor;
}
private static IEnumerable<IOptionsChangeTokenSource<TAppSettings>> CreateAppSettingsOptionsChangeTokenSources<TAppSettings>(this IConfiguration configuration,
Func<IEnumerable<IOptionsChangeTokenSource<TAppSettings>>> builder=null) where TAppSettings : class
{
return builder?.Invoke()??Enumerable.Empty<IOptionsChangeTokenSource<TAppSettings>>();
}
public static ConfigureFromConfigurationOptions<TAppSettings> CreateAppSettingsConfigureOptions<TAppSettings>(this IConfiguration configuration) where TAppSettings : class
{
return new ConfigureFromConfigurationOptions<TAppSettings>(configuration);
}
public static OptionsFactory<TAppSettings> CreateAppSettingsOptionsFactory<TAppSettings>(this IServiceCollection services,ConfigureFromConfigurationOptions<TAppSettings> appSettingsConfigureOptions) where TAppSettings : class
{
return new OptionsFactory<TAppSettings>(new[] {appSettingsConfigureOptions},
Enumerable.Empty<IPostConfigureOptions<TAppSettings>>());
}
}
Create a DependencyInjectionExtensions.cs class:
public static class DependencyInjectionExtensions
{
public static void Add<TService>(
this IServiceCollection services,
ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
where TService : class
{
switch (serviceLifetime)
{
case ServiceLifetime.Singleton:
services.AddSingleton<TService>();
break;
case ServiceLifetime.Scoped:
services.AddScoped<TService>();
break;
case ServiceLifetime.Transient:
services.AddTransient<TService>();
break;
default:
throw new ArgumentOutOfRangeException(nameof(serviceLifetime), (object) serviceLifetime, null);
}
}
public static void Add<TService, TImplementation>(
this IServiceCollection services,
ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
where TService : class
where TImplementation : class, TService
{
switch (serviceLifetime)
{
case ServiceLifetime.Singleton:
services.AddSingleton<TService, TImplementation>();
break;
case ServiceLifetime.Scoped:
services.AddScoped<TService, TImplementation>();
break;
case ServiceLifetime.Transient:
services.AddTransient<TService, TImplementation>();
break;
default:
throw new ArgumentOutOfRangeException(nameof(serviceLifetime), (object) serviceLifetime, null);
}
}
public static void Add<TService>(
this IServiceCollection services,
TService service,
ServiceLifetime serviceLifetime = ServiceLifetime.Singleton)
where TService : class
{
switch (serviceLifetime)
{
case ServiceLifetime.Singleton:
services.AddSingleton<TService>(service);
break;
case ServiceLifetime.Scoped:
services.AddScoped<TService>((Func<IServiceProvider, TService>) (_ => service));
break;
case ServiceLifetime.Transient:
services.AddTransient<TService>((Func<IServiceProvider, TService>) (_ => service));
break;
default:
throw new ArgumentOutOfRangeException(nameof(serviceLifetime), (object) serviceLifetime, null);
}
}
}
I know that there's a lot going on here, but my goal was to provide a library to help resolving common code between projects, this way I could simplify some dependency injection code.
Hope it helps, and please feel free to make suggestions to this code, I'll be trully grateful to make those extensions even better

Related

ServiceProvider.GetService() .net core returning the null object

In my repository level I'm trying to use ServiceProvider.GetService() and it is returning null. I'm not able to resolve these.
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddTransient<IUserUnitofWork, UserUnitofWork>();
}
now I have user detail class and I'm trying to access the
public class UserDetail
{
private IserviceProvider _serviceProvider;
UserDetail(IserviceProvider serviceProvider)
{
_serviceProvider = serviceProvider
}
public addUser()
{
using (var scope = _serviceProvider.CreateScope())
{
var uow = (IUserUnitofWork)scope.ServiceProvider.GetService(typeof(IUserUnitofWork));
isCreateSuccess = uow.addData(userlist);
// above code is throwing null error
}
}
}

How to use IHttpClientFactory in class library

I am trying to convert some code from net core api to class library.
I am stuck how to use HttpClientfactory.
Normally the httpclientfactory can be configured in program.cs or Startup like
services.AddHttpClient("abc", xxx config).
How to do configurations in class library for Httpclientfactory.
In your library add an extension method for IServiceCollection to "enable" it in the main project.
In the library:
public static class ServiceCollectionExt
{
public static void AddYourStaff(this IServiceCollection services)
{
services.AddHttpClient("xxx", client =>
{
//your staff here
});
services.AddSingleton<ISomethingElse, SomethingElse>();
}
}
Then in your Startup just call it:
services.AddYourStaff();
UPDATE: As the author described, he's working on the plugin based application. In that case you need some kind of convention, for instance:
each plugin library must have a static class called Registration with the method Invoke(IServiceCollection sc, IConfiguration config)
Then in your Startup you can iterate through all plugin libraries and call their Registration.Invoke(sc, config) using reflection:
foreach(var pluginAssembly in plugins)
{
pluginAssembly
.GetType("Registration")
.GetMethod("Invoke")
.Invoke(null, new object[] {services, Configuration});
}
You could try as below:
public class HttpClientUtil
{
private static IServiceProvider serviceProvider { get; set; }
public static void Initial(IServiceProvider Provider)
{
if (Provider == null)
{
IHostBuilder builder = Host.CreateDefaultBuilder();
builder.ConfigureServices(services =>
{
services.AddHttpClient("client_1", config =>
{
config.BaseAddress = new Uri("http://client_1.com");
config.DefaultRequestHeaders.Add("header_1", "header_1");
});
services.AddHttpClient("client_2", config =>
{
config.BaseAddress = new Uri("http://client_2.com");
config.DefaultRequestHeaders.Add("header_2", "header_2");
});
});
serviceProvider = builder.Build().Services;
}
else
{
serviceProvider = Provider;
}
}
public static IHttpClientFactory GetHttpClientFactory()
{
if(serviceProvider==null)
{
Initial(serviceProvider);
}
return (IHttpClientFactory)serviceProvider.GetServices<IHttpClientFactory>();
}
}
you could get the instance of httpclinetfactory through the interface

How dependency injection work for covariance type in .NET Core

Recently I was working with ASP.net core 3.0 and got stuck.
As we know how constructor inject work for dependency inject in .NET Core.
e.g.
public class A: IA{
private readonly IB<B> iB;
public A(IB<B> iB){
this.iB = iB;
}
}
Dependency resolve somehow like below.
IB<B> iB = new B();
IA iA = new A(iB);
That is fine. i understood but what about "Covariance generic type"
e.g ILogger<ControllerName>
See the below Code
public class AController {
private readonly ILogger<AController> iLogger;
public AController(ILogger<AController> iLogger){
this.iLogger = iLogger;
}
}
so in this case its looks like AController is getting circular reference.
If you look for the defination of ILogger it is like
ILogger<out TCategoryName> : ILogger { }
Covariance generic type.
For ILogger<out TCategoryName> : ILogger { }, it would not reference TCategoryName, TCategoryName will be used as string to indicates category name while logging.
You could check the source code for ILogger by Logging.
Here is the process.
For ILogger<AController>, it is crated by LoggerFactoryExtensions
public static ILogger<T> CreateLogger<T>(this ILoggerFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
return new Logger<T>(factory);
}
For new Logger<T>(factory);, it will get category name with TypeNameHelper.GetTypeDisplayName(typeof(T), includeGenericParameters: false, nestedTypeDelimiter: '.')
public Logger(ILoggerFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
_logger = factory.CreateLogger(TypeNameHelper.GetTypeDisplayName(typeof(T), includeGenericParameters: false, nestedTypeDelimiter: '.'));
}
And then, it will create logger with cateogoryname as parameter
public ILogger CreateLogger(string categoryName)
{
if (CheckDisposed())
{
throw new ObjectDisposedException(nameof(LoggerFactory));
}
lock (_sync)
{
if (!_loggers.TryGetValue(categoryName, out var logger))
{
logger = new Logger
{
Loggers = CreateLoggers(categoryName),
};
(logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers);
_loggers[categoryName] = logger;
}
return logger;
}
}
During CreateLoggers process, it just use category name as string.
For the whole process, it does not create an instace for TCategoryName, so it will not get circular reference.

How to retrieve configuration 'Options' instances from a ModelBinder?

I'm building a custom ModelBinder and I need to retrieve the MvcJsonOptions config instance that was set in Startup from
services.AddMvc(options => {...})
.AddJsonOptions(options => {
//I need this 'option' instance from my model binder
});
Not sure whether I should retrieve them from the service provider, what would be the best approach to retrieve them?
Dunno who told you it's not retrievable, but all configurations are registered via DI, even MvcOptions and MvcJsonOptions, as you can clearly see on the source code here
public static IMvcBuilder AddJsonOptions(
this IMvcBuilder builder,
Action<MvcJsonOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
// configure registers it with the DI system
builder.Services.Configure(setupAction);
return builder;
}
That being said, all you need to do is inject IOptions<MvcJsonOptions> where ever you need it and access options.Value property to get the instance.
Update
As pointed in the comments, IModelBinderProvider isn't supposed to have dependencies injected. IModelBinderProvider is only used to create the binder and should have no external dependencies.
public class MyBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (/* some condition to identify your model */)
return new BinderTypeModelBinder(typeof(MyBinder));
return null;
}
}
and MyBinder should have the dependencies:
public class MyBinder : IModelBinder
{
private readonly MvcJsonOptions jsonOptions;
public MyBinder(IOptions<MvcJsonOptions> options)
{
jsonOptions = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
// Your binding logic here
...
return Task.CompletedTask;
}
}

IHttpContextAccessor null after injecting

I'm currently developing a system where dependent on what domain a request comes from different website settings need to be loaded (eg. default language id) which is then used in the rest of the application. These settings are stored in a class WebsiteSettings which are injected into the rest of the application when needed.
The first option I tried was registering a service to access the HttpContext by doing this in my ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
//Register other services
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddScoped<WebsiteSettingsFiller>();
services.TryAddScoped(typeof(WebsiteSettings), s =>
{
var settingFiller = s.GetService<WebsiteSettingsFiller>();
return settingFiller.Create();
});
}
Next, in my WebsiteSettingsFiller service, I inject the IHttpContextAccessor and some other services that I need to load the site settings.
public class WebsiteSettingsFiller
{
protected readonly IRepository Database;
private readonly IHttpContextAccessor _accessor;
private readonly StartupSitePropertyService _sitePropertyService;
private IQueryable<Site> AllSites => Database.All<Site>();
private IQueryable<SiteLanguage> AllSiteLanguages => Database.All<SiteLanguage>();
public WebsiteSettingsFiller(IRepository db, StartupSitePropertyService siteProperties, IHttpContextAccessor accessor)
{
Database = db;
_accessor = accessor;
_sitePropertyService = siteProperties;
}
public WebsiteSettings Create()
{
var domain = _accessor.HttpContext.Request.Host.Host; //null exception on this line
#if DEBUG
domain = "www.somewebsite.com";
#endif
var config = GetConfigByDomain(domain);
return config;
}
private WebsiteSettings GetConfigByDomain(string domain)
{
var site = AllSites.OrderByDescending(s => s.Created).FirstOrDefault(t => t.Host == domain);
if (site == null) return null;
var languages = AllSiteLanguages.Where(sl => sl.SiteId == site.Id).ToList();
//get more variables
return new WebsiteSettings
{
/* Set variables */
}
}
}
Example injection of WebsiteSettings:
public class RouteService : BaseService
{
private IDictionary<int, string> _routeLanguages = null;
private readonly WebsiteRedisService _websiteRedisService;
public RouteService(IRepository db,
WebsiteSettings settings,
WebsiteRedisService websiteRedisService)
: base(db, settings)
{
_websiteRedisService = websiteRedisService;
}
public async Task<IDictionary<int, string>> RouteLanguagesAsync()
{
return _routeLanguages ??
(_routeLanguages = await _websiteRedisService.SiteLanguagesToAsync(Settings.SiteId));
}
}
Sadly, no matter what I try the HttpContext reference is always null. Does anyone have any idea what I can try to resolve this? Or am I just approaching this problem the wrong way? Any suggestions are greatly appreciated!

Categories

Resources