I'm working in a class library that does something according to the configuration set by the user in Setup.cs (I still dont konw which method fits better, Configure or ConfigureServices).
Soon, my library will be in nuget, users will can install it and configure it. Question is, how can create that options/config class, instantiate that class in Startup.cs (Configure or ConfigureServices) and pass that options to my class/lib/package?
Here goes my doubt in practice:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMyLib(s => s.Value = 1);
}
Inside my class library/nuget package
public class CalculationHelper
{
public bool GetSomething()
{
if (Options.Value == 1)
return true;
return false;
}
}
In extension method (DI)
public static void AddMyLib(this IServiceCollection app, Action<Options> options = null)
{
// Here in this Extension method, I need save this options that I can retrieve for my class library (CalculationHelper).
}
I have seen much libraries using this method of configuration, like, Swagger, AutoMapper, Serilog, etc.
This is as much as I can specify, I hope you understand.
Assuming
public class YourOptions {
public int Value { get; set; } = SomeDefaultValue;
}
public class YourService : IYourService {
private readonly YourOptions options;
public YourService (YourOptions options) {
this.options = options;
}
public bool GetSomething() {
if (options.Value == 1)
return true;
return false;
}
}
Create your extension method that allows for the option to be configured while adding you services.
public static class MyLibServiceCollectionExtensions {
public static IServiceCollection AddMyLib(this IServiceCollection services,
Action<YourOptions> configure = null) {
//add custom options and allow for it to be configured
if (configure == null) configure = o => { };
services.AddOptions<YourOptions>().Configure(configure);
services.AddScoped(sp => sp.GetRequiredService<IOptions<YourOptions>>().Value);
//...add other custom service for example
services.AddScoped<IYourService, YourService>();
return services;
}
}
Users of your library will then configure as needed
public void ConfigureServices(IServiceCollection services) {
services.AddMyLib(options => options.Value = 1);
//...
}
And when using your service
public SomeClass(IYourService service) {
bool result = service.GetSomething();
}
Yes, the standard practice is to use IOptions<T>. I personally am not a fan of injecting that and tend to use the pattern modeled above. I do still register it for those who would still like to use it.
Related
I want to be able to do services.AddMyCustomLibrary() like Telerik and sweetalert do rather than having to add every service from MyCustomLibrary like
services.AddSingleton<MyCUstomLibrary.MyService>();
What is the code I would add to MyCustomLibrary to make this work?
I want this:
builder.Services.AddTelerikBlazor();
builder.Services.AddSweetAlert2(options => {
options.Theme = SweetAlertTheme.Bootstrap4;
});
Not this:
builder.Services.AddScoped<ComponentService>();
builder.Services.AddScoped<AppState>();
You need to create static class with extension method for IServiceCollection. Inside of extension method, you can write your library registrations:
public static class Service Collection Extension {
public static IServiceCollection AddMyCustomLibrary(this IServiceCollection services) {
services. AddSingleton<MyCUstomLibrary.MyService>();
}
}
Then usage looks like this:
services.AddMyCustomLibrary();
Edit: you can create also options action that allows you pass some properties to your libraries.
public class MyCustomLibraryOptions {
public string MyProperty { get; set; }
}
public static class Service Collection Extension {
public static IServiceCollection AddMyCustomLibrary(this IServiceCollection services, Action<MyCustomLibraryOptions> configure) {
var options = new MyCustomLibraryOptions();
configure?.Invoke(options);
var myProp = options.MyProperty; //You can access options after action invocation.
services. AddSingleton<MyCUstomLibrary.MyService>();
}
}
Usage:
services.AddMyCustomLibrary(config => {
config.MyProperty = "some value";
});
I have a small class to obtain a series of information about my user on several of my MVC applications. A minimal reproducible example would be:
public class InformationGetter
{
public string GetUserInformation(string connectionStr, string storedProcedureName, int userId)
{
// Do SQL work
return info;
}
}
I'm injecting it on the ConfigureServices step using
services.AddScoped<InformationGetter>
And then in my classes I simply call it from the DI.
Now, obviously the connectionStr and storedProcedure only changes per application but right now I'm passing it as parameter.
I've tried to make those parameters public and configure it using services.Configure but when I call it from my controllers, I get null values.
services.AddOptions();
services.Configure<InformationGetter>(options =>
{
options.ConnectionString = Configuration.GetSection("Model").GetSection("ConnectionString").Value;
options.StoredProcedureName = "prInformationGetter";
});
I'm not sure if the reason why this is failing it's because I'm missing an interface on my original class or am I failing to understand this concept.
I've also thought on doing something like services.AddInformationGetter(options => {}) but my understanding is that this pattern is to implement middlewares and not DI specifically.
I tried checking the documentation (learn.microsoft.com) but I got even more confused.
There may be misunderstanding of the concepts involved.
Configure<TOption> will register IOptions<TOptions>. There are now two separate registrations in your example.
Once when you register the class
services.AddScoped<InformationGetter>()
and the other when you register the options.
Do the following
//..
services.AddOptions();
//Adds IOptions<InformationGetter>
services.Configure<InformationGetter>(options => {
options.ConnectionString = Configuration.GetSection("Model").GetSection("ConnectionString").Value;
options.StoredProcedureName = "prInformationGetter";
});
//Adds InformationGetter but gets it from the registered options
services.AddScoped<InformationGetter>(sp =>
sp.GetRequiredService<IOptions<InformationGetter>>().Value
);
//...
The scoped registration will use the factory delegate to extract the options registered and return the desired type.
public class InformationGetter {
public string ConnectionString { get; set; }
public string StoredProcedureName { get; set; }
//...
public string GetUserInformation(int userId) {
// Do SQL work
return info;
}
}
InformationGetter looks like a service.
I would suggest refactoring to follow a more Single Responsibility Principle (SRP) and Separation of Concerns (Soc) design.
//Needed by InformationGetter to perform its function
public class InformationGetterOptions {
public string ConnectionString { get; set; }
public string StoredProcedureName { get; set; }
}
//abstraction of InformationGetter
public interface IInformationGetter {
string GetUserInformation(int userId);
}
//implementation.
public class InformationGetter : IInformationGetter{
private readonly InformationGetterOptions options;
public InformationGetter(InformationGetterOptions options) {
this.options = options;
}
public string GetUserInformation(int userId) {
//use values in options to connect
// Do SQL work
return info;
}
}
I would have avoid options pattern altogether and just registered the class using the delegate factory, extracting what I need from configuration. That way your code is not tightly coupled to framework concerns like IOptions
public void ConfigureServices(IServiceCollection services) {
//...
InformationGetterOptions options = new InformationGetterOptions {
ConnectionString = Configuration.GetSection("Model").GetSection("ConnectionString").Value;
StoredProcedureName = "prInformationGetter";
};
services.AddSingleton(options);
services.AddScoped<IInformationGetter, InformationGetter>();
//...
}
Now IInformationGetter can be injected where needed and have all the necessary dependencies to perform its function.
is it possible to use Microsoft.Extensions.Logging like use logging in controllers(put in constructor and framework handle it with DI), in class library which my ASP.NET Core web application use that library? and how instantiate class and use method?
public class MyMathCalculator
{
private readonly ILogger<MyMathCalculator> logger;
public MyMathCalculator(ILogger<MyMathCalculator> logger)
{
this.logger = logger;
}
public int Fact(int n)
{
//logger.LogInformation($"Fact({n}) called.");
if (n == 0)
{
return 1;
}
return Fact(n - 1) * n;
}
}
Taked from a previous answer:
...That is the magic of dependency injection, just let the system create the object for you, you just have to ask for the type.
This is also a big topic, ... basically, all you have to do is to define classes as dependencies, so, when you ask for one, the system itself check the dependencies, and the dependencies of that objects, until resolves all the tree of dependencies.
With this, if you need one more dependency latter in your class, you can add directly but you do not need to modify all the classes that uses that class.
To use this in the controller, please check the official docs, you just have to add you dependencies to the constructor, and win!, basically two parts:
Add in your Startup.class
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<MySpecialClassWithDependencies>();
...
}
Then in your controller:
public class HomeController : Controller
{
private readonly MySpecialClassWithDependencies _mySpecialClassWithDependencies;
public HomeController(MySpecialClassWithDependencies mySpecialClassWithDependencies)
{
_mySpecialClassWithDependencies = mySpecialClassWithDependencies;
}
public IActionResult Index()
{
// Now i can use my object here, the framework already initialized for me!
return View();
}
This sould be no different if you library class is in other project, at the end of the day you will be adding the class to the startup, that is how asp net knows what to load.
If you want your code clean, you can use an Extension method to group all your declarations and the just calling services.AddMyAwesomeLibrary(), for example:
In your awesomeLibraryProject:
public static class MyAwesomeLibraryExtensions
{
public static void AddMyAwesomeLibrary(this IServiceCollection services)
{
services.AddSingleton<SomeSingleton>();
services.AddTransient<SomeTransientService>();
}
}
And in your Startup
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMyAwesomeLibrary();
}
I am constructing a HealthAPI Class Library Which provides a list of statistics to our HealthMonitor Service.
I have successfully got this working, The Middleware is recording Service boot time and recording response times, our health monitor is able to parse these values via a call to a our StatusController which has a number of actions returning IActionResult JSON responses.
We intend to reuse this over all of our services so have opted to keep the API controller within the Class Library along with the DI Service and middleware, to make the Controller accessable I originally did the following.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddApplicationPart(Assembly.Load(new AssemblyName("HealthApiLibrary"))); //Bring in the Controller for HealthAPI;
services.AddSingleton<HealthApiService>();
}
However at the refactoring stage I want to clean this up a little by doing the following:
1) Refactor services.AddSingleton<HealthApiService>(); into services.AddHealthApi(); (Which we have not done any work towards just yet, but still may be relevent when answering this question)
2) Load in my StatusController as part of the services.AddHealthApi(); call.
I have tried the following:
public class HealthApiService
{
public HealthApiService(IMvcBuilder mvcBuilder)
{
mvcBuilder.AddApplicationPart(Assembly.Load(new AssemblyName("HealthApiLibrary"))); //Bring in the Controller for HealthAPI
ResponseTimeRecords = new Dictionary<DateTime, int>();
ServiceBootTime = DateTime.Now;
}
public DateTime ServiceBootTime { get; set; }
public Dictionary<DateTime,int> ResponseTimeRecords { get; set; }
public string ApplicationId { get; set; }
}
however this just generates the following error:
InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.DependencyInjection.IMvcBuilder' while attempting to activate 'HealthApiLibrary.Services.HealthApiService'.
1. Dependency injection
You get the exception because there is no IMvcBuilder registered in the service collection. I would not make sense to add this type to the collection as it is only used during the startup.
2. Extension method
You could create an extension method to achieve the method you wanted.
public static class AddHealthApiExtensions
{
public static void AddHealthApi(this IServiceCollection services)
{
services.AddSingleton<HealthApiService>();
}
}
3. Assembly.Load
Look at the comment from #Tseng.
From what I gather, you are trying to allow the end user to supply their own dependencies to your HealthApiService. This is typically done using an extension method and one or more builder patterns. It is not a DI problem, but an application composition problem.
Assuming HealthApiService has 2 dependencies, IFoo and IBar, and you want users to be able to supply their own implementation for each:
public class HealthApiService : IHealthApiService
{
public HealthApiService(IFoo foo, IBar bar)
{
}
}
Extension Method
The extension method has one overload for the default dependencies and one for any custom dependencies.
public static class ServiceCollectionExtensions
{
public static void AddHealthApi(this IServiceCollection services, Func<HealthApiServiceBuilder, HealthApiServiceBuilder> expression)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (expression == null)
throw new ArgumentNullException(nameof(expression));
var starter = new HealthApiServiceBuilder();
var builder = expression(starter);
services.AddSingleton<IHealthApiService>(builder.Build());
}
public static void AddHealthApi(this IServiceCollection services)
{
AddHealthApi(services, builder => { return builder; });
}
}
Builder
The builder is what helps construct the HealthApiService one dependency at a time. It collects the dependencies and then at the end of the process the Build() method creates the instance.
public class HealthApiServiceBuilder
{
private readonly IFoo foo;
private readonly IBar bar;
public HealthApiServiceBuilder()
// These are the default dependencies that can be overridden
// individually by the builder
: this(new DefaultFoo(), new DefaultBar())
{ }
internal HealthApiServiceBuilder(IFoo foo, IBar bar)
{
if (foo == null)
throw new ArgumentNullException(nameof(foo));
if (bar == null)
throw new ArgumentNullException(nameof(bar));
this.foo = foo;
this.bar = bar;
}
public HealthApiServiceBuilder WithFoo(IFoo foo)
{
return new HealthApiServiceBuilder(foo, this.bar);
}
public HealthApiServiceBuilder WithBar(IBar bar)
{
return new HealthApiServiceBuilder(this.foo, bar);
}
public HealthApiService Build()
{
return new HealthApiService(this.foo, this.bar);
}
}
Usage
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Default dependencies
services.AddHealthApi();
// Custom dependencies
//services.AddHealthApi(healthApi =>
// healthApi.WithFoo(new MyFoo()).WithBar(new MyBar()));
}
Bonus
If your default IFoo or IBar implementations have dependencies, you can make a builder class for each one. For example, if IFoo has a dependency IFooey you can create a builder for the default IFoo implementation, then overload the HealthApiServiceBuilder.WithFoo method with an expression:
public HealthApiServiceBuilder WithFoo(IFoo foo)
{
return new HealthApiServiceBuilder(foo, this.bar);
}
public HealthApiServiceBuilder WithFoo(Func<FooBuilder, FooBuilder> expression)
{
var starter = new FooBuilder();
var builder = expression(starter);
return new HealthApiServiceBuilder(builder.Build(), this.bar);
}
This can then be used like
services.AddHealthApi(healthApi =>
healthApi.WithFoo(foo => foo.WithFooey(new MyFooey)));
More
Any other services (for example, controllers) that you need to register at application startup that you don't want the end user to interact with can be done inside of the extension method.
Reference
DI Friendly Library by Mark Seemann
I am trying to use the generic Lazy class to instantiate a costly class with .net core dependency injection extension. I have registered the IRepo type, but I'm not sure what the registration of the Lazy class would look like or if it is even supported. As a workaround I have used this method http://mark-dot-net.blogspot.com/2009/08/lazy-loading-of-dependencies-in-unity.html
config:
public void ConfigureService(IServiceCollection services)
{
services.AddTransient<IRepo, Repo>();
//register lazy
}
controller:
public class ValuesController : Controller
{
private Lazy<IRepo> _repo;
public ValuesController (Lazy<IRepo> repo)
{
_repo = repo;
}
[HttpGet()]
public IActionResult Get()
{
//Do something cheap
if(something)
return Ok(something);
else
return Ok(repo.Value.Get());
}
}
Here's another approach which supports generic registration of Lazy<T> so that any type can be resolved lazily.
services.AddTransient(typeof(Lazy<>), typeof(Lazier<>));
internal class Lazier<T> : Lazy<T> where T : class
{
public Lazier(IServiceProvider provider)
: base(() => provider.GetRequiredService<T>())
{
}
}
You only need to add a registration for a factory method that creates the Lazy<IRepo> object.
public void ConfigureService(IServiceCollection services)
{
services.AddTransient<IRepo, Repo>();
services.AddTransient<Lazy<IRepo>>(provider => new Lazy<IRepo>(provider.GetService<IRepo>));
}
Services that are to be fetched in Lazy will be re-introduced by the factory registration method with the new Lazy of the intended service type and provided for its implementation using serviceProvider.GetRequiredService.
services.AddTransient<IRepo, Repo>()
.AddTransient(serviceProvider => new Lazy<IRepo>(() => serviceProvider.GetRequiredService<IRepo>()));
To my opinion, the code below should do the work(.net core 3.1)
services.AddTransient<IRepo, Repo>();
services.AddTransient(typeof(Lazy<>), typeof(Lazy<>));
To register services as lazy
services.AddScoped<Lazy<AService>>();
services.AddScoped<Lazy<BService>>();
Or by creating an extension
static class LazyServiceCollection
{
public static void AddLazyScoped<T>(this IServiceCollection services)
{
services.AddScoped<Lazy<T>>();
}
}
...
services.AddLazyScoped<AService>();
services.AddLazyScoped<BService>();
And use it
[ApiController, Route("lazy")]
public class LazyController : ControllerBase
{
private readonly Lazy<AService> _aService;
private readonly Lazy<BService> _bService;
public LazyController(Lazy<AService> aService, Lazy<BService> bService)
{
_aService = aService;
_bService = bService;
}
[HttpGet("a")]
public ActionResult GetA()
{
_aService.Value.DoWork();
return new OkResult();
}
[HttpGet("b")]
public ActionResult GetB()
{
_bService.Value.DoWork();
return new OkResult();
}
}
Result
Init AService
AService Work
I use this form, I hope resolve your problem, this code for Scoped and Transient Life-Cycle you can write for other Life-Cycle look like this.
My Dot-Net-Core vertion is 6
public static class Lazier
{
public static IServiceCollection AddScopedLazier<T>(this IServiceCollection services)
{
return services.AddScoped(provider => new Lazy<T>(provider.GetService<T>));
}
public static IServiceCollection AddTransientLazier<T>(this IServiceCollection services)
{
return services.AddTransient(provider => new Lazy<T>(provider.GetService<T>));
}
}
Usage for Scoped Life-Cycle:
services.AddScoped<ISideDal, EfSideDal>().AddScopedLazier<ISideDal>();
Usage for Transient Life-Cycle:
services.AddTransient<ISideDal, EfSideDal>().AddTransientLazier<ISideDal>();
A bit late to the party here, but to throw another solution on to the pile... I wanted to selectively allow Lazy instantiation of services, so I created this extension method:
public static IServiceCollection AllowLazy(this IServiceCollection services)
{
var lastRegistration = services.Last();
var lazyServiceType = typeof(Lazy<>).MakeGenericType(
lastRegistration.ServiceType);
services.Add(new ServiceDescriptor(
lazyServiceType,
serviceLocator => Activator.CreateInstance(
lazyServiceType,
() => serviceLocator.GetRequiredService(
lastRegistration.ImplementationType ??
lastRegistration.ServiceType
))!,
lastRegistration.Lifetime
));
return services;
}
That way you can just tack .AllowLazy() on to your registration without having to re-specify the types or the scope -
public void ConfigureService(IServiceCollection services)
{
services.AddTransient<IRepo, Repo>().AllowLazy();
}