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.).
Related
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.
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>();
}
}
I'm using Autofac as my dependency resolver for an ASP.NET Core 1.1 application. I'm not using a startup class, rather I'm using a base class that configures everything including the Autofac container, and then allows a derived class to override methods for custom configuration.
Here's the basic setup of the host in the base class' initialization method:
_server = new WebHostBuilder()
.UseUrls(Settings.AspNet.Urls)
.UseKestrel()
.UseIISIntegration()
.UseContentRoot(_directory)
.ConfigureServices(services => ConfigureServicesInternal(services, containerBuilder))
.Configure(app => ConfigureInternal(app, containerBuilder))
.Build();
_server.Run();
ConfigureServices()
As you can see there is not Startup class involved. Here's what the base class'
ConfigureServicesInternal method does:
services.AddAutofac();
services.AddSingleton(Settings);
// Allow derived class to add services
ConfigureServices(services);
// Copy everything over to the Autofac container builder
builder.Populate(services);
// Allow derived class to register dependencies in the Autofac container
ConfigureContainer(builder);
Here's the derived class' ConfigureServices method:
// Tried with and without AddControllersAsServices(), no difference
services.AddMvcCore().AddControllersAsServices();
The derived class' ConfigureContainer method is empty.
Configure()
Here's the base class Configure method:
// Allow derived class to configure the app
Configure(app);
// Storing a reference to the Autofac container in the base class because I need it later
Container = builder.Build();
// Let Autofac take over. I tried removing this, and it did not help
app.ApplicationServices = new AutofacServiceProvider(Container);
And here's the derived class' Configure method:
// Also tried UseMvc(route => route.MapRoute(...)), no difference
app.UseMvcWithDefaultRoute();
Browing to the root of the application I get a 404. I can add Routing as a service as well as static files, and they both work (e.g. I can get a 200 response from this application, just not from an MVC controller).
I've tried inheriting from Controller but that also does not make a difference. I tried removing all constructor injection from the HomeController, still no luck.
Tried running it using IIS Express, no result.
Tried browing directly to the route, e.g. http://localhost:8001/home/index/, but again no luck.
There's no logging other than:
[20:58:26 INF] Request starting HTTP/1.1 GET http://localhost:8001/
[20:58:26 INF] Request finished in 304.8803ms 404
How can I debug this issue?
Edit:
The problem is in passing ConfigureInternal as the Configure method and calling a derived class' Configure method from within ConfigureInternal. When I do that, it starts returning a 404 for the default route.
When I pass the derived class' Configure method, I can see resolved services being added to the application after each app.UseSomething() call.
I can step through both methods, and I can see extra services being added after each use call in the derived class' Configure method (e.g. app.UseRouter() adds routing options and a 'routing marker service', and app.UseMvcWithDefaultRouter() adds another 19 services).
This happens just as well when I pass the base class' ConfigureInternal method, and call the base class' Configure method. So I don't understand why it makes a difference though...
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.
I have an MVC 5 application which uses Ninject and I am adding Hangfire to it.
When I have added Ninject, I have used the NinjectWebCommon nuget package because of its simplicity in the configuration. So for now Ninject is configured through the NinjectWebCommon class which create a standard kernel and add the bindings.
Moreover I have created some custom module that I load when creating the kernel
private static IKernel CreateKernel() {
var kernel = new StandardKernel( new MyCustomModule() );
try {
kernel.Bind<Func<IKernel>>().ToMethod( ctx => () => new Bootstrapper().Kernel );
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices( kernel );
return kernel;
}
catch {
kernel.Dispose();
throw;
}
}
The Ninject Web Common is registered through the WebActivatorEx class
[assembly: WebActivatorEx.PreApplicationStartMethod( typeof( MyProject.Web.NinjectWebCommon ), "Start" )]
[assembly: WebActivatorEx.ApplicationShutdownMethodAttribute( typeof( MyProject.Web.NinjectWebCommon ), "Stop" )]
Now the problem is related on how to make Hangfire to see the Ninject configuration. By looking at the Hangfire.Ninject package I can read
The package provides an extension method for IGlobalConfiguration
interface:
var kernel = new StandardKernel();
GlobalConfiguration.Configuration.UseNinjectActivator(kernel);
Now my question are:
Because of the IGlobalConfiguration interface, I should add the Hangfire Ninject configuration inside the OWIN startup method (where the Hangfire config is already placed). How should I get the current Ninject Kernel (the one that NinjectWebCommon has configured?
What about the order of execution? Is the WebActivatorExexecuting before or after the OWIN startup?
What happens if I try to execute the configuration twice?
More generally, how can I share the Ninject configuration between the two?
How should I get the current Ninject Kernel
Looking at the code of Ninject.Web.Common.Bootstrapper shows that it stores a single static instance of the kernel, and exposes it via the Kernel property. This means that you can do this inside the OWIN startup method:
GlobalConfiguration.Configuration.UseNinjectActivator(
new Ninject.Web.Common.Bootstrapper().Kernel);
and you'll have the same IKernel instance, complete with whatever bindings you configured in NinjectWebCommon.RegisterServices
What about the order of execution? Is the WebActivatorEx executing before or after the OWIN startup?
Before. You can verify this (as I did) by setting breakpoints in each. More info
More generally, how can I share the Ninject configuration between the two?
What happens if I try to execute the configuration twice?
The kernel configuration is the "composition root." According to Mark Seemann, a preeminent expert on the subject, there should only be one of these in the application, and it should be as close as possible to the application's entry point.