Dependency Injection in .Net Web Api 2.2 endpoint not available - c#

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 ...
}

Related

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();
}

How to use IConfiguration object that I built in my Startup class in some other class?

We are using asp.net core to build our web application. Below is our code which starts the server and passing all the values to underlying classes. This is the way it was designed from starting.
public class ProgramApiServer : IWebServer
{
public void StartServer()
{
var host = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseKestrel(o =>
{
o.Limits.MaxResponseBufferSize = null;
o.Limits.MinResponseDataRate = null;
o.Limits.MaxConcurrentConnections = 1000;
o.Limits.MaxConcurrentUpgradedConnections = 1000;
o.AllowSynchronousIO = true;
})
.UseStartup<Startup>()
.UseUrls("http://*:1330")
.Build();
host.Run();
}
}
This is what I have in my Startup class:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("clientsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"clientsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
// building IConfiguration object here
Configuration = builder.Build();
}
// how can I use this in DataProcess class
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
// ...
DataProcess.Evaluate(services);
// ...
}
public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime)
{
// some stuff here
}
Below is my DataProcess class:
public static class DataProcess {
public static void Evaluate(IServiceCollection services)
{
// ...
Process(services, ....);
}
private static void Process(IServiceCollection services, ....)
{
// ...
// here I need to use IConfiguration object to get value for my config
Console.WriteLine(_configuration["configKey"]);
}
}
Question:
As you can see above I am calling Evaluate method of DataProcess class from ConfigureServices method of Startup class. I need to use IConfiguration object in Process method of DataProcess class but I am not able to figure it out on how can I use it.
I recently started working with asp.net and trying to see on how can I pass around the IConfiguration object cleanly to my static DataProcess class. Is it a good idea to pass it via Evaluate method in Startup class and then store it in global variable in DataProcess class that can be used in Process method or any other better way?
Why don't you just pass it through your Evaluate method to your Process method?
public static class DataProcess {
public static void Evaluate(IServiceCollection services, IConfigurationRoot configuration)
{
// ...
Process(services, configuration, ....);
}
private static void Process(IServiceCollection services, IConfigurationRoot configuration, ....)
{
// ...
// here I need to use IConfiguration object to get value for my config
Console.WriteLine(configuration["configKey"]);
}
}
And in your Startup.cs just call it with required params:
DataProcess.Evaluate(services, this.Configuration);

Registering Modules Autofac

I am using .NetCore 2.1 with autofaq in an asp.net core web application, my problem is the load method of my service module is not firing, I am instantiating a new instance of it as a parameter to registermodule, and the constructor of my service module is firing, this is a pretty typical setup, is there something i am doing wrong that anyone here can see?
ServiceModule.cs
namespace MyApp.Managers.DependencyManagement
{
public class ServiceModule : Module
{
public ServiceModule()
{
Console.WriteLine("YES THIS LINE OF CODE IS FIRING?");
}
protected override void Load(ContainerBuilder builder)
{
Console.WriteLine("Why am i not firing? :-( ");
builder.RegisterType<ItemManager>().As<IItemManager>().InstancePerLifetimeScope();
}
}
}
Program.cs (pretty basic void main here)
namespace MyApi.Api
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.ConfigureServices(services => services.AddAutofac())
.ConfigureAppConfiguration((context, options) =>
{
options.SetBasePath(Directory.GetCurrentDirectory())
.AddCommandLine(args);
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
Startup.cs (lots of stuff going on here)
namespace MyApi.Api
{
public class Startup
{
private readonly IHostingEnvironment env;
private readonly IConfiguration config;
private readonly ILoggerFactory loggerFactory;
public Startup(
IHostingEnvironment env,
IConfiguration config,
ILoggerFactory loggerFactory)
{
this.env = env;
this.config = config;
this.loggerFactory = loggerFactory;
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", EnvironmentVariableTarget.Machine);
var appParentDirectory = new DirectoryInfo(this.env.ContentRootPath).Parent.FullName;
var environmentName = environment ?? "Dev";
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{environmentName}.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; private set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Item Service", Version = "v1" });
c.DescribeAllEnumsAsStrings();
});
services
.AddMvc()
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1)
.AddFluentValidation(x => x.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly()));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void ConfigureContainer(ContainerBuilder builder)
{
var connectionString = this.Configuration.GetConnectionString("GildedRose");
ServiceConfiguration.Register(this.AddWebServices, connectionString);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseMvc();
}
private void AddWebServices(ContainerBuilder builder)
{
}
}
}
ServiceConfiguration.cs (the constructor is firing, but the load method never fires)
namespace MyApi.Api
{
public class ServiceConfiguration
{
public static ContainerBuilder Register(Action<ContainerBuilder> additionalRegistration, string connectionString)
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<ConfigurationStore>().As<IConfigurationStore>().InstancePerLifetimeScope();
containerBuilder.RegisterType<Context>().AsSelf().InstancePerLifetimeScope();
containerBuilder.RegisterModule(new StoreModule()
{
ConnectionString = connectionString,
});
containerBuilder.RegisterModule(new Managers.DependencyManagement.ServiceModule());
additionalRegistration(containerBuilder);
return containerBuilder;
}
}
}
You are not using the ContainerBuilder passed to the ConfigureContainer() method, instead you are instantiating and using a new one in the ServiceConfiguration.Register(), but that is not the one wired in the ASP.NET Core framework and also won't be built by it. That is why the Load() is not firing, you should use the one which is used by the framework.
Try to pass it to your static method like this:
ServiceConfiguration.Register(this.AddWebServices, connectionString, builder);
And use it in your method like:
public static ContainerBuilder Register(Action<ContainerBuilder> additionalRegistration,
string connectionString,
ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<ConfigurationStore>()
.As<IConfigurationStore>()
.InstancePerLifetimeScope();
// the rest
}
With autofac you've got a couple ways of starting a service on creation:
Implementing IStartable on your service and adding a Start() method
or something like this:
var builder = new ContainerBuilder();
builder.RegisterBuildCallback(c => c.Resolve<DbContext>());
// The callback will run after the container is built
// but before it's returned.
var container = builder.Build();

How to invoke SignalR Clients.All.InvokeAsync() in places other than the Controller?

I'm able to access my IHubContext<MyHub> fine and dandy in my .NET Core WebAPI's Controller through DI in the constructor, but I want to access it elsewhere too.
Specifically, when I consume a message from RabbitMQ, sometimes I want to update clients through _myHubContext.Clients.All.InvokeAsync(), but I just can't figure out how to get it.
I'm also having issues finding documentation for doing this kind of thing outside of the controller.
Any help would be appreciated.
EDIT:
To add some detail, and where the cause of my problem may originate, I'm trying to access the IHubContext (and some of my own services registered in ConfigureServices) within my Startup class, specifically during IApplicationLifetime ApplicationStarted and ApplicationStopped which call a RabbitMQ consumer's methods to connect and disconnect.
I'm I correct in guessing that maybe I'm unable to access registered services in the Startup class? If so, how would I go about starting these services?
Update:
Moving services.AddSignalR() and some of the services that are called at startup one level up to the WebHost.ConfigureServices in Program.cs solved some of my problems, but of course there are more.
I wasn't getting any messages on my JS client when I received a message from RabbitMQ, but my client was connecting successfully. "Weird..." I thought. To get more info, I wired up a an GET action in my controller to sent some content through the SignalR Hub. Whenever I called that GET, it works... the IHubContext<MyHub>. I get the hubContext through the constructor in my RabbitMQ listener, just like I do with the controller.
The new question: are things injected differently in the controller than they are into services that I register myself at startup? How so, and how do I overcome that?
Some code to go with it...
Excerpt from Program.cs
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseIISIntegration()
.ConfigureServices(services => {
services.AddSignalR();
services.AddTransient<ISubscriber, Subscriber>();
services.AddTransient<IDataService, DataService>();
services.AddTransient<IHealthCheckProcessor, HealthCheckProcessor>();
services.AddTransient<INodeProcessor, NodeProcessor>();
})
.UseStartup<Startup>()
.Build();
From Startup.cs
public class Startup
{
public Startup(IConfiguration _configuration, ISubscriber _subscriber)
{
configuration = _configuration;
subscriber = _subscriber;
}
public IConfiguration configuration { get; }
public ISubscriber subscriber { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(builder => builder
// CORS stuff);
app.UseSignalR(routes =>
{
routes.MapHub<StatusHub>("Status");
});
app.UseMvc();
applicationLifetime.ApplicationStarted.Register(OnStartup);
applicationLifetime.ApplicationStopping.Register(OnShutdown);
}
private void OnStartup() {
// MessageBroker stuff
subscriber.Start(messageBroker);
}
private void OnShutdown() {
subscriber.Stop();
}
}
From Subscriber.cs
public class Subscriber : ISubscriber {
public static IConnection connection;
public static IModel channel;
public IHubContext<StatusHub> hubContext;
public static IHealthCheckProcessor healthCheckProcessor;
public static INodeProcessor nodeProcessor;
public Subscriber(IHubContext<StatusHub> _hubContext, INodeProcessor _nodeProcessor, IHealthCheckProcessor _healthCheckProcessor)
{
connection = new ConnectionFactory().CreateConnection();
channel = connection.CreateModel();
hubContext = _hubContext;
nodeProcessor = _nodeProcessor;
healthCheckProcessor = _healthCheckProcessor;
}
public void Start(MessageBroker messageBroker)
{
var factory = new ConnectionFactory() { HostName = messageBroker.URL }.CreateConnection();
foreach (Queue queue in messageBroker.Queues)
{
channel.QueueDeclare(
queue: queue.Name,
durable: queue.Durable,
exclusive: queue.Exclusive,
autoDelete: queue.AutoDelete,
arguments: null
);
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
byte[] body = ea.Body;
string message = Encoding.UTF8.GetString(body);
RouteMessage(queue, message);
};
channel.BasicConsume(
queue: queue.Name,
autoAck: queue.AutoAck,
consumer: consumer
);
hubContext.Clients.All.InvokeAsync("Send", "It worked - from the subscriber");
}
}
public void RouteMessage(Queue queue, string message) {
if(queue.Name == "discovery") {
nodeProcessor.Process(message);
}
if(queue.Name == "health") {
healthCheckProcessor.Process(message);
}
}
public void Stop()
{
Console.WriteLine("Terminating connection to RabbitMQ instance.");
channel.Close(200, "Goodbye");
connection.Close();
}
}
From HealthCheckProcessor.cs
public class HealthCheckProcessor : IHealthCheckProcessor {
private IDataService dataService;
private IHubContext<StatusHub> hubContext;
public HealthCheckProcessor(IDataService _dataService, IHubContext<StatusHub> _hubContext)
{
dataService = _dataService;
hubContext = _hubContext;
}
public void Process(string message) {
HealthCheck health = JsonConvert.DeserializeObject<HealthCheck>(message);
Node node = dataService.GetSingle(health.NodeId);
node.Health = health;
dataService.Update(node);
Console.WriteLine("It's sending.");
hubContext.Clients.All.InvokeAsync("Send", "It worked - from the processor");
}
}
From the Controller:
[Route("api/[controller]")]
public class MyController: Controller
{
private IDataService _dataService;
private readonly IConfiguration configuration;
private static IHubContext<StatusHub> hubContext;
public NodesController(IConfiguration config, IDataService dataService, IHubContext<StatusHub> _hubContext)
{
_dataService = dataService;
configuration = config;
hubContext = _hubContext;
}
[HttpGet]
public string Get()
{
hubContext.Clients.All.InvokeAsync("Send", "Blarg!");
return "Well, I tried.";
}
}
You are trying to access services that are not available at the time you request them.
Configure is called after ConfigureServices specifically so that any services registered can be accessible.
public class Startup {
public Startup(IConfiguration _configuration) {
configuration = _configuration;
}
public IConfiguration configuration { get; }
public void ConfigureServices(IServiceCollection services) {
services.AddCors();
services.AddMvc();
services.AddSignalR();
services.AddTransient<ISubscriber, Subscriber>();
services.AddTransient<IDataService, DataService>();
services.AddTransient<IHealthCheckProcessor, HealthCheckProcessor>();
services.AddTransient<INodeProcessor, NodeProcessor>();
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
IApplicationLifetime applicationLifetime,
IServiceProvider sp
) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseCors(builder => builder
// CORS stuff);
app.UseMvc();
app.UseSignalR(routes => {
routes.MapHub<StatusHub>("Status");
});
//At this point all the necessary dependencies have been registered and configured
var subscriber = sp.GetService<ISubscriber>();
applicationLifetime.ApplicationStarted.Register(() => OnStartup(subscriber));
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(subscriber));
}
private void OnStartup(ISubscriber subscriber) {
// MessageBroker stuff
subscriber.Start(messageBroker);
}
private void OnShutdown(ISubscriber subscriber) {
subscriber.Stop();
}
}
You should be able to now remove the convenience ConfigureServices when building the host.
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();

Using KeyFilter with ASP.NET Core 2.0

I have problem to using KeyFilter attribute in a simple ASP.NET Core 2.0 WebAPI application.
<PackageReference Include="Autofac" Version="4.6.1" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.2.0" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
Program.cs:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureServices(services => services.AddAutofac())
.UseStartup<Startup>()
.Build();
}
Startup.cs:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public IContainer ApplicationContainer { get; private set; }
// This method gets called by the runtime.
// Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Autofac
var builder = new ContainerBuilder();
builder.Populate(services);
builder.RegisterType<ClassX1>()
.Keyed<IInterfaceX>("first")
.WithParameter("name", "X1")
.SingleInstance();
builder.RegisterType<ClassX1>()
.Keyed<IInterfaceX>("second")
.WithParameter("name", "X2")
.SingleInstance();
builder.RegisterType<ClassX1>()
.As<IInterfaceX>()
.WithParameter("name", "X3")
.SingleInstance();
builder.RegisterType<ValuesController>()
.WithAttributeFiltering();
ApplicationContainer = builder.Build();
return ApplicationContainer.Resolve<IServiceProvider>();
// return new AutofacServiceProvider(this.ApplicationContainer);
}
// This method gets called by the runtime.
// Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
ClassX1.cs:
public class ClassX1: IInterfaceX
{
public ClassX1(string name)
{
Name = name;
}
public string Name { get; }
}
IInterfaceX.cs:
public interface IInterfaceX
{
string Name { get; }
}
ValuesController.cs:
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly IInterfaceX _x;
public ValuesController([KeyFilter("second")] IInterfaceX x)
{
_x = x;
}
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2", _x.Name };
}
}
Calling "/api/values" yield ["value1","value2","X3"].
But i expect ["value1","value2","X2"].
if i remove the third registration (with X3) then
InvalidOperationException: Unable to resolve service for type 'WebApplicationAutofac.IInterfaceX' while attempting to activate 'WebApplicationAutofac.Controllers.ValuesController'.
Trying to direct resolving works correct:
var temp = ApplicationContainer.ResolveKeyed<IInterfaceX>("second");
What is wrong?
Yes, it works pretty cool for WebAPI Controllers.
The solution is to add .AddControllersAsServices():
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddControllersAsServices();

Categories