How to get all the Services from Startup.cs - c#

How do I obtain all the services after StartupClass has run?
We have a Startup Configure Services class which
runs multiple dependency injection.
serviceCollection.AddScoped<IDepartmentRepository, DepartmentRepository>();
serviceCollection.AddScoped<IDepartmentAppService, DepartmentAppService>();
Team is using Xunit Test Project and wants to grab all the Service collection list for Xunit.
Need services from this below:
new WebHostBuilder()
.UseContentRoot("C:\\Test\\Test.WebAPI")
.UseEnvironment("Development")
.UseConfiguration(new ConfigurationBuilder()
.SetBasePath("C:\\Test\\Test.WebAPI")
.AddJsonFile("appsettings.json")
.Build()).UseStartup<Startup>());

Services in ServiceCollection are represented by the ServiceDescriptor class. You can get a list of all service descriptors by chaining a ConfigureTestServices method to your WebHostBuilder (you have to install Microsoft.AspNetCore.TestHost package)
using Microsoft.AspNetCore.TestHost;
....
IWebHost CreateWebHost(out ServiceDescriptor[] allServices)
{
ServiceDescriptor[] grabbedServices = null;
var host = new WebHostBuilder()
// ... other stuff ...
.UseStartup<Startup>()
// add the following call:
.ConfigureTestServices(services => {
// copy all service descriptors
grabbedServices = services.ToArray();
})
// build the host now to trigger the above chain
.Build();
allServices = grabbedServices;
return host;
}
It is then convenient to use like this:
[Fact]
public void TestSomething()
{
// arrange
var host = CreateWebHost(out var services);
// ... do something with the services
// act
// assert
}
The reason we have to use ConfigureTestServices (and not ConfigureServices) is that ConfigureServices is always invoked before the methods in the Startup class, whereas ConfigureTestServices is invoked afterward.
UPDATE
Of course, it's also possible to just store the reference to ServiceCollection in a variable/field, and enumerate the services whenever necessary.

So I understood your question that you want to test you services and have problems with loading them in test project.
I'd say you don't need to. In unit tests every time you test a single service. This service you instantiate directly:
var target= new ServiceToTest();
So you reference your testED project from your test project. In case your implementation is internal, add to your testED project an AssemblyInfo.cs file and put the following inside:
[assembly: InternalsVisibleTo("MyTestProject")]
Whenever you test your MyServiceToTest1, in case it has a dependecy to MyServiceTest2, you don't have to load or instantiate MyServiceTest2. Instead you mock it, e.g. with moq.

Related

.NET How to access TOptions during startup (Options Pattern)

I am using the .NET Options pattern to manage my configuration.
This configuration is needed in Controllers (easy with Dependency Injection) but also to configure other services during application startup.
I would have thought that the generic Services.Configure<MyOptionsClass> method would return an instance of MyOptionsClass but unfortunately it returns an IServiceCollection?
Is there a clean way to access a bound instance of MyOptionsClass here during startup?
var builder = WebApplication.CreateBuilder(args);
// Setup MyOptionsClass for DI
var unwantedServiceCollection = builder.Services.Configure<MyOptionsClass>(builder.Configuration.GetSection(MyOptionsClass.ConfigName));
// Already need to be able to access MyOptionsClass here:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => { options.Authority = instanceOfMyOptionsClass.Authority; });
I had a similar need in the past and this is what I did:
var configOptions = builder.Configuration.GetSection(MyOptionsClass.ConfigName);
//You can also add data annotations to your config validate it on start
builder.Services
.AddOptions<MyOptionsClass>()
.Bind(configOptions)
.ValidateDataAnnotations()
.ValidateOnStart();
var configInstance = configOptions.Get<MyOptionsClass>();
Alternatively, you can use ServiceProviderServiceExtensions GetService<> or GetRequiredService<> to get the service you need by its type. Also, please be wary of using BuildServiceProvider which may create duplicate services as mentioned here.
Hope this helps.

How to use settings from integration testing project in the Startup class of api project?

I'm trying to write integration tests for web api, I have two appsettings.json files one for api project and other for integration tests. I want to use the values(azure storage connection strings) from integration testing project in Startup class of web api.
I have tried creating CustomWebApplicatonFactory it didn't work since the Startup class of web api gets settings like shown below.
//Configure services method in Startup class
public virtual void ConfigureServices(IServiceCollection services)
{
var settings = ConfigurationProvider.GetConfiguration();
services.TryAddSingleton(settings);
services.AddHttpClient();
var azureTableStorageConnectionString =
settings["AzureMainStoreConnectionStringSecretName"];
//Other Startup related code
}
I want to change the value of "azureTableStorageConnectionString" from my integration test project. Help and suggestions are much appreciated.
First of all, instead of using the static function ConfigurationProvider.GetConfiguration() inject IConfiguration into your Startup class. The host that is usually defined in the Program class builds that for you so you can inject it.
Then instead of using WebApplicationFactory<TStartup> you can build a test-host yourself like this.
[Fact]
public async Task LoadWeatherForecastAsync()
{
var webHostBuilder = new WebHostBuilder()
.UseContentRoot(AppContext.BaseDirectory)
.ConfigureAppConfiguration(builder =>
{
builder.Sources.Clear();
builder.SetBasePath(AppContext.BaseDirectory);
// this is an appsettings file located in the test-project!
builder.AddJsonFile("appsettings.Testing.json", false);
})
.UseStartup<Startup>();
var host = new TestServer(webHostBuilder);
var response = await host.CreateClient().GetAsync("weatherforecast");
Assert.True(response.IsSuccessStatusCode);
}
I've created a sample on github. You can clone it and try it out.

Can't use registered singetons in ConfigureServices without them being instantiated twice

I have a .Net Core project that registers a number of Singletons as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddLogging();
services.AddSingleton<IConfiguration>(Configuration);
services.AddSingleton<IDbFactory, DefaultDbFactory>();
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<IEmailService, EmailService>();
services.AddSingleton<IHostedService, BackgroundService>();
services.AddSingleton<ISettingsRepository, SettingsRepository>();
services.AddSingleton(typeof(TokenManager));
var sp = services.BuildServiceProvider();
var userRepository = sp.GetService<IUserRepository>();
// ...
}
This is the only place these classes are registered, and no other instances are created anywhere, however I have noticed that the constructors are called twice. Why is that?
TL;DR - If you need one of your registered singletons to be passed in as a configuration option to AddMvc, Don't do what I did and use GetService in the ConfigureServices method. Skip down to EDIT 2 below.
I found this answer to a similar question but the answer wasn't clear about the order with respect to the service registration, however it did point out that calling services.BuildServiceProvider() builds a new container which causes the services to be reregistered. Kind of makes sense in hindsight...
EDIT:
My original fix was to move services.BuildServiceProvider() before the AddSingleton registrations, however it turns out this doesn't always work, as Buddha Buddy pointed out.
I've just seen this question which gives a lot more detail into what's happening. The original registrations are not discarded, as I thought they were. I was thinking about it all wrong.
Calling services.BuildServiceProvider() does build a new service provider/container, but this has nothing to do with registrations. The class implementing IUserRepository and all dependent services are instantiated by the new service provider when sp.GetService<IUserRepository>() is called, but the original IServiceProvider remains, and that's what the rest of the application will use.
So down the line when the application requires an instance of IUserRepository (say because it's an injected dependency of UsersController, which has now been requested), the IUserRepository service (and its dependencies, if any) are again instantiated, because it's now under the original IServiceProvider.
A comment in the answer to the question above states that you can prevent this and use the one IServiceProvider, "by returning the service provider instance from the ConfigureServices method so that will be the container your application uses as well". I'm not sure how you would do that short of storing a class variable pointing to it, so it can be swapped out in the Configure method by setting app.ApplicationServices - but that doesn't work for me either, because the new service provider is missing all the MVC services.
Some people recommend using the app.ApplicationServices in Configure() to access the required service instead, but that won't work for me as I need to use it in ConfigureServices as follows:
//...
serviceProvider = services.BuildServiceProvider();
var userRepository = serviceProvider.GetService<IUserRepository>();
// Add framework services
services.AddMvc(
config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new RolesAuthorizationFilter(userRepository));
});
EDIT 2:
Found the solution! This brilliant post described what I was trying to achieve and presented a very tidy solution.
If you need to pass a reference to one of your registered singletons in as MVC configuration options, rather than try and instantiate it inside ConfigureServices, you can simply create a new class that implements IConfigureOptions<MvcOptions> which can take injected dependencies, register this class as a singleton - and everything else is taken care of - brilliant!
Example:
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddLogging();
services.AddSingleton<IConfiguration>(Configuration);
services.AddSingleton<IDbFactory, DefaultDbFactory>();
services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<IEmailService, EmailService>();
services.AddSingleton<IHostedService, BackgroundService>();
services.AddSingleton<ISettingsRepository, SettingsRepository>();
services.AddSingleton(typeof(TokenManager));
// var sp = services.BuildServiceProvider(); // NO NEED for this after all
// var userRepository = sp.GetService<IUserRepository>();
services.AddMvc(); // No config options required any more
services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>(); // Here be the magic...
// ...
}
Then create the new class:
public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
private readonly IUserRepository userRepository;
public ConfigureMvcOptions(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
public void Configure(MvcOptions options)
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new RolesAuthorizationFilter(userRepository));
}
}
Thanks to the magic of DI, everything else is taken care of. My user repository singleton and its dependencies are only instantiated once.
While instantiating a singleton within ConfigureServices is arguably code smell, it is possible. Simply re-register the singleton instance you've instantiated within the ConfigureServices scope:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSingleton<IUserRepository, UserRepository>();
// ...
var sp = services.BuildServiceProvider();
var userRepository = sp.GetService<IUserRepository>();
services.AddSingleton<IUserRepository>(userRepository);
// ...
}
This ensures downstream attempts to inject a IUserRepository will use the instance you created within ConfigureServices instead of creating a new instance.

How to Update the HttpConfiguration AFTER the Owin TestServer creation

The http configuration is setup in te Startup class usually which is bound to the Create method.
But what if I want to start an owin server ONE time for ALL tests but update its http configuration depending on each test needs?
This is not possible. The server object has nothing useful.
using (var server = TestServer.Create<Startup>())
{
var data = server.HttpClient.GetAsync("/api/data);
}
What I want to do for CRUD integration tests is stub the service methods
// Do it ONE time fall ALL tests
WebApiConfig.Register(config);
WebServicesConfig.Register(config);
// Do it individually for each test, Update DI registerations with Fake components per test method
var builder = new ContainerBuilder();
var mockContext = new Mock<TGBContext>();
var mockService = new Mock<SchoolyearService>(mockContext.Object);
mockService.Setup<Task<IEnumerable<SchoolyearDTO>>>(c => c.GetSchoolyearsAsync()).Returns(Task.FromResult(Enumerable.Empty<SchoolyearDTO>()));
// builder.RegisterInstance<TGBContext>(); ****** NO NEED for this it works without registering the ctor parameter dependency
builder.RegisterInstance<SchoolyearService>(mockService.Object);
builder.Update(((AutofacWebApiDependencyResolver)config.DependencyResolver).Container as IContainer);
At the moment I am forced to create a TestServer per Test method.
Thats a total overhead in time.
SOLUTION
Make the HttpConfiguration static and this code should work:
var builder = new ContainerBuilder();
var mockContext = new Mock<TGBContext>();
var mockService = new Mock<SchoolyearService>(mockContext.Object);
mockService.Setup<Task<IEnumerable<SchoolyearDTO>>>(c => c.GetSchoolyearsAsync()).Returns(Task.FromResult(Enumerable.Empty<SchoolyearDTO>()));
builder.RegisterInstance<SchoolyearService>(mockService.Object);
builder.Update(((AutofacWebApiDependencyResolver)Configuration.DependencyResolver).Container as IContainer);
if you want to start the OWIN server once for all tests.
declare a base class for all your test classes
declare a private static readonly variable in your base class for the OWIN Server.
private static readonly TestServer _webServer = TestServer.Create<Startup>();
declare a protected static variable to expose this server to all the tests.
protected static TestServer WebServer { get { return _webServer; } }
this should solve your problem of instantiating the web server only once for all the test runs. and if you don't want to initialize the web server so early, you can use lazy instantiation etc. but the point is to make it static and initialize it at definition time itself, so that it is once per app domain.
as for getting access to the HttpConfiguration in your unit tests.. here is one possible way..
in your Startup.cs, declare a public property...
public static HttpConfiguration HttpConfiguration { get; private set; }
initialize this variable in the configure method of the Startup.cs class.
HttpConfiguration = new HttpConfiguration();
HttpConfiguration.MapHttpAttributeRoutes();
// do more stuff.. setting resolver etc.
// and then finally
app.UseWebApi(HttpConfiguration);
This HttpConfiguration property is your web api's configuration and because we made it a public property, it should be accessible in your test project and to all tests.
Startup.HttpConfiguration
you can access it to modify the dependency resolver definitions etc.
Startup.HttpConfiguration.DependencyResolver
please note that you can update the DependencyResolver definitions even after you initialize the web server... the updated definitions will still have their effect.
if you're concerned about your HttpConfiguration just publicly available, then make the Startup class internal and make it visible only to your unit test project.

Using StructureMap with unit tests

I'm using StructureMap in a web project for DI IOC. It works perfect, but I don't have a clue how to write unit tests with StructureMap.
Should I do this in AssemblyInitialize start Configuration of StructureMap like in global.asax except for datacontext not to use live LinqToSqlDataContext but some memory data like this:
[AssemblyInitialize]
public static void Start()
{
ObjectFactory.Configure(x =>
{
x.For<IDataContext>().HttpContextScoped().Use<MemoryDataContext>()
.Ctor<string>("connectionString")
.Is(ConfigurationManager.ConnectionStrings["DEVConnection"].ConnectionString);
x.For<IDepartamentRepository>().Use<DepartamentDB>();
x.For<IDevelopmentProcess>().Use<DevelopmentProcesses>().OnCreation(c => c.User = Current.CurrentUser);
x.For<IActivityProcess>().Use<ActivitiesProcess>().OnCreation(c=> c.User = Current.CurrentUser);
x.For<IDevDeveloperRepository>().Use<DevDeveloperDB>();
x.For<IDevelopmentRepository>().Use<DevelopmentDB>();
x.For<IActivityRepository>().Use<ActivityDB>();
x.For<IActivityTypeRepository>().Use<ActivityTypeDB>();
x.For<IDevUserRepository>().Use<DevUsersDB>();
x.For<IAttachmentRepository>().Use<AttachmentDB>();
}
);
}
and then use ObjectFactory.GetInstance() testing or how do I do this?
You shouldn't need to use a DI Container in unit tests at all.
A container is something you use to wire components together, but a unit test is a test of each component in isolation.
I agree with Mark. Testability is one of the primary reasons you are likely using a container in the first place.
There are times where creating an integration test for your container setup might be a good idea. For example if you have any behavior in your container configuration you will want to create tests for that behavior. In you container config you set IDataContext's connection string via the configuration manager.
The following code is similar to what I do to test such a setup. Notice I avoid ObjectFactory (static singleton objects have their own problems) and wrap my container setup in a bootstrapper helper class:
[Test]
public void connection_string_should_come_from_application_configuration()
{
var container = new ContainerBootstraper().Container;
var connectionString = container.GetInstance<IDataContext>().ConnectionString
connectionString.ShouldEqual("test project application configuration connection string");
}

Categories