ASP.NET 5 - Access to Dependency Container in Startup.Configure - c#

I want to access my Options instance which is added as singleton in ConfigureServices. Here is my code:
public class Startup
{
private IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton((serviceProvider) => ConfigurationBinder.Bind<Options>(Configuration));
}
public void Configure(IApplicationBuilder app)
{
var root = ""; // I want to access my Options instance to get root from it
var fileServerOptions = new FileServerOptions()
{
FileProvider = new PhysicalFileProvider(root)
};
app.UseFileServer(fileServerOptions);
}
}
My question is how to access instance of Options in Configure method to set root variable.

As suggested in How to use ConfigurationBinder in Configure method of startup.cs, the runtime can inject the options directly into the Configure method:
public void Configure(IApplicationBuilder app, Options options)
{
// do something with options
}

According to Joe Audette's comment this is the solution:
var options = app.ApplicationServices.GetService<Options>();

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

when use autofac accoure this error : : 'ConfigureServices returning an System.IServiceProvider isn't supported.'

i wqant ot use the autofac in my project .
i write this startup :
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddControllers();
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(Assembly.GetEntryAssembly())
.AsImplementedInterfaces();
builder.Populate(services);
builder.AddDispatchers();
var conteiner = builder.Build();
return new AutofacServiceProvider(conteiner);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
nad this is my program.cs :
public class Program
{
public static void Main(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webHostBuilder =>
{
webHostBuilder
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>();
})
.Build();
host.Run();
}
}
but it show me this errro :
'ConfigureServices returning an System.IServiceProvider isn't supported.'
How can is solve this problem?
This is because you are trying the pre 3.0 way. Check the ConfigureServices docs. It does not supprot the IServiceProvider return type.
public virtual void ConfigureServices (Microsoft.Extensions.DependencyInjection.IServiceCollection services);
From the autofac docs:
This is not for ASP.NET Core 3+ or the .NET Core 3+ generic hosting support - ASP.NET Core 3 has deprecated the ability to return a service provider from ConfigureServices
Check Autofac net core guide post 3.0
public class Program
{
public static void Main(string[] args)
{
// ASP.NET Core 3.0+:
// The UseServiceProviderFactory call attaches the
// Autofac provider to the generic hosting mechanism.
var host = Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureWebHostDefaults(webHostBuilder => {
webHostBuilder
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>();
})
.Build();
host.Run();
}
}
public class Startup
{
public Startup(IHostingEnvironment env)
{
// In ASP.NET Core 3.0 `env` will be an IWebHostingEnvironment, not IHostingEnvironment.
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; private set; }
public ILifetimeScope AutofacContainer { get; private set; }
// ConfigureServices is where you register dependencies. This gets
// called by the runtime before the ConfigureContainer method, below.
public void ConfigureServices(IServiceCollection services)
{
// Add services to the collection. Don't build or return
// any IServiceProvider or the ConfigureContainer method
// won't get called.
services.AddOptions();
}
// 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)
{
// Register your own things directly with Autofac, like:
builder.RegisterModule(new MyApplicationModule());
}
// Configure is where you add middleware. This is called after
// ConfigureContainer. You can use IApplicationBuilder.ApplicationServices
// here if you need to resolve things from the container.
public void Configure(
IApplicationBuilder app,
ILoggerFactory loggerFactory)
{
// If, for some reason, you need a reference to the built container, you
// can use the convenience extension method GetAutofacRoot.
this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMvc();
}
}

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

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

ASP.NET Core MVC main project can't reach Controllers in separate assembly

I want to use a separate project to store the Controllers for my test app. Due to how ASP.NET Core is working, you need to call services.AddMvc().AddApplicationPart with the assembly you want to add.
I use Visual Studio 2017 (so no more project.json there)
The problem is I can't reach the assembly I want to include!
I added reference there in my project:
Also, I decided to use polyfill for AppDomian like so (to reach assembly I need):
public class AppDomain
{
public static AppDomain CurrentDomain { get; private set; }
static AppDomain()
{
CurrentDomain = new AppDomain();
}
public Assembly[] GetAssemblies()
{
var assemblies = new List<Assembly>();
var dependencies = DependencyContext.Default.RuntimeLibraries;
foreach (var library in dependencies)
{
if (IsCandidateCompilationLibrary(library))
{
var assembly = Assembly.Load(new AssemblyName(library.Name));
assemblies.Add(assembly);
}
}
return assemblies.ToArray();
}
private static bool IsCandidateCompilationLibrary(RuntimeLibrary compilationLibrary)
{
return compilationLibrary.Name == ("TrainDiary")
|| compilationLibrary.Dependencies.Any(d => d.Name.StartsWith("TrainDiary"));
}
}
But when I used it there wasonly one assembly in the list (only linked to this project itself, not one with Controllers).
Here is my Startup.cs:
public class Startup
{
public IConfigurationRoot Configuration { get; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
var assemblies = GetAssemblies(); // I tought I will find there Assmbly I need but no
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddReact();
services.AddMvc();
services.AddLogging();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseReact(config =>
{
});
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
public List<Assembly> GetAssemblies()
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
List<Assembly> currentAssemblies = new List<Assembly>();
foreach (Assembly assembly in assemblies)
{
if (assembly.GetName().Name.Contains("TrainDiary"))
{
currentAssemblies.Add(assembly);
}
}
return currentAssemblies;
}
}
How can I get my project to also lookup controllers in the other project? Why couldn't it see it?
UPD
Tried to make directly like in this exapmle here.
So my code started looks like that:
var controllersAssembly = Assembly.Load(new AssemblyName("TrainDiary.Controllers"));
services.AddMvc()
.AddApplicationPart(controllersAssembly)
.AddControllersAsServices();
And it succeed to get the assembly:
But when I try to call Controller function - it fails!
Controller code:
[Route("Login")]
public class LoginController: Controller
{
[Route("Login/Test")]
public void Test(string name, string password)
{
string username = name;
string pass = password;
}
}
Requests:
UPD2:
For some reason remove Routes helped:
public class LoginController: Controller
{
public void Test(string name, string password)
{
string username = name;
string pass = password;
}
}
So, if sum this up:
1) You need to Add Reference to your separate project with
controllers.
2) Configure services (part):
public void ConfigureServices(IServiceCollection services)
{
var controllersAssembly = Assembly.Load(newAssemblyName("SomeProject.MeetTheControllers"));
services.AddMvc().AddApplicationPart(controllersAssembly).AddControllersAsServices();
}
3) My configure looks like that (pay attention to UseMvcWithDefaultRoute):
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseReact(config =>
{
});
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
So in my case of controller like this you don't need to put Routes attribute here, just call it by Some/Test url
namespace SomeProject.MeetTheControllers.Test
{
using Microsoft.AspNetCore.Mvc;
public class SomeController: Controller
{
public void Test(string name, string notname)
{
string username = name;
string nan = notname;
}
}
}

Categories