Set a custom SessionStore for ConfigureApplicationCookie without BuildServiceProvider() - asp.net-core

I have a .NET Core 3 project (recently upgraded from 2.2) that uses a Redis distributed cache and cookie authentication.
It currently looks something like this:
public void ConfigureServices(IServiceCollection services)
{
// Set up Redis distributed cache
services.AddStackExchangeRedisCache(...);
...
services.ConfigureApplicationCookie(options =>
{
...
// Get a service provider to get the distributed cache set up above
var cache = services.BuildServiceProvider().GetService<IDistributedCache>();
options.SessionStore = new MyCustomStore(cache, ...);
}):
}
The problem is that BuildServiceProvider() causes a build error:
Startup.cs(...): warning ASP0000: Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
This doesn't appear to be an option - ConfigureApplicationCookie is in Startup.ConfigureServices and can only configure new services, Startup.Configure can use the new services, but can't override CookieAuthenticationOptions.SessionStore to be my custom store.
I've tried adding services.AddSingleton<ITicketStore>(p => new MyCustomRedisStore(cache, ...)) before ConfigureApplicationCookie, but this is ignored.
Explicitly setting CookieAuthenticationOptions.SessionStore appears to be the only way to get it to use anything other than the local memory store.
Every example I've found online uses BuildServiceProvider();
Ideally I want to do something like:
services.ConfigureApplicationCookieStore(provider =>
{
var cache = provider.GetService<IDistributedCache>();
return new MyCustomStore(cache, ...);
});
Or
public void Configure(IApplicationBuilder app, ... IDistributedCache cache)
{
app.UseApplicationCookieStore(new MyCustomStore(cache, ...));
}
And then CookieAuthenticationOptions.SessionStore should just use whatever I've configured there.
How do I make the application cookie use an injected store?

Reference Use DI services to configure options
If all the dependencies of your custom store are injectable, then just register your store and required dependencies with the service collection and use DI services to configure options
public void ConfigureServices(IServiceCollection services) {
// Set up Redis distributed cache
services.AddStackExchangeRedisCache(...);
//register my custom store
services.AddSingleton<ITicketStore, MyCustomRedisStore>();
//...
//Use DI services to configure options
services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
.Configure<ITicketStore>((options, store) => {
options.SessionStore = store;
});
services.ConfigureApplicationCookie(options => {
//do nothing
}):
}
If not then work around what is actually registered
For example
//Use DI services to configure options
services.AddOptions<CookieAuthenticationOptions>(IdentityConstants.ApplicationScheme)
.Configure<IDistributedCache>((options, cache) => {
options.SessionStore = new MyCustomRedisStore(cache, ...);
});
Note:
ConfigureApplicationCookie uses a named options instance. - #KirkLarkin
public static IServiceCollection ConfigureApplicationCookie(this IServiceCollection services, Action<CookieAuthenticationOptions> configure)
=> services.Configure(IdentityConstants.ApplicationScheme, configure);
The option would need to include the name when adding it to services.

To implement Redis Tickets in .NET Core 3.0 we did the following which is the above in a bit more of a final form::
services.AddSingleton<ITicketStore, RedisTicketStore>();
services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
.Configure<ITicketStore>((options, store) => {
options.SessionStore = store;
});
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// ...configure identity server options
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);
Here is a Redis implementation:
public class RedisTicketStore : ITicketStore
{
private const string KeyPrefix = "AuthSessionStore-";
private IDistributedCache cache;
public RedisTicketStore(IDistributedCache cache)
{
this.cache = cache;
}
public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
var guid = Guid.NewGuid();
var key = KeyPrefix + guid.ToString();
await RenewAsync(key, ticket);
return key;
}
public Task RenewAsync(string key, AuthenticationTicket ticket)
{
var options = new DistributedCacheEntryOptions();
var expiresUtc = ticket.Properties.ExpiresUtc;
if (expiresUtc.HasValue)
{
options.SetAbsoluteExpiration(expiresUtc.Value);
}
byte[] val = SerializeToBytes(ticket);
cache.Set(key, val, options);
return Task.FromResult(0);
}
public Task<AuthenticationTicket> RetrieveAsync(string key)
{
AuthenticationTicket ticket;
byte[] bytes = null;
bytes = cache.Get(key);
ticket = DeserializeFromBytes(bytes);
return Task.FromResult(ticket);
}
public Task RemoveAsync(string key)
{
cache.Remove(key);
return Task.FromResult(0);
}
private static byte[] SerializeToBytes(AuthenticationTicket source)
{
return TicketSerializer.Default.Serialize(source);
}
private static AuthenticationTicket DeserializeFromBytes(byte[] source)
{
return source == null ? null : TicketSerializer.Default.Deserialize(source);
}
}
Redis implementation from: https://mikerussellnz.github.io/.NET-Core-Auth-Ticket-Redis/

Related

Cannot replace default JSON contract resolver in ASP.NET Core 3

After creating basic Web API project based on .NET Core 3.0 framework, all API responses were coming in camel case. I installed SwashBuckle Swagger + built-in JSON serializer from System.Text.Json, specifically, to display enums as strings, everything worked as before. Then, I decided to switch to NSwag + NewtonSoftJson, because of some limitations of built-in serializer with dynamic and expando objects. Now, all API responses are displayed in PascalCase and I cannot change neither naming policy, nor even create custom contract resolver.
Example
https://forums.asp.net/t/2138758.aspx?Configure+SerializerSettings+ContractResolver
Question
I suspect that maybe some package overrides contract resolver behind the scene. How to make sure that API service uses ONLY custom contract resolver that I assign at startup and ignores all other similar settings?
Custom JSON contract resolver:
public class CustomContractResolver : IContractResolver
{
private readonly IHttpContextAccessor _context;
private readonly IContractResolver _contract;
private readonly IContractResolver _camelCaseContract;
public CustomContractResolver(IHttpContextAccessor context)
{
_context = context;
_contract = new DefaultContractResolver();
_camelCaseContract = new CamelCasePropertyNamesContractResolver();
}
// When API endpoint is hit, this method is NOT triggered
public JsonContract ResolveContract(Type value)
{
return _camelCaseContract.ResolveContract(value);
}
}
Controller:
[ApiController]
public class RecordsController : ControllerBase
{
[HttpGet]
[Route("services/records")]
[ProducesResponseType(typeof(ResponseModel<RecordEntity>), 200)]
public async Task<IActionResult> Records([FromQuery] QueryModel queryModel)
{
var response = new ResponseModel<RecordEntity>();
return Content(JsonConvert.SerializeObject(response), "application/json"); // NewtonSoft serializer
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services
.AddCors(o => o.AddDefaultPolicy(builder => builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()));
services
.AddControllers(o => o.RespectBrowserAcceptHeader = true)
/*
.AddJsonOptions(o =>
{
o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
o.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
o.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
})
*/
.AddNewtonsoftJson(o =>
{
o.UseCamelCasing(true);
o.SerializerSettings.Converters.Add(new StringEnumConverter());
//o.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver { NamingStrategy = new CamelCaseNamingStrategy() };
o.SerializerSettings.ContractResolver = new CustomContractResolver(new HttpContextAccessor());
});
services.AddOpenApiDocument(o => // NSwag
{
o.PostProcess = document =>
{
document.Info.Version = "v1";
document.Info.Title = "Demo API";
};
});
DataConnection.DefaultSettings = new ConnectionManager(DatabaseOptionManager.Instance); // LINQ to DB
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(o => o.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(o => o.MapControllers());
app.UseOpenApi(); // NSwag
app.UseSwaggerUi3(o => o.Path = "/v2/docs");
app.UseReDoc(o => o.Path = "/v1/docs");
}
Still don't understand why custom contract resolver is not triggered by API endpoint, but found a combination that works for me to switch API to camel case. Feel free to explain why it works this way.
services.AddControllers(o => o.RespectBrowserAcceptHeader = true)
// Options for System.Text.Json don't affect anything, can be uncommented or removed
//.AddJsonOptions(o =>
//{
// o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
// o.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
// o.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
//})
.AddNewtonsoftJson(o =>
{
o.UseCamelCasing(true);
o.SerializerSettings.Converters.Add(new StringEnumConverter());
// This option below breaks global settings, so had to comment it
//o.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver
//{
// NamingStrategy = new CamelCaseNamingStrategy()
//};
});
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
Idea was taken from this article.
NewtonSoft allows to set global serialization settings disregarding MVC, Web API, and other frameworks.

Is It Possible to Dynamically Add SwaggerEndpoints For SwaggerUI?

We're building out a services oriented architecture in .NET Core. We've decided to use Ocelot as our API gateway. I have integrated Ocelot with Consul for service discovery. Now I'm trying to attempt to create a unified Swagger UI for all the downstream services.
Prior to service discovery we had Swagger setup like this:
// Enable middleware to serve generated Swagger as a JSON endpoint
app.UseSwagger(c => { c.RouteTemplate = "{documentName}/swagger.json"; });
// Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/docs/customer/swagger.json", "Customers Api Doc");
c.SwaggerEndpoint("/docs/employee/swagger.json", "Employee Api Doc");
c.SwaggerEndpoint("/docs/report/swagger.json", "Reports Api Doc");
});
On the Swagger UI this provides a "select a spec" dropdown. The developers like this functionality and we'd like to keep it. However, now that we've removed the manual configuration in favor of service discovery we would also like to have these endpoints dynamically updated.
With the current Swagger solution that's available is this possible? I haven't seen anything relating to service discovery or being able to dynamically configure the UI. Thoughts and suggestions?
Update
I've come up with a way to do this. It is a bit hack-ish and I'm hoping there is a way to do this that isn't so heavy handed.
public class Startup
{
static object LOCK = new object();
SwaggerUIOptions options;
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<SwaggerUIOptions>((provider) =>
{
return this.options;
});
services.AddSingleton<IHostedService, SwaggerUIDocsAggregator>();
services.AddSingleton<IConsulDiscoveryService, MyCounsulDiscoveryServiceImplementation>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Enable middleware to serve generated Swagger as a JSON endpoint
app.UseSwagger(c => { c.RouteTemplate = "{documentName}/swagger.json"; });
// Enable middleware to serve swagger-ui assets (HTML, JS, CSS etc.)
app.UseSwaggerUI(c =>
{
this.options = c;
});
}
}
public class SwaggerUIDocsAggregator : IHostedService
{
static object LOCK = new object();
IConsulDiscoveryService discoveryService;
SwaggerUIOptions options;
Timer timer;
bool polling = false;
int pollingInterval = 600;
public ConsulHostedService(IConsulDiscoveryService discoveryService, SwaggerUIOptions options)
{
this.discoveryService = discoveryService;
this.options = options;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
this.timer = new Timer(async x =>
{
if (this.polling)
{
return;
}
lock (LOCK)
{
this.polling = true;
}
await this.UpdateDocs();
lock (LOCK)
{
this.polling = false;
}
}, null, 0, pollingInterval);
}
public async Task StopAsync(CancellationToken cancellationToken)
{
this.timer.Dispose();
this.timer = null;
}
private async Task UpdateDocs()
{
var discoveredServices = await this.discoveryService.LookupServices();
var urls = new JArray();
foreach (var kvp in discoveredServices)
{
var serviceName = kvp.Key;
if (!urls.Any(u => (u as JObject).GetValue("url").Value<string>().Equals($"/{serviceName}/docs/swagger.json")))
{
urls.Add(JObject.FromObject(new { url = $"/{serviceName}/docs/swagger.json", name = serviceName }));
}
}
this.options.ConfigObject["urls"] = urls;
}
}
Easy way for integration Ocelot api gateway as a unified Swagger UI for all the downstream services is project MMLib.SwaggerForOcelot.

Storing and retrieving values from IDistributedCache (Redis) in .NET Core 2

I have an ASP.NET Core2 application. I am using both builtin and Autofac IoC containers. I am setting up all the component registrations in my Startup.cs file. While doing this, I am also setting up my DBContext which inherits from a custom DataContext which in turn inherits from DbContext and implements a custom IDataContextAsync. This DbContext expects a connection string as a constructor parameter.
My problem is that the connection string is stored in the Redis Cache which is an IDistributedCache. The cache is setup in the startup.cs file. The Connection String also is required in the same ConfigureServices method in Startup.cs. So, I don't seem to have access to this cache at this point.
Everything was working when I was using the HttpContext Session to store the connection string. Now that the application is being deployed to a Web farm, I can't use in proc session. We are using Redis for state management. This is where I am having a problem with.
Here is my ConfigureServices method from startup.cs file (unnecessary code removed for brevity).
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddJsonOptions(op => op.SerializerSettings.ContractResolver = new DefaultContractResolver());
services.AddSession(opt =>
{
opt.IdleTimeout = TimeSpan.FromMinutes(20);
opt.Cookie.Name = "apexportal.RulesSession";
opt.Cookie.HttpOnly = true;
});
services.AddDistributedRedisCache(o =>
{
var host = Configuration.GetValue<string>($"{AppConstants.REDIS}:{AppConstants.REDISHOST}");
var port = Configuration.GetValue<string>($"{AppConstants.REDIS}:{AppConstants.REDISPORT}");
o.Configuration = $"{host}";
o.InstanceName = Configuration.GetValue<string>($"{AppConstants.REDIS}:{AppConstants.REDISNAME}");
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//services.AddTransient<IConnectionStringProvider, ConnectionStringProvider>();
services.AddTransient<IDataContextAsync>(s => new PortalEFContext(GetPortalConnectionString()));
services.AddAuthentication(IISDefaults.AuthenticationScheme);
ContainerBuilder builder = new ContainerBuilder();
builder.Populate( services );
var container = builder.Build();
return container.Resolve<IServiceProvider>();
}
and here is my GetPortalConnectionString() method which is also in the startup.cs file. I want to replace the line accessor.HttpContext.Session.Get() with an injected RedisCache.Get().
private string GetPortalConnectionString()
{
IHttpContextAccessor accessor = new HttpContextAccessor();
//this is where I need to access the RedisCache and access the stored properties
// instead of using HttpContext.Session. But I don't know how to inject the IDistributedCache
// to this spot.
var connString = accessor.HttpContext.Session.Get<string>(AppConstants.SPCONNSTRING);
return connString ?? Configuration.GetConnectionString("PortalEFContext");
}
Later when the user has selected a database to use in the application, I am storing the connectionstring to that database in Redis Cache like so.
Here is my BaseController class which does that.
public abstract class BaseController : Controller
{
//private readonly IRulesEngineService reService;
protected readonly IHttpContextAccessor httpCtxAccessor;
protected readonly IConfiguration config;
private readonly IAuthService authService;
protected readonly IDistributedCache redisCache;
public BaseController(IHttpContextAccessor _httpContext, IConfiguration _config, IAuthService _authService, IDistributedCache _redisCache)
{
//reService = _reService;
httpCtxAccessor = _httpContext;
config = _config;
authService = _authService;
redisCache = _redisCache;
//SetupCurrentWindowsUserAsync();
}
protected async Task<string> SetCurrentDBConnString( int dbId )
{
var currDbId = await GetCurrentDBId();
if ( currDbId == 0 || currDbId != dbId )
{
var envConnStr = config.GetConnectionString( AppConstants.ENVCONNSTRING );
var connStr = await AppHelper.SetCurrentDBConnectionString( dbId, envConnStr );
//httpCtxAccessor.HttpContext.Session.Set<string>( AppConstants.SPCONNSTRING, connStr );
//httpCtxAccessor.HttpContext.Session.Set<int>( AppConstants.CURRDBID, dbId );
await redisCache.SetAsync<string>( AppConstants.SPCONNSTRING, connStr );
await redisCache.SetAsync<int>( AppConstants.CURRDBID, dbId );
await SetupCurrentWindowsUserAsync();
return connStr;
}
return null;
}
}
Can someone please tell me how I can access the Redis cache in my startup.cs file? Thanks.
It's actually very simple. You were almost there already.
Take a closer look at this line in your startup:
services.AddTransient<IDataContextAsync>(s => new PortalEFContext(GetPortalConnectionString()));
See the s parameter in the lambda? This is the DI container of .NET Core called IServiceProvider. This is what you were looking for. Just pass it down into your function and use it there to resolve anything you want.
So, the code will be the following:
public IServiceProvider ConfigureServices(IServiceCollection services)
...
services.AddTransient<IDataContextAsync>(s => new PortalEFContext(GetPortalConnectionString(s))); // <-- pass the container to the function
...
}
private string GetPortalConnectionString(IServiceProvider container)
{
// Here you go:
var cache = container.GetService<IDistributedCache>();
// and now do whatever you want with it.
var connString = cache.Get<string>(AppConstants.SPCONNSTRING);
// BTW, configuration can be resolved from container as well in order to avoid hard dependency on global Configuration object:
var config = container.GetService<IConfiguration>();
return connString ?? config.GetConnectionString("PortalEFContext");
}

How to correctly get dependent scoped services from ISecurityTokenValidator

In my asp.net core 2.0 web app, I've got a custom ISecurityTokenValidator which validates tokens.
It depends on a repository to do a db lookup - the repository itself is setup as a scoped dependency:
services.AddScoped<IMyRepository>(MyRepository);
Now the funkiness comes about because of the way the ISecurityTokenValidator is setup.
It's added in ConfigureServices:
.AddJwtBearer(options =>
{
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(new MyTokenValidator(services.BuildServiceProvider()));
})
This is how it looks:
public class MyTokenValidator : ISecurityTokenValidator
{
private readonly IServiceProvider _serviceProvider;
public MyTokenValidator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public bool CanReadToken(string securityToken) => true;
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters,
out SecurityToken validatedToken)
{
var serviceScopeFactory = _serviceProvider.GetRequiredService<IServiceScopeFactory>();
using (var scope = serviceScopeFactory.CreateScope())
{
var myRepository = scope.ServiceProvider.GetService<IMyRepository>();
var principalFactory = scope.ServiceProvider.GetService<IUserClaimsPrincipalFactory<User>>();
// Use the repo....
}
}
}
Now, because the IsecurityTokenProvider is only instantiated once, it's effectively a singleton. When I use the service provider to ask for a IMyRepository I was finding that I was always received the same object - there is no new scope as far as it was concerned, because it's in a singleton class.
To get round that, you'll see in the code above Ive had to manually force a new scope every time the token validator is called. Is this really the only way to resolve this, it seems like I'm hacking around to make it work here...
Old question but the best way I have found to solve this problem is to use IPostConfigureOptions<JwtBearerOptions> to configure SecurityTokenValidators.
First register the JWT bearer and options
services.AddAuthentication(options =>
{
...
}).AddJwtBearer(AuthenticateScheme, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
...
};
});
Then register a custom implementation of IPostConfigureOptions<JwtBearerOptions>
services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>, CustomJwtBearerOptionsPostConfigureOptions>();
And register a custom implementation of ISecurityTokenValidator
services.AddSingleton<MyCustomSecurityTokenValidator>();
CustomJwtBearerOptionsPostConfigureOptions could look something like:
public class CustomJwtBearerOptionsPostConfigureOptions : IPostConfigureOptions<JwtBearerOptions>
{
private readonly MyCustomSecurityTokenValidator _tokenValidator; //example dependancy
public CustomJwtBearerOptionsPostConfigureOptions(MyCustomSecurityTokenValidator tokenValidator)
{
_tokenValidator = tokenValidator;
}
public void PostConfigure(string name, JwtBearerOptions options)
{
options.SecurityTokenValidators.Clear();
options.SecurityTokenValidators.Add(_tokenValidator);
}
}
Now options.SecurityTokenValidators is configured by CustomJwtBearerOptionsPostConfigureOptions which is instantiated by dependency injection and can pass on the relevant decencies.

How to properly configure mock dependencies for testing Web Api (ASP.NET Core) controllers using Autofac

I'm using ASP.NET Core (2.0) with Autofac, and a Microsoft.AspNetCore.TestHost.TestServer for integration testing. However, for some test scenarios, I would like to inject some service mocks instead of the implementations loaded in ConfigureContainer method (as described here: http://docs.autofac.org/en/latest/integration/aspnetcore.html#quick-start-with-configurecontainer).
Example:
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.ConfigureServices(s => s.AddAutofac())
.UseStartup<Startup>()
.Build();
host.Run();
}
}
public class Startup
{
...
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterModule(new Modules.ApiModule());
}
...
}
And the test class:
public class BasicControllerTests
{
TestServer server;
HttpClient client;
public BasicControllerTests()
{
var resellerRepo = new Mock<IResellerProvider>();
resellerRepo.Setup(a => a.Query())
.Returns(new[] {
new Model.Reseller
{
Id = Guid.NewGuid(),
Code = "R1",
Name = "Reseller 1"
}
}.AsQueryable());
// How to inject mock properly in the lines below?
server = new TestServer(new WebHostBuilder()
.ConfigureServices(a => a.AddAutofac())
.UseStartup<Startup>());
client = server.CreateClient();
}
...
What I would like to do is to use the TestServer with all the dependencies as they are, but just the IResellerProvider mocked as in the test example. What is the best way to accomplish that? Of course, I could create a TestStartup class for this exact case, but I would like to know what is the proper way to handle this situation.
I found a workaround that works just fine and lets you inject any other dependency in .net core api.
You will have this standard code to start up in your tests
var clientFactory = new WebApplicationFactory<Startup>();
var client = clientFactory.WithWebHostBuilder(builder =>
builder.ConfigureTestServices(services =>
{
}));
var _httpClient = client.CreateClient();
Now you need to pass to .ConfigureTestServices an Action and you can use this to remove the registration you have on the normal app startup and add another one that lets say it's a fake one.This in possible because if you look with debugger on services you will see that all those you registered are presend and you will just need to replace the ones you want to moke.Here is a simple example that I used
RemoveVehicleServiceRegistrationFrom(services);
services.AddScoped<IVehicleService, FakeVehicleService>();
In the Remove method you just need to find and remove old registration.Something like this
private static void RemoveVehicleServiceRegistrationFrom(IServiceCollection services)
{
var vehicleService = services.Single(x => x.ServiceType == typeof(IVehicleService));
services.Remove(vehicleService);
}
Final version looks like this
private HttpClient _httpClient;
[OneTimeSetUp]
public void Setup()
{
var clientFactory = new WebApplicationFactory<Startup>();
var client = clientFactory.WithWebHostBuilder(builder =>
builder.ConfigureTestServices(services =>
{
RemoveVehicleServiceRegistrationFrom(services);
services.AddScoped<IVehicleService, FakeVehicleService>();
}));
var _httpClient = client.CreateClient();
}
private static void RemoveVehicleServiceRegistrationFrom(IServiceCollection services)
{
var vehicleService = services.Single(x => x.ServiceType == typeof(IVehicleService));
services.Remove(vehicleService);
}