question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Add option to pass in existing builder.

See original GitHub issue

Currently, a new ContainerBuilder is created by the AutofacServiceProviderFactory.

I have a situation in which i want to pass in an existing builder. I have a Windows Service running using TopSelf. The topshelf application hosts various things (2 WCF services) and I also want to host a WebApi from the service. The service itself is already configured using Autofac and I want the WebApi to use the same container.

I achieve running the WebApi by registering:

builder.Register(c =>
            {
                return WebHost.CreateDefaultBuilder()
                   .ConfigureServices(s => s.AddAutofac())
                   .UseHttpSys()
                   .UseSerilog(c.Resolve<ILogger>())
                   .UseStartup<Startup>()
                   .Build();
            })
            .As<IWebHost>()
            .SingleInstance();

and then in my main class resolving an IWebHost and calling Start(). This all works. However, the s.AddAutofac() method creates a new AutofacServiceProviderFactory which creates a new ContainerBuilder. I would like something along the lines of:

builder.Register(c =>
            {
                return WebHost.CreateDefaultBuilder()
                   .ConfigureServices(s => s.AddAutofac(builder))
                   .UseHttpSys()
                   .UseSerilog(c.Resolve<ILogger>())
                   .UseStartup<Startup>()
                   .Build();
            })
            .As<IWebHost>()
            .SingleInstance();

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:5 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
MJLHThomassen-Eurocomcommented, Jan 5, 2018

@Strizzi12 and other interested.

I’ve debated with my coworker and we came up with the following solution:

I have 3 project:

-Project A: Code for an AspNetCore WebApi + DI Modules for Project A -Project B: Main application code requiring WCF services to be injected. -Project C: Hosting project using Topshelf for running application as a Windows Service + DI modules for both Project B and Project C + main DI “Installer”

Project C is the application entry point. The first thing that it does is call “Initialize” on my DI container. We’re using Autofac so I’m going to have that in my example code:

public static IContainer Initialize()
{
	var builder = new ContainerBuilder();

	// Do stuff for registering settings and logging

	// Register modules
	builder
		.RegisterModule("Project B Module(s)")
		.RegisterModule("Project C Module(s)")
		.RegisterModule("Project C Module that initializes Project A");

	return builder.Build();
}

“Project B Module(s)” and “Project C Module(s)” are regular Autofac modules (available in some way or form in other DI containers aswell) and have nothing to do with AspNetCore. I register regular dependencies there and set up everything required for my main application, WCF hosting etc. The main entry point after DI has ran, is a “Host” class (which gets started by Topshelf) which looks a bit like this:

public class Host
{
    public Host(
            ILogger log,
            IEnumerable<ServiceHost> serviceHosts,
            IWebHost webHost)
        {
            this.log = log;
            this.serviceHosts = serviceHosts;
            this.webHost = webHost;
        }
		
    public void Start()
    {
        // WCF
	foreach (var host in serviceHosts)
        {
            host.Open();
        }
			
	// WebApi
	webHost.Start();
    }
		
	public void Stop()
	{
	    // Stop Code
	}
}

Both the WCF services and the IWebHost get injected neatly, no static references required. In order to achieve this, I register IWebHost in “Project C Module that initializes Project A” which looks something like the following:

using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using "Project A";
	
public class "Project C Module that initializes Project A" : Module
{
	protected override void Load(ContainerBuilder builder)
	{
		builder
			.Register(c =>
			{
				return WebHost.CreateDefaultBuilder()
				  .ConfigureServices(s => s.AddAutofac(b =>
				  {
					  // Register dependencies the WebApi has on components already registered in the WinService to the WebApi DI Container
					  b.RegisterInstance(c.Resolve<IMyAlreadyConfiguredService>());
				  }))
				  .UseStartup<Startup>()
				  .Build();
			})
			.As<IWebHost>()
			.SingleInstance();

		base.Load(builder);
	}
}

I used the existing builder action of the AddAutofac() extension to add dependencies I know I need (and want) in my WebApi Project to the container created by the extension. Also, Startup is a class defined in Project A (hence the using "Project A"; at the top) which is the default startup class and looks like this:

using Autofac;
using "Project A.DIModules";
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
	/// <summary>
	/// Use this method to add services.
	/// </summary>
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddMvc();
		
		// Add more services here
	}

	/// <summary>
	/// Use this method to add non-framework dependencies to the DI container;
	/// </summary>
	/// <param name="builder"></param>
	public void ConfigureContainer(ContainerBuilder builder)
	{
		builder.RegisterModule<LogModule>();
		builder.RegisterModule<DbContextModule>();
		// Add other DI modules or dependencies here
	}

	/// <summary>
	/// Use this method to configure the HTTP request pipeline middleware.
	/// </summary>
	/// <param name="app">The <see cref="IApplicationBuilder"/></param>
	/// <param name="env">The <see cref="IHostingEnvironment"/></param>
	public void Configure(IApplicationBuilder app, IHostingEnvironment env)
	{
		if (env.IsDevelopment())
		{
			app.UseDeveloperExceptionPage();
		}

		app.UseMvcWithDefaultRoute();
		app.UseStaticFiles();
		
		// Configure w.e. other things
	}
}

This way, the WebApi “Project A” has its own DI container, dependencies setup etc. but dependencies that need to be shared from the main Host to the WebApi project get “sideloaded” during webhost creation. I hope this helps (I did not post on Stack Overflow btw).

NOTE: All projects are .NET 4.6.2 projects in my case. I created a new empty project and manually imported all AspNetCore packages via nuget instead of using the Visual Studio Template for this. The default template creates a .NetCore project which can’t be referenced by a .Net Framework project (which my “Project C” is). You have to take this into acount, depending on your own application layout.

1reaction
tilligcommented, Jan 4, 2018

In thinking about adding something like this, we have to consider things like…

  • If you weren’t using Autofac, is this something you could do? This is interesting because as .NET Core and ASP.NET Core grow, the extension points and patterns they add are based on what is possible/available in the out-of-the-box dependency injection support. You don’t get to create your own IServiceCollection (their version of ContainerBuilder) to register dependencies. If we add something like this, are we going to have difficulty keeping that support later?
  • How can people shoot themselves in the foot with this? Any new feature like this gets documented, tested, and incurs long term support cost in the form of answering questions, troubleshooting complex setups, etc. For example, what happens if (when?) someone tries doing their app setup in a multithreaded scenario, not realizing the ContainerBuilder is being shared around like this? What happens if someone tries building the ContainerBuilder themselves after the Startup class has already run? (You can only build the container once, right?)

Getting down to more specifics, I can say:

  • Using AddAutofac, the Startup class is going to call ContainerBuilder.Build() at the end of startup. So the proposed code snippet wouldn’t actually work - you’d get an exception during host startup when the host tries to build the container saying that the container has already been built.
  • You would never be able to get the complete application container after it’s run through Startup because the AddAutofac stuff masks all that. You’d only ever have the .NET Core dependency resolver after that. Ostensibly if you need support in WCF and Web API, you’ll need a reference to a single built container. You won’t get that with AddAutofac.

The question becomes, then, “How do I share a container across ASP.NET Core, Web API, and WCF?”

Am I characterizing that all correctly? If not, let me know.

If I am, though, this is a great question to ask on StackOverflow. Your use case is pretty far outside the realm of “standard use cases,” to be sure. I can try to give you some pointers, but I don’t really have time to go all the way into full code snippets.

I would imagine the answer will involve…

  • Don’t use AddAutofac for the web host. You need more control over when the ContainerBuilder actually builds. Follow the quick start without ConfigureContainer.
  • You’ll need some static property in the app where you store the built container so all the disparate pieces can reach it.
  • Take control over the startup and container building order. Since you need to return a fully built container from ASP.NET Core startup, chances are you’ll need to do something like:
    • Register everything into the container for WCF.
    • Register everything into the container for Web API.
    • Run the ASP.NET Core startup, which will both register things and build the container. When you build the container, set it in that static property as well as setting up the ASP.NET Core resolver.
    • Set the complete container with WCF and Web API as needed.
  • Don’t over-DI things. Does the web host really need to be in the container? Is it grabbed and used elsewhere? Or if it’s a global singleton, could it just be a static somewhere? A tiny bit of manual composition can go a long way to unwinding things like this.

Yeah, it’s going to be pretty complicated. To be honest, if it was me, I wouldn’t try mixing all of these things in one app. I recognize there are business reasons to do things and you may have the need, I’m just saying, if it was me, I wouldn’t. ASP.NET Core handles REST calls just fine; I’d move everything out of Web API and into ASP.NET Core. WCF is a trickier thing, but I might consider having two services instead of a single service - one for the ASP.NET Core and one for the WCF. Instead of forcing the square peg into the round hole, I’d probably sidestep the problem entirely.

Does this help?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Builder with conditional inclusion of element - java
I'd like to resolve condition where I have it without over-engineering builder and without overcoding for every complex condition check. upd: ...
Read more >
Solved: How do I pass arguments in a Builder design pattern
Suppose I have user input values that I don't know ahead of time and I want to pass these values while creating the...
Read more >
@Builder
@Builder with @Singular adds a clear method since lombok v1.16.8. ... In the builder: A build() method which calls the method, passing in...
Read more >
Builder pattern in multiple stages
There a few options the way I see it: Option 1: Pass the Builder Around. There's no benefit in actually building and returning...
Read more >
Builder Design Pattern in Java
Let's see how we can implement builder design pattern in java. First of all you need to create a static nested class and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found