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
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've a .Net Core(3.1) Console App, that has 2 service classes, one has an event and other listens to it with a handler to that event. I've setup getting the DI containers but the event field is always null, so not able to call its Invoke(). Any pointers on what am I missing in setting up the services in ConfigureServices() that involves event handling. Below is the complete test code:
public class RefreshEventArgs : EventArgs
{
public string RefreshEventData { get; set; }
}
public interface INotifierService
{
event EventHandler<RefreshEventArgs> RefreshEventHandler;
}
public class NotifierService : INotifierService
{
public event EventHandler<RefreshEventArgs> RefreshEventHandler;
public RefreshEventArgs RefreshEventData { get; set; }
// GeneralAppSettings is a POCO class to read all appsettings.json key values.
private readonly IOptions<GeneralAppSettings> myAppSettings;
public NotifierService(IOptions<GeneralAppSettings> appSettings)
{
myAppSettings = appSettings;
}
public void RunInvokingRefreshEvent()
{
RefreshEventData = new RefreshEventArgs();
RefreshEventData.RefreshEventData = "somedata";
// Main problem! In the below line, RefreshEventHandler is null all the time
RefreshEventHandler?.Invoke(this, RefreshEventData);
}
public void SomeBackgroundThreadMonitorsExternalEvents()
{
// Some external events triggers below method
RunInvokingRefreshEvent();
}
}
Refresh Service
public interface IRefreshService
{
void Refresh(RefreshEventArgs eventData = null);
}
public class RefresherService : IRefreshService
{
private readonly IOptions<GeneralAppSettings> myAppSettings;
private readonly INotifierService notify;
public RefresherService(IOptions<GeneralAppSettings> _appSettings, INotifierService _notifyService)
{
myAppSettings = _appSettings;
notify = _notifyService;
notify.RefreshEventHandler += _notify_RefreshEventHandler;
}
private void _notify_RefreshEventHandler(object sender, RefreshEventArgs e)
{
// Call Refresh() based say based on a config value from myAppSettings
Refresh(e);
}
public void Refresh(RefreshEventArgs eventData = null)
{
// final business logic processing based on eventData
}
}
public class GeneralAppSettings // POCO
{
public string SomeConfigKeyInAppSettingsJson { get; set; }
}
Program
class Program
{
public static IConfiguration Configuration { get; set; }
static void Main(string[] args)
{
// read appsettings
var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
// Host builder, setting up container
var host = Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddConfiguration(Configuration);
})
.ConfigureServices((context, services) =>
{
services.Configure<GeneralAppSettings>(Configuration.GetSection("GeneralAppSettings"));
services.AddSingleton<INotifierService, NotifierService>();
services.AddSingleton<IRefreshService, RefresherService>();
})
.Build();
// Need to get NotifierService instance to run some initial logic, so using ActivatorUtilities
var svc = ActivatorUtilities.GetServiceOrCreateInstance<NotifierService>(host.Services);
svc.SomeBackgroundThreadMonitorsExternalEvents();
// Need to get RefresherService instance to have initial Refresh logic so using ActivatorUtilities
var refresh = ActivatorUtilities.GetServiceOrCreateInstance<RefresherService>(host.Services);
refresh.Refresh(null);
// need to keep this main thread alive
Thread.Sleep(Timeout.Infinite);
}
}
When you request something from the DI container, you must request the "Service" type (the interface or first/only generic argument). If you request a type that's not been registered and you use ActivatorUtilities, it will create an instance if and only if all the types required to construct it are available. What's happening to you is you are getting two distinct objects (one registered as the interface and one pseudo-registered as the concrete type)! It doesn't matter that your class implements the interface and you've used it as the "Implementation" type in the registration. DI is always based on the service type and you've not registered any services of type NotifierService directly.
Your problem is that you have a weird coupling between your classes and the method you want to call on NotifierService isn't actually part of the interface. The usual trick would be to just register and request the concrete type as the service type:
services.AddSingleton<NotiferService>();
//...
var notifier = services.GetService<NotifierService>();
That would work, except now you haven't registered INotifierService for injection into the RefresherService.
Never fear, we have a work around. Register the concrete type as a singleton and then use a factory to register the interface:
// register the concrete type directly
services.AddSingleton<NotifierService>();
// use a factory to register the interface
services.AddSingleton<INotifierService>(sp => sp.GetRequiredService<NotifierService>());
Now, the same instance will be returned whether you are requesting the interface or the concrete type. You no longer need to use ActivatorUtilities either (in fact you shouldn't)--you can now use the host's services directly:
var notifier = host.Services.GetRequiredService<NotifierService>();
notifier.SomeBackgroundThreadMonitorsExternalEvents();
All that said, you're project is a perfect candidate for an IHostedService/BackgroundService. You can restructure it a bit (splitting NotifierService into two classes: one with just the event and the other for the background service) such that you'll then only be dealing with interfaces and you'd be able to actually call Host.Run() which will in turn wait for shutdown. This is the standard pattern for things like this, rather than abusing the Host simply for the DI container and including the weird Thread.Sleep.
I have a .NET core WebAPI project that uses Hangfire for background jobs. I am trying to setup Simple Injector for DIs. My porject has an IFoo and a Foo class that looks as follows
public interface IFoo
{
void DoSomething();
}
public class Foo : IFoo
{
public Foo() { }
public void DoSomething()
{
Console.WriteLine($"Foo::DoSomething");
}
}
Below is how I setup the Simple Injector container. I am using Hangfire.SimpleInjector nuget package
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
var container = new SimpleInjector.Container();
container.Register<IFoo, Foo>();
GlobalConfiguration.Configuration.UseActivator(
new Hangfire.SimpleInjector.SimpleInjectorJobActivator(container));
services.AddHangfire(x => x.UseSqlServerStorage(<My Connection string>));
services.AddHangfireServer();
services.AddControllers();
}
}
The background job is setup as following in controller
public IActionResult DoSomething()
{
var jobID = BackgroundJob.Enqueue<IFoo>( x => x.DoSomething());
return Ok();
}
But this job fails with following stack trace.
An exception occurred during processing of a background job.
System.InvalidOperationException A suitable constructor for type
'MyWebAPI.Controllers.IFoo' could not be located. Ensure the type is
concrete and services are registered for all parameters of a public
constructor.
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider, Type, Object[])
at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetServiceOrCreateInstance(IServiceProvider, Type)
at Hangfire.AspNetCore.AspNetCoreJobActivatorScope.Resolve(Type type)
at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_0.<PerformJobWithFilters>b__0()
at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter, PerformingContext, Func`1)
at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass9_1.<PerformJobWithFilters>b__2()
at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext, IEnumerable`1)
at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext, IStorageConnection, String)
What am I doing wrong in setting all this up?
I am not sure the DI in Hangfire is for this purpose.
You need dependency injection to resolve inner dependencies,
not to resolve the main type you want to use.
You can check the documentation here.
Check this answer with same problem.
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.
In ASP.NET Core we can register all dependencies during start up, which executed when application starts. Then registered dependencies will be injected in controller constructor.
public class ReportController
{
private IReportFactory _reportFactory;
public ReportController(IReportFactory reportFactory)
{
_reportFactory = reportFactory;
}
public IActionResult Get()
{
vart report = _reportFactory.Create();
return Ok(report);
}
}
Now I want to inject different implementations of IReportFactory based on data in current request (User authorization level or some value in the querystring passed with an request).
Question: is there any built-in abstraction(middleware) in ASP.NET Core where we can register another implementation of interface?
What is the possible approach for this if there no built-in features?
Update
IReportFactory interface was used as a simple example. Actually I have bunch of low level interfaces injected in different places. And now I want that different implementation of those low level interfaces will be injected based on request data.
public class OrderController
{
private IOrderService _orderService;
public OrderController(IOrderService orderService)
{
_orderService = orderService;
}
public IActionResult Create()
{
var order = _orderService.Create();
return Ok(order);
}
}
public class OrderService
{
private OrderBuilder _orderBuilder;
private IShippingService _shippingService; // This now have many different implementations
public OrderService(
OrderBuilder _orderBuilder,
IShippingService _shippingService)
{
_orderService = orderService;
_shippingService = shippingService;
}
public Order Create()
{
var order = _orderBuilder.Build();
var order.ShippingInfo = _shippingService.Ship();
return order;
}
}
Because we know which implementation we need to use on entry point of our application (I think controller action can be considered as entry point of application), we want inject correct implementation already there - no changes required in already existed design.
No, you can't. The IServiceCollection is populated during application startup and built before Configure method is called. After that (container being built), the registrations can't be changed anymore.
You can however implement an abstract factory, be it as factory method or as an interface/class.
// Its required to register the IHttpContextAccessor first
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IReportService>(provider => {
var httpContext = provider.GetRequired<IHttpContextAccessor>().HttpContext;
if(httpContext.User.IsAuthorized)
{
return new AuthorizedUserReportService(...);
// or resolve it provider.GetService<AuthorizedUserReportService>()
}
return new AnonymousUserReportService(...);
// or resolve it provider.GetService<AnonymousUserReportService>()
});
Alternatively use an abstract factory class
I'm afraid you can not directly acheive the goal via simple dependency injection , as the the dependency injection configured at Startup stage , in other words , all services and implementions has been configured before a request comming .
However , you can inject a Create Service delegate so that can we create the required service implemention instance in runtime .
For instance , if we have a IReportFactory Interface and two implementions as blew :
public interface IReportFactory
{
object Create();
}
public class ReportFactory1 : IReportFactory
{
public object Create()
{
return new { F = 1, };
}
}
public class ReportFactory2 : IReportFactory {
public object Create()
{
return new { F = 2, };
}
}
As we want to get the required implemention in future , we need to register the Implementions first .
services.AddScoped<ReportFactory1>();
services.AddScoped<ReportFactory2>();
and here's where the magic happens :
We don't register a IReportFactory
We just add a Func<HttpContext,IReportFactory> instead , which is a CreateReportFactoryDelegate
public delegate IReportFactory CreateReportFactoryDelegate(Microsoft.AspNetCore.Http.HttpContext context);
We need add the CreateReportFactoryDelegate to servies too.
services.AddScoped<CreateReportFactoryDelegate>(sp => {
// return the required implemention service by the context;
return context => {
// now we have the http context ,
// we can decide which factory implemention should be returned;
// ...
if (context.Request.Path.ToString().Contains("factory1")) {
return sp.GetRequiredService<ReportFactory1>();
}
return sp.GetRequiredService<ReportFactory2>();
};
});
Now , we can inject a CreateReportFactoryDelegate into controller :
public class HomeController : Controller
{
private CreateReportFactoryDelegate _createReportFactoryDelegate;
public HomeController(CreateReportFactoryDelegate createDelegate) {
this._createReportFactoryDelegate = createDelegate;
// ...
}
public async Task<IActionResult> CacheGetOrCreateAsync() {
IReportFactory reportFactory = this._createReportFactoryDelegate(this.HttpContext);
var x=reportFactory.Create();
// ...
return View("Cache", cacheEntry);
}
}
It is possible by using the HttpContextAccessor in Startup.cs
services.AddHttpContextAccessor();
services.AddScoped<IYourService>(provider =>
{
var contextAccessor = provider.GetService<IHttpContextAccessor>();
var httpContext = contextAccessor.HttpContext;
var contextVariable = httpContext. ...
// Return implementation of IYourService that corresponds to your contextVariable
});
Expanding on #JohanP comment about using IEnumerable
//Program.cs
//get the builder
var builder = WebApplication.CreateBuilder(args);
//register each type
builder.Services.AddScoped<IReport,Report1>();
builder.Services.AddScoped<IReport,Report2>();
builder.Services.AddScoped<IReport,Report3>();
//register the factory class
builder.Services.AddScoped<IReportFactory,ReportFactory>();
//IReport Interface
public interface IReport
{
string ReportType{ get; set; }
}
//ReportFactory.cs
public class ReportFactory : IReportFactory
{
private IEnumerable<IReport> _handlers;
//ctor
public ReportFactory(IEnumerable<IReport> handlers)
=> _handlers = handlers;
internal IReport? Creat(string reportType) =>
_handlers.Where(h => h.ReportType== reportType).First();
}
//Controller
public class ReportController
{
private IReportFactory _reportFactory;
public ReportController(IReportFactory reportFactory)
{
_reportFactory = reportFactory;
}
//modify to your project needs
public IActionResult Get([FromBody] string reportType)
{
if (HttpContext.User.IsAuthorized)
{
var report = _reportFactory.Create(reportType);
return Ok(report);
}
}
}