How to inject nested IOptions with IServiceCollection.Configure - .net-core

I have a Settings object that contains nested objects. I call WebApplicationBuilder.Configuration.Bind("Settings", options) to bind all the nested settings to the big Settings object, and I call IServiceCollection.Configure to make the big Settings object available through dependency injection. The only thing I don't like about this is that I have to inject the whole big object whenever I want to use it; I'm not able just to inject one of the nested objects. Can someone tell me what would be the best way to set things up so I can inject just one of the nested options objects if I need to?
public class Settings
{
public RepositoryOptions Repository { get; set; } = new();
public StoredProcedureOptions StoredProcedures { get; set; } = new();
...
}
Program.cs Main:
...
builder.Services.AddBLLServices(options =>
{
builder.Configuration.Bind("Settings", options);
options.Repository.IntegrationDatabaseConnectionString = builder.Configuration.GetConnectionString("Integration") ?? string.Empty;
options.StoredProcedures.MessageBrokerDatabaseConnectionString = builder.Configuration.GetConnectionString("MessageBroker") ?? string.Empty;
});
...
AddBLLServices method:
public static IServiceCollection AddBLLServices(this IServiceCollection services, Action<Settings> configureOptions)
{
// Make Settings object available through DI
services.Configure(configureOptions);
// Add services based on settings
var options = new Settings();
configureOptions.Invoke(options);
Configure(services, options);
return services;
}
Attempting to inject IConfig of a nested object injects an unconfigured object. Because I'm passing an Action to IServiceCollection.Configure I don't know how I could pull out one of the nested objects.
Edit:
Attempting to follow the advice of itsdaniel0 I have changed my AddBLLServices method to this to try to register the nested objects with DI:
public static IServiceCollection AddBLLServices(this IServiceCollection services, Action<Settings> configureOptions)
{
var options = new Settings();
configureOptions.Invoke(options);
services.Configure<Settings>(o => o = options);
services.Configure<RepositoryOptions>(o => o = options.Repository);
services.Configure<StoredProcedureOptions>(o => o = options.StoredProcedures);
services.Configure<WireTapListenerOptions>(o =>
{
var opt = new Settings();
configureOptions.Invoke(opt);
o = opt.WireTapListener;
});
services.Configure<WireTapQueueListenerOptions>(o => o = options.WireTapQueueListener);
services.Configure<ServiceListenerOptions>(o => o = options.ServiceListener);
services.Configure<EmailAlertOptions>(o => o = options.EmailAlert);
Configure(services, options);
return services;
}
But when I try to inject the options in a constructor the value is not configured.

Your original problem is because you're registering a single instance of the class, then inject the subclasses. You need to register them each individually.
Usually you would bind each class to a configuration section
services.Configure<SubClass>(config.Bind("Parent:Nested"));
In the position you're in, there is no built-in way.
I'd suggest the following extension method to register an instance
It's worth pointing out, this won't allow for the use of IOptionsSnapshot, but can be adapted to suit your needs as required
public static class IServiceCollectionExtensions
{
public static IServiceCollection Configure<TOptions>(this IServiceCollection services, TOptions instance)
where TOptions : class
{
if (services is null)
{
throw new ArgumentNullException(nameof(services));
}
_ = services.AddOptions();
_ = services.AddSingleton<IOptions<TOptions>>(new OptionsWrapper<TOptions>(instance));
return services;
}
}
Then you can use it like so services.Configure(options.EmailAlert);

Related

.Net core 2.2 API versioning and proper routing

I am creating an API. I use swagger but due to a huge number of controllers and actions, I want to split API endpoint by domain. To get this I thought about versioning of the API. I thought about using the Status of ApiVersion. The code of my controllers is below.
[ApiVersion("1.0-First")] //This is ApiVersion MajorVersion = 1, Status = "First"
[Route("api/v{version:apiVersion}/[controller]")]
public class FirstController
[ApiVersion("1.0-Second")]
[Route("api/v{version:apiVersion}/other")]
public class SecondController
My swagger looks fine and the definitions of parts of API are good. (I know that path should be without capital letters - this is for test purposes only)
But swagger can't reach any endpoint. Because the valid endpoint is at /api/v1.0-First/First not /api/v1/First.
My startUp class looks like below:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().AddApiExplorer();
services.AddApiVersioning(c =>
{
c.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("V"),
new UrlSegmentApiVersionReader());
c.ReportApiVersions = false;
c.DefaultApiVersion = new ApiVersion(1, 0);
});
services.AddVersionedApiExplorer(options =>
{
options.SubstituteApiVersionInUrl = true;
options.SubstitutionFormat = "V";
options.DefaultApiVersion = new ApiVersion(1, 0);
});
services.RegisterSwaggerConfiguration();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseHttpsRedirection();
app.UseMvc();
app.AddSwagger(app.ApplicationServices.GetService<IApiVersionDescriptionProvider>(), Configuration);
}
There is some static class I wrote to add the dependencies based on IApiVersionDescriptionProvider
public static class SwaggerExtension
{
public static void RegisterSwaggerConfiguration(this IServiceCollection services)
{
services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
services.AddSwaggerGen();
}
public static void AddSwagger(this IApplicationBuilder app, IApiVersionDescriptionProvider provider, IConfiguration configuration)
{
var prefix = "swagger";
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.RoutePrefix = string.Empty;
foreach (var description in provider.ApiVersionDescriptions)
{
c.SwaggerEndpoint($"{prefix}/{description.GroupName}/swagger.json", description.GroupName);
}
});
}
}
And another class for SwaggerDoc generation.
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IApiVersionDescriptionProvider provider;
private readonly IConfiguration configuration;
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider, IConfiguration configuration)
{
this.provider = provider;
this.configuration = configuration;
}
public void Configure(SwaggerGenOptions options)
{
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
}
}
private OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
var info = new OpenApiInfo()
{
Title = description.GroupName,
Version = description.ApiVersion.ToString(),
};
if (description.IsDeprecated)
{
info.Description += " This API version has been deprecated.";
}
return info;
}
}
I want to get the routing work as api/v1/First or api/v1.0/First (this should not matter).
Maybe writting some custom middleware to handle this case would be good idea?
By now I am out of ideas and in general I couldn't find any articles about using status of ApiVersion.
EDIT:
Changed Title.
We had a similar problem some time ago. We needed to split an Api by a customer privilege/domain. The research took some time as well :), please note that we are using NSwag.
So as you already mentioned (custom middleware) we've created a custom OperationProcessor and used base type checking. Take a look at an example:
services.AddOpenApiDocument(document =>
{
document.Title = "API A";
document.OperationProcessors.Insert(0, new IncludeAApiControllersInSwagger());
});
services.AddOpenApiDocument(document =>
{
document.Title = "API B";
document.OperationProcessors.Insert(0, new IncludeBApiControllersInSwagger());
});
and then
private class IncludeAApiControllersInSwagger : IOperationProcessor
{
public bool Process(OperationProcessorContext context)
{
return IsControllerInType(context, typeof(AApiController));
}
}
private class IncludeBApiControllersInSwagger : IOperationProcessor
{
public bool Process(OperationProcessorContext context)
{
return IsControllerInType(context, typeof(BApiController));
}
}
The last step would be to build a proper inheritance over your controllers.
An API version is always an API version; the values are explicit - by design. There is no universe where 1.0-First can map to an API, but not include the status.
The status is most useful for pre-releases. For example, you might have /first?api-version=1.0-preview.1. When you have a volatile, preview version of an API, this prevents you from having to bump up to 1.1 and so on. 1.0 is greater than 1.0-preview.1.
From your description, it sounds like you want to group or categorize your APIs by an additional level. The Swagger UI only supports a single level of grouping, but ASP.NET API Versioning 7.0+ now has support to make custom group names with API versions easy to configure using the FormatGroupName option.
If your API has a custom group name like this:
[ApiVersion(1.0)]
[ApiExplorerSettings(GroupName = "First")]
[Route("api/v{version:apiVersion}/[controller]")]
public class FirstController : ControllerBase
{
[HttpGet]
public IActionResult Get() => Ok();
}
You can now configure the combination of both like this:
services.AddApiVersioning()
.AddApiExplorer(
options =>
{
options.SubstituteApiVersionInUrl = true;
options.FormatGroupName = (group, version) => $"{version}-{group}";
});
This only works if you set a custom group name and define a callback. The rules are:
Default configuration; formatted ApiVersion
Group name set, but not callback; use group name
Group name and callback set; result for callback with group and formatted ApiVersion
Only callback set; ignored and uses default configuration as there's no group name
The ApiVersion is formatted according to GroupNameFormat. By default, this will simply be ApiVersion.ToString(). You can still use it if you want to. For example, if GroupNameFormat = "'v'VVV";, then the formatted name via the callback will result in v1-First.
Despite all of this configuration and grouping, the route to your API will still be: api/v1/first. I believe that will get you both of your goals.

.NET Core default dependency injection with Castle DynamicProxy

I have many AOP libraries that use Castle DynamicProxy with Autofac DI container for logging, auditing, transaction control, etc.
I wonder if there is a way to declare interceptors using the default .NET Core DI container. It will be good to have this flexibility since many .NET Core projects don't use Autofac.
Yes, you can use DynamicProxy using Core DI. I've written up a blog post explaining it at http://codethug.com/2021/03/17/Caching-with-Attributes-in-DotNet-Core5/, but here is the code for it:
Create an attribute
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CacheAttribute : Attribute
{
public int Seconds { get; set; } = 30;
}
Create an interceptor (requires Castle.Core nuget package)
public class CacheInterceptor : IInterceptor
{
private IMemoryCache _memoryCache;
public CacheInterceptor(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
// Create a cache key using the name of the method and the values
// of its arguments so that if the same method is called with the
// same arguments in the future, we can find out if the results
// are cached or not
private static string GenerateCacheKey(string name,
object[] arguments)
{
if (arguments == null || arguments.Length == 0)
return name;
return name + "--" +
string.Join("--", arguments.Select(a =>
a == null ? "**NULL**" : a.ToString()).ToArray());
}
public void Intercept(IInvocation invocation)
{
var cacheAttribute = invocation.MethodInvocationTarget
.GetCustomAttributes(typeof(CacheAttribute), false)
.FirstOrDefault() as CacheAttribute;
// If the cache attribute is added ot this method, we
// need to intercept this call
if (cacheAttribute != null)
{
var cacheKey = GenerateCacheKey(invocation.Method.Name,
invocation.Arguments);
if (_memoryCache.TryGetValue(cacheKey, out object value))
{
// The results were already in the cache so return
// them from the cache instead of calling the
// underlying method
invocation.ReturnValue = value;
}
else
{
// Get the result the hard way by calling
// the underlying method
invocation.Proceed();
// Save the result in the cache
var options = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
new System.TimeSpan(hours: 0, minutes: 0,
seconds: cacheAttribute.Seconds)
};
_memoryCache.Set(cacheKey, invocation.ReturnValue,
options);
}
}
else
{
// We don't need to cache the results,
// nothing to see here
invocation.Proceed();
}
}
}
Add an extension method to help register classes in DI:
public static void AddProxiedScoped<TInterface, TImplementation>
(this IServiceCollection services)
where TInterface : class
where TImplementation : class, TInterface
{
// This registers the underlying class
services.AddScoped<TImplementation>();
services.AddScoped(typeof(TInterface), serviceProvider =>
{
// Get an instance of the Castle Proxy Generator
var proxyGenerator = serviceProvider
.GetRequiredService<ProxyGenerator>();
// Have DI build out an instance of the class that has methods
// you want to cache (this is a normal instance of that class
// without caching added)
var actual = serviceProvider
.GetRequiredService<TImplementation>();
// Find all of the interceptors that have been registered,
// including our caching interceptor. (you might later add a
// logging interceptor, etc.)
var interceptors = serviceProvider
.GetServices<IInterceptor>().ToArray();
// Have Castle Proxy build out a proxy object that implements
// your interface, but adds a caching layer on top of the
// actual implementation of the class. This proxy object is
// what will then get injected into the class that has a
// dependency on TInterface
return proxyGenerator.CreateInterfaceProxyWithTarget(
typeof(TInterface), actual, interceptors);
});
}
Add these lines to ConfigureServices in Startup.cs
// Setup Interception
services.AddSingleton(new ProxyGenerator());
services.AddScoped<IInterceptor, CacheInterceptor>(
After that, if you want to use the cache interceptor, you need to do two things:
First, add the attribute to your method
[Cache(Seconds = 30)]
public async Task<IEnumerable<Person>> GetPeopleByLastName(string lastName)
{
return SomeLongRunningProcess(lastName);
}
Second, register the class in DI using the Proxy/Interception:
services.AddProxiedScoped<IPersonRepository, PersonRepository>();
Instead of the normal way without the Proxy/Interception:
services.AddScoped<IPersonRepository, PersonRepository>();
The base .NET Core container does not have any extra features like interceptors. The whole reason the DI container in .NET Core can be swapped out for something like Autofac is so you can move to a different container once you outgrow the default one.

How to convert to Xunit using mocking

I have these two methods in my service class
public class PatientService : IPatientService
{
private readonly IRestClient _restClient;
private readonly IAppSettings _appSettings;
public PatientService(IRestClient restClient, IAppSettings appSettings)
{
_restClient = restClient;
_appSettings = appSettings;
}
public async Task<IList<PatientViewModel>> GetPatients(int wardId)
{
var url = _appSettings.Server + _appSettings.PatientServiceEndPoint + wardId;
var token = _appSettings.Token;
return GetPatientList(await _restClient.GetAsync<List<PatientInfo>>(url, token));
}
public IList<PatientViewModel> GetPatientList(IList<PatientInfo> patientInfoList)
{
return patientInfoList.Select(p => new PatientViewModel(p)).ToList();
}
}
I need to add this code to my Xunit.cs. How to do it?
I've implemented this and I do not know how to proceed.
private readonly PatientListPageViewModel _patientListPageViewModel;
private readonly Mock<IPatientService> _patient;
public PatientServiceTests()
{
_patient = new Mock<IPatientService>();
_patientListPageViewModel = new PatientListPageViewModel(_patient.Object);
}
[Fact]
public void GetListByWard_PassingWardId_GetPatientsCountAccordingToWardId()
{
}
This is what I tried to do. How to convert those two methods in service to be testable?
You did get mocking a bit wrong. It is not the component under test that is mocked, but its dependencies. When unit-testing you'd like to test a unit in isolation. Your case of mocking would be kind of correct if you unit-tested the PatientListPageViewModel, but since your test class is named PatientServiceTests I assume that you really wanted to test PatientService. If you wanted to test the former, you would be quite right to mock IPatientService, but when testing PatientService, IRestClient and IAppSettings shall be mocked
public PatientServiceTests()
{
_restClientMock = new Mock<IRestClient>();
_appSettingsMock = new Mock<IAppSettings>();
_patientService = new PatientService(_restClientMock.Object, _appSettingsMock.Object);
}
And your test could be something like
[Fact]
public async Task ReturnsCorrectPatientList() // async supported as of xUnit 1.9
{
// set up the mock
_restClientMock.SetUp(restClient => restClient.GetAsync<List<Patient>>(It.IsAny<string>(), It.IsAny<string>())
.Returns(() => Task.FromResult(/* what patients it shall return */));
var result = await _patientService.GetPatients(0);
// compare whether the returned result matches your expectations
}
If you wanted to test whether the URL is formed correctly, you could use Verify
[Theory]
[InlineData("SERVER", "ENDPOINT", 12, "1234", "SERVERENDPOINT12")]
[InlineData("https://localhost:65000", "/patients/", 5, https://localhost:65000/patients/5")]
public void TestWhetherCorrectUrlIsCalled(string server, string endpoint, int wardId, string token, string expectedUrl)
{
_appSettingsMock.SetupGet(appSettings => appSettings.Server).Returns(server);
_appSettingsMock.SetupGet(appSettings => appSettings.PatientServiceEndPoint).Returns(endpoint);
_appSettingsMock.SetupGet(appSettings => appSettings.Token).Returns(token);
_restClientMock.SetUp(restClient => restClient.GetAsync<List<Patient>>(It.IsAny<string>(), It.IsAny<string>())
.Returns(() => Task.FromResult(new List<Patient>()));
// we do not need the result
await _patientService.GetPatients(wardId);
_restClientMock.Verify(restClient => restClient.GetAsync<List<Patient>>(exptectedUrl, token), Times.Once);
}
We are setting up the IRestClient in this case, since it would return null otherwise. And await null would cause your test to fail. After GetPatients has been called we are using Verify to verify that GetAsync has been called with the correct parameters. If it has not been called, Verify will throw and your test will fail. Times.Once means, that GetAsync shall have been called once and only once.
On a side note: Viewmodels shall have a meaning in the context of your user interface only. Services shall be independent and hence not return viewmodels, as you did, but POCOs (or maybe domain models). In this case the interface of your service should be
public interface IPatientService
{
public async Task<IList<Patient>> GetPatients(int wardId);
// ...
}

ASP.NET Core ControllerContext vs ActionContext in UrlHelper

I am trying to implement pagination in my Asp.net core 2 API. To create pagination links, I am using UrlHelper. The constructor for UrlHelper requires the context in which the action runs.
The examples I've seen have been using below configuration in startup and then injecting IUrlHelper into the controller where it is needed.
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper>(x => {
var actionContext = x.GetRequiredService<IActionContextAccessor>().ActionContext;
var factory = x.GetRequiredService<IUrlHelperFactory>();
return factory.GetUrlHelper(actionContext);
});
But controllers also have ControllerContext which derives from ActionContext (https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllercontext?view=aspnetcore-2.1).
I am able to do the following:
public Object GetAll() //ignore object return, for test purposes
{
var urlHelper = new UrlHelper(ControllerContext);
var nextLink = urlHelper.Link("GetPosts", new { page = 1, pageSize = 3 });
//return _context.Posts;
return new
{
NextPageLink = nextLink,
Results = _context.Posts,
test = ControllerContext.RouteData.Values
};
}
The code above is able to create the links correctly. I don't have a firm grasp on the nuances of the framework so I am wondering if above is a correct way to initialize UrlHelper. Will this lead to problems? If you can point me in the direction of some documentation around this or explain the reason behind if the approach is good/bad, that would be very helpful.
What you have can work.
It does however tightly couple the controller to an implementation concern.
If you have need for the helper you can follow a similar format to what was configured at startup by injecting the IUrlHelperFactory into the controller and getting the helper using the controller's ControllerContext, which as you have already discovered, derives from ActionContext
public class MyController : Controller {
private readonly IUrlHelperFactory factory;
//...other dependencies
public MyController(IUrlHelperFactory factory) {
this.factory = factory;
//...other dependencies
}
public IActionResult GetAll() {
var urlHelper = factory.GetUrlHelper(ControllerContext);
var nextLink = urlHelper.Link("GetPosts", new { page = 1, pageSize = 3 });
return Ok(new {
NextPageLink = nextLink,
Results = _context.Posts,
test = ControllerContext.RouteData.Values
});
}
//...other actions
}

in API, create multiple controller constructor with one parameter

[Route("api/[controller]")]
public class DigitalDocumentController : Controller
{
private IDigitalDocumentService digitalDocumentService;
private IDatabaseInitializer databaseInitializer;
public DigitalDocumentController(IDigitalDocumentService digitalDocumentService)
{
this.digitalDocumentService = digitalDocumentService;
}
public DigitalDocumentController(IDatabaseInitializer databaseInitializer)
{
this.databaseInitializer = databaseInitializer;
}
i want two controller constructor in my project to Mock in xUnit Testing, but there was an error in my swagger interface {
"error": "Multiple constructors accepting all given argument types have been found in type 'i2ana.Web.Controllers.DigitalDocumentController'. There should only be one applicable constructor."
}
can anybody help me how i can do it ?
…
what i am try to do , is to test Uniquness of the Name Field in my database
My testing code:
[Fact]
public void AddNotUniqueName_ReturnsNotFoundObjectResult()
{
var digitalDocument = new DigitalDocument
{
Image = new byte[] { 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 },
CreatedOn = DateTime.Today,
Id = 6,
Location = "temp",
Name = "Flower",
Tages = new List<Tag> { new Tag { Id = 1, Value = "Tag 1" }, new Tag { Id = 1, Value = "Tag 2" } }
};
// Arrange
var mockRepo = new Mock<IDatabaseInitializer>();
mockRepo.Setup(repo => repo.SeedAsync()).Returns(Task.FromResult(AddUniqueDigitalDocument(digitalDocument)));
var controller = new DigitalDocumentController(mockRepo.Object);
// Act
var result = controller.Add(digitalDocument);
// Assert
var viewResult = Assert.IsType<NotFoundObjectResult>(result);
var model = Assert.IsAssignableFrom<int>(viewResult.Value);
Assert.NotEqual(6, model);
}
the "AddUniqueDigitalDocument" returns 6 only to test that the new digitaldocumet is not the same id of my initialize data.
When using dependency injection, you should only have one constructor where all dependencies can be satisfied. Otherwise, how is the DI container to know which constructor to utilize? That's your issue here. Using the Microsoft.Extensions.DependencyInjection package, and since this is a controller you're injecting into, there's only one reasonable way to solve this: don't register one or the other of the services, IDigitalDocumentService or IDatatabaseInitializer. If only one is registered, the service collection will simply use the constructor it has a registered service for.
It's possible with a more featured DI container, you might be able to configure something to allow it choose the proper constructor. How to do that would be entirely dependent on the DI container you end up going with, though, so not much more can be said on the subject at this point. Just realize that the default container (Microsoft.Extensions.DependencyInjection) is intentionally simplistic, so if you needs are more complex, you should sub in a full DI container.
UPDATE
You should be doing integration testing with the test host and an in-memory database. The basic approach is:
public MyTests()
{
_server = new TestServer(new WebHostBuilder().UseStartup<TestStartup>());
_context = _server.Host.Services.GetRequiredService<MyContext>();
_client = _server.CreateClient();
}
In your app's Startup, create a virtual method:
public virtual void ConfigureDatabase(IServiceCollection services)
{
// normal database setup here, e.g.
services.AddDbContext<MyContext>(o =>
o.UseSqlServer(Configuration.GetConnectionString("Foo")));
}
Then, in ConfigureServices, replace your database setup with a call to this method.
Finally, in your test project, create a TestStartup class and override the ConfigureDatabase method:
public class TestStartup : Startup
{
public override void ConfigureDatabase(IServiceCollection services)
{
var databaseName = Guid.NewGuid().ToString();
services.AddDbContext<MyContext>(o =>
o.UseInMemoryDatabase(databaseName));
}
}
Now, in your tests you just make requests against the test client (which is just an HttpClient instance, so it works like any other HttpClient). You start by setting up your database with appropriate test data, and then ensure that the correct response is returned:
// Arrange
_context.Add(new DigitalDocument { Name = "Foo" });
await _context.SaveChanges();
// Act
// Submit a `DigitalDocument` with the same name via `_client`
// Assert
// Inspect the response body for some indication that it was considered invalid. Or you could simply assert that no new `DigitalDocument` was created by querying `_context` (or both)
This is admittedly a lot easier with an API, as with a web application, you're going to invariably need to do some HTML parsing. However, the docs and corresponding sample app help you with that.
Additionally, in actual practice, you'd want to use a test fixture to prevent having to bootstrap a test server for every test. Again, the docs have you covered there. One thing to note, though, is that once you switch to using a fixture, your database will then be persisted between tests. To segregate your test data, make sure that you call EnsureDeleted() on your context before each test. This can be easily done in the test class' constructor:
public class MyTests : IClassFixture<WebApplicationFactory<Startup>>
{
private readonly HttpClient _client;
private readonly MyContext _context;
public MyTests(WebApplicationFactory<Startup> factory)
{
factory = factory.WithWebHostBuilder(builder => builder.UseStartup<TestStartup>());
_client = factory.CreateClient();
_context = factory.Server.Host.Services.GetRequiredService<MyContext>();
_context.EnsureDeleted();
}
I don't even like this much bootstrapping code in my tests, though, so I usually inherit from a fixture class instead:
public class TestServerFixture : IClassFixture<WebApplicationFactory<Startup>>
{
protected readonly HttpClient _client;
protected readonly MyContext _context;
public TestServerFixture(WebApplicationFactory<Startup> factory)
{
factory = factory.WithWebHostBuilder(builder => builder.UseStartup<TestStartup>());
_client = factory.CreateClient();
_context = factory.Server.Host.Services.GetRequiredService<MyContext>();
_context.EnsureDeleted();
}
}
Then, for each test class:
public class MyTests : TestServerFixture
{
public MyTests(WebApplicationFactory<Startup> factory)
: base(factory)
{
}
This may seem like a lot, but most of it is one-time setup. Then, your tests will be much more accurate, more robust, and even easier in many ways.

Resources