I am converting my startup code into new ServiceStack Modular Startup approach and have hit a snag.
I have this code in old startup
public void Configure(IApplicationBuilder app)
{
app.UseHangfireDashboard(hangfirePath, new DashboardOptions
{
Authorization = new[] { new HangFireAuthorizationFilter() }
});
var appHost = new AppHost
{
AppSettings = settings
};
app.UseServiceStack(appHost);
var container = appHost.Resolve<Container>();
GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(container));
app.UseHangfireServer();
}
It's important that app.UseHangfireDashboard is registered before app.UseServiceStack or the dashboard wont work.
I got it working all fine except for the part where it links the IoC container to hangfire:
GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(container));
This is the working code without linking container:
[Priority(-1)]
public class ConfigureHangfirePostgreSql : IConfigureServices, IConfigureApp
{
IConfiguration Configuration { get; }
public ConfigureHangfirePostgreSql(IConfiguration configuration) => Configuration = configuration;
public void Configure(IServiceCollection services)
{
var conn = Configuration.GetValue<string>("database:connectionString");
services.AddHangfire((isp, config) =>
{
config.UsePostgreSqlStorage(conn, new PostgreSqlStorageOptions
{
InvisibilityTimeout = TimeSpan.FromDays(1)
});
config.UseConsole();
});
}
public void Configure(IApplicationBuilder app)
{
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new HangFireAuthorizationFilter() }
});
// commented out because I dont think it's possible to get container yet and also code doesn't work
//var container = app.Resolve<Container>();
//GlobalConfiguration.Configuration.UseActivator(new ContainerJobActivator(container));
app.UseHangfireServer();
}
}
I am setting the priority to -1 so it runs before servicestack is registered. Because of that I guess the container isn't yet created so I need to make another module to run after like this:
[Priority(2)]
public class ConfigureHangfirePostgreSqlPost : IConfigureApp
{
public void Configure(IApplicationBuilder app)
{
//how do I resolve container here?
}
}
The container isn't registered as a service (as I am asking the container for the service) so im not sure how I am able to access it.
What is the right way of getting hold of the container in a startup module?
It's hard to decipher what the actual question is, answering a clear one found on the last line:
What is the right way of getting hold of the container in a startup module?
This existing answer has a good summary of ASP.NET Core IOC, where you can access any singleton dependencies from app.ApplicationServices, e.g:
public void Configure(IApplicationBuilder app)
{
var serviceProvider = app.ApplicationServices;
var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}
Related
Following the example code I'm attempting to hook up an ImageSharp.Web's AzureBlobStorageImageProvider but all image requests result in 404's. Any idea what I'm missing from my wiring-up?
Looking at the docs it seems the url convention should be https://localhost:44336/container-name/file-name.jpg?width=30
In my Startup.cs I have the following code:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddImageSharp()
.Configure<AzureBlobStorageImageProviderOptions>(options =>
{
// The "BlobContainers" collection allows registration of multiple containers.
options.BlobContainers.Add(new AzureBlobContainerClientOptions
{
ConnectionString = "REDACTED",
ContainerName = "test-container"
});
})
.AddProvider<AzureBlobStorageImageProvider>()
.Configure<AzureBlobStorageCacheOptions>(options =>
{
options.ConnectionString = "REDACTED";
options.ContainerName = "cache";
// Optionally create the cache container on startup if not already created.
AzureBlobStorageCache.CreateIfNotExists(options, PublicAccessType.None);
})
.SetCache<AzureBlobStorageCache>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseImageSharp();
}
Any ideas what I might be missing?
It seems you need to ClearProviders() first before adding the AzureBlobStorageImageProvider. So the code becomes:
.ClearProviders()
.AddProvider<AzureBlobStorageImageProvider>()
I have different classes which inherit of a base class. The base class implements the interface IHealthCheck. Each class has a constructor which need a logger and parameters according to the class.
For example :
public ConnectionHealthCheck(ILogger logger, string address)
: base(logger)
{
Address = address;
}
I have a appSettings.json which allows me to configure several diagnostics to do in my Health Check service.
I get the list of diagnostics in my App.xaml.cs and i'm trying to add them in the HealthCheck list.
The problem is that I cannot do a dependency injection with parameters next to it and I don't know what is the best solution to do it...
Here is some parts of my code.
The OnStartup method :
protected override void OnStartup(StartupEventArgs e)
{
var a = Assembly.GetExecutingAssembly();
using var stream = a.GetManifestResourceStream("appsettings.json");
Configuration = new ConfigurationBuilder()
.AddJsonStream(stream)
.Build();
var host = new HostBuilder()
.ConfigureHostConfiguration(c => c.AddConfiguration(Configuration))
.ConfigureServices(ConfigureServices)
.ConfigureLogging(ConfigureLogging)
.Build();
[...] }
The configureService Method :
private void ConfigureServices(IServiceCollection serviceCollection)
{
// create and add the healthCheck for each diag in the appSettings file
List<DiagnosticConfigItem> diagnostics = Configuration.GetSection("AppSettings:Diagnostics").Get<List<DiagnosticConfigItem>>();
diagnostics.ForEach(x => CreateHealthCheck(serviceCollection, x));
[...] }
And the method CreateHealthCheck where is the problem :
private void CreateHealthCheck(IServiceCollection serviceCollection, DiagnosticConfigItem configItem)
{
EnumDiagType type;
try
{
type = (EnumDiagType)Enum.Parse(typeof(EnumDiagType), configItem.Type, true);
}
catch (Exception)
{
throw new Exception("Diagnostic type not supported");
}
switch (type)
{
case EnumDiagType.Connection:
serviceCollection.AddHealthChecks().AddCheck(nameof(ConnectionHealthCheck), new ConnectionHealthCheck(???, configItem.Value));
break;
case EnumDiagType.Other:
[...] }
As you can see, I cannot create the instance of the ConnectionHealthCheck class because I cannot reach the ILogger object...
So how can I do it ? I think about different solutions but I don't have the answer or the way to do it
Build the HealthCheck service not in the App.xaml.cs but after ? (In a view model for exemple where I have access to the serviceCollection and the logger)
Find a way to get the logger to use it in the CreateHealthCheck method ?
Do something like that but I don't know when I can pass the parameters
serviceCollection.AddHealthChecks().AddCheck<ConnectionHealthCheck>(nameof(ConnectionHealthCheck));
You can use HealthCheckRegistration to register your class (it should implement IHealthCheck), it has constructors accepting delegate Func<IServiceProvider,IHealthCheck> which allows you to use IServiceProvider to resolve required parameters to create an instance of your healthcheck class. Something like this:
public static class ConnectionHealthCheckBuilderExtensions
{
const string DefaultName = "example_health_check";
public static IHealthChecksBuilder AddConnectionHealthCheck(
this IHealthChecksBuilder builder,
string name,
DiagnosticConfigItem configItem,
HealthStatus? failureStatus = default,
IEnumerable<string> tags = default)
{
return builder.Add(new HealthCheckRegistration(
name ?? DefaultName,
sp => new ConnectionHealthCheck(sp.GetRequiredService<ISomeService>(), configItem.Value),
failureStatus,
tags));
}
}
See this part of docs for more details.
The .NET Core in-built DI can inject the components on the Constructor level.
So use the following way, which I use in my ASP.NET Core Projects.
public class Startup
{
public Startup(IWebHostEnvironment environment, IConfiguration configuration, ILoggerFactory loggerFactory)
{
Environment = environment;
Configuration = configuration;
LoggerFactory = loggerFactory;
}
public IConfiguration Configuration { get; }
public ILoggerFactory LoggerFactory { get; }
public IWebHostEnvironment Environment { get; }
private void ConfigureServices(IServiceCollection serviceCollection)
{
List<DiagnosticConfigItem> diagnostics = Configuration.GetSection("AppSettings:Diagnostics").Get<List<DiagnosticConfigItem>>();
diagnostics.ForEach(x => CreateHealthCheck(serviceCollection, x, LoggerFactory));
}
private void CreateHealthCheck(IServiceCollection serviceCollection, DiagnosticConfigItem configItem)
{
// Create a ILogger<T> based on your Type by
loggerFactory.CreateLogger<MessagingServices>())
}
}
This might be crude, but hope this helps.
I have problem with understanding source of errors in my code. I try to get throw course about microservices in .net core. After running build solution I get:
------- Project finished: CrossX.Services.Identity. Succeeded: True. Errors: 0. Warnings: 0
But when I run it I get:
/opt/dotnet/dotnet /RiderProjects/crossx/src/CrossX.Services.Identity/bin/Debug/netcoreapp2.2/CrossX.Services.Identity.dll
Unhandled Exception: System.InvalidOperationException: Cannot resolve scoped service 'CrossX.NETCore.Commands.ICommandHandler`1[CrossX.NETCore.Commands.CreateUser]' from root provider.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at CrossX.NETCore.Services.ServiceHost.BusBuilder.SubscribeToCommand[TCommand]() in /RiderProjects/crossx/src/CrossX.NETCore/Services/ServiceHost.cs:line 78
at CrossX.Services.Identity.Program.Main(String[] args) in /RiderProjects/crossx/src/CrossX.Services.Identity/Program.cs:line 11
When I added to webHostBuilder .UseDefaultServiceProvider(options => options.ValidateScopes = false) my problem was solved. But turning off validations isn't good idea from what I know. Also When I changed AddScope to AddTransient problem was solved (or at least it run).
Problem is that I have no idea where to look for source of this error. I guess I lack of understanding what is wrong, so I would appreciate if someone would help me, or at least give a hint.
Here is my
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddRabbitMq(Configuration);
services.AddScoped<ICommandHandler<CreateUser>, CreateUserHandler>();
services.AddScoped<IEncrypter, Encrypter>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
Program.cs
public class Program
{
public static void Main(string[] args)
{
ServiceHost.Create<Startup>(args)
.UseRabbitMq()
.SubscribeToCommand<CreateUser>()
.Build()
.Run();
}
}
ServiceHost.cs
public class ServiceHost : IServiceHost
{
private readonly IWebHost _webHost;
public ServiceHost(IWebHost webHost)
{
_webHost = webHost;
}
public void Run() => _webHost.Run();
public static HostBuilder Create<TStartup>(string[] args) where TStartup : class
{
Console.Title = typeof(TStartup).Namespace;
var config = new ConfigurationBuilder()
.AddEnvironmentVariables()
.AddCommandLine(args)
.Build();
var webHostBuilder = WebHost.CreateDefaultBuilder(args)
.UseConfiguration(config)
// .UseDefaultServiceProvider(options => options.ValidateScopes = false)
.UseStartup<TStartup>();
return new HostBuilder(webHostBuilder.Build());
}
public abstract class BuilderBase
{
public abstract ServiceHost Build();
}
public class HostBuilder : BuilderBase
{
private readonly IWebHost _webHost;
private IBusClient _bus;
public HostBuilder(IWebHost webHost)
{
_webHost = webHost;
}
public BusBuilder UseRabbitMq()
{
_bus = (IBusClient) _webHost.Services.GetService(typeof(IBusClient));
return new BusBuilder(_webHost, _bus);
}
public override ServiceHost Build()
{
return new ServiceHost(_webHost);
}
}
public class BusBuilder : BuilderBase
{
private readonly IWebHost _webHost;
private IBusClient _bus;
public BusBuilder(IWebHost webHost, IBusClient bus)
{
_webHost = webHost;
_bus = bus;
}
public BusBuilder SubscribeToCommand<TCommand>() where TCommand : ICommand
{
var handler = (ICommandHandler<TCommand>) _webHost.Services.GetService(typeof(ICommandHandler<TCommand>));
_bus.WithCommandHandlerAsync(handler);
return this;
}
public BusBuilder SubscribeToEvent<TEvent>() where TEvent : IEvent
{
var handler = (IEventHandler<TEvent>) _webHost.Services.GetService(typeof(IEventHandler<TEvent>));
_bus.WithEventHandlerAsync(handler);
return this;
}
public override ServiceHost Build()
{
return new ServiceHost(_webHost);
}
}
}
Cannot resolve scoped service ICommandHandler<CreateUser> from root provider
As the error says, you cannot create a scoped service instance from the root provider. The root provider is the root service provider that exists outside of service scopes. As such, it cannot resolve services that should only be consumed within service scopes.
If you want to resolve a scoped service from the root provider, for example when you are consuming it from a singleton service, you should create a service scope first using the IServiceScopeFactory:
var serviceScopeFactory = _webHost.Services.GetService<IServiceScopeFactory>();
using (var scope = serviceScopeFactory.CreateScope())
{
var handler = (IEventHandler<TEvent>)scope.ServiceProvider.GetService(typeof(IEventHandler<TEvent>))
// …
}
Note that service scopes are supposed to be short lived, and that you need to dispose them afterwards to clean up.
Looking at your implementation, it seems as if you pass your scoped services to some other service in order to subscribe to events. This generally seems like a bad idea since that means that a reference to a scoped service will be kept by a (likely) singleton service for the whole lifetime of the application. This is generally a bad idea (as I said, scoped services are supposed to live only a short time).
You should ask yourself why you need the services to be scoped there, and whether you cannot make them singleton instead. Or if you actually need the subscribe mechanism to be based on the instance (instead of for example just the type, using a factory pattern or something).
Been doing some sample code with ASP.NET Core to try to understand how it fits together and I am stumped as to why I am unable to successfully resolve a service.
The configure services method has the call to add ISeedDataService
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<CustomerDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<ICustomerDbContext, CustomerDbContext>();
services.AddScoped<ICustomerRepository, CustomerRepository>();
services.AddScoped<ISeedDataService, SeedDataService>();
}
In Configure I am calling AddSeedData() as below
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.AddSeedData();
}
which is calling the extension method below
public static async void AddSeedData(this IApplicationBuilder app)
{
var seedDataService = app.ApplicationServices.GetRequiredService<ISeedDataService>();
await seedDataService.EnsureSeedData();
}
and the SeedDataService is below
public class SeedDataService : ISeedDataService
{
private ICustomerDbContext _context;
public SeedDataService(ICustomerDbContext context)
{
_context = context;
}
public async Task EnsureSeedData()
{
_context.Database.EnsureCreated();
_context.Customers.RemoveRange(_context.Customers);
_context.SaveChanges();
Customer customer = new Customer();
customer.FirstName = "Chuck";
customer.LastName = "Norris";
customer.Age = 30;
customer.Id = Guid.NewGuid();
_context.Add(customer);
Customer customer2 = new Customer();
customer2.FirstName = "Fabian";
customer2.LastName = "Gosebrink";
customer2.Age = 31;
customer2.Id = Guid.NewGuid();
_context.Add(customer2);
await _context.SaveChangesAsync();
}
}
Totally unsure as to what I am doing wrong, the error is System.InvalidOperationException: 'Cannot resolve scoped service 'secondapp.Services.ISeedDataService' from root provider.'
You are (and should be) adding the ISeedDataService as scoped service. However, you are attempting to resolve it from the root service provider (e.g. app.ApplicationServices) which is not scoped. This means that scoped services resolved from it effectively are turned into a singleton service and are not disposed until the application shuts down or it will result in an error.
The solution here is to create a scope yourself:
public void Configure(IApplicationBuilder app)
{
using (var scope = app.ApplicationServices.CreateScope())
{
var seedDataService = scope.ServiceProvider.GetRequiredService<ISeedDataService>();
// Use seedDataService here
}
}
Please take a look at the documentation regarding dependency injection scopes.
On a second note: your AddSeedData extension method is async void and you are not waiting for the result. You should return a task (async Task) call AddSeedData().GetAwaiter().GetResult() to make sure you block until the seeding is complete.
The Configure() method allows parameter dependency injection so you can do the following.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ISeedDataService seedService)
{
seedService.EnsureSeedData().Wait(); // Configure() is not async so you have to wait
}
I would like to write integration tests for my Asp .net core application, but I don't want my tests to use real implemetation of some services.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<IExternalService,ExternalService>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
}
}
public interface IExternalService
{
bool Verify(int id);
}
public class ExternalService : IExternalService
{
public bool Verify(int id)
{
//Implemetation is here.
//I want to fake this implemetation during testing.
}
}
[Fact]
public void TestCase()
{
//Stub out service
var myExtService = new Mock<IExternalService>();
//Setup response by stub
myExtService
.Setup(p => p.Verify(It.IsAny<int>()))
.Returns(false);
var host = new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureServices((services) =>
{
//Setup injection
services.AddTransient<IExternalService>((a) =>
{
return myExtService.Object;
});
});
var server = new TestServer(host);
var client = server.CreateClient();
var response = client.GetAsync("/").Result;
var responseString = response.Content.ReadAsStringAsync().Result;
Assert.Contains("Your service returned: False", responseString);
}
Current injection setup in test case does not work, because ExternalService is injected over the mock.
However the test will pass when I remove services.AddTransient<IExternalService,ExternalService>; from Startup.
Most likely the one in Startup is called later and all the setup in that class is preferred by application.
What options do I have to setup some dependecies in tests, but use everything else as they are declared in Startup?
UPDATE
Application code should be unaware of tests.
Tests should be aware of:
(weakly typed) Endpoint - if this changes then test should fail
IExternalService interface
Tests should not care if application has razor pages or uses mvc or how the application is wired between endpoint and IExternalService.
Tests should not have to setup or configure application (apart from stubbing IExternalService) in order to make it work.
I understand that WebHostBuilder still has to be created, but my point is that configuration should be bare minimum in test case and majority of configuration should still be described on application side.
The only option I know of is to setup WebHostBuilder with UseEnvironment:
var host = new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureServices(services =>
{
//Setup injection
services.AddTransient<IExternalService>(provider =>
{
return myExtService.Object;
});
})
.UseEnvironment("IntegrationTest");
And then add a condition in the ConfigureServices method in the Startup:
public void ConfigureServices(IServiceCollection services)
{
if (Configuration["Environment"] != "IntegrationTest")
{
services.AddTransient<IExternalService, ExternalService>();
}
services.AddMvc();
// ...
}
UPDATE:
I did some more poking around and another option is to not use UseStartup extension method but rather configure the WebHostBuilder directly. You can do this a number of ways but I thought that you could possibly create your own extension method to create a template in your tests:
public static class WebHostBuilderExt
{
public static WebHostBuilder ConfigureServicesTest(this WebHostBuilder #this, Action<IServiceCollection> configureServices)
{
#this.ConfigureServices(services =>
{
configureServices(services);
services.AddMvc();
})
.Configure(builder =>
{
builder.UseMvc();
});
return #this;
}
}
Now your tests can be setup like the following:
var host = new WebHostBuilder()
.ConfigureServicesTest(services =>
{
//Setup injection
services.AddTransient<IInternalService>(provider =>
{
return myExtService.Object;
});
});
var server = new TestServer(host);
This means that you will have to explicitly setup all the implementations that the container will resolve for the specific endpoint you are calling. You can choose to mock or use the the concrete implementations.
The only thing yo need to change is to use ConfigureTestServices instead of ConfigureServices. ConfigureTestServices runs after your Startup, therefor you can override real implementations with mocks/stubs. ConfigureServices was newer intended for that purpose, rather, it configures "host services", which are used during the host-building phase of the application, and copied into the application's DI container.
ConfigureTestServices is available in ASP Core version 2.1 and higher.
var host = new WebHostBuilder()
.UseStartup<Startup>()
.ConfigureTestServices((services) =>
{
//Setup injection
services.AddTransient<IExternalService>((a) =>
{
return myExtService.Object;
});
});
So after hours of research I found a solution.
I could not find a way to solely use built-in dependency injection solution, so I opted to choose 3rd party DI solution - Autofac
Idea is to use WebHostBuilder (declared Main Program) and add necessary options so I can fake some services during testing.
Something that I learned:
If you use Startup as host.UseStartup<Startup> it will be created after host.ConfigureServices()
You cannot inject something to Startup like host.UseStartup<Startup>(new Dependency())
However if you have registred dependency in host.ConfigureServices(services => services.AddTransient<IDependency, MyDependency>()), then it will be resolved before Startup is created and constructor public Startup(IDependency dependency) is used to create Startup.
My application side:
public class Program
{
public static void Main(string[] args)
{
CreateWebHost(args)
.Build()
.Run();
}
public static IWebHostBuilder CreateWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureServices((services) =>
{
//Setup autofac.
services.AddAutofac();
//Register module dependency that Startup requires.
services.AddTransient<Module, MyAutofacModule>();
////It would a bit cleaner to use autofac to setup Startup dependency,
////but dependency did not get resolved for Startup.
//services.AddAutofac((builder) =>
//{
// builder.RegisterModule(new AutofacModule());
//});
})
.UseStartup<Startup>();
}
public class MyAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
//Register all application dependencies in this module.
builder.Register((c) => new ExternalService()).As<IExternalService>();
}
}
public class Startup
{
private Module applicationDIModule;
public Startup(Module applicationDIModule)
{
this.applicationDIModule = applicationDIModule;
}
public void ConfigureServices(IServiceCollection services)
{
//We can add build-in services such as mvc and authorization,
//but I would not use Add(Transient/Scoped/Singleton) here.
//You should register domain specific dependecies in MyAutofacModule,
//since it will be added after this method call.
services.AddMvc();
}
//This method is called after ConfigureServices (refer to Autofac link).
public void ConfigureContainer(ContainerBuilder builder)
{
//We will register injected module.
builder.RegisterModule(applicationDIModule);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvcWithDefaultRoute();
}
}
Test case:
public class IntegrationTests
{
[Fact]
public void TestCase()
{
//Create and setup moq object as usual.
var service = new Mock<IExternalService>();
service
.Setup(p => p.Verify(It.IsAny<int>()))
.Returns(false);
//Bundle moq objects together for registration.
var attachFakes = new Action<ContainerBuilder>((builder) =>
{
builder.Register(c => service.Object);
});
//Use host builder that application uses.
var host = Program.CreateWebHost(new string[] { })
.UseContentRoot(GetContentRoot()) //Adjust content root since testproject.csproj is not in same folder as application.csproj
.ConfigureServices((services) =>
{
//We re-configure Module registration,
//so Startup is injected with our TestModule.
services.AddTransient<Module>((a) =>
{
return new TestModule(attachFakes);
});
});
//Create server to use our host and continue to test.
var server = new TestServer(host);
var client = server.CreateClient();
var response = client.GetAsync("/").Result;
var responseString = response.Content.ReadAsStringAsync().Result;
Assert.Contains("External service result: False", responseString);
}
private static string GetContentRoot()
{
var current = Directory.GetCurrentDirectory();
var parent = Directory.GetParent(current).Parent.Parent.Parent;
return Path.Combine(parent.FullName, "src");
}
}
public class TestModule : MyAutofacModule
{
private Action<ContainerBuilder> attachFakes;
public TestModule(Action<ContainerBuilder> attachFakes)
{
this.attachFakes = attachFakes;
}
protected override void Load(ContainerBuilder builder)
{
//We register everything in MyAutoFacModule before adding our fakes.
base.Load(builder);
//We add fakes and everything that is re-registered here will be used instead.
attachFakes.Invoke(builder);
}
}
Although it feels a bit brittle, but I still prefer this solution over what #ODawg suggested. His solution would work, but I see it would cause troubles in future when new test cases are added.