MVC - one controller action with multiple model types/views or individual controller actions? - asp.net-mvc

In my project I have a controller that allows you to create multiple letters of different types. All of these letter types are stored in the database, but each letter type has different required fields and different views.
Right now I have a route set up for the following URL: /Letters/Create/{LetterType}. I currently have this mapped to the following controller action:
public ActionResult Create(string LetterType)
{
var model = new SpecificLetterModel();
return View(model);
}
I also have a View called Create.cshtml and an EditorTemplate for my specific letter type. This all works fine right now because I have only implemented one Letter Type. Now I need to go ahead and add the rest but the way I have my action set up it is tied to the specific letter type that I implemented.
Since each Letter has its own model, its own set of validations, and its own view, what is the best way to implement these actions? Since adding new letter types requires coding for the model/validations and creating a view, does it make more sense to have individual controller actions:
public ActionResult CreateABC(ABCLetterModel model);
public ActionResult CreateXYZ(XYZLetterModel model);
Or is there a way I can have a single controller action and easily return the correct model/view?

You can do one of the following:
Have a different action method for each input. This is because the mvc framework will see the input of the action method, and use the default model binder to easily bind the properties of that type. You could then have a common private method that will do the processing, and return the view.
Assuming XYZLetterModel and ABCLetterModel are subclasses of some base model, your controller code could look like:
public class SomeController : Controller
{
private ISomeService _SomeService;
public SomeController(ISomeService someService)
{
_SomeService = someService;
}
public ViewResult CreateABC(ABCLetterModel abcLetterModel)
{
// this action method exists to allow data binding to figure out the model type easily
return PostToServiceAndReturnView(abcLetterModel);
}
public ViewResult CreateXYZ(XYZLetterModel xyzLetterModel)
{
// this action method exists to allow data binding to figure out the model type easily
return PostToServiceAndReturnView(xyzLetterModel);
}
private ViewResult PostToServiceAndReturnView(BaseLetterModel model)
{
if (ModelState.IsValid)
{
// do conversion here to service input
ServiceInput serviceInput = ToServiceInput(model);
_SomeService.Create(serviceInput);
return View("Success");
}
else
{
return View("Create", model);
}
}
}
The View code could look like:
#model BaseLetterModel
#if (Model is ABCLetterModel)
{
using (Html.BeginForm("CreateABC", "Some"))
{
#Html.EditorForModel("ABCLetter")
}
}
else if (Model is XYZLetterModel)
{
using (Html.BeginForm("CreateXYZ", "Some"))
{
#Html.EditorForModel("XYZLetter")
}
}
You would still have an editor template for each model type.
Another option is to have a custom model binder that figures out the type, based on some value in a hidden field, and then serializes it using that type.
The first approach is much more preferred because the default model binder works well out of the box, and it's a lot of maintenance to build custom model binders.

Related

Should you have separate view Models for input to and output from a controller

I am new to asp.net mvc. I have this controller that takes in a few parameters and then returns a view that gets data based on the input parameters.
I want to accept the input parameters as an object (for example instead of first name, last name and age, I want a person class that has these three parameters as its properties). Now my question is does the input parameter class (Person class) qualify to be called view model?
If yes. Do I make the return view model a part of this class?
In other words which of the bottom two approaches is preferred
Case 1: Same class for input and return
public ActionResult GetPersonDetails(Person p)
{
return View(new Person {....})
}
Case 2: Separate classes for input and return
public ActionResult GetPersonDetails(Person p)
{
return View(new PersonDetails {....})
}
Now my question is does the input parameter class (Person class)
qualify to be called view model?
Yes.
If yes. Do I make the return view model a part of this class?
Not necessarily. You could have different view model passed to the view as the one that your controller action is taking as parameter, although this is rare case scenario. It would really depend on your specific case but the general pattern is the following:
[HttpGet]
public ActionResult Index()
{
MyViewModel model = ...
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
// Some validation error occurred => redisplay the same view so
// that the user can fix his errors
return View(model);
}
// at this stage the view model has passed all validations =>
// here you could attempt to pass those values to your backend
// TODO: do something with the posted values like updating a database or something
// Finally redirect to a successful action Redirect-After-Post pattern
// http://en.wikipedia.org/wiki/Post/Redirect/Get
return RedirectToAction("Success");
}

How to convert DTO to View Model and then back again? [duplicate]

This question already has answers here:
Where to convert business model to view model?
(3 answers)
Closed 5 years ago.
I'm using MVC 4 with the repository pattern and unit testing also. I have a typical controller that has simple CRUD functionality. I've separated my View Models from my DTOs and I would like to know the best way to convert between the 2:
Models:
I have Admin.Models.Product which is my view model and AdminAssembly.Models.Product which is my DTO.
Controller:
//repo that handles product operations
AdminAssembly.Interfaces.IEntityRepository<AdminAssembly.Models.Product> db;
//default constructor
public ProductController() { db = new AdminAssembly.Repositories.EntityRepo<AdminAssembly.Models.Product>(new AdminAssembly.Models.EntitiesContext()); }
//unit testing constructor
public ProductController(AdminAssembly.Interfaces.IEntityRepository<AdminAssembly.Models.Product> context) { db = context; }
//
// POST: /Product/Create
[HttpPost]
public ActionResult Create(Admin.Models.Product product) {
if (ModelState.IsValid) {
//COMPILE-ERROR: how to convert to DTO?
db.Add(product);
}
return View();
}
//
// GET: /Product/Edit/5
public ActionResult Edit(int id) {
//COMPILE-ERROR: how to convert to view model?
Admin.Models.Product product = db.GetAll().Where(p => p.ID == id);
return View(product);
}
How do I convert between the 2?
Do I reference my DTO assembly in my view model and do something like: (won't this break my unit testing?)
//convert to AdminAssembly.Models.Product
db.Add(product.ToDTO());
//convert back to Admin.Models.Product via constructor
Admin.Models.Product product = Admin.Models.new Product(db.GetAll().Where(p => p.ID == id));
Do I need some sort of object conversion black box?
Converter.ToViewProduct(product);
Some sort of interface?
or something else?
Update 1:
public static class Product {
public static Admin.Models.Product ToView(AdminAssembly.Models.Product dto) {
Admin.Models.Product viewProduct = new Admin.Models.Product();
//straight copy
viewProduct.Property1 = dto.Property1;
viewProduct.Property2 = dto.Property2;
return viewProduct;
}
public static AdminAssembly.Models.Product ToDTO(Admin.Models.Product viewModel) {
AdminAssembly.Models.Product dtoProduct = new AdminAssembly.Models.Product();
//straight copy
dtoProduct.Property1 = viewModel.Property1;
dtoProduct.Property2 = viewModel.Property2;
//perhaps a bit of wizza-majig
dtoProduct.Property1 = viewModel.Property1 + viewModel.Property2;
return dtoProduct;
}
}
The long-hand response
[HttpPost]
public ActionResult Create(Admin.Models.Product product)
{
if (ModelState.IsValid)
{
//COMPILE-ERROR: how to convert to DTO?
var dtoProduct = new AdminAssembly.Models.Product();
dtoProduct.Property1 = product.Property1;
dtoProduct.Property2 = product.Property2;
//...and so on
db.Add(dtoProduct);
}
return View();
}
While this looks verbose and tedious (and it is) it has to happen eventually, somewhere.
You can hide this mapping either in another class or extension method, or you can use a third party like AutoMapper, as Charlino points out.
As a side note, having two classes with the same name in two different namespaces will eventually get confusing (if not for you, then for the next person who has to maintain your code.) Implement friendlier and more descriptive names wherever possible. For example, put all your view models in a folder called ViewModels, not Models. And append all your view models with ViewModel, or VM. It's also a good convention, imo, to name your view models based on the view that they are for, not so much the domain model that they will be mapped to, as not all view models will map directly to a domain model. Sometimes you'll want parts of more than one domain model, for a single view, and that will blow up your naming convention.
So in this particular case I would suggest changing Admin.Models to Admin.ViewModels and then rename the view model version of Product to CreateViewModel. Your code will be much more readable and will not be littered with namespaces throughout your methods.
All of that would result in a method that would look more like this:
[HttpPost]
public ActionResult Create(CreateViewModel viewModel)
{
if (ModelState.IsValid)
{
var product = new Product();
product.Property1 = viewModel.Property1;
product.Property2 = viewModel.Property2;
//...and so on
db.Add(product);
}
return View();
}
Check out a library called AutoMapper.
From their wiki:
What is AutoMapper?
AutoMapper is a simple little library built to solve a deceptively complex problem - getting rid of code that mapped one object to another. This type of code is rather dreary and boring to write, so why not invent a tool to do it for us?
If you dont want to use AutoMapper you may use extensions, as suggested by #Forty-Two. If the number of things to map is no very great, I would go with this approach, just because then, AutoMapper == YAGNI
public static class Extensions
{
public static ViewModel ToViewModel(this Model )
{
var vm = new ViewModel()
{
//map
};
return vm;
}
public static Model ToModel(this ViewModel viewModel)
{
var model = new Model()
{
//map
};
return model;
}
}
Similar to your code in UPDATE, but using extensions instead.

ASP.NET MVC Posting models to an action with an an Interface

My understanding is that ASP.NET MVC only allows you to POST objects to Actions in the Controller, where the Action's arguments accept the posted object as a Concrete class.
Is there any way around this, or a good alternative?
In my case, I have an action which accepts an interface as an argument:
public ActionResult SaveAdjustment(IModel model)
{
switch (model.SubsetType)
{
// factory like usage
}
}
And for this action, I have numerous views, all strongly typed to objects that implement IModel, all which I want to be able to post to this one method.
Of course, running this give me the error:
Cannot create an instance of an interface
Is there a nice work around to this? Or do I need to create an Action method for each and send them over to a method like this?
MVC generally binds models when posting from Request.Form, that is collection of name=value pairs. The reason that in default implementation there's no support of binding interfaces or abstract classes is obvious - mvc cannot determine which concrete class to create from name=value pairs. If you got hidden field on client side, or any other parameter anywhere by which you are able to determine which type of concrete class to create, you can simply create custom model binder. I believe you can override DefaultModelBinder's CreateModel method and reuse all other built in binding functionality
public class IModelModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType)
{
//Create and return concrete instance
}
}
And model binder registration in global.asax
ModelBinders.Binders.Add(typeof(IModel?), new IModelModelBinder());
Actually, controllers and actions in mvc are meant to be thin, and some kind of service layer should be thick. As action logic you are trying to implement may get complicated soon, I would recommend moving it into separate service.
Although I mentioned this as a possible solution in my original question, its the solution I have gone with in the end and I actually quite like it now. This way I didn't need to touch the model default binding implementation and I think this approach is a more readable/understandable approach than what I was originally asking for.
In case its not clear why I wanted to go for this approach, I have added an example of how I can use this for its OO benifits.
[HttpPost]
public ActionResult SaveModelA(ModelA model)
{
return SaveModel(model);
}
[HttpPost]
public ActionResult SaveModelB(ModelB model)
{
return SaveModel(model);
}
private ActionResult SaveModel(IModel model)
{
IExampleService exampleService;
IRequirements requirements;
switch (model.SubsetType)
{
case SubsetType.ModelA:
myService = new ModelAService();
requirements = new ModelARequirements
{
ModelASpecificProperty = "example"
};
break;
case SubsetType.ModelB:
myService = new ModelBService();
requirements = new ModelBRequirements
{
ModelBSpecificProperty1 = "example",
ModelBSpecificProperty2 = "example2",
ModelBSpecificProperty3 = "example3"
};
break;
default:
throw new InvalidEnumArgumentException();
}
var serviceResonse = exampleService.ExecuteExample(model, requirements);
return RedirectToAction("Index", new
{
ExampleData = serviceResponse.ExampleDate
});
}
In case it isn't clear in the code:
ModelA : IModel
ModelB : IModel
ModelARequirements : IModelRequirements
ModelBRequirements : IModelRequirements
ModelAService : IExampleService
ModelBService : IExampleService
// and IModel defines a property SubsetType SubsetType { get; }

Using Data Annotations with similar models and the same view to have different validation

I have two separate classes derived from the same interface, but have different validation/data annotations assigned. The requirement is that the same data needs to be collected, but on one screen nothing is required (a save screen), but on the other there are some required fields (a submit/finalize screen.)
I've made a PartialView that is to be used in two separate View, one for save, one for final submit.
I've tried using the parent Interface as the View's model, however my validators don't fire (as I expect, I'm guessing that because the Interface itself doesn't have any annotations, nothing will fire.) Is there a way to have the page dynamically choose one class or the other depending on which page I'm using instead of the Interface?
As a side-note, this is being done in ASP.net MVC 3 with Razor.
You can achieve what you want with one class, and a little lateral thinking.
First, create your class, with the validation baked in. Next, create a custom ModelValidatorProvider inheriting from DataAnnotationsModelValidatorProvider, like so:
public class MyMetadataValidatorProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var vals = base.GetValidators(metadata, context, attributes);
// check to see if any keys have been inserted
if (context.Controller.ViewData.Keys.Count > 0)
{
// check if we have a key named "NoValidate" with a value of true
// do not return the validtors if we do
if ((bool)context.Controller.ViewData.FirstOrDefault(k => k.Key == "NoValidate").Value)
{
// we do not want to return our validators, return an empty list
return new List<ModelValidator>();
}
}
else
{
// check if the form has a key named "NoValidate" with a value of true
// do not return the validtors if we do
if (context.HttpContext.Request.Form["NoValidate"].ToLowerInvariant() == "true")
{
// we do not want to return our validators, return an empty list
return new List<ModelValidator>();
}
}
// we want to return our validators
return vals;
}
}
Next, register the custom ModelValidatorProvider in Application_Start in Global.asax.cs, like so:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MyMetadataValidatorProvider());
Then, add the following to your view (this will govern whether the validators are returned when the form is POSTed):
#Html.Hidden("NoValidate", ViewData.FirstOrDefault(k => k.Key == "NoValidate").Value)
Finally, add actions like the following:
public ActionResult Index()
{
var model = new MyModel();
// this will set validation to appear
ViewData.Add("NoValidate", false);
// this will suppress validation
ViewData.Add("NoValidate", true);
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel model)
{
// we DO want validation, so let's test for it in addition
// to testing if the ModelState is valid
if (Request.Form["NoValidate"].ToLowerInvariant() != "true" && ModelState.IsValid)
{
ModelState.Clear();
var newmodel = new MyModel();
ViewData.Add("NoValidate", true);
return View(newmodel);
}
ViewData.Add("NoValidate", false);
return View(model);
}
Note that you can control whether the validation appears in your GET action by setting the NoValidate key in ViewData as you want. On the POST, the validation is governed by the form value for NoValidate.
IMPORTANT NOTE: In your action which requires validation, you need to add a test to confirm that the Form does not have the key NoValidate, or its value is not True, in order to enforce that a user cannot avoid the validation.
UPDATE
At first, I had validation only appearing when certain conditions were true. Idecided this was a BAD IDEA, so now validation will only be suppressed if the conditions are true.
Each view should be strongly typed to a separate view model. Each viewmodel then has the validation logic on it (annotations) or inherits from a base that has the required validation on it.
Any logic that cannot be inherited is simply set on your ViewModel itself. If its a small moderl I would consider just copy/paste and two separate viewmodels with their own set of attributes.
You can use AutoMapper to easily map between some concrete object that implements your interface and your ViewModels.
Could you use one class? You can create a filter that allows you to manage the validation errors for an action. In your case you can add an attribute to the Save action and ignore the required errors, but the validations will run for the submit/finalize action. This example will discard all the errors.
public class DontValidateEmailAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var modelState = filterContext.Controller.ViewData.ModelState;
var incomingValues = filterContext.Controller.ValueProvider;
foreach (var key in modelState.Keys)
modelState[key].Errors.Clear();
}
}
I learnt this technique from Steve Sanderson's Pro ASP NET MVC 3. He uses the technique to validate a model that has required fields but the data entry is a multistep wizard. If the value has not been returned in the form post, he removes the errors for that property.

Determine the model of a partial view from the controller within MVC

My current problem is that I have a partial view that I want to determine what model is being used by it.
I have had to deal with a few strange scenarios for my project so I will try to outline it here, maybe someone can offer a better way to do this.
I am designing something like the Google iGoogle page. A main page with multiple widgets that are able to move around or be configured as needed. The current system loads the actual widget's data asynchronously view a POST to a controller within my application. That controller will either render a partial view to HTML that can be returned (and then loaded into the page view JQUERY) or just straight HTML/JavaScript that is stored in a database.
This was working fine for me, I had a model for the widgets that holds a dictionary of options that are described via the database, and then used by the partial view. The problem came when I wanted to pass data to a partial view. The best solution I could come up with was having the controller determine which model the partial view in question uses, have some function that will fill the model, and then pass it, along with the partial view, to the function that will render it to HTML within the controller.
I realize this is an odd scenario for MVC (the layers are blending...) and any advice on fundamental design, or implementation of this would be greatly appreciated.
I am currently using MVC3/Razor. Feel free to ask any other questions.
I prototyped a possible solution to this, because it seemed like a fun problem. I hope it's useful to you.
Models
First, the models. I decided to create two 'widgets', one for news, and one for a clock.
public class NewsModel
{
public string[] Headlines { get; set; }
public NewsModel(params string[] headlines)
{
Headlines = headlines;
}
}
public class ClockModel
{
public DateTime Now { get; set; }
public ClockModel(DateTime now)
{
Now = now;
}
}
Controller
My controller doesn't know anything about the views. What it does is returns a single model, but that model has the ability to dynamically fetch the right model as required by the view.
public ActionResult Show(string widgetName)
{
var selector = new ModelSelector();
selector.WhenRendering<ClockModel>(() => new ClockModel(DateTime.Now));
selector.WhenRendering<NewsModel>(() => new NewsModel("Headline 1", "Headline 2", "Headline 3"));
return PartialView(widgetName, selector);
}
Delegates are used so that the correct model is only created/fetched if it is actually used.
ModelSelector
The ModelSelector that the controller uses is pretty simple - it just keeps a bag of delegates to create each model type:
public class ModelSelector
{
private readonly Dictionary<Type, Func<object>> modelLookup = new Dictionary<Type, Func<object>>();
public void WhenRendering<T>(Func<object> getter)
{
modelLookup.Add(typeof(T), getter);
}
public object GetModel(Type modelType)
{
if (!modelLookup.ContainsKey(modelType))
{
throw new KeyNotFoundException(string.Format("A provider for the model type '{0}' was not provided", modelType.FullName));
}
return modelLookup[modelType]();
}
}
The Views - Simple solution
Now, the easiest way to implement a view would be:
#model MvcApplication2.ModelSelector
#using MvcApplication2.Models
#{
var clock = (ClockModel) Model.GetModel(typeof (ClockModel));
}
<h2>The time is: #clock.Now</h2>
You could end here and use this approach.
The Views - Better solution
That's pretty ugly. I wanted my views to look like this:
#model MvcApplication2.Models.ClockModel
<h2>Clock</h2>
#Model.Now
And
#model MvcApplication2.Models.NewsModel
<h2>News Widget</h2>
#foreach (var headline in Model.Headlines)
{
<h3>#headline</h3>
}
To make this work, I had to create a custom view engine.
Custom view engine
When a Razor view is compiled, it inherits a ViewPage<T>, where T is the #model. So we can use reflection to figure out what type the view wanted, and select it.
public class ModelSelectorEnabledRazorViewEngine : RazorViewEngine
{
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
var result = base.CreateView(controllerContext, viewPath, masterPath);
if (result == null)
return null;
return new CustomRazorView((RazorView) result);
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
var result = base.CreatePartialView(controllerContext, partialPath);
if (result == null)
return null;
return new CustomRazorView((RazorView)result);
}
public class CustomRazorView : IView
{
private readonly RazorView view;
public CustomRazorView(RazorView view)
{
this.view = view;
}
public void Render(ViewContext viewContext, TextWriter writer)
{
var modelSelector = viewContext.ViewData.Model as ModelSelector;
if (modelSelector == null)
{
// This is not a widget, so fall back to stock-standard MVC/Razor rendering
view.Render(viewContext, writer);
return;
}
// We need to work out what #model is on the view, so that we can pass the correct model to it.
// We can do this by using reflection over the compiled views, since Razor views implement a
// ViewPage<T>, where T is the #model value.
var compiledViewType = BuildManager.GetCompiledType(view.ViewPath);
var baseType = compiledViewType.BaseType;
if (baseType == null || !baseType.IsGenericType)
{
throw new Exception(string.Format("When the view '{0}' was compiled, the resulting type was '{1}', with base type '{2}'. I expected a base type with a single generic argument; I don't know how to handle this type.", view.ViewPath, compiledViewType, baseType));
}
// This will be the value of #model
var modelType = baseType.GetGenericArguments()[0];
if (modelType == typeof(object))
{
// When no #model is set, the result is a ViewPage<object>
throw new Exception(string.Format("The view '{0}' needs to include the #model directive to specify the model type. Did you forget to include an #model line?", view.ViewPath));
}
var model = modelSelector.GetModel(modelType);
// Switch the current model from the ModelSelector to the value of #model
viewContext.ViewData.Model = model;
view.Render(viewContext, writer);
}
}
}
The view engine is registered by putting this in Global.asax.cs:
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new ModelSelectorEnabledRazorViewEngine());
Rendering
My home view includes the following lines to test it all out:
#Html.Action("Show", "Widget", new { widgetName = "Clock" })
#Html.Action("Show", "Widget", new { widgetName = "News" })
One option would be to extend the idea of partial requests in your application. Steve Sanderson has a fantastic example of this, although the post relates to MVC 1 & 2. I think it would still help in you v3, but I haven't investigated v3 to see if the MVC team implemented their own version. In your asynch scenario, you'll need to toy with the implementation a bit, perhaps change the PartialRequest definition to accept different information as needed, but I think this might be a good start. The net result would be better isolation of concerns, allowing individual controllers to manage a particular type of partial, and in turn be better aware of the model Type you want to work with.
I'm not 100% sure that this is what you'd be looking for, but the [ChildActionOnly] attribute can be added to a method within your controller. That requires that the method can only be called from a partial view. Then you can set up your partial view for that method that basically resembles one of your widgets. Check out the MVC Music Store example here:
http://www.asp.net/mvc/tutorials/mvc-music-store-part-10
What about a dynamic view model? Layouts in MVC3 use them, and maybe you can use something similar for your purposes:
Dynamic in C# 4.0: Introducing the ExpandoObject
Fun With Method Missing and C# 4
Dynamic View Page, MVC without a View Model
I blogged about doing exactly this. Please see http://blogs.planetcloud.co.uk/mygreatdiscovery/?tag=/widget
Essentially I built out a similar widget system. The posts also cover how to handle configuration of those widgets. This makes use of the dynamic support in Mvc3 so that any model object can be passed to the view, from a single controller action.
By default all widgets have a collection of KVP properties (I believe this is what the OP has). So for a simple widget we get access to those properties from within the view. I used for a widget that displayed some html (where the html was stored in one of those properties).
However, for more complex widgets we implement IWidgetWithDisplayModel. This tells us that before we pass the loaded widget back to the view, we need to "build" our display model.
Here's the controller action that does that. Check the posts for full details.
[HttpGet]
public ActionResult Get(string name)
{
var widget = widgetService.GetWidgetBySystemName(name, true);
if (widget == null)
return Content(string.Format("Widget [{0}] not found!", name));
if (!this.ViewExists(widget.WidgetName))
return Content(string.Format("A template for widget [{0}] was not found.", widget.WidgetName));
if (widget is IWidgetWithDisplayModel) {
(widget as IWidgetWithDisplayModel).CreateDisplayModel();
}
return PartialView(widget.WidgetName, widget);
}

Resources