I am currently trying to reconfigure StructureMap in our application after upgrading from an old version (2.6) that was never correctly implemented in the first place.
I am new to using DI Containers to begin with, and am finding documentation for newer StructureMap versions hard to find. I uninstalled the old 2.6 version of StructureMap and installed StructureMap.MVC5 (since I am using MVC5).
What I am having issues with is the AccountController. I have StructureMap set up to use the parameterless constructor, but when my application tries to create the UserManager, I get an InvalidOperationException, "No owin.Environment item was found in the context."
Obviously I need additional configuration for StructureMap, but I have no idea what/how. I can find a million sources for this error, all suggesting adding a tag in web.config, but none of them seem to be DI container specific - and I only have this issue when I use StructureMap vs letting the framework create the controller.
Below is the relevant code; that section of the AccountController is just stock template code.
AccountController.cs
private ApplicationUserManager _userManager;
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager)
{
UserManager = userManager;
}
public ApplicationUserManager UserManager
{
get
{
// This is where the exception is thrown
return _userManager ??
HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
DefaultRegistry.cs
public DefaultRegistry()
{
Scan(
scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.With(new ControllerConvention());
});
For<IBasicRepository>()
.Use<EntityRepository>()
.LifecycleIs<HttpContextLifecycle>()
.Ctor<string>("ConnectionString")
.Is(ConfigurationManager.ConnectionStrings["MyContext"].ConnectionString);
For<AccountController>()
.Use<AccountController>()
.SelectConstructor(() => new AccountController());
}
As #Erik Funkenbusch pointed out, I was doing competing things. I ended up making UserManager an auto-property, removed the parameterless constructor, and let StructureMap inject the ApplicationUserManager.
public ApplicationUserManager UserManager { get; private set; }
public AccountController(ApplicationUserManager userManager)
{
UserManager = userManager;
}
Then, I simply needed to configure the IUserStore and DbContext that Identity uses in DefaultRegistry.cs:
For<IUserStore<ApplicationUser, int>>()
.Use<UserStore<ApplicationUser, CustomRole, int, CustomUserLogin,
CustomUserRole, CustomUserClaim>>()
.LifecycleIs<HttpContextLifecycle>();
For<DbContext>()
.Use(() => new ApplicationDbContext())
.LifecycleIs<HttpContextLifecycle>();
This was all I needed to do to get StructureMap.MVC working with Identity.
Part of my initial hangup was that I didn't realize the way StructureMap.MVC (and other DI containers) worked. (See my related question.) I was expecting it to just work with my stock AccountController which got initialized by the framework (and thought it magically intercepted object creation to inject anything I had configured), not realizing that StructureMap must initialize the controllers itself in order for it to perform constructor injection. So when I ran into issues, I was A. Surprised that StructureMap had anything to do with my AccountController in the first place (since I wasn't explicitly configuring injection for any of its parameters - only for my Repository used in other controllers), and B. I wasn't thinking about changing my stock code but rather thinking about how to configure StructureMap. It turned out I needed to do both. Luckily, it was an easy modification and I learned a little more about how DI containers work.
Related
I have a project in .NET 5 with RazorPages, I set this code to validate the Dependecy Injection in the Progam.cs file:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseDefaultServiceProvider(options =>
{
options.ValidateOnBuild = true;
options.ValidateScopes = true;
})....
I forgot to register the service that is injected into my page, so I would have expected that when I try to start the app an error page would show this kind of problem, but I don't understand why it doesn't happen, because for example in case I don't register ILocalizerService this happens:
This is my RazorPage:
public class SignupModel : IdentityPageModel
{
[BindProperty]
public Models.Account.Signup Signup { get; set; }
private readonly CustomUserManager _userManager;
private readonly ILogger<SignupModel> _logger;
private readonly INcsService _ncsService;
public SignupModel(CustomUserManager userManager,
ILogger<SignupModel> logger,
INcsService ncsService) : base(localizerService)
{
Guard.Against.Null(userManager, nameof(userManager));
Guard.Against.Null(logger, nameof(logger));
Guard.Against.Null(ncsService, nameof(ncsService));
_userManager = userManager;
_logger = logger;
_ncsService = ncsService;
}
// Other code....
}
This is my service:
[PublicAPI]
public class NcsService : INcsService
{
private readonly IHttpClientFactory _httpClientFactory;
public NcsService(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
// Other code...
}
I have only registered IHttpClientFactory but not INcsService interface and implementation:
services.AddHttpClient(nameof(NcsService), client =>
{
client.BaseAddress = new Uri(ncsSettings.BaseUri);
client.DefaultRequestHeaders.Add("x-functions-key", ncsSettings.ApiKey);
client.DefaultRequestHeaders.Add("x-app-name", "TSID");
}).AddHeaderPropagation(options =>
{
options.Headers.Add("x-request-id");
options.Headers.Add("x-correlation-id");
})
.AddPolicyHandler(GetRetryPolicy());
I hope I was clear.
Thank you
The root of the issue is Microsoft's default IComponentActivator implementation (the DefaultComponentActivator). The Component Activator is in control of creating your Razor Pages, but the built-in behavior does not request those pages from the built-in container. Instead, it just creates them using Activator.CreateInstance.
This means that Blazor does not register your pages in the built-in container and because of that, the page will not be part of the container's verification process.
This is, IMO, a design flaw in Blazor, because it well known, and well understood that, in case you are using a DI Container, you should let all your application components go through the container pipeline. That's the only way that the container can give you a reasonable amount of certainty about the validity of your application components.
Blazor, however, is not the only part of the ASP.NET Core framework where this happens. ASP.NET MVC Controllers, for instance, by default aren't registered in the container, and aren't resolved from the container. This is configurable though, but since this is not the default behavior, the ValidateOnBuild gives a false sense of security.
Other containers might have a more sensible default. Simple Injector, for instance, (the container that I maintain) contains extension methods that always register all MVC controllers up front. And with the Blazor integration, similar things happen.
If you stick with the built-in container, it would be good to ensure all components are resolved from the container. With MVC this is easy, because you can simply call AddControllersAsServices. With Blazor, unfortunately, this is much more difficult, because there exists no such method as AddComponentsAsServices. This means that you have to create a custom IComponentActivator that calls back into the container. But still, you'll likely have to fallback to the original behavior using Activator.CreateInstance for all Blazor Components that are created by Microsoft, because it might be much harder to find and register them using reflection. For inspiration on how to create such custom Component Activator and register your application Blazor components, take a look at the code presented here.
I have implemented an application which makes use of .Net Core 3.1 and Entity Framework.
The application uses entity framework dbcontext pooling, utilizing the Pomelo mysql ef library.
services.AddDbContextPool<myDbContext>(
options => options
.UseMySql(Configuration.GetConnectionString("DefaultConnection"),
mysqlOptions =>
{
mysqlOptions.MaxBatchSize(MySqlConfig.EfBatchSize);
mysqlOptions.EnableRetryOnFailure();
if (MySqlConfig.EfRetryOnFailure > 0)
{
mysqlOptions.EnableRetryOnFailure(MySqlConfig.EfRetryOnFailure, TimeSpan.FromSeconds(5), null);
}
}
).UseLoggerFactory(consoleLoggerFactory));
What is important to note is the use of the AddDbContextPool
Please see here: https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.entityframeworkservicecollectionextensions.adddbcontextpool?view=efcore-3.1
When using Context Pooling, your application is required to have a single constructor with DbContextOptions. ie: I cannot inject another object (in my case, I need a application service class for listing allowed tenants, and other claims related logic...) into this class, otherwise pooling cannot be used.
public myDbContext(DbContextOptions<myDbContext> options)
: base(options)
{
}
Another caveat is that I utilize the HttpContextAccessor to access the claims on the User which is authenticated, which include the Tenants allowed to be accessed by that user which of course is accessed via Dependency Injection as well.
The Authentication and Claims are retrieved via openid ultimately retrieving claims as Active Directory Groups, so I do not and would not have this info in the DB either as it originates from Active Directory...
The guts of this question is really, how can i access the HttpContext within my DbContext in order to implement a global filter for multi-tenant support in my application, and still use the DB Context Pooling.
I can achieve this by removing pooling, and make use of DI as normal via the constructor, but that is not what i'm after. I need to keep pooling, and implement the multi-tenant feature.
First, if your tenants are in different databases, DbContext pooling simply won't work. After a DbContext has been opened, it's connected to a single database. It's a minor performance feature, so it's not a great loss.
If you're setting query filters, you can probably make it work by simply not directly injecting a DbContext into your controllers, instead injecting a pre-configured bundle of services. And this also lets you centralize the code to rreconfigure the DbContext for the current tenant. EG:
public class ServiceContext
{
MyDbContext dbContext;
HttpContext httpContext;
IConfiguration config;
public ServiceContext(MyDbContext dbContext, IHttpContextAccessor httpContextAccessor, IConfiguration config)
{
this.dbContext = dbContext;
this.httpContext = httpContextAccessor.HttpContext;
this.config = config;
//use the httpContext to reconfigure the DbContext for single-tenant access
}
public HttpContext HttpContext { get => httpContext; set => throw new NotImplementedException(); }
public MyDbContext DbContext { get => dbContext; }
public IConfiguration Configuration { get => config; }
}
I have a service with the following constructor
public TokenService(UserManager<IdentityUser> userManager, ApplicationDbContext dbContext)
{
_userManager = userManager;
_dbContext = dbContext;
}
In Startup.cs I have registered the service :
services.AddScoped<TokenService>();
I would like to add a string parameter to the constructor and pass the string to the service where I register the service in Startup.cs
I know you can pass parameters by creating a new instance during registration but I don't know how to pass the UserManger and ApplicationDbContext objects when doing it manually.
If I do this :
services.AddScoped<>(_ => new TokenService(null , null, "secret");
it doesn't work with the dependency injection of the other services.
As a workaround, I have added the string with a method in Program.cs but would like to remove this.
Normally when you want to provide parameters to something that also needs to get dependencies injected you end up creating a factory. However in this case since the parameters are known during registration you can easily fix this registering a factory method directly:
services.AddSingleton(serviceProvider => new TokenService(serviceProvider.GetRequiredService<UserManager<IdentityUser>>(), serviceProvider.GetRequiredService<ApplicationDbContext>(), "hello"));
In you comments you state that this can't be a singleton, that doesn't matter with the factory method, just as easy to register a transient for example:
services.AddTransient(serviceProvider => new TokenService(serviceProvider.GetRequiredService<UserManager<IdentityUser>>() , serviceProvider.GetRequiredService<ApplicationDbContext>(), "hello"));
Hopes this helps
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.
I'm writing a web app and I have used the default Microsoft MVC site as a starting point. After this I created a database of recipes to use in my web app using entity framework and have written a repository and some business layer methods. I then used dependency injection with Unity to remove the coupling between them. I used this code placed in the MvcApplication class in global.asax.cs.
private IUnityContainer Container;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
ConfigureObjects();
}
private void ConfigureObjects()
{
Container = new UnityContainer();
Container.RegisterType<IRecipeRequest, BasicRecipeRequest>();
Container.RegisterType<IRecipeRepository, RecipeRepositoryEntityFramework>();
Container.RegisterType<IRecipeContext, RecipeContext>();
DependencyResolver.SetResolver(new UnityDependencyResolver(Container));
}
The bits that are mine are only the bits relating to dependency injection.
After doing this using any page related to user logins would return and error e.g sign up, log in. It would return an error titled this:
The current type, Microsoft.AspNet.Identity.IUserStore`1[UKHO.Recipes.Www.Models.ApplicationUser], is an interface and cannot be constructed. Are you missing a type mapping?
Looking at the exception thrown in visual studio using diagnostic tools I got this:
"An error occurred when trying to create a controller of type 'UKHO.WeeklyRecipes.Www.Controllers.AccountController'. Make sure that the controller has a parameterless public constructor."
AccountsContoler is a controller created by deafult which i have not touched and it contains a parameterless constructor and another constructor. They look like this:
public AccountController()
{
}
public AccountController(ApplicationUserManager userManager, ApplicationSignInManager signInManager )
{
UserManager = userManager;
SignInManager = signInManager;
}
By putting [InjectionConstructor()] in front of the parameterless constructor the error went away. It seems to me that Unity is trying to resolve the ApplicationUserManager and ApplicationSignInManager even though I have not registered these types and that putting [InjectionConstructor()] made unity see the empty constructor so do nothing. I mainly want to why this has happened as I was under the impression that unity should only interfere with types you have registered. Butter solutions are also welcome.
Edit: This also happens when you wish to change an account setting but instead errors with the ManageContoler this can also be solved by putting [InjectionConstructor()] in front of the empty constructor.
You configured the dependencies only of the following objects:
Container.RegisterType<IRecipeRequest, BasicRecipeRequest>();
Container.RegisterType<IRecipeRepository, RecipeRepositoryEntityFramework>();
Container.RegisterType<IRecipeContext, RecipeContext>();
But on your controller, you have 2 dependencies that are not configured, ApplicationUserManager and ApplicationSignInManager .
Unity doesnt know about these dependencies, so it can't inject on the constructor, thus trying to invoke the parameterless constructor.
If you have a constructor on your controller with parameters, unity will look for it and try to resolve all the dependencies, no matter which ones your configured. If it doenst find an object, it breaks.