Inject ApplicationDbContext into Configure method in Startup - c#

I'm using EntityFrameworkCore 2.0.0-preview2-final and I would like to inject the ApplicationDbContext into Configure method in Startup class.
Here's my code:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, ApplicationDbContext context)
{
// rest of my code
}
but when I run my app I'm getting an error message:
System.InvalidOperationException: Cannot resolve scoped service
'ProjectName.Models.ApplicationDbContext' from root provider.
Here's also my code from ConfigureServices method:
services.AddDbContext<ApplicationDbContext>(options =>
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
}
else
{
options.UseSqlite("Data Source=travelingowe.db");
}
});
Do you have any idea how can I solve this problem?

This will work in 2.0.0 RTM. We've made it so that there is a scope during the call to Configure so the code you originally wrote will work. See https://github.com/aspnet/Hosting/pull/1106 for more details.

EF Core DbContext is registered with scoped lifestyle. In ASP native DI container scope is connected to instance of IServiceProvider. Normally when you use your DbContext from Controller there is no problem because ASP creates new scope (new instance of IServiceProvider) for each request and then uses it to resolve everything within this request. However during application startup you don't have request scope so you should create scope yourself. You can do it like this:
var scopeFactory = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
// rest of your code
}
EDIT
As davidfowl stated this will work in 2.0.0 RTM since scoped service provider will be created for Configure method.

Related

How to configure IdentityOptions outside ConfigureServices?

I want to configure ASP.NET Core Identity based on settings which resides in database rather than AppSetting.json or hard coded values. Consequently, I'am eager to call following line of code outside of method ConfigureServices(IServiceCollection services):
services.Configure<IdentityOptions>(x => x.Password.RequireDigit = true);
This way of calling will allow me to initialize DbContext before trying to configure Identity.
Currently I'm using services.BuildServiceProvider() in the ConfigureServices() to access database values. This style has a huge disadvantage for me: It puts an extra initialization on the application's DbContext which is dirty and slow. In the other hand, DbContext is instantiated two times instead of one.
If I was able to call services.Configure<IdentityOptions>() outside the ConfigureServices(), for example in the configure() method, I would be able to configure Identity options based on database values without initializing DbContext twice.
Again, my question is how to configure IdentityOptions outside ConfigureServices?
Any help is appreciated.
I ended up with injecting IOptions<IdentityOptions> options to the Configure() method as what follows:
public virtual void Configure(IApplicationBuilder app, IOptions<IdentityOptions> options)
{
options.Value.Password.RequireDigit = true;
//rest of configurations...
}
And it worked!
Thanks to #Kirk for the link.

How do I set up 'connectionString' for a mySQL database for Hangfire?

I'm trying to integrate hangfire into my .NET core web app. I have followed the instructions on the Hangfire quickstart by installing all necessary packages. I also installed an extension called Hangfire MySql and installed the necessary packages for it.
Step 1 says to 'Create new instance of MySqlStorage with connection string constructor parameter and pass it to Configuration with UseStorage method:'
GlobalConfiguration.Configuration.UseStorage(
new MySqlStorage(connectionString));
Also it is noted that 'There must be Allow User Variables set to true in the connection string. For example: server=127.0.0.1;uid=root;pwd=root;database={0};Allow User Variables=True'
so my current code for Hangfire inside the 'Configure' service within my Startup.CS file is this:
Hangfire.GlobalConfiguration.Configuration.UseStorage(
new MySqlStorage(connectionString));
app.UseHangfireDashboard();
app.UseHangfireServer();
however MySqlStorage returns the error ''MySqlStorage' does not contain a constructor that takes 1 arguments'
Looking at the readMe for Hangfire mySQL if I use and define my connectionString to
e.g.
connectionString = "server=127.0.0.1;uid=root;pwd=root;database={0};Allow User Variables=True"
GlobalConfiguration.Configuration.UseStorage(
new MySqlStorage(
connectionString,
new MySqlStorageOptions
{
TablesPrefix = "Hangfire"
}));
the application will say there are no errors but I still get an error on startup.
I've tried entering a connection string but nothing that I enter seems to work. Every time I launch the application i get the error:
"crit: Microsoft.AspNetCore.Hosting.Internal.WebHost[6]
Application startup exception
System.InvalidOperationException: Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddHangfire' inside the call to 'ConfigureServices(...)' in the application startup code.
at Hangfire.HangfireApplicationBuilderExtensions.ThrowIfNotConfigured(IApplicationBuilder app)
at Hangfire.HangfireApplicationBuilderExtensions.UseHangfireDashboard(IApplicationBuilder app, String pathMatch, DashboardOptions options, JobStorage storage)
at Alerts.API.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) in /Users/Admin/Desktop/Code Projects/Alerts/Alerts.API/Startup.cs:line 178"
Wondering if someone could give me an example of how to set up Hangfire with a mySqlStorage connection that launches and let's me view the Hangfire dashboard.
References: https://github.com/arnoldasgudas/Hangfire.MySqlStorage
Hangfire: http://docs.hangfire.io/en/latest/quick-start.html
Based on the exception details, it seems that first you need to configure the Hangfire service before be able to call app.UseHangfireDashboard().
In your Startup.cs file you should have a ConfigureServices(IServiceCollection services) method, it seems that you must do the setup here instead of using the GlobalConfiguration class, so you can try this:
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(configuration => {
configuration.UseStorage(
new MySqlStorage(
"server=127.0.0.1;uid=root;pwd=root;database={0};Allow User Variables=True",
new MySqlStorageOptions
{
TablesPrefix = "Hangfire"
}
)
);
};
}

Resolve Autofac dependencies in OWIN Startup class

I have a multi-tenant MVC5 webapp, which is using Autofac v3.5.2 and Autofac.Mvc5 v3.3.4.
My Autofac DI wiring takes place in a class within my MVC project. For authentication, we are using OWIN OpenId middleware to integrate with Azure B2C. In the OWIN Startup class I need a dependency to set tenantId/clientId using information from the current request.
I try to grab the dependency via :
DependencyResolver.Current.GetService<...>();
However, this always throws an ObjectDisposedException
Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.
We have an ISiteContext in our application that has a request-lifetime. It gets populated with configuration values specific to the current request. I am trying to fetch these values like this:
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
var options = new OpenIdConnectAuthenticationOptions
{
Notifications = new OpenIdConnectAuthenticationNotifications
{
...
RedirectToIdentityProvider = SetSettingsForRequest
}
}
}
private Task SetSettingsForRequest(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
var siteContext = DependencyResolver.Current.GetService<ISiteContext>();
context.ProtocolMessage.ClientId = siteContext.ClientId;
return Task.FromResult(0);
}
The error happens when trying to use the DependencyResolver in SetSettingsForRequest. I have no clue as to what I am doing wrong here. Currently I have no Autofac DI setup in my Startup Configuration(IAppBuilder app) method, since this is already setup in my MVC project.
As Mickaƫl Derriey pointed out, the following code is a solution to being able to resolve request-scoped dependencies in OWIN:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var builder = new ContainerBuilder();
// Register dependencies, then...
var container = builder.Build();
// Register the Autofac middleware FIRST. This also adds
// Autofac-injected middleware registered with the container.
app.UseAutofacMiddleware(container);
// ...then register your other middleware not registered
// with Autofac.
}
}
and then use in any later code to resolve a dependency:
// getting the OWIN context from the OWIN context parameter
var owinContext = context.OwinContext;
var lifetimeScope = owinContext.GetAutofacLifetimeScope();
var siteContext = lifetimeScope.GetService<ISiteContext>();
context.ProtocolMessage.ClientId = siteContext.ClientId;
Using this solution, I did not have any issues anymore resolving request-scoped dependencies, since Autofac seems to accord to the OWIN way of creating/disposing of a request scope.
I'm afraid that at this point you need to set up DI in the OWIN pipeline. It's not a difficult operation.
You'll have to:
follow the steps listed in the official documentation on OWIN integration with ASP.NET MVC
move your existing registration code (that is most probably in Global.asax.cs in Startup.cs as the documentation linked above shows
Doing this means Autofac will create the per-request lifetime scope lower in the stack, at the OWIN level. This will allow you to get hold of the HTTP request lifetime scope from the OWIN context:
private Task SetSettingsForRequest(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
// getting the OWIN context from the OIDC notification context
var owinContext = context.OwinContext;
// that's an extension method provided by the Autofac OWIN integration
// see https://github.com/autofac/Autofac.Owin/blob/1e6eab35b59bc3838bbd2f6c7653d41647649b01/src/Autofac.Integration.Owin/OwinContextExtensions.cs#L19
var lifetimeScope = owinContext.GetAutofacLifetimeScope();
var siteContext = lifetimeScope.GetService<ISiteContext>();
context.ProtocolMessage.ClientId = siteContext.ClientId;
return Task.FromResult(0);
}
I hope this helps you get over that issue.

Get per-request dependency from an Autofac func factory

I'm using ASP.NET Core and Autofac. Almost everything is registered as per lifetime scope ("per request"). So my database context DbContext is the same instance throughout a request.
However I have a singleton which also depends on DbContext. To avoid a captive dependency, it is injected as Func<Owned<DbContext>>, which means a new DbContext instance each time.
The problem is I need the same instance, as everywhere else during the request, not a new one.
I want to avoid a captive dependency bug, but I also want the same instance. Is that possible via tagging or a custom registration?
From the comments the least "architectural" painful approach may be by creating your own Scoped<T> class which will resolve the DbContext from current HttpContext
// Use an interface, so we don't have infrastructure dependencies in our domain
public interface IScoped<T> where T : class
{
T Instance { get; }
}
// Register as singleton too.
public sealed class Scoped<T> : IScoped<T> where T : class
{
private readonly IHttpContextAccessor contextAccessor;
private HttpContext HttpContext { get; } => contextAccessor.HttpContext;
public T Instance { get; } => HttpContext.RequestServices.GetService<T>();
public Scoped(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor ?? throw new ArgumentNullException(nameof(contextAccessor));
}
}
Register it as
// Microsoft.Extensions.DependencyInjection
services.AddSingleton(typeof(IScoped<>), typeof(Scoped<>);
// Autofac
containerBuilder.RegisterType(typeof(Scoped<>))
.As(typeof(IScoped<>));
Then inject this into your validator service.
public class CustomerValidator: AbstractValidator<Customer>
{
private readonly IScoped<AppDbContext> scopedContext;
protected AppDbContext DbContext { get } => scopedContext.Instance;
public CustomValidator(IScoped<AppDbContext> scopedContext)
{
this.scopedContext = scopedContext ?? throw new ArgumentNullException(nameof(scopedContext));
// Access DbContext via this.DbContext
}
}
This way you can inject any scoped service w/o further registrations.
Additional notes
Autofac is considered a "conformer" (see docs) DI and integrates well with ASP.NET Core and Microsoft.Extensions.DependencyInjection.
From the documentation
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add services to the collection.
services.AddMvc();
// Create the container builder.
var builder = new ContainerBuilder();
// Register dependencies, populate the services from
// the collection, and build the container. If you want
// to dispose of the container at the end of the app,
// be sure to keep a reference to it as a property or field.
builder.RegisterType<MyType>().As<IMyType>();
builder.Populate(services);
this.ApplicationContainer = builder.Build();
// Create the IServiceProvider based on the container.
return new AutofacServiceProvider(this.ApplicationContainer);
}
There a few subtle differences to the default usage of Startup class and Microsoft.Extensions.DependencyInjection container.
ConfigureServices isn't void anymore, it returns IServiceProvider. This will tell ASP.NET Core to use the returned provider instead of DefaultServiceProvider from Microsoft.Extensions.DependencyInjection.
We return the Autofac container adapter: new AutofacServiceProvider(this.ApplicationContainer) which is the root container.
This is important to make ASP.NET Core use the container everywhere in ASP.NET Core, even inside middlewares which resolve per request dependencies via HttpContext.RequestedServices.
For that reasons you can't use .InstancePerRequest() lifetime in Autofac, because Autofac isn't in control of creating scopes and only ASP.NET Core can do it. So there is no easy way to make ASP.NET Core use Autofac's own Request lifetime.
Instead ASP.NET Core will create a new scope (using IServiceScopeFactory.CreateScope()) and use a scoped container of Autofac to resolve per-request dependencies.

ASP.NET Core Identity does not inject UserManager<ApplicationUser>

I've got an older asp.net core identity database, and I want to map a new project (a web api) to it.
Just for the test, I copied the Models folder, and the ApplicationUser file from the previous project (ApplicationUser simply inherits from IdentityUser, no changes whatsoever) - doing DB first seems to be a bad idea.
I'm registering Identity in ConfigureServices (but I'm not adding it to the pipeline since my only intention is to use the UserStore)
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
My expectation is that now
UserManager<ApplicationUser>
...should be automatically injected into constructors.
However, when adding the following code to a controller
private UserManager _userManager;
public UserController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
... every call to the api ends with an exception:
HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).
Removing the "injection" code results in smoothly running web api that can accept requests.
It's hard to debug as this occurs before any of my code is reached. Any idea why this is occurring?
P.S. After enabling all exceptions from the Exception Settings window I got this one:
Exception thrown: 'System.InvalidOperationException' in
Microsoft.Extensions.DependencyInjection.dll
Additional information: Unable to resolve service for type
'Namespace.Data.ApplicationDbContext' while attempting to activate
'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`4[Namespace.Models.
ApplicationUser,Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole,Namespace.Data.ApplicationDbContext,System.String]'.
Do you have the app.UseIdentity(); call in the Configure method:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env, ILoggerFactory loggerFactory)
{
/*...*/
app.UseIdentity();
/*...*/
}
EDIT
Do you also have this line before the services.AddIdentity<ApplicationUser, IdentityRole>() line?
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
This should work OK. Also please check if ApplicationDbContext inherits from IdentityDbContext.
DI container is unable to resolve a dependency. Add it to the services collection
services.AddTransient<UserManager<ApplicationUser>>();
services.AddTransient<ApplicationDbContext>();
You should also familiarize yourself with the official documentation
public void ConfigureServices(IServiceCollection services){
...
var identityBuilder = services.AddIdentityCore<ApplicationUser>(user =>
{
// configure identity options
user.Password.RequireDigit = true;
user.Password.RequireLowercase = false;
user.Password.RequireUppercase = false;
user.Password.RequireNonAlphanumeric = false;
user.Password.RequiredLength = 6;
});
identityBuilder = new IdentityBuilder(identityBuilder.UserType, typeof(IdentityRole), identityBuilder.Services);
identityBuilder.AddEntityFrameworkStores<DbContext>().AddDefaultTokenProviders();
...
}

Categories

Resources