Use IServiceCollection extensions with DryIoc - c#

There are many extensions for the IServiceCollection - in my case I use AddHttpClient.
My scenario is that I register general stuff in the ConfigureServices method in the Startup.cs where IServiceCollection is used to register services. Everything that is needed only in specific projects is registered in an extension method in the respective project, but there the DryIoc IContainer is used due to how the DryIoc container must be integrated in an ASP .NET Core project.
Now I have a HttpClient that I only need in a specific project. Therefore I would like to put the registration for it in the respective project. Problem is I want to use AddHttpClient for it which I normally can only use with IServiceCollection.
My question: Is there any way to use it in my other project. Maybe by getting it from the DryIoc container or something else.
This is the general structure of the described files:
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.RegisterSomeService();
// register other stuff
}
}
Program.cs
public class Startup
{
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.UseServiceProviderFactory(new DryIocServiceProviderFactory())
.ConfigureContainer<Container>(SomeProject.ContainerSetup.Initialize);
}
ContainerSetup.cs in SomeProject
public static class ContainerSetup
{
public static void Initialize(HostBuilderContext hostContext, IContainer container)
{
container.Register<SomeService>();
// register other stuff
// here I want to use AddHttpClient
}
}

I was able to solve the problem by using the IContainer extension Populate which is part of DryIoc.Microsoft.DependencyInjection.
With it I edited my ContainerSetup.cs as follows
public static class ContainerSetup
{
public static void Initialize(HostBuilderContext hostContext, IContainer container)
{
var services = new ServiceCollection();
services.AddHttpClient<MyTypedClient>()
.Configure[...];
container.Populate(services); // with this call all services registered above will be registered in the container
// register other stuff if necessary
container.Register<SomeService>();
}
}

I suggest to look inside the AddHttpClient https://source.dot.net/#Microsoft.Extensions.Http/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs,72bc67c4aadb77fc
and maybe make the same registrations with IContainer instead of the service collection.
Update:
Another idea is to register IServiceCollection into itself (or maybe it is already automatically registered?), then resolve it from IContainer and AddHttpClient..

Related

Implementing Dependency Injection from a new file

I`ve been working on an ASP.NET Core 5 MVC project and it is working as expected, the only issue i'm having right now is the size of the Startup.cs file, i'm using the Microsoft.Extensions.DependencyInjection a lot, and it's very good!, but as i mentioned it is getting very crowded with those "services.AddTransient, Scoped or Singleton", is there a way to create my own class to add those services and call it from the Startup.cs?.
So far i've been trying to make a static class with an "Inject" method that will return an IServiceCollection, but it is not working, i've been searching on google for some examples but it looks like this is not a "thing".
Let me share some sample code:
using FluentValidation;
using Microsoft.Extensions.DependencyInjection;
using Models;
namespace MyFirstAzureWebApp
{
public class Injections
{
private static readonly IServiceCollection _services;
public static IServiceCollection Inject()
{
_services.AddTransient<IValidator<Customer>, CustomerValidator>();
_services.AddTransient<IValidator<Requirement, RequirementValidator>();
_services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_CONNECTIONSTRING"]);
_services
.AddFluentEmail("noreply#myownmail.com")
.AddRazorRenderer()
.AddSmtpSender("smtp.myownmail.com",445);
return _services;
}
}
}
And at the Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddFluentValidation(opt =>
{
opt.DisableDataAnnotationsValidation = true;
opt.ValidatorOptions.LanguageManager.Culture = new CultureInfo("es");
});
//Dependency Injetions call
services = Injections.Inject();
}
I hope this information is enougth to bring some light over my problem.
Thank you very much!
yes you absolutely can do that. I use it all the time to keep my code nice and clean. You can easily do it with Extension methods:
public class Injections
{
public static IServiceCollection RegisterServices(this IServiceCollection services) => services
.AddTransient<IValidator<Customer>, CustomerValidator>()
.AddTransient<IValidator<Requirement, RequirementValidator>();
public static IServiceCollection AddOtherServices(this IServiceCollection services, IConfiguration configuration) => services
.AddApplicationInsightsTelemetry(configuration["APPINSIGHTS_CONNECTIONSTRING"])
.AddFluentEmail("noreply#myownmail.com")
.AddRazorRenderer()
.AddSmtpSender("smtp.myownmail.com",445);
}
Then in your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddFluentValidation(opt =>
{
opt.DisableDataAnnotationsValidation = true;
opt.ValidatorOptions.LanguageManager.Culture = new CultureInfo("es");
});
//Dependency Injetions call
services.RegisterServices();
services.AddOtherServices(Configuration);
}
This is quite a common thing (because yes - it does get crowded!).
One approach is to write extension methods on IServiceCollection grouping bits of functionality together.
For example, you might create a file called DatabaseServices.cs, which adds entity framework or Dapper or whatever.
// DatabaseServices.cs
public static class DatabaseServices
{
public static IServiceCollection AddDatabases(this IServiceCollection services)
{
// Set up Entity Framework
services.AddDbContext<MyContext>(/* configure EF */);
// Do other stuff related to databases.
// Return the service collection to allow chaining of calls.
return services
}
}
Then in Startup.cs you can just do:
// Startup.cs
services.AddDatabases();
Create other files to add logging, configuration, services, HTTP clients, etc. etc.

Initialize class as part of DI

I am writing a new ASP.NET Core Application and I am using the inbuilt DI Framework.
I have a service that I need to run an Initaliaze method as part of the DI - is this possible with the in built Framework DI?
I have done something like this before with Simple Injector using the following code:
container.RegisterInitializer<MyService>(instance =>
{
instance.Initialize("Parameter to Initialize method");
});
I am registering most of my service in the .NET Core as below:
public static void RegisterServiceDependencies(this IServiceCollection services)
{
services.AddTransient<IServiceA, ServiceA>();
services.AddTransient<IServiceB, ServiceB>();
//etc etc
However looking at the services intellisense I don't see anything like RegisterInitializer.
Something like this?
public static void RegisterServiceDependencies(this IServiceCollection services)
{
services.AddTransient(sp =>
{
var instance = sp.GetService<MyClass>(); /* depends on your type */
instance.Initialize("Parameter to Initialize method");
return instance;
});
});

ASP.NET Core does not replace IoC with StructureMap

My application is based on ASP.NET Core 2.1 and .NET Core 2.1 (downgraded from 2.2) generic host as Windows Service. So, IHostBuilder is launched first with other services and frameworks and then (if role permits) web service gets launched on top using IWebHostBuilder with all that WebHost.CreateDefaultBuilder(args).UseStartup<Startup>().StartAsync(). Secondary WebHost is another story; it is initialized and works, but I haven't checked yet if IoC replacement has the same trouble as generic host.
For now, generic host initialization:
new HostBuilder().ConfigureServices((hostContext, services) =>
{
services.AddHostedService<LifetimeService>(); // Gets launched when host is up
var container = ContainerBuilder.BuildBaseContainer(services, new WorkingPath());
services.AddSingleton<IContainer>(container);
services.AddStructureMap(); // Has no effect
});
IContainer initialization:
public static Container BuildBaseContainer(IServiceCollection services, IWorkingPath workingPath)
{
var container = new Container();
container.Configure(config =>
{
config.Scan(scan =>
{
workingPath.OwnLoadedAssemblies.Where(asm => !asm.IsDynamic).ForEach(scan.Assembly);
scan.LookForRegistries();
scan.AddAllTypesOf<IPlatformService>();
});
config.For<IContainer>().Use(() => container);
config.Populate(services);
});
container.AssertConfigurationIsValid();
return container;
}
And the trouble is here, in the constructor of that registered hosted service (or anywhere else)
public LifetimeService(IEnumerable<IPlatformService> services,
IServiceProvider sp, IContainer c)
{
var inCollection = services.Any();
var inContainer = c.TryGetInstance<IPlatformService>() != default;
var inProvider = sp.GetRequiredService<IPlatformService>() != default;
}
ps: IServiceProvider and IContainer are for demonstration purposes only, I only need 'services'
When LifetimeService is initialized during container.AssertConfigurationIsValid() I get
inCollection is true
inContainer is true
inProvider is true
IServiceProvider is StructureMapServiceProvider
Actual LifetimeService execution shows that
inCollection is false
inContainer is true
inProvider is false
IServiceProvider is ServiceProviderEngineScope
I don't plan to pass IServiceProvider or IContainer into constructors, but it seems that dependencies are resolved using IServiceProvider, not IContainer, and I get nulls. Silly thing like sp.GetRequiredService<IContainer>().TryGetInstance<IPlatformService>() does work.
There been some happy-path examples using WebHost and Startup classes where injection ought to be working properly. Doesn't seem relevant for generic host ...which might replace WebHost one day, but is little known and not widely used. Well, could be due to .NET Core version downgrade too, but quite unlikely. I've also tried replacing IServiceProvider and IServiceScopeFactory from IContainer during ConfigureServices() without luck. My idea is to replace or forward internal container to StructureMap. I might be misunderstanding how that should work...
Has anyone successfully tried to 'marry' generic host and external IoC?
I've solved the puzzle! Finally, according to a too much simplified example (https://github.com/aspnet/Hosting/blob/master/samples/GenericHostSample/ProgramFullControl.cs), I had to change HostBuilder initialization to
new HostBuilder()
.UseServiceProviderFactory(new StructureMapContainerFactory(workingPath))
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<LifetimeService>();
});
and introduce provider factory itself
public class StructureMapContainerFactory : IServiceProviderFactory<IContainer>
{
private readonly IWorkingPath workingPath;
// pass any dependencies to your factory
public StructureMapContainerFactory(IWorkingPath workingPath)
{
this.workingPath = workingPath;
}
public IContainer CreateBuilder(IServiceCollection services)
{
services.AddStructureMap();
return ContainerBuilder.BuildBaseContainer(services, workingPath);
}
public IServiceProvider CreateServiceProvider(IContainer containerBuilder)
{
return containerBuilder.GetInstance<IServiceProvider>();
}
}
Now internal container is substituted with StructureMap and resolved IServiceProvider in LifetimeService is of type StructureMapServiceProvider.

How to start an injected service before a call has been made to the API?

In ASP.NET Core I have a service that prepare for injection in startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IService, Service>();
services.AddMvc();
}
If I make a call to the API, it will start up.
However, I'd like it to initialize before hand (when the app starts).
I'm simply putting a breakpoint in the controller to see if that happens.
The AddSingleton method can take either a type or an object to give. So you can simply do:
public void ConfigureServices(IServiceCollection services)
{
var serviceInstance = new Service();
serviceInstance.DoWhatever();
services.AddSingleton<IService, serviceInstance>();
services.AddMvc();
}
ASP.NET Core apps use a Startup class, which is named Startup by convention. The Startup class:
Can optionally include a ConfigureServices method to configure the app's services.
Must include a Configure method to create the app's request processing pipeline.
ConfigureServices and Configure are called by the runtime when the app starts:
public class Startup
{
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
...
}
// Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
...
}
}
Specify the Startup class with the WebHostBuilderExtensions UseStartup method:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
**.UseStartup<Startup>();**
}
So if you want to cal something at start the project in Startup put your method in Confirgure
GoodLuck.
you could try placing your initialization logic in the constructor so you don't need to worry about calling the "proper method". this will also help you in the case when your service is dependent on another one in order to function properly. long live dependency injection :)

Azure Stateful Service - with remoting and custom singleton service

I would like to use remoting in a .net core stateful service. I have a custom class which needs to be added as a singleton. Is this possible?
First I tried to register the custom class in Startup.cs's ConfigureServices() method but then I realized this method will be never called, since I used return this.CreateServiceRemotingReplicaListeners(); to generate my replica listeners in the CreateServiceReplicaListeners() method, and removed the Kestrel configuration (which would make this method to be called).
Is there a way to make the Startup.cs's ConfigureServices() method to be called, or add the singleton service at another place while keeping the remoting configuration?
The CreateServiceReplicaListeners() method in the MyStefulService.cs class looks like the following:
protected override IEnumerable<ServiceReplicaListener>
CreateServiceReplicaListeners()
{
return this.CreateServiceRemotingReplicaListeners();
}
The ConfigureServices method in the Startup.cs looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IMyHandler>(x => new MyHandler());
}
Finally i found a solution to my problem: I used Autofac to make sure the class I register is the same instance wherever it is used.
I extended the Program.cs with the Autofac container, so this way I didn't need the Startup.cs class at all:
I defined static variable of my custom class and an Autofac container, then in Main() method I added the implementation:
public static IContainer AutofacContainer;
private static IMyHandler _handler;
private static void Main()
{
try
{
if (_autofacContainer == null)
{
var builder = new ContainerBuilder();
builder.RegisterType<MyHandler>()
.As<IMyHandler>()
.SingleInstance();
_autofacContainer = builder.Build();
_handler = autofacContainer.Resolve<IMyHandler>();
}
//[...] normal service registration continues here
}
catch (Exception e)
{
ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
throw;
}
}
Since the container is static and public, all the other classes in the project can access it and get the singleton instance.
Configuration, environment variables, etc. can also be configured from here, similarly to the Startup.cs.

Categories