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());
Related
I am using the new top-level statements in .NET 6 to create a simple console application, but I don't understand the advantages/disadvantages of using the "Generic Host".
Can you explain?
My code with Generic Host:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Console.WriteLine("Hello, World!");
using var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
{
services.AddTransient<ITestInterface, TestClass>();
})
.Build();
Test();
Console.ReadKey();
void Test()
{
var testClass = host.Services.GetRequiredService<ITestInterface>();
testClass.TestMethod();
}
versus
using Microsoft.Extensions.DependencyInjection;
Console.WriteLine("Hello, World!");
var services = new ServiceCollection();
services.AddTransient<ITestInterface, TestClass>();
var servicesProvider = services.BuildServiceProvider();
Test();
Console.ReadKey();
void Test()
{
var testClass = servicesProvider.GetRequiredService<ITestInterface>();
testClass.TestMethod();
}
The benefits of using the generic host is that by default a lot of services are already setup for you, see the docs.
The CreateDefaultBuilder method:
Sets the content root to the path returned by GetCurrentDirectory().
Loads host configuration from:
Environment variables prefixed with DOTNET_.
Command-line arguments.
Loads app configuration from:
appsettings.json.
appsettings.{Environment}.json.
Secret Manager when the app runs in the Development environment.
Environment variables.
Command-line arguments.
Adds the following logging providers:
Console
Debug
EventSource
EventLog (only when running on Windows)
Enables scope validation and dependency validation when the environment is Development.
The ConfigureServices method exposes the ability to add services to the Microsoft.Extensions.DependencyInjection.IServiceCollection instance. Later, these services can be made available from dependency injection.
You are not using the generic host correctly. For instance: normally one would add a hosted service so you can use proper DI instead of resolving the required services manually.
An example can be found at the docs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
If we extend this example with an implementation of Worker that takes in a dependency it will look like this:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddTransient<ITestInterface, TestClass>();
services.AddHostedService<Worker>();
});
}
internal class Worker : IHostedService
{
public Worker(ITestInterface testClass)
{
testClass.Foo();
}
public Task StartAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
public interface ITestInterface
{
void Foo();
}
public class TestClass : ITestInterface
{
public void Foo()
{
}
}
Now you see a new instance of Worker is created and an instance of ITestInterface is injected. There is no need to call servicesProvider.GetRequiredService<ITestInterface>(); which is an anti-pattern.
Decision Tree
If you don't need all those additional services you can choose not to use the Generic Host like in your second code example in the question.
If you do want to make use of services like logging, app configuration etc. you should use the Generic Host.
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.
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;
});
});
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();
})
}
}
I am trying to get an instance of the DbContext (so I can do some additional work upon startup with it), I get the following error when trying to get an instance in the Configure method:
System.InvalidOperationException: 'Cannot resolve scoped service 'MyApp.Data.MyDbContext' from root provider.'
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(
options => options.UseSqlServer(Configuration.GetConnectionString("MyDbContext")));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var dbContext = app.ApplicationServices.GetService(typeof(MyDbContext)) as MyDbContext;
}
I can access an instance of the DbContext fine via the controller, etc
Paul Hiles comment is correct but that method works better in .NET Core 1.0.
In ASP.NET Core 2.0 it's generally a bad idea to run any database setup in Startup.cs. This is because if you run any migrations from the CLI or Visual Studio it will run all of Startup.cs and try to run your configuration which will fail. Of course if you don't use Entity-Framework then this isn't a problem however its still not the recommended way of doing it in 2.0. It's now recommended to do it in Program.cs.
For example you can create a extension method of IWebHost that will run any setup you need.
public static IWebHost MigrateDatabase(this IWebHost webHost)
{
var serviceScopeFactory = (IServiceScopeFactory)webHost.Services.GetService(typeof(IServiceScopeFactory));
using (var scope = serviceScopeFactory.CreateScope())
{
var services = scope.ServiceProvider;
var dbContext = services.GetRequiredService<YourDbContext>();
dbContext.Database.Migrate();
}
return webHost;
}
And then in Program.cs you can then call that method before running.
public static void Main(string[] args)
{
BuildWebHost(args)
.MigrateDatabase()
.Run();
}
Update for Core 2.1 onwards
Just to add to #Travis Boatman's excellent answer, the preferred Main method syntax has changed slightly from Core 2.1 onwards and the default Main method now has CreateWebHostBuilder instead of BuildWebHost.
The revised code to call the extension method is shown below.
NB: the order is important here, the Build method returns a WebHost, which is what the extension method is extending, so you need to call the migrate method after Build() and before Run()):
public static void Main(string[] args)
{
CreateWebHostBuilder(args)
.Build()
.MigrateDatabase()
.Run();
}
Migrating more than one DbContext
We have more than one DbContext in our project, so I changed the extension method to a generic method that can take any type of DbContext:
public static IWebHost MigrateDatabase<T>(this IWebHost webHost) where T:DbContext
{
var serviceScopeFactory = (IServiceScopeFactory)webHost
.Services.GetService(typeof(IServiceScopeFactory));
using (var scope = serviceScopeFactory.CreateScope())
{
var services = scope.ServiceProvider;
var dbContext = services.GetRequiredService<T>();
dbContext.Database.Migrate();
}
return webHost;
}
You can then chain the calls to migrate the different contexts:
CreateWebHostBuilder(args)
.Build()
.MigrateDatabase<ApiAuthDbContext>()
.MigrateDatabase<MainDbContext>()
.MigrateDatabase<SomeOtherDbContext>()
.Run();
see this question and he answerd himself in the 'Update' section
Add in program.cs at CreateWebHostBuilder method
.UseDefaultServiceProvider(options => {
options.ValidateScopes = false;//to use any scoped
option.validateOnBuild = false;//to use dbContext
})
The full code:
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
Host.CreateWebHostBuilder(args).ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>().UseDefaultServiceProvider(options =>
{
options.ValidateScopes = false;
option.ValidateOnBuild = false;
});
})
}