Accessing a service before BuildServiceProvider - c#

Im failing to build the following service, since configStoreService need to be injected to the AddInMemoryConfiguration, ConfigurationBuilder. So, is there a way to retrieve configStoreService and use it before building the service provider.
var services = new ServiceCollection();
var configStoreService = services.AddSingleton<ConfigurationStore>();
var configuration = new ConfigurationBuilder()
.AddInMemoryConfiguration(configStoreService)
.Build();
services.AddOptions();
services.Configure<Temp>(configuration.GetSection(typeof(Temp).Name));
services.RegisterOptionsType<Temp>(configuration);
_serviceProvider = services.BuildServiceProvider();

Foreword:
This seems a problematic request. You need configuration to configure services, but you need to configure services to get configuration. A chicken and egg problem.
What to do:
Create an instance of configuration use it to configure your services and also add it to your service collection
var configStoreService = new ConfigurationStore(location, sku);
services.AddSingleton(configStoreService);
var configuration = new ConfigurationBuilder()
.AddInMemoryConfiguration(configStoreService)
.Build();
services.AddOptions();
services.Configure<Temp>(configuration.GetSection(typeof(Temp).Name));
services.RegisterOptionsType<Temp>(configuration);
_serviceProvider = services.BuildServiceProvider();
FYI:
I also have to add you can't get anything from a IServiceCollection. You can get things from IServiceProviderwhich, you get AFTER registration is done and BuildServiceProvider called. So before that you can't access items in the collection.

Related

Why is IConfiguration null when integration testing my Azure Functions?

The current version of the Microsoft.Azure.Functions.Extensions package exposes an additional property that allows you easy access to the IConfiguration provided to the function. Previously this required manually building a service provider, which was obviously problematic.
Using that package my FunctionsStartup.cs looks like this:
public override void Configure(IFunctionsHostBuilder builder)
{
base.Configure(builder);
var config = builder.GetContext().Configuration; // new in v1.1.0 of Microsoft.Azure.Functions.Extensions
var mySetting = config["MySetting"];
int.Parse(mySetting, out var mySetting);
// ... use mySetting...
}
In order to test my HTTP-triggered functions I've used this article as a base, which details how to manually build and start a host to execute my function as if it was running in Azure, similar to how TestServer works in ASP.NET Core:
var host = new HostBuilder()
.ConfigureWebJobs(new FunctionsStartup().Configure)
.Build();
var functionsInstance = ActivatorUtilities.CreateInstance<MyFunctions>(host.Services);
I can then execute the function methods defined on MyFunctions to test their responses:
var request = new DefaultHttpRequest(new DefaultHttpContext());
var response = (OkObjectResult)functionsInstance.HttpTriggerMethod(request);
... assert that response is valid
The problem is that when I run my tests, builder.GetContext().Configuration is returning null in FunctionsStartup.Configure, which of course causes those tests to fail. How can I work around this?
The article I linked to hasn't been updated to take into account the existence of builder.GetContext().Configuration, but you can make this work for testing purposes with a little tweaking. Instead of using:
var host = new HostBuilder()
.ConfigureWebJobs(new FunctionsStartup().Configure)
.Build();
you need to explicitly copy the host's settings into a new WebJobsBuilderContext that you then pass to your function's startup:
var host = new HostBuilder()
.ConfigureWebJobs((context, builder) => new FunctionsStartup().Configure(new WebJobsBuilderContext
{
ApplicationRootPath = context.HostingEnvironment.ContentRootPath,
Configuration = context.Configuration,
EnvironmentName = context.HostingEnvironment.EnvironmentName,
}, builder))
.Build();
I'm not sure if this is the completely correct way to achieve this, but it has worked well for me.

.NET Core Identity How to set option value based on database

I need to change parameter value for IdentityOptions dynamically from db. So, in my ConsigureServices(...) method in Startup.cs:
services.AddIdentity<AppUser, IdentityRole>(option =>
{
option.Lockout.MaxFailedAccessAttempts = 3; // I need to set this value dynamically from database when server starts
}).AddEntityFrameworkStores<DataContext>()
.AddDefaultTokenProviders();
I have tried to inject IdentityOptions in my Configure(...) method but with no success:
public void Configure(
IApplicationBuilder app,
DataContext dataContext,
IdentityOptions identityOptions)
{
var sysPolicy = dataContext.SysPolicy.FirstOrDefault();
identityOptions.Lockout.MaxFailedAccessAttempts = sysPolicy.DisablePwdLoginFail;
}
It throws an exception like this (it seems that I can't inject it on my Configure):
System.Exception: Could not resolve a service of type 'Microsoft.AspNetCore.Identity.IdentityOptions' for the parameter 'identityOptions' of method 'Configure' on type 'App.Startup'.
You can try out this:
services.AddIdentity<AppUser, IdentityRole>(
options =>
{
var scopeFactory = services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>();
using var scope = scopeFactory.CreateScope();
var provider = scope.ServiceProvider;
using var dataContext = provider.GetRequiredService<DataContext>();
options.Lockout.MaxFailedAccessAttempts = dataContext.SysPolicy.FirstOrDefault();
})
.AddEntityFrameworkStores<DataContext>()
.AddDefaultTokenProviders();
NOTE: Building a service provider is an antipattern and will result in creating an additional copy of singleton services. I would suggest reading the configs from appsettings.json for example, then you can implement it without building the service provider

Inject IServiceProvider using ServiceProvider into SignInManager

I've made some research, and there isn't much info about registering standard classes for manual DI. Experiencing lots of issues trying to implement unit tests in asp net core 2.1 app, here is the last one. Getting null refrence exception when trying to SignInAsync user created by UserManager - it seems it can't inject IServiceProvider instance
var userManager = Resolve<UserManager<ApplicationUser>>();
await signInManager.SignInAsync(userManager.FindByIdAsync(adminId), false); // here
Value cannot be null.
Parameter name: provider
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
at Microsoft.AspNetCore.Identity.SignInManager1.SignInAsync(TUser user, AuthenticationProperties authenticationProperties, String authenticationMethod)
at UnitTests.TestBase1.ConfigureIdentity() in C:\Users\alexa\source\repos\octopusstore\UnitTests\TestBase.cs:line 72
resolving like this
protected static ServiceCollection services = new ServiceCollection();
protected T Resolve<T>()
{
var serviceProvider = services.BuildServiceProvider();
return serviceProvider.GetRequiredService<T>();
}
how I am registering dependencies, it worked so far
services.AddDbContext<AppIdentityDbContext>();
var conf = new Mock<IConfiguration>();
services.AddSingleton<IConfiguration>(conf.Object);
services.AddSingleton<IServiceProvider, ServiceProvider>() ; // except for this
services.AddSingleton(services.BuildServiceProvider()); // and this
services.AddScoped<SignInManager<ApplicationUser>> ();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<AppIdentityDbContext>()
.AddDefaultTokenProviders();
services.AddScoped<IAuthorizationService, DefaultAuthorizationService> ();
services.AddScoped<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>();
services.AddScoped<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>();
services.AddScoped<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>();
services.AddScoped<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>();
interesting that UserManager worked fine
To register the service provider itself, you can do it like this:
services.AddSingleton<IServiceProvider>(sp => sp);
This uses the factory function which you can use to get an instance using the service provider sp. Since you want to register the service provider itself, you can just return sp directly.

services.AddLogging() prevent NLog logger running?

I have the following .net core 2.0 code and the NLog is not output anything.
private static IServiceProvider BuildDi()
{
var services = new ServiceCollection();
services.AddTransient<Runner>();
services.AddSingleton<ILoggerFactory, LoggerFactory>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
services.AddLogging((builder) => builder.SetMinimumLevel(LogLevel.Trace));
var serviceProvider = services.BuildServiceProvider();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
//configure NLog
loggerFactory.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true });
NLog.LogManager.LoadConfiguration("nlog.config");
return serviceProvider;
}
And it starts to output after the following line is removed?
services.AddLogging((builder) => builder.SetMinimumLevel(LogLevel.Trace));
What is AddLogging for?
AddLogging basically does exactly what the two lines above it does in your original example.
And the delegate is used to configure the log builder.
In this case the minimum log level is trace.
If lets say it was set to Information the it would have ignored Trace and Debug logs.
The following should provide the same desired functionality if using .net core 2.0
private static IServiceProvider BuildDi() {
var services = new ServiceCollection();
services.AddTransient<Runner>();
// Adds logging services to the service collection
// If you don't explicitly set the minimum level, the default value is
// Information, which means that Trace and Debug logs are ignored.
services.AddLogging();
var serviceProvider = services.BuildServiceProvider();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
/// Enable NLog as logging provider in .NET Core.
loggerFactory.AddNLog(new NLogProviderOptions { CaptureMessageTemplates = true, CaptureMessageProperties = true });
// Apply NLog configuration from XML config.
loggerFactory.ConfigureNLog("nlog.config");
return serviceProvider;
}

Api Exception filters for autofac

I have Configured webapi with IAutofacExceptionFilter to get the Exception Details. registered all the things in autofac.config
HttpConfiguration config = ServiceConfig.Initialize(new ConfigBuilder(options, (configuration, builder) =>
{
builder.RegisterType<ErrorLoggingExceptionFilter>()
.AsWebApiExceptionFilterFor<BaseController>()
.InstancePerApiRequest();
builder.RegisterWebApiFilterProvider(GlobalConfiguration.Configuration);
//builder.RegisterType<ErrorLoggingExceptionFilter>()
//.AsWebApiExceptionFilterFor<BaseController>().SingleInstance();
builder.OverrideWebApiActionFilterFor<BaseController>();
builder.RegisterSource(
new AnyConcreteTypeNotAlreadyRegisteredSource(t =>
!(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Meta<>))
));
}
var serviceResolver = (AutofacWebApiDependencyResolver)config.DependencyResolver;
but it gives me error like
The AutofacWebApiDependencyResolver must be configured as the dependency resolver for Web API before the AutofacControllerConfigurationAttribute can resolve services from the container for a controller type.
You should do something like this to make AutofacWebApiDependencyResolver your default Web API dependency resolver :
// Set the dependency resolver to be Autofac.
var builder = new ContainerBuilder();
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
See Autofac documentation for more info.

Categories