ASP.NET Core with existing IoC container & environment? - c#

I'd like to run the ASP.NET Core web stack along with MVC within a Windows service environment which already hosts an existing application in order to provide a frontend for it. The application uses Autofac for DI concerns which is nice because it already has an extension for Microsoft.Extensions.DependencyInjection (on which ASP.NET Core heavily relies upon).
Now, my problem is: ASP.NET Core wants to register its own services within the IoC container, at a stage where my own container has already been built. The only way I can see to run my own 'application part' along with ASP.NET Core is by setting up my application within the scope of the ASP.NET Core web host Startup class, what appears to make ASP.NET Core behaving like a full blown application framework rather than web framework. What I also tried is dropping the Startup completely and setting the web host up like this:
var containerBuilder = new ContainerBuilder();
var container = containerBuilder.Build();
var webHost = new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddMvc();
})
.Configure(app =>
{
app.ApplicationServices = new AutofacServiceProvider(container);
app.UseStaticFiles();
app.UseMvc();
})
.UseKestrel()
.UseIISIntegration()
.Build();
webHost.Run();
However this doesn't work because ASP.NET Core seems to override all configurations as soon as the web host is getting built. So, is there a way to integrate ASP.NET Core as well as MVC in an existing environment rather than the other way around? Maybe by setting it up manually rather than using WebHostBuilder etc.?

The only way I found is using Update() function of Autofac container. But Autofac lib marks this approach as bad practice.
Outside of Startup class:
class Program {
public static IContainer Container { get; private set; }
void Main(){
var builder = new ContainerBuilder();
...
Container = builder.Build();
}
}
And in Startup class:
public abstract class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
var builder = new ContainerBuilder();
builder.Populate(services);
// Update existing container
builder.Update(Program.Container);
}
}
For sure its a bad hack + I suggest to avoid this approach.
But the answer
don't build your container before the Startup.Configure
is not always applicable. In my case I used Azure Service Fabric .Net Core WebAPI stateless service and suggestion to build container inside Startup is wrong since I need to inject StatelessService before Startup runs.

Related

Custom WebApplicationFactory in integration testing not registering services in Startup.cs (ASP.NET Core 3.1)

I am running into an issue where I am using a custom WebApplicationFactory and it is not registering the services in my Startup.cs. I am using Asp.Net core 3.1 and Xunit.
I have below in my API (startup.cs) registered using extension methods:
public void ConfigureServices(IServiceCollection services)
{
services.AddBaseApiConfiguration(Configuration);
services.AddRepositoryServices();
services.AddApiServices();
services.AddMediatrServices();
}
Per MS documentation:
The SUT's database context is registered in its
Startup.ConfigureServices method. The test app's
builder.ConfigureServices callback is executed after the app's
Startup.ConfigureServices code is executed.
But above is not occurring. None of my services from the startup I am using are being registered (i.e. the 4 extension methods I have are never being called and are never registered). This is causing a few problems: my DB context uses IMediatr in its constructor so that events can be published during context.SaveChangesAsync() (on success). But as my service is never registered, it fails to find it. How do I ensure that this takes place correctly?
Below is my custom factory:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder
.UseEnvironment("Development")
.ConfigureServices(services =>
{
// Remove the app's db context registrations
var descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<AppDbContext>));
if (descriptor != null)
{
services.Remove(descriptor );
}
services.AddSingleton<IDbConnectionFactoryService, SqliteConnectionFactoryService>();
// Add a database context using an in-memory
// database for testing.
services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlite("Data Source=sqlitedemo.db");
});
// Build the service provider.
var sp = services.BuildServiceProvider();
// Create a scope to obtain a reference to the database context
using (var scope = sp.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var db = scopedServices.GetRequiredService<AppDbContext>();
// Ensure the database is created.
db .Database.EnsureDeleted();
db .Database.EnsureCreated();
}
});
}
Edit: It seems the services in Startup.cs are being called after the CustomWebApplication factory services. This is completely odd as per .Net core 3.0, the order was changed to be the opposite. So why is this still occuring?
Edit 2: Need to use ConfigureTestServices instead of ConfigureServices.
I didn't find this in the docs but calling ConfigureTestServices ensures that the CustomWebApplicationFactory services are called after Startup services are called. Per the docs, this is not pointed out correctly but at least this might help someone else who ran into the same issue.
If you have just a few integration tests with common mock service registrations, ConfigureTestServices works just fine, but it doesn’t scale well when many tests need their own specific mock registrations. ConfigureTestServices requires that you rebuild the host and its DI container for each case. I developed an extension to Microsoft Dependency Injection that allows local mocks without rebuilding the container. Check it out - Rsi.DependencyInjection.

Why is logging setup in CreateHostBuilder and not in ConfigureServices?

Context: Simple out-of-the-box blazor 3.1 SERVER SIDE example - adding logging (just trying to get stuff to the debug conolse in VS2019 in components).
I leave ConfigureServices as is, and have found SO examples that logging is setup in program.cs(!)
This works:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(
logging =>
{
logging.ClearProviders();
logging.AddConsole();
})
ect.
In my components, everything is resolved fine with
[Inject] public ILogger<Foo> Logger { get; set; }
But why not in ConfigureServices, where rest of stuff, that should be DI'ed, is setup ???
Is there a "under the hood" DI going on? (a little like what was
going on in .Net frameework w/ webapi)?
Can it be moved to ConfigureServices with excact same result (or
at all )?
In ASP.NET core 2.x you could partially configure your dependency injection container in Program.cs, and inject the configured classes into Startup.cs. So the ConfigureLogging() method calls ConfigureServices() on the IWebHostBuilder instance, and configures some settings. As these services are configured in the DI container before Startup is instantiated, they can be injected into the Startup constructor:
public Startup(
IConfiguration configuration,
ILogger<Startup> logger) // Inject pre-configured service
{
}
More
But this approach is no longer supported in ASP.NET Core 3.0! The problem is that it requires building the dependency injection container twice.
Is there a "under the hood" DI going on? (a little like what was going
on in .Net frameework w/ webapi)?
The ConfigureLogging just call ConfigureServices without building of service provider
public static IWebHostBuilder ConfigureLogging(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, ILoggingBuilder> configureLogging)
{
return hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(context, builder)));
}
Can it be moved to ConfigureServices with excact same result (or at all )?
yes, you can move it to ConfigureServices and the result will be the same
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(builder =>
{
builder.ClearProviders();
builder.AddConsole();
});
}
So, where to add logging configuration is up to you. Logging is infrastructure and can be added as a configuration of host to Program.cs. Your business classes should be added to DI in Startup.

ASP.Net Core How to perform final actions on database when application is about to stop

I'm working on ASP.NET Core 3.1 application. I want to perform some final actions related to database while my application is about to stop. To do that I'm trying to call a function in my scoped service which is a additional layer between database and ASP.NET Core.
Startup.cs
public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime)
{
lifetime.ApplicationStopping.Register(() =>
{
app.ApplicationServices.GetService<MyService>().SynchronizeChanges();
});
}
But unfortunately i get Cannot resolve scoped service 'MyApplication.Services.MyService' from root provider error while I'm trying to do so.
There is no scope when app is closing, so Asp.Net core is unable to create scoped service. Scope is created during http request only. If you do need to create scoped service without a scope, you can create scope manually:
public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime)
{
lifetime.ApplicationStopping.Register(() =>
{
using (var scope = app.ApplicationServices.CreateScope())
{
scope.ServiceProvider
.GetRequiredService<MyService>().SynchronizeChanges();
}
});
}
Also you can probably register your service as singleton or try to disable Asp.Net Core scopes validation (not recommended).

How can I use ILogger<T> from an Azure Functions V2 function?

I have some services that were initially designed to be called from my ASP.NET Core 2.1 web application. They have dependencies injected to their constructors using Microsoft.Extensions.DependencyInjection package stuff. Some of them have a dependency of ILogger logger.
public GroupService(ILogger<GroupService> logger)
{
...
}
I am building a service provider within the function so that they can still work as expected however I'm wondering what I should do about the logger dependencies. An Azure Function (V2) gets an ILogger injected into it by default but that can't be used in the DI container to create the additional loggers that the services require.
Is there a LoggerFactory registered somewhere "under the covers" that I can get access to to be used in my DI container? I think that would allow me to add additional loggers that log to the functions output window or am I completely misunderstanding how logging in a function would work?
Do I just need to set up a new LoggerFactory and make that log to the ApplicationInsights instance used by the functions project?
Using the most recent Azure Function runtime, you can now have the runtime inject your dependencies through constructors.
You can now remove the static keywords from your Azure Function and have the runtime.
Here is how you can set this up:
[assembly: WebJobsStartup(typeof(StartUp))]
public class StartUp : IWebJobsStartup
{
public void Configure(IWebJobsBuilder webJobsBuilder)
{
// Enables logging and sets the minimum level of logs.
webJobsBuilder.Services.AddLogging(loggingBuilder =>
{
loggingBuilder.SetMinimumLevel(LogLevel.Debug);
});
// Registers your services.
webJobsBuilder.Services.AddTransient<IGroupService, GroupService>();
}
}

Autofac Injection of data into OWIN Startup class

I've been looking around on SO for a while and can't find the answer to my particular question:
How do you use Autofac to inject data (via constructor or property) into the OWIN Startup class?
I'm using Autofac in a Windows service which also handles creation of back-end services etc. so I'm doing all the configuration reading and container building in there.
I want to be able to register the Startup class so I can inject allowed origins for CORS but even when I register the object with a property like so:
var builder = new ContainerBuilder();
// This will actually be read from config.
var origins = new[]
{
"http://localhost",
"http://localhost:8082",
};
builder.Register(c => new Startup(origins));
var container = builder.Build();
At runtime when the Startup class is instantiatedthe callstack shows it comes from external code and my Autofac builder didn't push the property in.
I'd ideally like to keep my Autofac registrations in a single place (the Windows service class) and just inject the required data to Startup so I can just do something like this below (where allowedOrigins is set as a property or injected via the constructor)
public void Configuration(IAppBuilder app)
{
var configuration = new HttpConfiguration();
...
var origins = string.Join(",", allowedOrigins);
var cors = new EnableCorsAttribute(origins, "*", "*") { SupportsCredentials = true };
configuration.EnableCors(cors);
....
}
Any ideas would be much appreciated.
Thanks
peteski
UPDATE
I should add that after attempting Autofac registration and building, I was kicking off the OWIN self host app by doing:
var webApp = WebApp.Start<Startup>(baseAddress);
From a conversation with a friend they suggested creating the Startup object and passing it to the WebApp:
var startup = new Startup(origins);
var webApp = WebApp.Start(startup, baseAddress);
Here, I'd add the IEnumerable<string> origins to the ctor for the Startup class. This does actually work! But feels like it goes around using Autofac to handle giving the Startup class what it needs.
Out of the box Autofac doesn't support what you're looking for.
In an IIS or other non-self-hosted environment, there's no hook for building a container and then injecting into the startup class. It's sort of a chicken/egg problem - the startup class is your hook to start building the container.
In a self-hosted environment, the suggestion you mentioned - creating the startup object and passing that into the call to WebApp.Start - is your best bet.
Basically, at some point you're going to hit the entry point for your application (usually in OWIN that's the startup class) and that's the point at which you have to build your container and be the bootstrapper for resolving and starting things up. Autofac can't help you with that - that's up to the framework(s) you're using (e.g., MVC, OWIN, Web API, etc.).

Categories