How to retrieve configuration 'Options' instances from a ModelBinder? - c#

I'm building a custom ModelBinder and I need to retrieve the MvcJsonOptions config instance that was set in Startup from
services.AddMvc(options => {...})
.AddJsonOptions(options => {
//I need this 'option' instance from my model binder
});
Not sure whether I should retrieve them from the service provider, what would be the best approach to retrieve them?

Dunno who told you it's not retrievable, but all configurations are registered via DI, even MvcOptions and MvcJsonOptions, as you can clearly see on the source code here
public static IMvcBuilder AddJsonOptions(
this IMvcBuilder builder,
Action<MvcJsonOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
// configure registers it with the DI system
builder.Services.Configure(setupAction);
return builder;
}
That being said, all you need to do is inject IOptions<MvcJsonOptions> where ever you need it and access options.Value property to get the instance.
Update
As pointed in the comments, IModelBinderProvider isn't supposed to have dependencies injected. IModelBinderProvider is only used to create the binder and should have no external dependencies.
public class MyBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (/* some condition to identify your model */)
return new BinderTypeModelBinder(typeof(MyBinder));
return null;
}
}
and MyBinder should have the dependencies:
public class MyBinder : IModelBinder
{
private readonly MvcJsonOptions jsonOptions;
public MyBinder(IOptions<MvcJsonOptions> options)
{
jsonOptions = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
// Your binding logic here
...
return Task.CompletedTask;
}
}

Related

ValueProvider Is Null MVC

I use this startup class in asp.net MVC 5 (using Microsoft.Extensions.DependencyInjection)
[assembly: OwinStartupAttribute(typeof(MVC5.Startup))]
namespace MVC5
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
var services = new ServiceCollection();
// configure all of the services required for DI
ConfigureServices(services);
// Create a new resolver from our own default implementation
var resolver = new MyDependencyResolver(services.BuildServiceProvider());
// Set the application resolver to our default resolver. This comes from "System.Web.Mvc"
//Other services may be added elsewhere through time
DependencyResolver.SetResolver(resolver);
}
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersAsServices(new[]
{ typeof(HomeController), typeof(AccountController), typeof(ManageController)});
services.AddTransient<AppDbContext>();
//====================================================
// ApplicationUserManager
//====================================================
// instantiation requires the following instance of the Identity database
services.AddTransient(typeof(IUserStore<ApplicationUser>), p => new UserStore<ApplicationUser>(new IdentityContext()));
// with the above defined, we can add the user manager class as a type
services.AddTransient<ApplicationUserManager>();
//====================================================
// ApplicationSignInManager
//====================================================
// instantiation requires two parameters, [ApplicationUserManager] (defined above) and [IAuthenticationManager]
services.AddTransient(typeof(Microsoft.Owin.Security.IAuthenticationManager), p => new OwinContext().Authentication);
services.AddTransient<ApplicationSignInManager>();
//====================================================
// ApplicationRoleManager
//====================================================
// Maps the rolemanager of identity role to the concrete role manager type
services.AddTransient<RoleManager<IdentityRole>>();
// Maps the role store role to the implemented type
services.AddTransient<IRoleStore<IdentityRole, string>, RoleStore<IdentityRole>>();
}
/// <summary>
/// Provides the default dependency resolver for the application - based on IDependencyResolver, which hhas just two methods.
/// This is combined dependency resolver for MVC and WebAPI usage.
/// </summary>
public class MyDependencyResolver : System.Web.Mvc.IDependencyResolver, System.Web.Http.Dependencies.IDependencyResolver
{
protected IServiceProvider serviceProvider;
protected IServiceScope scope = null;
public MyDependencyResolver(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public MyDependencyResolver(IServiceScope scope)
{
this.scope = scope;
this.serviceProvider = scope.ServiceProvider;
}
public IDependencyScope BeginScope()
{
return new MyDependencyResolver(serviceProvider.CreateScope());
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
scope?.Dispose();
}
public object GetService(Type serviceType)
{
var ser = this.serviceProvider.GetService(serviceType);
return ser;
}
public IEnumerable<object> GetServices(Type serviceType)
{
return this.serviceProvider.GetServices(serviceType);
}
}
}
public static class ServiceProviderExtensions
{
public static IServiceCollection AddControllersAsServices(this IServiceCollection services, IEnumerable<Type> serviceTypes)
{
foreach (var type in serviceTypes)
{
services.AddTransient(type);
}
return services;
}
}
In ManageController all services are injected and Action method is ready to run, but due to its Base Controller that has ValueProvider is NULL, The controller is disposed before doing any job. Why controller's valueprovider is null and how to solve this?
I changed, ApplicationUserManager and ApplicationSignInManager and RoleManager injections to the code bellow and now, everything works:
...
services.AddTransient<ApplicationUserManager>(p => HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>());
...
services.AddTransient<ApplicationSignInManager>(p => HttpContext.Current.GetOwinContext().Get<ApplicationSignInManager>());
...
services.AddTransient<RoleManager<IdentityRole>>(p =>
HttpContext.Current.GetOwinContext().Get<RoleManager<IdentityRole>>());
...

Can I inject a dependency into a model object created in a post method with .Net Core WebApi?

If in my Startup I have registered my dependency:
services.AddScoped<IService, SomeService>();
Is there a way for this to be injected into my model object at instantiation via a post method?
[HttpPost]
public void Post([FromBody] Model myModel) { ... }
And my model class looks something like this.
public class Model {
...
public Model(IService service) { ... }
...
}
If you wan to inject something into the model you can go with custom model binding. Inside ModelBinder you get access to context with all your registered services and you can call your model's constructor with any arguments that you need.
In general models should not keep reference to services that are using them. So please think it through, if you really want to go this way.
You can create a custom model binder for this purpose. We can use ComplexTypeModelBinder as base for this
public class ServiceModelBinder : ComplexTypeModelBinder
{
public ServiceModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory)
: base(propertyBinders, loggerFactory)
{
}
public ServiceModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory, bool allowValidatingTopLevelNodes) : base(propertyBinders, loggerFactory, allowValidatingTopLevelNodes)
{
}
protected override object CreateModel(ModelBindingContext bindingContext)
{
var modelTypeInfo = bindingContext.ModelType.GetTypeInfo();
//if type satisfies default binder requirements just use it
if (!modelTypeInfo.IsAbstract && modelTypeInfo.GetConstructor(Type.EmptyTypes) != null)
{
return base.CreateModel(bindingContext);
}
var constructors = modelTypeInfo.GetConstructors();
if (constructors.Length > 1)
{
throw new InvalidOperationException("Model should contain one constructor only");
}
var constructor = constructors.First();
var parameters = constructor.GetParameters();
//require that all parameters are marked with [FromServices]
//this step is completely optional
if (!parameters.Any(p => p.IsDefined(typeof(FromServicesAttribute))))
{
throw new InvalidOperationException("Mark all parameters with [FromServices]");
}
//build arguments from registered services
var arguments = parameters
.Select(p => bindingContext.ActionContext.HttpContext.RequestServices.GetService(p.ParameterType))
.ToArray();
return constructor.Invoke(arguments);
}
}
Then add a model binder provider (copied from ASP.NET Core sources)
public class ServiceModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
var property = context.Metadata.Properties[i];
propertyBinders.Add(property, context.CreateBinder(property));
}
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new ServiceModelBinder(
propertyBinders,
loggerFactory,
allowValidatingTopLevelNodes: true);
}
return null;
}
}
And register it in startup
services
.AddControllersWithViews()
.AddMvcOptions(options =>
{
options.ModelBinderProviders.Insert(0, new ServiceModelBinderProvider());
})

MVC 6 Custom Model Binder with Dependency Injection

Right now my ViewModel looks like this:
public class MyViewModel
{
private readonly IMyService myService;
public ClaimantSearchViewModel(IMyService myService)
{
this.myService = myService;
}
}
My Controller that consumes this ViewModel looks like this:
public class MyController : Controller
{
private readonly IMyService myService;
public HomeController(IMyService myService)
{
this.myService = myService;
}
public IActionResult Index()
{
var model = new MyViewModel(myService);
return View(model);
}
[HttpPost]
public async Task<IActionResult> Find()
{
var model = new MyViewModel(myService);
await TryUpdateModelAsync(model);
return View("Index", model);
}
}
What I need is my Controller to look like is this:
public class MyController : Controller
{
private readonly IServiceProvider servicePovider;
public MyController(IServiceProvider servicePovider)
{
this.servicePovider = servicePovider;
}
public IActionResult Index()
{
var model = servicePovider.GetService(typeof(MyViewModel));
return View(model);
}
[HttpPost]
public IActionResult Index(MyViewModel model)
{
return View(model);
}
}
Right now, calling the first Index method works fine (with
builder.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource(x => x.Name.Contains("ViewModel")));
in my Startup class) but doing the POST to Index(MyViewModel model) gives you a No parameterless constructor defined for this object exception. I realize that a custom model binder that can use my DI will be the most likely solution... but I'm not able to find any help on how to even get started here. Please help me with this, especially for Autofac in MVC 6.
We got the answer here: https://github.com/aspnet/Mvc/issues/4167
And the answer is to use: [FromServices]
My Model ends up looking like this:
public class MyViewModel
{
[FromServices]
public IMyService myService { get; set; }
public ClaimantSearchViewModel(IMyService myService)
{
this.myService = myService;
}
}
Although it's sad to make that property public, it's much less sad than having to use a custom model binder.
Also, supposedly you should be able to pass [FromServices] as part of the param in the Action method, it does resolve the class, but that breaks the model binding... ie none of my properties got mapped. It looks like this: (but again, THIS DOES NOT WORK so use the above example)
public class MyController : Controller
{
... same as in OP
[HttpPost]
public IActionResult Index([FromServices]MyViewModel model)
{
return View(model);
}
}
UPDATE 1
After working with the [FromServices] attribute we decided that property injection in all of our ViewModels was not the way we wanted to go, especially when thinking about long term maintenance with testing. SO we decided to remove the [FromServices] attributes and got our custom model binder working:
public class IoCModelBinder : IModelBinder
{
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
var serviceProvider = bindingContext.OperationBindingContext.HttpContext.RequestServices;
var model = serviceProvider.GetService(bindingContext.ModelType);
bindingContext.Model = model;
var binder = new GenericModelBinder();
return binder.BindModelAsync(bindingContext);
}
}
It's registered like this in the Startup ConfigureServices method:
services.AddMvc().AddMvcOptions(options =>
{
options.ModelBinders.Clear();
options.ModelBinders.Add(new IoCModelBinder());
});
And that's it. (Not even sure that options.ModelBinders.Clear(); is needed.)
UPDATE 2
After going through various iterations of getting this to work (with help https://github.com/aspnet/Mvc/issues/4196), here is the final result:
public class IoCModelBinder : IModelBinder
{
public async Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{ // For reference: https://github.com/aspnet/Mvc/issues/4196
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
if (bindingContext.Model == null && // This binder only constructs viewmodels, avoid infinite recursion.
(
(bindingContext.ModelType.Namespace.StartsWith("OUR.SOLUTION.Web.ViewModels") && bindingContext.ModelType.IsClass)
||
(bindingContext.ModelType.IsInterface)
)
)
{
var serviceProvider = bindingContext.OperationBindingContext.HttpContext.RequestServices;
var model = serviceProvider.GetRequiredService(bindingContext.ModelType);
// Call model binding recursively to set properties
bindingContext.Model = model;
var result = await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(bindingContext);
bindingContext.ValidationState[model] = new ValidationStateEntry() { SuppressValidation = true };
return result;
}
return await ModelBindingResult.NoResultAsync;
}
}
You'd obviously want to replace OUR.SOLUTION... with whatever the namespace is for your ViewModels Our registration:
services.AddMvc().AddMvcOptions(options =>
{
options.ModelBinders.Insert(0, new IoCModelBinder());
});
UPDATE 3:
This is the latest iteration of the Model Binder and its Provider that works with ASP.NET Core 2.X:
public class IocModelBinder : ComplexTypeModelBinder
{
public IocModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory) : base(propertyBinders, loggerFactory)
{
}
protected override object CreateModel(ModelBindingContext bindingContext)
{
object model = bindingContext.HttpContext.RequestServices.GetService(bindingContext.ModelType) ?? base.CreateModel(bindingContext);
if (bindingContext.HttpContext.Request.Method == "GET")
bindingContext.ValidationState[model] = new ValidationStateEntry { SuppressValidation = true };
return model;
}
}
public class IocModelBinderProvider : IModelBinderProvider
{
private readonly ILoggerFactory loggerFactory;
public IocModelBinderProvider(ILoggerFactory loggerFactory)
{
this.loggerFactory = loggerFactory;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType) return null;
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
foreach (ModelMetadata property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
return new IocModelBinder(propertyBinders, loggerFactory);
}
}
Then in Startup:
services.AddMvc(options =>
{
// add IoC model binder.
IModelBinderProvider complexBinder = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(ComplexTypeModelBinderProvider));
int complexBinderIndex = options.ModelBinderProviders.IndexOf(complexBinder);
options.ModelBinderProviders.RemoveAt(complexBinderIndex);
options.ModelBinderProviders.Insert(complexBinderIndex, new IocModelBinderProvider(loggerFactory));
This question is tagged with ASP.NET Core, so here's our solution for dotnet core 3.1.
Outline of our solution: TheProject needs to make ICustomerService available to an object created automatically in the request pipeline. Classes that need this are tagged with an interface, IUsesCustomerService. This interface is then checked by the Binder on object creation, and special case is handled.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Logging;
namespace TheProject.Infrastructure.DependencyInjection
{
/// <summary>
/// This is a simple pass through class to the binder class.
/// It gathers some information from the context and passes it along.
/// </summary>
public class TheProjectModelBinderProvider : IModelBinderProvider
{
public TheProjectModelBinderProvider()
{
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
ILoggerFactory ilogger;
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// The Binder that gets returned is a <ComplexTypeModelBinder>, but I'm
// not sure what side effects returning early here might cause.
if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType)
{
return null;
}
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
foreach (ModelMetadata property in context.Metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
ilogger = (ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory));
return new TheProjectModelBinder(propertyBinders, ilogger);
}
}
/// <summary>
/// Custom model binder.
/// Allows interception of endpoint method to adjust object construction
/// (allows automatically setting properties on an object that ASP.NET creates for the endpoint).
/// Here this is used to make sure the <see cref="ICustomerService"/> is set correctly.
/// </summary>
public class TheProjectModelBinder : ComplexTypeModelBinder
{
public TheProjectModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, ILoggerFactory loggerFactory)
: base(propertyBinders, loggerFactory)
{
}
/// <summary>
/// Method to construct an object. This normally calls the default constructor.
/// This method does not set property values, setting those are handled elsewhere in the pipeline,
/// with the exception of any special properties handled here.
/// </summary>
/// <param name="bindingContext">Context.</param>
/// <returns>Newly created object.</returns>
protected override object CreateModel(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var customerService = (ICustomerService)bindingContext.HttpContext.RequestServices.GetService(typeof(ICustomerService));
bool setcustomerService = false;
object model;
if (typeof(IUsesCustomerService).IsAssignableFrom(bindingContext.ModelType))
{
setcustomerService = true;
}
// I think you can also just call Activator.CreateInstance here.
// The end result is an object that's constructed, but no properties are set yet.
model = base.CreateModel(bindingContext);
if (setcustomerService)
{
((IUsesCustomerService)model).SetcustomerService(customerService);
}
return model;
}
}
}
Then in the startup code, make sure to set AddMvcOptions.
public void ConfigureServices(IServiceCollection services)
{
// ...
// asp.net core 3.1 MVC setup
services.AddControllersWithViews()
.AddApplicationPart(assembly)
.AddRazorRuntimeCompilation()
.AddMvcOptions(options =>
{
IModelBinderProvider complexBinder = options.ModelBinderProviders.FirstOrDefault(x => x.GetType() == typeof(ComplexTypeModelBinderProvider));
int complexBinderIndex = options.ModelBinderProviders.IndexOf(complexBinder);
options.ModelBinderProviders.RemoveAt(complexBinderIndex);
options.ModelBinderProviders.Insert(complexBinderIndex, new Infrastructure.DependencyInjection.TheProjectModelBinderProvider());
});
}

Implementing IAuthenticationFilter, Unit of Work and Dependency Injection

I have a Web API 2 project that is implementing a custom IAuthenticationFilter such as the following.
My problem is the UnitOfWork is not injected by Unity in the BasicAuthenticator class. As expected Unity successfully injects UnitOfWork in Controllers.
public class BasicAuthenticator : Attribute, IAuthenticationFilter
{
[Dependency]
public UnitOfWork UoW { get; set; }
public bool AllowMultiple { get { return false; } }
public Task AuthenticateAsync(HttpAuthenticationContext context,
CancellationToken cancellationToken)
{
// ignore missing implementation
context.Principal = new ClaimsPrincipal(new[] { id });
return Task.FromResult(0);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context,
CancellationToken cancellationToken) {}
}
You will need to create a custom IFilterProvider that will perform a BuildUp() on the applicable filters. The BuildUp operation will inject all dependencies into the filter.
Here is a UnityFilterProvider that does that:
public class UnityFilterProvider : ActionDescriptorFilterProvider, IFilterProvider
{
private readonly IUnityContainer container;
public UnityFilterProvider(IUnityContainer container)
{
this.container = container;
}
public new IEnumerable<FilterInfo> GetFilters(
HttpConfiguration configuration,
HttpActionDescriptor actionDescriptor)
{
var filters = base.GetFilters(configuration, actionDescriptor);
foreach (var filter in filters)
{
container.BuildUp(filter.Instance.GetType(), filter.Instance);
}
return filters;
}
}
Next at application startup you need to replace the default filter provider with the custom provider above:
GlobalConfiguration.Configuration.Services.Add(
typeof(System.Web.Http.Filters.IFilterProvider),
new UnityFilterProvider(container));
var providers = GlobalConfiguration.Configuration.Services.GetFilterProviders()
.ToList();
var defaultprovider = providers.First(p => p is ActionDescriptorFilterProvider);
GlobalConfiguration.Configuration.Services.Remove(
typeof(System.Web.Http.Filters.IFilterProvider),
defaultprovider);
I usually use the Unity bootstrapper for ASP.NET Web API so I put the above code in the UnityConfig.cs after RegisterTypes().

Unity IoC Lifetime per HttpRequest for UserStore

I'm trying to clean up the default implementation of AccountController.cs that comes out of the box in the new MVC5/Owin security implementation. I have modified my constructor to look like this:
private UserManager<ApplicationUser> UserManager;
public AccountController(UserManager<ApplicationUser> userManager)
{
this.UserManager = userManager;
}
Also, I have created a lifetime manager for Unity that looks like this:
public class HttpContextLifetimeManager<T> : LifetimeManager, IDisposable
{
private HttpContextBase _context = null;
public HttpContextLifetimeManager()
{
_context = new HttpContextWrapper(HttpContext.Current);
}
public HttpContextLifetimeManager(HttpContextBase context)
{
if (context == null)
throw new ArgumentNullException("context");
_context = context;
}
public void Dispose()
{
this.RemoveValue();
}
public override object GetValue()
{
return _context.Items[typeof(T)];
}
public override void RemoveValue()
{
_context.Items.Remove(typeof(T));
}
public override void SetValue(object newValue)
{
_context.Items[typeof(T)] = newValue;
}
}
I'm not sure how to write this in my UnityConfig.cs, but this is what I have so far:
container.RegisterType<UserManager<ApplicationUser>>(new HttpContextLifetimeManager(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new RecipeManagerContext()))));
I did find another example (using AutoFac) that does it this way:
container.Register(c => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>( new RecipeManagerContext())))
.As<UserManager<ApplicationUser>>().InstancePerHttpRequest();
How would I translate the above statement using Unity IoC lifetime management?
Your approach where the UserManager is registered for specific lifetime is correct. However, I don't understand why it even compiles, since your HttpContextLifetimeManager expects the HttpContext as a parameter.
Another issue is that your implementation is wrong. The parameterless constructor takes current http context, however you rather want the lifetime manager to use the context the instance is created on rather the one the type is registered on. If the parameterless constructor is used, this could lead to http context mismatch issues.
First, change your implementation to
public class HttpContextLifetimeManager : LifetimeManager
{
private readonly object key = new object();
public override object GetValue()
{
if (HttpContext.Current != null &&
HttpContext.Current.Items.Contains(key))
return HttpContext.Current.Items[key];
else
return null;
}
public override void RemoveValue()
{
if (HttpContext.Current != null)
HttpContext.Current.Items.Remove(key);
}
public override void SetValue(object newValue)
{
if (HttpContext.Current != null)
HttpContext.Current.Items[key] = newValue;
}
}
and then register your type
container.RegisterType<UserManager<ApplicationUser>>( new HttpContextLifetimeManager() );

Categories

Resources