Programmatic add / remove services at runtime - c#

As far as I have been able to do is to add new services though the IServiceCollection
Is there a way to add and remove these services at run time?
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMyService, MyService>();
var serviceProvider = services.BuildServiceProvider();
var service = serviceProvider.GetService<IMyService>();
AzureMultiTenantServiceBuilders.Build(services);
}
I am trying to figure out how to configure additional AzureAd for our tenants at run time without having to restart the system.
public static class AzureMultiTenantServiceBuilders
{
public static void Build(IServiceCollection services)
{
foreach (var tenant in Tenant.GetAll())
{
services.AddAuthentication()
.AddAzureADTenanted(options =>
{
options.ClientId = tenant.ClientId;
options.TenantId = tenant.TenantId;
options.Instance = "https://login.microsoftonline.com";
});
}
I found this Installing a new middleware at runtime in ASP.Net Core which was close as its adding middleware but what i am trying to do is adding a service at runtime so this isnt helping.
public static class RuntimeMiddlewareExtensions
{
public static IServiceCollection AddRuntimeMiddleware(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Singleton)
{
services.Add(new ServiceDescriptor(typeof(RuntimeMiddlewareService), typeof(RuntimeMiddlewareService), lifetime));
AzureMultiTenantServiceBuilders.Build(services);
return services;
}
public static IApplicationBuilder UseRuntimeMiddleware(this IApplicationBuilder app, Action<IApplicationBuilder> defaultAction = null)
{
var service = app.ApplicationServices.GetRequiredService<RuntimeMiddlewareService>();
service.Use(app);
if (defaultAction != null)
{
service.Configure(defaultAction);
}
return app;
}
}

Related

How to access Singleton directly from ConfigureServices without BuildServiceProvider?

How to access singletons from ConfigureServices? There's a reason that I can't use appsettings for few configs.
For example, let's say that I want to set swagger title and version from database, not appsettings. My actual problem is I want to set consul address from my database. The problem should be the same, that I need to access my database in ConfigureServices. I have a custom extension like this:
public static IServiceCollection AddConsulConfig(this IServiceCollection services, string address)
{
services.AddSingleton<IConsulClient, ConsulClient>(p => new ConsulClient(consulConfig =>
{
consulConfig.Address = new Uri(address);
}));
return services;
}
I call it from startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IGlobalParameter, GlobalParameterManager>();
//I want to use IGlobalParameter here directly but without BuildServiceProvider
//This part is the problem
var service = ??
var varTitle = service.GetById("Title").Result.Value;
var varConsulAddress = service.GetById("ConsulAddress").Result.Value;
services.AddConsulConfig(varConsulAddress);
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = varTitle, Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// I can use it here or in the controller no problem
var service = app.ApplicationServices.GetRequiredService<IGlobalParameter>();
var varTitle = service.GetById("Title").Result.Value;
var varConsulAddress = service.GetById("ConsulAddress").Result.Value;
}
I DO NOT want to use BuildServiceProvider as it will make multiple instances, even visual studio gives warning about it. referenced in How to Resolve Instance Inside ConfigureServices in ASP.NET Core
I knew the existence of IConfigureOptions from the following link
https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/#the-new-improved-answer
But, I can't seem to find how exactly do I use that in ConfigureService:
public class ConsulOptions : IConfigureOptions<IServiceCollection>
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public ConsulOptions(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public void Configure(IServiceCollection services)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var provider = scope.ServiceProvider;
IGlobalParameter globalParameter = provider.GetRequiredService<IGlobalParameter>();
var ConsulAddress = globalParameter.GetById("ConsulAddress").Result.Value;
services.AddConsulConfig(ConsulAddress);
}
}
}
Set it in startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IGlobalParameter, GlobalParameterManager>();
services.AddSingleton<IConfigureOptions<IServiceCollection>, ConsulOptions>(); // So what? it's not called
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// IConsulClient is still null here
}
Any solution to how do I achieve this?
Thank you Jeremy, it's as simple as that. I don't know why I spend way too much time figuring out how to set this
The solution is to add singleton :
services.AddSingleton<IConsulClient, ConsulClient>(
p => new ConsulClient(consulConfig =>
{
var ConsulAddress = p.GetRequiredService<IGlobalParameter>().GetById("ConsulAddress").Result.Value;
consulConfig.Address = new Uri(ConsulAddress);
}
));

Avoid using the WebBulder and use the Startup file

I've a .NET Core application that needs to peform operation based on a scheduler.
I've used the following code which also installs Kestrel but I don't need to use it at all
public class Program
{
public static void Main(string[] args)
{
var processModule = System.AppDomain.CurrentDomain.BaseDirectory;
var assemblyName = Assembly.GetCallingAssembly().GetName();
var version = assemblyName.Version;
Directory.SetCurrentDirectory(processModule);
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
var applicationName = configuration.GetValue<string>("Properties:Application");
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration).Enrich.WithProperty("Version", version).Enrich
.WithProperty("ApplicationName", applicationName)
.CreateLogger();
Log.Logger = logger;
Log.Logger.Information("Started {ApplicationName} with version : {Version}", applicationName, version);
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }).UseSerilog()
.UseWindowsService();
}
And the Startup.cs is as follow :
class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
DataConnection.DefaultSettings = new Linq2DBSettings(configuration);
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//OMISS
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
}
}
Is there a way I can have Startup.cs (or IServiceCollection ) so that I can initialize my DI in this way?
Thanks
If you have all your services available in separate libraries, or you at least have the option to move them there from Web app, you could create some extension to configure DI both in your Web and Console applications
Library project
using Microsoft.Extensions.DependencyInjection;
namespace ClassLibrary
{
public static class ServiceCollectionExtensions
{
public static IServiceCollection ApplyMyServices(this IServiceCollection services)
{
services.AddScoped<MyService>();
return services;
}
}
public class MyService
{ }
}
Console app
using ClassLibrary;
using Microsoft.Extensions.DependencyInjection;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var serviceCollection = new ServiceCollection();
serviceCollection.ApplyMyServices();
var serviceProvider = serviceCollection.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var myService = scope.ServiceProvider.GetService<MyService>();
}
}
}
Web app
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.ApplyMyServices();
}

Autofac does not recognize my IServiceCollection

I'm creating a project that is based on the eShopOnContainers Microservices architecture
I Made a few changes to program.cs and startup.cs according to .NET Core 3+
Program.cs:
public static IHostBuilder CreateHostBuilder(IConfiguration configuration, string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
Startup.cs:
// ConfigureContainer is where you can register things directly
// with Autofac. This runs after ConfigureServices so the things
// here will override registrations made in ConfigureServices.
// Don't build the container; that gets done for you by the factory.
public void ConfigureContainer(ContainerBuilder builder)
{
//configure autofac
// Register your own things directly with Autofac, like:
builder.RegisterModule(new MediatorModule());
builder.RegisterModule(new ApplicationModule(Configuration));
}
Now in Startup.cs the AddCustomIntegrations() method Registers the IRabbitMQPersistentConnection which returns the DefaultRabbitMQPersistentConnection with IConnectionFactory configured
public static IServiceCollection AddCustomIntegrations(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IIdentityService, IdentityService>();
services.AddTransient<IVehicleManagementIntegrationEventService, VehicleManagementIntegrationEventService>();
services.AddTransient<Func<DbConnection, IIntegrationEventLogService>>(
sp => (DbConnection c) => new IntegrationEventLogService(c));
services.AddSingleton<IRabbitMQPersistentConnection>(sp =>
{
var logger = sp.GetRequiredService<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = configuration["EventBusConnection"],
DispatchConsumersAsync = true
};
if (!string.IsNullOrEmpty(configuration["EventBusUserName"]))
{
factory.UserName = configuration["EventBusUserName"];
}
if (!string.IsNullOrEmpty(configuration["EventBusPassword"]))
{
factory.Password = configuration["EventBusPassword"];
}
var retryCount = 5;
if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(configuration["EventBusRetryCount"]);
}
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
});
return services;
}
public static IServiceCollection AddEventBus(this IServiceCollection services, IConfiguration configuration)
{
var subscriptionClientName = configuration["SubscriptionClientName"];
services.AddSingleton<IEventBus, EventBusRabbitMQ>(sp =>
{
var rabbitMQPersistentConnection = sp.GetRequiredService<IRabbitMQPersistentConnection>();
var iLifetimeScope = sp.GetRequiredService<ILifetimeScope>();
var logger = sp.GetRequiredService<ILogger<EventBusRabbitMQ>>();
var eventBusSubcriptionsManager = sp.GetRequiredService<IEventBusSubscriptionsManager>();
var retryCount = 5;
if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(configuration["EventBusRetryCount"]);
}
return new EventBusRabbitMQ(rabbitMQPersistentConnection, logger, iLifetimeScope, eventBusSubcriptionsManager, subscriptionClientName, retryCount);
});
services.AddSingleton<IEventBusSubscriptionsManager, InMemoryEventBusSubscriptionsManager>();
return services;
}
When I run the application I get the following error:
Autofac.Core.DependencyResolutionException: An exception was thrown while activating IFMS.GMT.BuildingBlocks.Infrastructure.Events.EventBusRabbitMQ.EventBusRabbitMQ -> IFMS.GMT.BuildingBlocks.Infrastructure.Events.EventBusRabbitMQ.DefaultRabbitMQPersistentConnection.
---> Autofac.Core.DependencyResolutionException: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'IFMS.GMT.BuildingBlocks.Infrastructure.Events.EventBusRabbitMQ.DefaultRabbitMQPersistentConnection' can be invoked with the available services and parameters:
Cannot resolve parameter 'RabbitMQ.Client.IConnectionFactory connectionFactory' of constructor 'Void .ctor(RabbitMQ.Client.IConnectionFactory, Microsoft.Extensions.Logging.ILogger`1[IFMS.GMT.BuildingBlocks.Infrastructure.Events.EventBusRabbitMQ.DefaultRabbitMQPersistentConnection], Int32)'.
Autofac cant seem to find the service registered with AddCustomIntegrations()
I Moved all the code from AddCustomIntegrations() and AddEventBus() to a separate Module class that inherites from Autofac.Module class and it worked
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<InMemoryEventBusSubscriptionsManager>()
.As<IEventBusSubscriptionsManager>()
.InstancePerLifetimeScope();
builder.Register<IRabbitMQPersistentConnection>(fff =>
{
var logger = fff.Resolve<ILogger<DefaultRabbitMQPersistentConnection>>();
var factory = new ConnectionFactory()
{
HostName = Configuration["EventBusConnection"],
DispatchConsumersAsync = true
};
if (!string.IsNullOrEmpty(Configuration["EventBusUserName"]))
{
factory.UserName = Configuration["EventBusUserName"];
}
if (!string.IsNullOrEmpty(Configuration["EventBusPassword"]))
{
factory.Password = Configuration["EventBusPassword"];
}
var retryCount = 5;
if (!string.IsNullOrEmpty(Configuration["EventBusRetryCount"]))
{
retryCount = int.Parse(Configuration["EventBusRetryCount"]);
}
return new DefaultRabbitMQPersistentConnection(factory, logger, retryCount);
});
}

Dependency Injection in .Net Web Api 2.2 endpoint not available

I have a console application which works quit like a web api.
At the Program.cs I register
var collection = new ServiceCollection();
collection.AddScoped<IInfoBusinessComponent, InfoBusinessComponent>();
The InfoBusinessComponent need also a dependency injection which I do before adding the InfoBusinessComponent. Also I register my ILogger.
At my InfoController I use the di like that:
public InfoController(IInfoBusinessComponent businessComponent, ILogger<InfoController> logger)
When I call now that endpoint, I get immediately a 500 response.
When I erase the arguments from the controller, than the process is going into the constructor and controller. But that's not what I want.
public InfoController()
Why is the constructor not getting the dependency injection or why is the constructor not called?
public class Program
{
#region fields and propetries
public IConfiguration Configuration { get; }
//# if DEBUG
//#endif
public static IConnection Connection { get; set; }
public static ITimeSeriesBusinessComponent TimeSeriesBusinessComponent { get; set; }
public static IInfoBusinessComponent InfoBusinessComponent { get; set; }
private static int counter;
#endregion fields and propetries
public static void Main(string[] args)
{
IConfiguration config = GetConfigurations();
ILogger logger = GetLogger();
ServiceProvider appServiceProvider = GetServiceProvider(config);
Parallel.Invoke
(
() =>
{
BuildWebHost(args).Build().Run();
},
() =>
{
//...
}
);
}
private static IConfiguration GetConfigurations()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
IConfiguration config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
return config;
}
private static ILogger GetLogger()
{
ILogger logger = new LoggerFactory().AddNLog().CreateLogger<Program>();
return logger;
}
private static ServiceProvider GetServiceProvider(IConfiguration config)
{
var collection = new ServiceCollection();
collection.AddLogging(configuration => configuration.AddNLog());
//...
collection.AddScoped<IInfoRepository>(serviceProvider =>
{
return new InfoRepository(
config["ConnectionStrings:MainConnection"],
config["ConnectionStrings:MetaDataConnection"],
config["InfoFunctionName"],
config["UserName"],
config["Password"],
config["VirtualHost"],
config["ConnectionHostName"]);
});
collection.AddScoped<IInfoBusinessComponent, InfoBusinessComponent>();
var appServiceProvider = collection.BuildServiceProvider();
return appServiceProvider;
}
public static IWebHostBuilder BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseApplicationInsights()
.UseUrls("http://0.0.0.0:5003")
.UseNLog();
}
Here the Startup.cs:
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Title = "My CLI"
});
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My CLI");
c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
c.RoutePrefix = string.Empty;
});
app.UseMvc();
}
}
The problem is that the endpoint you create with BuildWebHost uses its own instance of ServiceProvider. The instance of ServiceProvider that you create doesn't get into the pipeline.
Why: ServiceCollection doesn't use any kind of singleton registry, so it's not enough to register services through some instance of ServiceCollection and build some instance of ServiceProvider. You have to make the endpoint use your specific instance of ServiceCollection/ServiceProvider. Or you can copy your ServiceCollection into one that's used by the endpoint - that's how I'd solve it.
So, let's use a ServiceCollection to register your services (as it is now). Then, instead of doing collection.BuildServiceProvider(), let's use that ServiceCollection in the Startup, to copy all registrations into the service collection used by the pipeline.
First, let's expose your ServiceCollection to be accessible from Startup:
class Program
{
public static ServiceCollection AppServices { get; set; }
public static void Main(string[] args)
{
// ...other stuff...
AppServices = GetServiceCollection(config);
// ...other stuff...
}
// renamed from GetServiceProvider
private static ServiceCollection GetServiceCollection(IConfiguration config)
{
var collection = new ServiceCollection();
// ... register services...
return collection;
}
}
Then in the Startup class, use Program.AppServices in ConfigureServices() as follows:
EDIT: pay attention to the usings in Startup.cs
// make sure these usings are present:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
....
public class Startup
{
// ... other members ...
public void ConfigureServices(IServiceCollection services)
{
// ... the usual stuff like services.AddMvc()...
// add this line:
services.TryAdd(Program.AppServices);
}
// ... other members ...
}

StructureMap .Net Core Windows Service Nested Containers

There are lots of articles talking about how to use Structure Map with ASP.NET Core, but not very many talking about console applications or windows services. The default behavior in ASP.Net Core is that StructureMap creates a Nested Container per HTTPRequest so that a concrete class will be instantiated only once per HTTP Request.
I am creating a .Net Core Windows Service using the PeterKottas.DotNetCore.WindowsService nuget package. I setup StructureMap using this article: https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/
My windows service is setup on a Timer and performs an action every X number of seconds. I want each of these actions to use a nested container similar to how ASP.NET does it. In other words, I want everything created for polling pass #1 to be disposed of once that polling pass completes. When polling pass #2 starts I want all new instances of objects to be instantiated. However, within the scope of a single polling pass I only want one instance of each object to be created.
What is the proper way to do this?
Here is my program class
public class Program
{
public static ILoggerFactory LoggerFactory;
public static IConfigurationRoot Configuration;
static void Main(string[] args)
{
var applicationBaseDirectory = AppContext.BaseDirectory;
string environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
if (string.IsNullOrWhiteSpace(environment))
throw new ArgumentNullException("Environment not found in ASPNETCORE_ENVIRONMENT");
ConfigureApplication(applicationBaseDirectory, environment);
var serviceCollection = ConfigureServices();
var serviceProvider = ConfigureIoC(serviceCollection);
ConfigureLogging(serviceProvider);
var logger = LoggerFactory.CreateLogger<Program>();
logger.LogInformation("Starting Application");
ServiceRunner<IWindowsService>.Run(config =>
{
var applicationName = typeof(Program).Namespace;
config.SetName($"{applicationName}");
config.SetDisplayName($"{applicationName}");
config.SetDescription($"Service that matches Previous Buyers to Vehicles In Inventory to Fine Upgrade Opportunities.");
config.Service(serviceConfig =>
{
serviceConfig.ServiceFactory((extraArgs, microServiceController) =>
{
return serviceProvider.GetService<IWindowsService>();
});
serviceConfig.OnStart((service, extraArgs) =>
{
logger.LogInformation($"Service {applicationName} started.");
service.Start();
});
serviceConfig.OnStop((service =>
{
logger.LogInformation($"Service {applicationName} stopped.");
service.Stop();
}));
serviceConfig.OnError(error =>
{
logger.LogError($"Service {applicationName} encountered an error with the following exception:\n {error.Message}");
});
});
});
}
private static void ConfigureApplication(string applicationBaseDirectory, string environment)
{
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
var builder = new ConfigurationBuilder()
.SetBasePath(applicationBaseDirectory)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{environment}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
private static IServiceCollection ConfigureServices()
{
var serviceCollection = new ServiceCollection().AddLogging().AddOptions();
serviceCollection.AddDbContext<JandLReportingContext>(options => options.UseSqlServer(Configuration.GetConnectionString("JandLReporting")), ServiceLifetime.Transient);
//serviceCollection.AddDbContext<JLMIDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("JLMIDB")), ServiceLifetime.Scoped);
serviceCollection.Configure<TimerSettings>(Configuration.GetSection("TimerSettings"));
serviceCollection.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
return serviceCollection;
}
private static IServiceProvider ConfigureIoC(IServiceCollection serviceCollection)
{
//Setup StructureMap
var container = new Container();
container.Configure(config =>
{
config.Scan(scan =>
{
scan.AssemblyContainingType(typeof(Program));
scan.AssembliesFromApplicationBaseDirectory();
scan.AssembliesAndExecutablesFromApplicationBaseDirectory();
scan.WithDefaultConventions();
});
config.Populate(serviceCollection);
});
return container.GetInstance<IServiceProvider>();
}
private static void ConfigureLogging(IServiceProvider serviceProvider)
{
LoggerFactory = serviceProvider.GetService<ILoggerFactory>()
.AddConsole(Configuration.GetSection("Logging"))
.AddFile(Configuration.GetSection("Logging"))
.AddDebug();
}
}
Here is my WindowsService class:
public class WindowsService : MicroService, IWindowsService
{
private readonly ILogger _logger;
private readonly IServiceProvider _serviceProvider;
private readonly TimerSettings _timerSettings;
public WindowsService(ILogger<WindowsService> logger, IServiceProvider serviceProvider, IOptions<TimerSettings> timerSettings)
{
_logger = logger;
_serviceProvider = serviceProvider;
_timerSettings = timerSettings.Value;
}
public void Start()
{
StartBase();
Timers.Start("ServiceTimer", GetTimerInterval(), async () =>
{
await PollingPassAsyc();
},
(error) =>
{
_logger.LogCritical($"Exception while starting the service: {error}\n");
});
}
private async Task PollingPassAsyc()
{
using (var upgradeOpportunityService = _serviceProvider.GetService<IUpgradeOpportunityService>())
{
await upgradeOpportunityService.FindUpgradeOpportunitiesAsync();
}
}
private int GetTimerInterval()
{
return _timerSettings.IntervalMinutes * 60 * 1000;
}
public void Stop()
{
StopBase();
_logger.LogInformation($"Service has stopped");
}
}
There is extension method CreateScope for IServiceProvider in Microsoft.Extensions.DependencyInjection namespace. What it does is resolve special interface (IServiceScopeFactory) from current DI container, which is responsible for creating new scopes, and creates new scope using this factory. StructureMap registers implementation of this interface, so when you call CreateScope - StructureMap will create nested container. Sample usage:
using (var scope = _serviceProvider.CreateScope()) {
// use scope.ServiceProvider, not _serviceProvider to resolve instance
var service = scope.ServiceProvider.GetService<IUpgradeOpportunityService>‌​();
}

Categories