Autofac Web API 2 and Owin resolve in StartupConfig - c#

I have a project that is both OWIN and Web API 2.
I have tried to follow the instructions as best as I can.
Inside my Configration method in the StartupConfig class I have this:
// Get our configuration
var config = new HttpConfiguration();
var container = ConfigureInversionOfControl(app, config);
var scope = config.DependencyResolver.GetRequestLifetimeScope();
var serverOptions = ConfigureOAuthTokenGeneration(app, scope);
//** removed for brevity **//
// Cors must be first, or it will not work
app.UseCors(CorsOptions.AllowAll);
// Register the Autofac middleware FIRST. This also adds
// Autofac-injected middleware registered with the container.
app.UseAutofacMiddleware(container);
//app.UseAutofacWebApi(config);
app.UseOAuthAuthorizationServer(serverOptions);
app.UseWebApi(config);
My ConfigurInversionOfControl method looks like this:
private static IContainer ConfigureInversionOfControl(IAppBuilder app, HttpConfiguration config)
{
// Create our container
var builder = new ContainerBuilder();
// You can register controllers all at once using assembly scanning...
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// Add our dependencies (Can't use life because this is available to all controllers http://stackoverflow.com/questions/36173210/get-same-instance-of-a-component-registered-with-autofac-as-instanceperlifetimes)
builder.RegisterType<UnitOfWork<DatabaseContext>>().As<IUnitOfWork>().InstancePerRequest();
// Register our services
builder.Register(c => new AdvancedEncryptionStandardProvider(ConfigurationManager.AppSettings["rm:key"], ConfigurationManager.AppSettings["rm:secret"])).As<IAdvancedEncryptionStandardProvider>();
builder.RegisterType<LogService>().As<ILogService>();
builder.RegisterType<EmailService>().As<IEmailService>();
builder.RegisterType<RefreshTokenService>().As<IRefreshTokenService>();
// Register our providers
builder.Register(c => new SendGridProvider(c.Resolve<IUnitOfWork>(), c.Resolve<IEmailService>(), ConfigurationManager.AppSettings["SendGridApiKey"])).As<ISendGridProvider>();
builder.RegisterType<LogProvider>().As<ILogProvider>();
builder.RegisterType<RefreshTokenProvider>().As<IAuthenticationTokenProvider>();
builder.RegisterType<OAuthProvider>().As<OAuthProvider>();
// Build
var container = builder.Build();
// Lets Web API know it should locate services using the AutofacWebApiDependencyResolver
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
// Return our container
return container;
}
Now I have set this up (I assume correctly). How do I resolve one of the services in my StartupConfig class?
I need to be able to create an instance of the OAuthProvider and RefreshTokenProvider. I tried something similar to this:
// Get our providers
var authProvider = scope.Resolve<OAuthProvider>();
var refreshTokenProvider = scope.Resolve<IAuthenticationTokenProvider>();
// Create our OAuth options
return new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true, // TODO: Remove this line
TokenEndpointPath = new PathString("/oauth/access_token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
AccessTokenFormat = new Business.Authentication.JwtFormat("http://localhost:62668"),
Provider = authProvider,
RefreshTokenProvider = refreshTokenProvider
};
But when it reaches the line var authProvider = scope.Resolve<OAuthProvider>();, I get an error stating:
Parameter cannot be null: Parameter name: context
Now I would assume that the context has not been created yet, so I assume I am doing something wrong with my resolve.
Can anyone help?

This was actually really simple.
I needed the change the scope from:
var scope = config.DependencyResolver.GetRequestLifetimeScope();
to
var scope = config.DependencyResolver.GetRootLifetimeScope();
I assume because the "request" is not available in the StartupConfig class.
Anyway, changing this fixed my issue.

Related

How can I inject a service into an Entity Framework AddDbContextFactory lambda function in Blazor Server?

In Blazor server, how can I inject a scoped service into the lambda below so that I can read the authenticated user and select a SQL connection string based on the user.
builder.Services.AddDbContextFactory<GlueDbContext>((provider, options) =>
{
var AuthenticationStateProvider = provider.GetService<AuthenticationStateProvider>();
// *** Compiles but FAILS because AuthenticationStateProvider is not a Singleton ***
var user = _authenticationStateProvider.GetAuthenticationStateAsync().Result.User;
//
string sqlConnectString = SomeFunctionDerivingTheConnectionFromTheUser(user);
options.UseMySql(connectionString);
});
Following this link, it should look something like this (I'm typing without IDE so it may contain some typos):
var serviceScopeFactory = provider.GetService<IServiceScopeFactory>(); //IServiceScopeFactory is a singleton, so you can easily get it here
using var scope = serviceScopeFactory.CreateScope();
var authenticationStateProvider = scope.GetService<AuthenticationStateProvider>();
//...
You can't do that. Services.Add.... simply adds classes/interfaces to a collection. It's not till later that the services container gets initialised, and until you use a DI object that an instance of that object gets initialised.
To illustrate the process, here's the code to set up a services container serviceProvider in a test.
var services = new ServiceCollection();
services.AddDbContextFactory<InMemoryWeatherDbContext>(options => options.UseInMemoryDatabase("WeatherDatabase"));
services.AddSingleton<IDataBroker, ServerDataBroker>();
var serviceProvider = services.BuildServiceProvider();
Whatever you design, it needs rethinking. You can't get a user until an SPA session has initialized.
AddDbContextFactory has a defaulted parameter set to Singleton.
add ServiceLifetime.Scoped
builder.Services.AddDbContextFactory<GlueDbContext>((provider, options) =>
{
var AuthenticationStateProvider = provider.GetService<AuthenticationStateProvider>();
var user = _authenticationStateProvider.GetAuthenticationStateAsync().Result.User;
string sqlConnectString = SomeFunctionDerivingTheConnectionFromTheUser(user);
options.UseMySql(connectionString);
}, ServiceLifetime.Scoped);

GetService with IOptionsMonitor<Settings> returns null object

I have the following on an ASP.NET Core 3.0 application:
IServiceCollection services = new ServiceCollection();
services.AddSingleton<Settings>(new Settings { DefaultPageSize = 40 });
IServiceProvider provider = services.BuildServiceProvider();
var result = provider.GetService<IOptionsMonitor<Settings>>();
On the last line result is null ... Any idea why?
services.AddSingleton<Settings>(...
Does not automatically associate Settings with the IOptionsMonitor feature.
Need to configure that Settings class as an option with the service collection using one of the Options pattern extensions
For example
IServiceCollection services = new ServiceCollection();
// Options bound and configured by a delegate
services.Configure<Settings>(option => {
option.DefaultPageSize = 40;
});
IServiceProvider provider = services.BuildServiceProvider();
var result = provider.GetService<IOptionsMonitor<Settings>>();
Reference Options pattern in ASP.NET Core: Configure simple options with a delegate

Get Hangfire working with ASP.NET MVC and LightInject

I have an ASP.NET MVC App and I recently upgraded to use LightInject DI.
However I cant seem to get Hangfire running correctly, even using the LightInject Extension!
My Hangfire setup in Startup.cs:
public void Configuration(IAppBuilder app)
{
var container = new ServiceContainer();
container.RegisterControllers(typeof(Web.Controllers.DashboardController).Assembly);
ConfigureServices(container);
ConfigureHangfire(container,app);
container.EnableMvc();
}
private void ConfigureHangfire(ServiceContainer container, IAppBuilder app)
{
var hangfireConnString = ConfigurationManager.ConnectionStrings["HfConnString"].ConnectionString;
GlobalConfiguration.Configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseLightInjectActivator(container)
.UseSqlServerStorage(hangfireConnString, new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.FromSeconds(10),
UseRecommendedIsolationLevel = true,
UsePageLocksOnDequeue = true,
DisableGlobalLocks = true
});
var options = new DashboardOptions()
{
Authorization = new[] {new SystemAuthorizationFilter()}
};
app.UseHangfireDashboard("/hangfire",options);
app.UseHangfireServer();
}
However I get the following error when running a hangfire job:
System.NullReferenceException: Object reference not set to an instance of an object.
at LightInject.Web.PerWebRequestScopeManager.GetOrAddScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 148
at LightInject.Web.PerWebRequestScopeManager.get_CurrentScope() in C:\projects\lightinject-web\build\tmp\Net46\Binary\LightInject.Web\LightInject.Web.cs:line 129
at LightInject.ScopeManager.BeginScope() in C:\projects\lightinject\src\LightInject\LightInject.cs:line 6091
I would love any help to get this going!
Thanks so much in advance.
I actually fixed this by giving hangfire its own container. So the start of my ConfigureHangfire method became:
private void ConfigureHangfire(ServiceContainer container, IAppBuilder app)
{
var hangfireConnString = ConfigurationManager.ConnectionStrings["HfConnString"].ConnectionString;
var container = new ServiceContainer();
ConfigureServices(container);
GlobalConfiguration.Configuration etc....
Im not sure that this is entirely correct, and if its not i would really like to be corrected! But in any case I hope this helps someone!

Data protection operation unsuccessful when using Autofac on Azure

I am getting this error when hosting my application on azure:
The data protection operation was unsuccessful. This may have been caused by not having the user profile loaded for the current thread's user context, which may be the case when the thread is impersonating.
When trying creating a user or resending a confirmation email.
This article:
http://tech.trailmax.info/2014/06/asp-net-identity-and-cryptographicexception-when-running-your-site-on-microsoft-azure-web-sites/
says that you should create a single instance on the startup class, but I am using autofac, so I have done this:
builder.Register(c => new IdentityFactoryOptions<UserProvider>() { DataProtectionProvider = new DpapiDataProtectionProvider(c.Resolve<PiiiCKConfig>().Issuer) }).SingleInstance();
and then in my UserManager constructor, I do this:
// Get our data protection provider
var dataProtectionProvider = options.DataProtectionProvider;
// If we have on
if (dataProtectionProvider != null)
{
// Set our token provider
UserTokenProvider = new DataProtectorTokenProvider<User>(dataProtectionProvider.Create("PiiiK Identity"))
{
// Set our long the email confirmation token will last
TokenLifespan = TimeSpan.FromHours(6)
};
}
But I still get the error.
Does anyone know how I can solve this issue?
According to your description, I checked this issue on my side and I could reproduce this issue. Per my test, you could follow the approaches below to solve this issue:
builder.Register(c => new IdentityFactoryOptions<UserProvider>() { DataProtectionProvider = new DpapiDataProtectionProvider(c.Resolve<PiiiCKConfig>().Issuer) }).SingleInstance();
You could using IAppBuilder.GetDataProtectionProvider() instead of declaring a new DpapiDataProtectionProvider, based on the above code, you need to modify it as follows:
builder.Register(c => new IdentityFactoryOptions<UserProvider>()
{
DataProtectionProvider = app.GetDataProtectionProvider()
}).SingleInstance();
Or
builder.Register<IDataProtectionProvider>(c => app.GetDataProtectionProvider()).SingleInstance();
Additionally, here is a similar issue, you could refer to here.
UPDATE:
Here is the code snippet of Startup.Auth.cs as follows:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
var builder = new ContainerBuilder();
builder.RegisterControllers(typeof(MvcApplication).Assembly);
builder.Register(c => new IdentityFactoryOptions<ApplicationUserManager>()
{
DataProtectionProvider = app.GetDataProtectionProvider()
}).SingleInstance();
//Or
//builder.Register<IDataProtectionProvider>(c =>app.GetDataProtectionProvider()).SingleInstance();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
}
}

dotnet core autofac and json configuration can they all work together in a console app

I have been trying to get some dependency injection into my .net core console app as well as use an appsettings.json file.
However I can't find an example with both together.
Below is a method I have to set this up from inside my Main()
private static IContainer SetupDependencyInjection()
{
var builder2 = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(
typeof(Program).GetTypeInfo().Assembly // Console
)
.AsSelf()
.AsImplementedInterfaces();
var container = builder.Build();
return container;
}
You can see I have the builder2 variable to set up the config file but then I need the builder variable for the Dependency Injection.
Any ideas on this?
Autofac provides a ConfigurationModule class that can be build using a Iconfiguration provided by your ConfigurationBuilder
// Add the configuration to the ConfigurationBuilder.
var config = new ConfigurationBuilder();
config.AddJsonFile("autofac.json");
// Register the ConfigurationModule with Autofac.
var module = new ConfigurationModule(config.Build());
var builder = new ContainerBuilder();
builder.RegisterModule(module);
// configure whatever you want at runtime the ContainerBuilder
builder.RegisterType<Foo>().As<IFoo>();
// build the container
IContainer container = builder.Build();
In this case you will have both registrations configured in your autofac.json file and registrations configured in your code.
See Autofac documentation JSON/XML Configuration for more detail

Categories

Resources