Initialize class as part of DI - c#

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;
});
});

Related

How to system / end-to-end test a NET5 class library

I'd like to system test a class library I've written. I'm planning on creating a servicecollection extension method as described here:
public static class IServiceCollectionExtension
{
public static IServiceCollection AddExampleAzureLibrary(this IServiceCollection services)
{
services.AddScoped<IGetSecret, GetSecret>();
services.AddScoped<IKeyVaultCache, KeyVaultCache>();
services.AddScoped<IBlobStorageToken, BlobStorageToken>();
services.AddScoped<IBlobWriter, BlobWriter>();
return services;
}
}
Which can then be called by my system test to configure the services, but how exactly to do that? At the moment I'm thinking the best way would be to create a console app to consume my library and test with that as described in this answer but is there a better way?
Edit: I have seen Microsoft's suggested approach which is to use a Test WebApplicationFactory, but as this isn't a web app, the approach is unsuitable.
Inspired by this answer and this tutorial from Microsoft, I have solved this by doing the following:
Adding a service collection extension method as described in my question
Creating a hostbuilder in my test:
[TestMethod]
public void MyTest()
{
using var host = CreateHostBuilder().Build();
using var serviceScope = host.Services.CreateScope();
var provider = serviceScope.ServiceProvider;
var className = new MyClass(provider.GetRequiredService<IMyRootInterfaceToBePassedIn>());
myClass.CallSomeMethod();
}
private static IHostBuilder CreateHostBuilder() =>
Host.CreateDefaultBuilder()
.ConfigureServices((_, services) =>
services.AddMyServices());

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.

Use IServiceCollection extensions with DryIoc

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..

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.

How do I validate the DI container in ASP.NET Core?

In my Startup class I use the ConfigureServices(IServiceCollection services) method to set up my service container, using the built-in DI container from Microsoft.Extensions.DependencyInjection.
I want to validate the dependency graph in an unit test to check that all of the services can be constructed, so that I can fix any services missing during unit testing instead of having the app crash at runtime. In previous projects I've used Simple Injector, which has a .Verify() method for the container. But I haven't been able to find anything similar for ASP.NET Core.
Is there any built-in (or at least recommended) way of verifying that the entire dependency graph can be constructed?
(The dumbest way I can think of is something like this, but it will still fail because of the open generics that are injected by the framework itself):
startup.ConfigureServices(serviceCollection);
var provider = serviceCollection.BuildServiceProvider();
foreach (var serviceDescriptor in serviceCollection)
{
provider.GetService(serviceDescriptor.ServiceType);
}
A built-in DI container validation was added in ASP.NET Core 3 and it is enabled only in the Development environment by default. If something is missing, the container throws a fatal exception on startup.
Keep in mind that controllers aren't created in the DI container by default, so a typical web app won't get much from this check until the controllers are registered in the DI:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddControllersAsServices();
}
To disable/customize the validation, add a IHostBuilder.UseDefaultServiceProvider call:
public class Program
{
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
//...
.UseDefaultServiceProvider((context, options) =>
{
options.ValidateOnBuild = false;
});
This validation feature has several limitations, read more here: https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/
Actually, I just used the example from your question with a few modifications and it worked pretty well for me. The theory behind filtering by classes in my namespace is that those will end up asking for everything else I care about.
My test looked a lot like this:
[Test or Fact or Whatever]
public void AllDependenciesPresentAndAccountedFor()
{
// Arrange
var startup = new Startup();
// Act
startup.ConfigureServices(serviceCollection);
// Assert
var exceptions = new List<InvalidOperationException>();
var provider = serviceCollection.BuildServiceProvider();
foreach (var serviceDescriptor in services)
{
var serviceType = serviceDescriptor.ServiceType;
if (serviceType.Namespace.StartsWith("my.namespace.here"))
{
try
{
provider.GetService(serviceType);
}
catch (InvalidOperationException e)
{
exceptions.Add(e);
}
}
}
if (exceptions.Any())
{
throw new AggregateException("Some services are missing", exceptions);
}
}
I had same problem in one of my project. My resolve:
add methods like AddScopedService, AddTransientService and AddSingletonService, that add service to DI and then add it to some List. Use this methods instead of AddScoped, AddSingleton and AddTransient
on startup application frist time i iterate by this list and call GetRequiredService. If any service can't be resolved, application will not start
I had CI: auto build and deploy on commit to develop branch. So if someone merge changes that broke DI, application fail and we all know about it.
If u whant to do it faster, u can use TestServer in Dmitry Pavlov's answer with my solution together
Here is a unit test that you can add to your project:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using [X/N]Unit;
namespace MyProject.UnitTests
{
public class DITests
{
[Fact or Test]
public void AllServicesShouldConstructSuccessfully()
{
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.UseDefaultServiceProvider((context, options) =>
{
options.VailidateOnBuild = true;
})
.UseStartup<Startup>();
}).Build();
})
}
}

Categories