3

I'm currently working on a POC project and I'm trying to figure out how I can share a service dependency between different endpoints to control application state and handle all service requests (lets call it ControlService) - specifically when one of those endpoints is a KestrelCommunicationListener / HttpSysCommunicationListener and combined with a FabricTransportServiceRemotingListener (or any other type of custom listener)

Autofac looked promising but the examples don't show how to get a HTTP listener working when the container is built in startup rather than the main entry point - would I need to pass the container to MyFabricService so it can be passed into and added to by the startup registrations?

I've seen references to using container.Update() or adding registrations on the fly using container.BeginLifetimeScope() but they are all using a container built in main and then I'm not sure how I would add the APIs created by the HTTP listener to the original container.

I'm possibly not explaining it that well so in summary I'm looking to have something like the below service that can receive communications via n. different endpoints - process the message and then send messages out via n. clients (aka other service endpoints)

Control Service Endpoints

enter image description here

Happy to clarify if anything is unclear - perhaps even using another creative diagram :)

Updated:

From Program.Main()

   ServiceRuntime.RegisterServiceAsync("ManagementServiceType",
                                context => new ManagementService(context)).GetAwaiter().GetResult();

Here is my fabric service

public ManagementService(StatefulServiceContext context)
        : base(context)
    {
        //this does not work but is pretty much what I'm after
        _managementService = ServiceProviderFactory.ServiceProvider.GetService(typeof(IManagementService)) as IManagementService;
    }

    protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners() =>
       new ServiceReplicaListener[]
       {
           //create external http listener
            ServiceReplicaListenerFactory.CreateExternalListener(typeof(Startup), StateManager, (serviceContext, message) => ServiceEventSource.Current.ServiceMessage(serviceContext, message), "ServiceEndpoint"),

            //create remoting listener with injected dependency
            ServiceReplicaListenerFactory.CreateServiceReplicaListenerFor(() => new RemotingListenerService(_managementService), "ManagmentServiceRemotingEndpoint", "ManagementServiceListener")

       };

ServiceReplicaListener

public static ServiceReplicaListener CreateExternalListener(Type startupType, IReliableStateManager stateManager, Action<StatefulServiceContext, string> loggingCallback, string endpointname)
    {
        return new ServiceReplicaListener(serviceContext =>
        {
            return new KestrelCommunicationListener(serviceContext, endpointname, (url, listener) =>
            {
                loggingCallback(serviceContext, $"Starting Kestrel on {url}");

                return new WebHostBuilder().UseKestrel()
                            .ConfigureServices((hostingContext, services) =>
                            {
                                services.AddSingleton(serviceContext);
                                services.AddSingleton(stateManager);

                                services.AddApplicationInsightsTelemetry(hostingContext.Configuration);
                                services.AddSingleton<ITelemetryInitializer>((serviceProvider) => new FabricTelemetryInitializer(serviceContext));
                            })
                            .ConfigureAppConfiguration((hostingContext, config) =>
                            {
                                config.AddServiceFabricConfiguration(serviceContext);
                            })
                            .ConfigureLogging((hostingContext, logging) =>
                            {
                                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                                logging.AddDebug();
                            })
                            .UseContentRoot(Directory.GetCurrentDirectory())
                            .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                            .UseStartup(startupType)
                            .UseUrls(url)
                            .Build();
            });
        });
    }

Startup

public class Startup
{
    private const string apiTitle = "Management Service API";
    private const string apiVersion = "v1";

    private readonly IConfiguration configuration;

    public Startup(IConfiguration configuration)
    {
        this.configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        var modules = new List<ICompositionModule>
                      {
                          new Composition.CompositionModule(),
                          new BusinessCompositionModule()
                      };

        foreach (var module in modules)
        {
            module.AddServices(services, configuration);
        }

        services.AddSwashbuckle(configuration, apiTitle, apiVersion, "ManagementService.xml");
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddApplicationInsights(app.ApplicationServices);

        // app.UseAuthentication();
        //  app.UseSecurityContext();

        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
          //  app.UseBrowserLink();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }

        app.UseStaticFiles();

        app.UseCors("CorsPolicy");

        app.UseMvc();

        app.UseSwagger(apiTitle, apiVersion);

        //app.UseMvc(routes =>
        //{
        //    routes.MapRoute(
        //        name: "default",
        //        template: "{controller=Home}/{action=Index}/{id?}");
        //});

    }
}

All the service dependencies are added in the CompositionModules using Microsoft.Extensions.DependencyInjection (not autofac) in startup.cs

This works great and creates my HTTP listener - I now just need a way of getting access to my services that were added to the container during startup of my http listener / webhost.

KnowHoper
  • 4,352
  • 3
  • 39
  • 54
Tim
  • 495
  • 1
  • 4
  • 10
  • Can you elaborate a bit more on where your dependencies are instantiated? Do you need an ability to perform registration inside *Listener in WebHostBuilder.ConfigureServices? – Oleg Karasik Jun 11 '18 at 14:39
  • @OlegKarasik I've edited the question with some sample code – Tim Jun 11 '18 at 15:59
  • Does `IManagementService` has some of the dependencies that should be resolved through the dependency injection container being configured? Example: `ManagementService(IEnvironmentService)` where `IEnvironmentService` for `WebHost` in `KestrelListener` can be `WebHostEnvironmentService` and for `RemotingListener` - `RemotingWebHostEnvironmentService`. – Oleg Karasik Jun 12 '18 at 06:30
  • I think my sample code is a bit misleading sorry - IManagementService is where all my business logic will be held and is essentially the 'ControlService' in my example diagrams. It is likely the ControlService will contain multiple dependencies but I don't envisage it needing a dependency on a hosting environment as it will essentially just be handling requests received on multiple endpoints. – Tim Jun 12 '18 at 09:31

2 Answers2

0

You can use Autofac.Integration.ServiceFabriс, an Autofac extension to support Service Fabric. You need to create a container in Program.cs

var builder = new ContainerBuilder();

builder.RegisterServiceFabricSupport();
builder.RegisterType<SomeService>().As<IManagementService>();

builder.RegisterStatelessService<ManagementService>("ManagementServiceType");
using (builder.Build())
{
   // Prevents this host process from terminating so services keep running.
   Thread.Sleep(Timeout.Infinite);
}

Then you can inject it to the constructor of your fabric service. You can find more information on this topic on https://alexmg.com/posts/introducing-the-autofac-integration-for-service-fabric

Alex Riabov
  • 8,655
  • 5
  • 47
  • 48
  • Thanks for the suggestion Alex - I've already had a look at that post and also read the Autofac docs but what isn't clear to me is how I would go about configuring and creating a KestrelCommunicationListener if I was to create the container in Program.cs. What would I need to do with .UseStartup(startupType) as that is normally where you would configure your services for a ASP.Net Core API? – Tim Jun 11 '18 at 18:59
  • @Tim, at this point you can register the service on listener creation in ConfigureServices method (it will be singleton, but it's what you need, right?). After that you can inject it using .net core built-in DI container, or combine it with Autofac if you feel it more comfortable. – Alex Riabov Jun 11 '18 at 20:24
  • If IManagementService aka IControlService was already registered with the container in Program.cs would I need to add it during ConfigureServices again? It would need to go in Program.cs so I can pass it into the FabricService constructor so I can then use it when creating my remoting endpoints. Its almost like I need to pass the container I created in Program.cs into the Startup.cs so I can add the additional services such as UseMvc(), UseCors, but as Core has control over calling the Configure method in Startup I'm not sure how to override it. – Tim Jun 12 '18 at 09:45
  • As mentioned in the original question I could update the container as per https://stackoverflow.com/questions/38916620/asp-net-core-with-existing-ioc-container-environment using container.update but as this is widely considered bad practice I'm looking for a 'best practice' solution – Tim Jun 12 '18 at 09:47
  • @Tim to be honest, I don't think that it's quite common problem to have some 'best practice'. Also there will be several containers in your case - each for each endpoint. Let me elaborate more on my suggestion: you can add line `services.AddSingleton(controlService);` to ConfigureServices method, where controlService is the service injected into your Service Fabric instance – Alex Riabov Jun 12 '18 at 11:44
0

@Tim

Sorry for a late response. Currently I am working on library package that we use in our company for internal projects. This library simplifies configuration of Reliable Services. I think our recent enhancements can do what you need to do (hope I get the use case correctly).

All the information about the library can be found on project page on GitHub and NuGet package can be found here (please note that it is a pre-release version but we are planning to turn into complete release soon).

In case you have any questions or need more information feel free to contact me.

UPDATE

I have create a sample application. Please feel free to try it.

Here is a code example.

public interface IManagementService
{
    string GetImportantValue();
}

public interface IMessageProvider
{
    string GetMessage();
}

public class MessageProvider : IMessageProvider
{
    public string GetMessage()
    {
        return "Value";
    }
}

public class ManagementService : IManagementService
{
    private readonly IMessageProvider provider;

    public ManagementService(
        IMessageProvider provider)
    {
        this.provider = provider;
    }

    public string GetImportantValue()
    {
        // Same instances should have the same hash
        return this.provider.GetMessage() + $"Hash: {this.GetHashCode()}";
    }
}

public interface IRemotingImplementation : IService
{
    Task<string> RemotingGetImportantValue();
}

public class RemotingImplementation : IRemotingImplementation
{
    private readonly IManagementService managementService;

    public RemotingImplementation(
        IManagementService managementService)
    {
        this.managementService = managementService;
    }

    public Task<string> RemotingGetImportantValue()
    {
        return Task.FromResult(this.managementService.GetImportantValue());
    }
}

public class WebApiImplementationController : ControllerBase
{
    private readonly IManagementService managementService;

    public WebApiImplementationController(
        IManagementService managementService)
    {
        this.managementService = managementService;
    }

    [HttpGet]
    public Task<string> WebApiGetImportantValue()
    {
        return Task.FromResult(this.managementService.GetImportantValue());
    }
}

public class WebApiStartup
{
    private readonly IConfiguration configuration;

    public WebApiStartup(
        IConfiguration configuration)
    {
        this.configuration = configuration;
    }

    public void ConfigureServices(
        IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(
        IApplicationBuilder app,
        IHostingEnvironment env,
        ILoggerFactory loggerFactory)
    {
        app.UseMvcWithDefaultRoute();
    }
}

internal static class Program
{
    /// <summary>
    ///     This is the entry point of the service host process.
    /// </summary>
    private static void Main()
    {
        var host = new HostBuilder()
           .ConfigureServices(
                services =>
                {
                    services.AddTransient<IMessageProvider, MessageProvider>();
                    services.AddSingleton<IManagementService, ManagementService>();
                })
           .ConfigureStatefulService(
                serviceBuilder =>
                {
                    serviceBuilder
                       .UseServiceType("StatefulServiceType")
                       .DefineAspNetCoreListener(
                            listenerBuilder =>
                            {
                                listenerBuilder
                                   .UseEndpointName("ServiceEndpoint")
                                   .UseKestrel()
                                   .UseUniqueServiceUrlIntegration()
                                   .ConfigureWebHost(
                                        webHostBuilder =>
                                        {
                                            webHostBuilder.UseStartup<WebApiStartup>();
                                        });
                            })
                       .DefineRemotingListener(
                            listenerBuilder =>
                            {
                                listenerBuilder
                                   .UseEndpointName("ServiceEndpoint2")
                                   .UseImplementation<RemotingImplementation>();
                            });
                })
           .Build()
           .Run();
    }
}
Oleg Karasik
  • 959
  • 6
  • 17