This is a series of blog posts:
- Part 1: Stateful service
- Part 2: Stateless service
- Part 3: Unit testing
Update: this approach is incorrect, unfortunately. It actually creates two containers: one to instantiate the service and another to instantiate the Startup object. So it is even less elegant than initially I thought it is.
Last time we instantiated a stateful service from a DI container, it was elegant and relatively easy. What I can’t say about instantiating a stateless service that hosts a web server. The problem is that it needs a reference to the same container in 3 different places: Program.cs, MyStatelessService.cs, Startup.cs.
First the near identical code between stateless and statefull services:
ServiceRuntime.RegisterServiceAsync(nameof(MyStatelessService) + "Type", context => CreateService(context)) .GetAwaiter() .GetResult(); private static StatelessServiceBase CreateService(StatelessServiceContext context) { var container = ContainerConfig.CreateContainer(context); return container.GetInstance(); }
Now where the differences start. Let’s start from refactoring the Startup class. From the template it comes as a so-called conventional startup, in other words follows the Convention over Configuration principle. Now it inherits StartupBase and accepts Container in its constructor:
internal sealed class Startup : StartupBase { private readonly Container _container; public Startup(Container container) { _container = container; } }
Then inject IStartup
into service’s ctor alongside its other dependencies (if any):
internal sealed class MyStatelessService : StatelessService { private readonly IStartup _startup; public MyStatelessService (StatelessServiceContext context, IStartup startup, ...) : base(context) { _startup = startup; } protected override IEnumerable CreateServiceInstanceListeners() { yield return new ServiceInstanceListener(serviceContext => new HttpSysCommunicationListener(serviceContext, nameof(MyStatelessService) + "Endpoint", (url, listener) => { var builder = new WebHostBuilder(); return builder.UseHttpSys() .ConfigureServices(services => services.AddSingleton(serviceContext) .AddSingleton(_startup)) .UseContentRoot(Directory.GetCurrentDirectory()) .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None) .UseUrls(url) .Build(); })); } }
The CreateServiceInstanceListeners()
method differs from the template just slightly. Instead of UseStartup()
now we use AddSingleton(_startup)
. What means the Startup class should be registered in the container by its interface:
container.RegisterInstance(context); container.RegisterInstance(context); container.RegisterSingleton(); container.RegisterSingleton();
The only thing left is to configure the ASP.NET Core infrastructure pipeline to use the container, for more details see the documentation:
public override void ConfigureServices(IServiceCollection services) { services.AddSingleton(new SimpleInjectorControllerActivator(container)); services.AddSingleton(new SimpleInjectorViewComponentActivator(container)); services.EnableSimpleInjectorCrossWiring(container); services.UseSimpleInjectorAspNetRequestScoping(container); } public override void Configure(IApplicationBuilder app) { container.AutoCrossWireAspNetComponents(app); }
That’s it! Now your statefull service itself and all its dependencies are recursively instantiated and controlled by the container.
Hi, did you ever find a way to get this working with a single container? š
Yes and no š
Yes if it’s a not a web service where you want to use single container to construct both the service class and Startup. That combination is impossible I concluded, unfortunately. In all other cases – it is.
What kind of service you got?
I’m looking for a service that will basically acts as a host for a MassTransit service bus based on RabbitMq. Tried looking into stateless services because I don’t need the overhead and extra “bulk” of Web API, but Web API does have that built-in DI container. So basically I’m looking to host a simple process in Service Fabric which will consume RBMQ message š
I agree with your choice of stateless service, you (luckily) don’t need persisted state. I’m not very familiar with MassTransit but it seems you don’t need Web API either.
So it’s more like a worker, correct? Then it shouldn’t be a problem to use single container. You can use Microsoft DI and it works well, to a degree.
But since you don’t have ASP.NET then you don’t have to use it, so are free to use something else and I definitely would recommend to give Simple Injector a try.
There is another choice you can make: whether to use SDK-dependent services (inherit from StatelessService class, etc.) or do a guest executable. In the latter case, IHostedService is a great option.
That’s what I do at work: a SF application consists of 2 services, both stateless: one is ASP.NET Core Web API which receives requests and puts messages into a queue (Azure Blob Storage) for deferred processing. And the another one is a worker that listens to the said queue and does the heavy lifting.
nice post!!!