Constructor injection of a View Model instance used as an Action method parameter - asp.net-mvc-3

When a view model is created you can populate the options (e.g. used in a dropdown list) into a setter property of the view model.
The problem is that when that view model is later passed as a parameter (by the framework!) into an action method, those property values has not become automagically
repopulated, so if you need to redisplay the form because of validation errors, you need to repopulate those options again.
One potential solution, which I am asking for specifically in this question, is how to make the MVC framework instantiate the view model with constructor injection, which would provide the view model constructor with an implementation of some kind of data access object (e.g. a repository) that can be used for retrieving the options when they are requested by the view (e.g. in the helper method "DropDownListFor") ?
I think the solution might have something to do with implementations of IModelBinderProvider or IModelBinder but after having experimented with these things from example code snippets here and there on the net, I am still looking for a completely working example, with downloadable executable code without any missing piece of how putting all things together.
If you are looking for some alternative discussion about how to populate a select list, e.g. with "Dependecy Lookup" instead of "Dependecy Injection" you may want to check out the following discussion:
Best way to populate SelectList for ViewModel on GET/POST
Best way to populate SelectList for ViewModel on GET/POST
Some days ago I wrote the following follow-up-question in that thread about the "Dependecy Injection" I am now looking for in this thread:
https://stackoverflow.com/a/8674525/310457
(which provides a code example about the problem I am looking for a solution of)
But instead of hoping that someone will find that old thread with a less specific title, I have created this new question with a more specific subject about what I am looking for.
And I will also provide a link from that thread into this new question for anyone that want to follow-up regarding this specific solution I am looking for.

I'm assuming you want to have your ViewModels automatically injected with something via their Constructor - for example some kind of configuration object that the View will use to determine what to show. I'm also assuming that this approach is causing a "No parameterless constructor defined for this object" error when MVC tries to automatically create and bind a model instance, from the arguments of your Controller Action. Let's also then assume that we will use a DI framework to inject the SiteConfig object into our Controllers automatically at runtime.
This means that the only problem we have to solve is how to get the injected object from our Controller into its Actions' ViewModels when they are automatically bound.
So let's define a base model for others to inherit from.
BaseViewModel
public class BaseViewModel
{
public ISiteConfig SiteConfig { get; set; }
public BaseViewModel(ISiteConfig siteConfig)
{
this.SiteConfig = siteConfig;
}
}
And now let's create a model that inherits from it.
IndexViewModel
public class IndexViewModel : BaseViewModel
{
public string SomeIndexProperty { get; set; }
public IndexViewModel (ISiteConfig siteConfig) : base(siteConfig) {}
}
And now let's define a Base Controller that our Controllers will inherit from.
BaseController
public abstract class BaseController : Controller
{
protected BaseController(ISiteConfig siteConfig)
{
_siteConfig = siteConfig;
}
private readonly ISiteConfig _siteConfig;
public ISiteConfig SiteConfig
{
get
{
return _siteConfig;
}
}
}
Now we define our actual controller.
HomeController
public HomeController: BaseController
{
public HomeController(ISiteConfig siteConfig): base(siteConfig) {}
}
Assuming we're using Ninject for DI, Ninject would be configured to automatically create the Controller and pass a concrete ISiteConfig object into its Constructor at runtime.
Now we add our Action to the Controller.
Index Action
public ActionResult Index(IndexViewModel model)
{
return View(model);
}
And so this is the point where without doing anything else, MVC will explode with a "Parameterless Constructor" error if you try to call the Index Action, because MVC can't find a ViewModel constructor that takes no arguments.
And so, the answer. We need to override the default ModelBinder.
BaseViewModelBinder
public class BaseViewModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
if (modelType == typeof(BaseViewModel) || modelType.IsSubclassOf(typeof(BaseViewModel)))
{
var baseControl = controllerContext.Controller as BaseController;
if (baseControl == null)
{
throw new Exception("The Controller must derive from BaseController");
}
var instance = Activator.CreateInstance(modelType, baseControl.SiteConfig);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, modelType);
return instance;
}
else
{
return base.CreateModel(controllerContext, bindingContext, modelType);
}
}
}
And we need to set this as the default model binder in global.asax.cs :
protected void Application_Start()
{
...
ModelBinders.Binders.DefaultBinder = new BaseViewModelBinder();
}
That's all. As you can see, when you view the Index Action now, MVC will use our custom model binder. It will realise that the IndexViewModel derives from BaseViewModel, and so will attempt to spin up an IndexViewModel instance using the ISiteConfig it can find in the Action's Controller (because the Controller derives from BaseController).

Related

Injecting Non-User-Submitted Data For Use During Validation

From what I can tell, ASP.Net Core performs model state validation before calling the relevant controller action method. This means that code in the action method isn't given an opportunity to add data to the model before it is validated.
What is the ASP.Net Core way of giving a view model access to additional, non-user-submitted data prior to validation?
Example
What I'm trying to do (doesn't work).
The view model's Validate method expects data to be in ValidOptions. However, since validation occurs before the controller can set this property, validation causes the view model to throw an ArgumentNullException.
// From the Controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Process([Bind("SelectedId")]ViewModels.Import details)
{
// data needed for validation
details.ValidOptions = await service.ImportTypes.ToListAsync();
if (ModelState.ValidationState != ModelValidationState.Valid) {
// ...
}
}
// From ViewModels.Import
public IEnumerable<Option> ValidOptions { get; set; }
public int SelectdId {get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// throws ArgumentNullException because ValidOptions hasn't been set when this is executed
var option = ValidOptions.Single(t => t.Id == SelectdId);
//...
}
Probably many ways to skin a cat here. But the easiest for you is probably custom model binders. It's a way to "supplement" or change the binding of your model before it hits the controller. I will say that some see it as extremely bad practice to call an external service/repository at the point of model binding, but it does work and can come in handy.
You need to implement a class that inherits from IModelBinder.
public class MyViewModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
//Bind here. Including calling external services if you want.
}
}
Then you need to implement a provider, this essentially says "when" to bind.
public class MyViewModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(MyViewModel))
return new MyViewModelBinder();
return null;
}
}
In your configure method of your startup.cs, you need to add the provider to the ModelBinderProviders list.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc(config =>
config.ModelBinderProviders.Add(new MyViewModelBinderProvider())
);
}
Further Documentation :
http://dotnetcoretutorials.com/2016/12/28/custom-model-binders-asp-net-core/
http://intellitect.com/custom-model-binding-in-asp-net-core-1-0/
I don't think the official documentation has an article on custom model binders yet unfortunately.

MVC 3 split parameters in HttpPost action

I have an MVC 3 app and I have created a generic wrapper object, which has some navigation properties and the wrapped object of T, whose values I'm editing/displaying.
public class NavigationViewModel<T>
{
public T Model { get; set; }
public NavigationHelper NavigationHelper { get; set; }
public NavigationViewModel() { }
public NavigationViewModel(T model, NavigationHelper helper)
{
this.Model = model;
this.NavigationHelper = helper;
}
}
My controller resolves this object nicely in an action like this:
public ActionResult Foo(NavigationViewModel<Bar> viewModel)
Code in my view looks like this:
#Html.EditorFor(model => model.Model.SomeProperty)
My colleague said that that code is not nice to read. I already have a strongly typed view, the Model and this Model has another property called Model. He suggested to rename the Model property to ViewModel and I agreed with his reasoning.
Now, the code with the renamed properties does not work anymore: NavigationViewModel viewModel is null. So I changed the signature of the HttpPost method to the following and it works again:
[HttpPost]
public ActionResult Foo(NavigationHelper helper, Bar viewModel)
I like this very much! I can directly access my viewModel in code, the code in the view makes sense and the helper object does not get in the way. I haven't seen this convention before and I guess it worked before because of the naming convention. Using a property called Model hinted at how to resolve the object. Without that property, it couldn't resolve it anymore.
I would like to adopt this for other kinds of helpers that contain view-specific properties, like select-lists or other properties that I otherwise might have put in my ViewBag. Would you guys recommend this approach or will I run into trouble later on using this?
I think I have a really simple answer for you, just don't name your action parameter viewModel, so change:
public ActionResult Foo(NavigationViewModel viewModel)
public ActionResult Foo(NavigationViewModel model)
Or any other parameter name that does not collide with your ViewModel property on your NavigationViewModel class.

Inject strongly typed instance with structuremap

I have this IPageModel interface which is the base for all my models in my project.
The current model is a part of the RouteData and I want to inject this instance to my controllers.
this is how I do it today
x.For<IPageModel>().UseSpecial(y => y.ConstructedBy( r => ((MvcHandler) HttpContext.Current.Handler).RequestContext.RouteData.GetCurrentModel<IPageModel>(‌​)));
Is it possible to tell structuremap to inject the correct type instead of the IPageModel?
An couple examples would be like this:
public HomeController(Home model) {
// Home implements IPageModel
}
and
public PageController(Page model) {
// Page implements IPageModel
}
The RouteData object has the correct instance of the model
You can make your controllers generic, so they work with a specific type of IPageModel.
so you would have
HomeController<Home> and
PageController<Page>
If you make them derive from a single base class which only has a constructor with 1 parameter (an instance of T) so:
public abstract class BaseController<T> where T : IPageModel
{
protected T Model { get; private set; }
public BaseController(T model)
{
Model = model;
}
This way you will get a controller with a newly constructed instance of (for example) Home. I don't think you want an empty model but you'll have to handle this in your registrations in structuremap by scoping them on the session or request for example.

MVC - Interface as Controller Action parameter

I want to pass one of a number of classes that implement an interface from my view back to my controller action. I use an ActionLink in my view passing the instance to my action, but it naturally fails because MVC cannot deal with interfaces via default model binding.
So :
<%=Html.ActionLink(flow.Source.Name, "Get", new {container=flow.Source})%>
is in a loop and each flow.Source conforms to IContainer.
public class Flow
{
public virtual IContainer Source { get; private set; }
}
public interface IContainer
{
//members here
}
public class File : IContainer
{}
public class Worksheet : IContainer
{}
Basically I want to call an action method :
public ActionResult Get(IContainer container)
{
// Do something
}
The reason being that I need to retrieve the state of the current container passed to my action method from the database. I use NHibernate and have entities mapped on a table per entity, so have one for File and one for Worksheet for example, so need to able to decide which data access class to use. Make sense? Probably not!
Can this be done without moving towards a base class Container? Can I stick with an interface being passed to my action method and resolve the subtype instance passed in place of the interface?
Any help with this would be gratefully appreciated.
An interface needs 'some' concrete implementation to reference when you would call your class. I think judging by your post you are aware of this : )
With that said there is 'kinda' of an approach handled here where you just create your own model binder that has to know about (or how) to map to and create a concrete type (either directly or by dependency injection)
ASP.NET MVC - Custom Model Binder on Interface Type

How can I load multiple controller factories and pass control on to the next one?

I've created a generic controller factory to load entities from the database by parsing out the url:
entity/products/123456.htm
However, I'd like to be able to load an actual controller if the entity is not found, or to override the default entity behavior if necessary by creating a physical controller, instead of a "virtual" one created by the URL pattern.
Right now, in global.asax.cs I'm doing:
ControllerBuilder.Current.SetControllerFactory(typeof(EntityControllerFactory));
How can I, either in EntityControllerFactory, or here in global.asax.cs, pass control on to another factory, in the event that I'd like MVC's default controller/action scheme to take over?
You could create a composite IControllerFactory implementation:
public class EntityControllerFactory : IControllerFactory {
private IControllerFactory defaultFactory = new DefaultControllerFactory();
public IController CreateController(RequestContext requestContext, string controllerName) {
if(needsCustomLogic) {
// do your custom logic here and return appropriate result
} else {
return defaultFactory.CreateController(requestContext, controllerName);
}
}
// same for the other methods on IControllerFactory
}
This works because by default the value of ControllerBuilder.Current.GetControllerFactory() is an instance of DefaultControllerFactory.
You might also consider making your factory more future-proof (in case a new version of MVC starts returning a different type from GetControllerFactory; unlikely but it could happen) by getting the default instance and passing it into your factory:
// in Global.asax
var defaultFactory = ControllerBuilder.Current.GetControllerFactory();
ControllerBuilder.Current.SetFactory(new EntityControllerFactory(defaultFactory));

Resources