Can you send different view models to a view? - c#

Say I am implementing a view for Food. (ASP.NET MVC2)
Then depending on the type (say fruit or vegetable for example) I will change the view.
Can I do this without creating a seperate view for fruit and vegetable?
I.e. Say we have url structure like http://localhost:xxxx/Food/{foodID}
and don't want
http://localhost:xxxx/Veg/{foodID}
http://localhost:xxxx/Fruit/{foodID}
So I want to be able to change the view depending on the type. I'm using a tabstrip control from telerik, to give you an idea of the difference in the views - it'd just be say - not displaying one particular tab for Veg, and displaying it if fruit, for example.
Can a view accept two different view models ? so when we hit http://localhost:xxxx/Food/{foodID} the code determines what type the object is (Fruit or Vegetable) then sends either FruitViewModel or VegetableViewModel ? If I just send one viewmodel how can I control the logic to display or not display certain things in the view?

If you define FoodViewModel as a base class and have FruitViewModel and VegetableViewModel extend it, you could have ViewPage<FoodViewModel> and pass in either. Then, your view can check for which specific subclass it got and render the appropriate output.
Alternatively, if the only difference between FruitViewModel and VegetableViewModel is that one is Fruit and one is Vegetable (but all other properties are shared, i.e., Name, Calories, Color, Cost), have a FoodType property on the shared FoodViewModel and use it to conditionally render the appropriate output.

What alternative is the best depends on how much the Fruit and Veg Views differ:
Alternative 1:
You can create two different Views (you can pass the view name to the View method):
public ViewResult Food(int id)
{
var food = ...
if (/* determine if food is Veg or Fruit */)
return View("Fruit", new FruitViewModel { ... });
else
return View("Veg", new VegViewModel { ... });
}
By returning a different View the url doesn't change (as it does when using return RedirectToAction("Action", "Controller", ...) which implies a HTTP redirect.
Alternative 2:
You can have a common FoodViewModel extended by both the FruitViewModel and the VegViewModel. Then your FoodView can be of type FoodViewModel and decide what to show in your View code.

If the only thing you need to change is the tabstrip setting. You can provide a property called "ShowTabItem" on your ViewModel, then bind that property to your TabStripItem in your view.
public class FoodViewModel
{
Food _food;
public FoodViewModel(Food food)
{
}
public bool ShowTabItem
{
get
{
return _food.Type == FoodType.Vegetable;
}
}
}
Bind your ShowTabItem property to the Visibility or Enabled property of the tabstrip item. (whatever fits)
then your controller will simply be
public ActionResult Food(int id)
{
Food food = getFood(id);
return View(new FoodViewModel(food));
}
Let your ViewModel decide what needs to be displayed.
Hope this helps!

Related

MVC - Generic Actions

I've got a page in an app I'm building. The page contains a few bits and pieces, then a partial view that loads a different view depending on what's selected from a dropdown. Each of the options from the dropdown has a different view associated with it, and each view has its own fields and model.
Whatever the view is that loads, I'm performing the same action - I'm serializing the model and storing the XML in a database. This is always the case, and there is no unique processing based on the views/models (other than the fact that the fields are different). All models inherit from the same base class for serialization purposes.
I wanted to be able to do something like:
public ActionResult SubmitPartialView<T>(T model)
{
BaseClass baseClassModel = (BaseClass)(object)model;
// serialize and save to database
}
But MVC doesn't allow this - "cannot call action on controller because the action is a generic method".
If I try passing the BaseClass in as a parameter itself, it only contains the properties of the base class and therefore none of the model's properties.
Is there no other option other than to create a separate action for every single view that can submit, and make each one call a separate method that handles the logic?
I see this question is a little old, but if it helps anyone - I was doing some reading with dynamic models and MVC, saw this and it led me to think of a possible solution. Not sure why you would want to have dynamic models. But the great thing with MVC is, you can!
So;
[HttpPost]
public ActionResult SubmitPartial([DynamicModelBinder] dynamic model)
{
// Our model.ToString() serialises it from the baseModel class
var serialisedString = model.ToString();
// do something .. echo it back for demo
return Content(serialisedString);
}
And the model binder is something like this;
public class DynamicModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var currentModel = controllerContext.HttpContext.Request.Form["CurrentModel"];
if (currentModel == "CompanyModel")
{
Type customModel = typeof(CompanyModel);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, customModel);
}
if (currentModel == "UserModel")
{
Type customModel = typeof(UserModel);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, customModel);
}
return base.BindModel(controllerContext, bindingContext);
}
}
hth

Share a single Razor Create View with a set of Models that have the property in a scalable way

In MVC C# Web Applications with Razor, I constantly end up wanting to reuse View code for Create actions.
Imagine this scenario:
public class Taco
{
public Lunch Lunch { get; set; }
public Supper Supper { get; set; }
public string Cheese { get; set; }
}
public class Lunch
{
public IEnumerable<Taco> Taco { get; set; }
}
public class Supper
{
public IEnumerable<Taco> Taco { get; set; }
}
You have Lunch and Supper that have Tacos.
Now take these two use cases:
From Supper's Details View
Want to add a Taco
Click 'Create New Taco'
Enter Taco information
Click 'Create' Button
Redirected to Supper Details with new Taco there
From Lunch's Details View
Want to add a Taco
Click 'Create New Taco'
Enter Taco information
Click 'Create' Button
Redirected to Lunch Details with new Taco there
What is a scalable and MVC-Correct way to do this?
I have always felt like my process for this is hacked together and not very scalable. I might do something like:
Supper View:
#Url.Action("Create", "Taco", new { From = "Supper" })
Lunch View:
#Url.Action("Create", "Taco", new { From = "Lunch" })
Then take the "From" variable and pass it to
Taco Controller>Taco View Model>Taco View>Link Back To From
Is there a built in way to pass referrer information and is there a pre-defined design template for MVC to handle these situations?
Just literally reuse everything. For example, you can have just one action with one view and use the URL to determine behavior. All you need is a view model so the form can work with just one class type, and then you can map the data onto wherever it should go. For example:
[HttpPost]
[ValidateAntiForgeryToken]
[Route("{mealType:regex(supper|lunch)}/create")]
public ActionResult CreateMeal(string mealType, MealViewModel model)
{
if (ModelState.IsValid)
{
switch (mealType)
{
case "supper":
// map data to new `Supper` and save
break;
case "lunch":
// map data to new `Lunch` and save
break;
}
// do redirect
}
return View(model);
}
There's other ways to handle this without using Attribute Routing, but the general idea is that in some form or fashion you indicate which type of meal is being saved, and branch accordingly, creating and saving the appropriate class.
As far as I know there is no pre-defined template. But you can create a EditorTemplate if you want one and widely used in your razor views.
Also, instead of sending From in route parameters, you can have a property in Supper and Lunch classes like gobackUrl (just example) and generate gobackUrl in Create GET action and have it in hidden form value. So, the child action view will be more generic and you don't need to have if-else logic in parent view.

Do formatting helper methods belong in the model, the view model, or a separate class?

I have a model that stores company information, including tax IDs. In the US, these are 9 digit numbers and are typically displayed as ##-#######. However, in my system, I am storing these as strings with no hyphen - since other countries can have identification numbers that differ in length and format, I don't want be limited to a US standard.
Now I want to program my views to display US tax IDs in their "friendly" format. I have this working right now with a helper method I put in the Company model class:
public string FormatTaxID(string TaxID)
{
if (Address.Country == "United States")
return Regex.Replace(TaxID, #"(\d{2})(\d{7})", "$1-$2");
else
return TaxID;
}
Then in my view, I'm using:
#item.FormatTaxID(item.TaxID)
This all works fine, but it doesn't feel right to store a method like this in the model - it feels like this is more of a view/view model responsibility than a model responsibility, as it is solely for presentation.
I am using view models and thought of putting it there, but I I have multiple view models for the underlying model and don't want to repeat code if I don't have to. Also, my view model for the index uses collections and I'm not sure how I would work the method into it:
public class CompanyIndexViewModel
{
public IEnumerable<Company> Companies { get; set; }
public IEnumerable<Document> Documents { get; set; }
}
How would I apply this method to a collection like that?
Another option is creating a new helper/utility class and sticking it in there. What would MVC convention dictate?
For one-offs, I'd say use the view model. If it's something that you will reuse over and over, move it into a utility class that your views/view models/etc. can reference.
And, there's technically nothing wrong sort of doing it both ways. Put the method in a utility class and then add a property to your view model that returns this, e.g.:
public class CompanyIndexViewModel
{
...
public string TaxID { get; set; }
public string USFormattedTaxID
{
get { return Utilities.FormatTaxID(TaxID); }
}
}
The localized-to-the-Company-context TaxID of the company is properly a property of the Company, and is not a presentation detail.

What is the model in MVVM for?

I have read several articles, tutorials and blog posts about the MVVM pattern. However there is one thing I don't understand. Taking the three "layers":
Model
View
View Model
As far as I have understood MVVM the model contains the "raw" data, e.g. a name and address in case of a Student class. The view model exposes properties to the view which represent data of the model.
Example for a property in the view model
public string Name {
get { return model.Name; }
set { model.Name = value; }
}
Example for the model
private string name;
public string Name {
get { return name; }
set { name = value; }
}
This might sound a bit stupid but doesn't this create a redundancy? Why do I have to keep the name in the model and in the view model? Why should one not handle the name on the view model completely?
In such a simple example, this answer would be yes (it is unreasonably redundant). But, presumably, a page will contain more than just a single Model object. You may have the page state as well as multiple other Model objects which must all be tracked. This is done in the ViewModel.
For example, you may have additional information about the logged in user displayed in a status bar, as well as a service running to detect changes to a text file.
You may also have a form for editing the Student object. If you intend to validate those changes, then you wouldn't want to directly edit the Student object until after the modifications have been verified. The ViewModel can act as a temporary storage location in such a case.
Note on the above: It is not uncommon for validation to occur in the Model, but even then you will probably want the user to be able to enter invalid values while in the process of editing a form. For example, if your Model does not allow a zero-length value in a field, you still want to enable your user to delete the value, move to another field (say, for example, to copy it) then return to the field and finish editing (paste). If you are tied directly to the Model, then your validation logic may not handle this "in-between", "not-yet-finished" state as you'd like. For example, you might not want to accost your user with validation errors until they've finished and clicked 'Save'.
You will also probably have Command objects in the ViewModel to handle button clicks and the like. These would be domain-specific objects that would be useless in a Model.
ViewModels are also useful when you need to filter or somehow temporarily "modify" Model objects to get something useful on the screen. For example, you may want to display a list of all the Users in a system along with a real-time list of the top ten performers among them (updated every 10 seconds). Or you may want to show a list of Reports and a graph showing the overall usage rate, etc. Filtering, sorting and customizing that data would take place within the ViewModel.
The Model, on the other hand, is typically as pure as possible. Ideally, you want only POCOs that (usually) model exactly what's in your persistent storage (database, or what have you). If your persistent storage has FirstName and LastName fields, then so would your Model. Only in your ViewModel would you combine them to get a Name field (either "First Last" or "Last, First" depending on the View's needs).
For example:
namespace Model
{
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Class
{
public string Name { get; set; }
public float Score { get; set; }
}
}
namespace ViewModel
{
public class EditStudentRecordViewModel
{
private Model.Student _student;
private IEnumerable<Model.Class> _studentClasses;
/* Bind your View to these fields: */
public string FullName
{
return _student.LastName + ", " + _student.FirstName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public IEnumerable<Model.Class> PassingClasses
{
get
{
return _studentClasses.Where( c => c.Score >= 78 );
}
}
public IEnumerable<Model.Class> FailingClasses
{
get
{
return _studentClasses.Where( c => c.Score < 78 );
}
}
public void Save()
{
List<string> l_validationErrors = new List<string>();
if ( string.IsNullOrEmpty( this.FirstName ) )
l_validationErrors.Add( "First Name must not be empty." );
if ( string.IsNullOrEmpty( this.LastName ) )
l_validationErrors.Add( "Last Name must not be empty." );
if ( l_validationErrors.Any() )
return;
_student.FirstName = this.FirstName;
_student.LastName = this.LastName;
Model.Utilities.SaveStudent( _student );
}
}
}
The model is the object graph that contains your business logic.
That's where you hold the behaviour (validation, calculation and such).
The ViewModel is something that models the UI and its interactions.
These are different and have different reasons for existing - the point of the pattern is to separate your display logic to the VVM (View and ViewModel) and have your business logic completely separated.
The view model is where you would keep track of properties that are specific to the view and not necessary to the model.
Let's take your model, assume it's called Person.
And then you create a view model for Person called PersonViewModel, which looks like this:
public class PersonViewModel
{
public Person Person { get; set; }
}
(Note, you might not want to expose the model like this directly, but that's another story)
Now let's say that you have an button in the view which is used to save the Person instance. To provide a better user experience (UX), you want to enable the button only if your model has actually changed. So you implement the INotifyPropertyChanged interface on the Person class:
public class Person : INotifyPropertyChanged
{
...
Now, you could expose a HasUnsavedChanges property from your Person which the Enabled property on the save button would bind to, but that logic has nothing to do with the person.
This is where the view model comes in. You would define this view-specific property on the view model, like so:
public class PersonViewModel
{
public Person Person { get; set; }
public bool HasUnsavedChanges { get; set; }
}
Then, your view model would subscribe to the PropertyChanged event of the INotifyPropertyChanged interface, and toggle the HasUnsavedChanges property on the view model.
Then, if the binding is set up correctly, the save button would enable/disable when any change happens on your model, but your model doesn't have any logic tying it to the view.
Note that you'd have to also implement INotifyPropertyChanged on the view model as well for your view to pick up when changes are made to the view model it is bound to.
Again, the point is acting as a bridge to contain the logic that is a combination of model properties and view properties that don't belong on the model.
Model in MVVM is exactly the same as in MVP or Model2 MVC. It is the one part of MVC-inspired patterns that is not affected by variations on the theme.
Model is the layer which contains repositories, units of work, domain/model objects, data mappers, services and some other structures. All they combined create the model layer, which contains all of the domain business logic for the particular application.
Model is not any single instance. Anyone who tels you otherwise is full of it.
The specific usecases, for which MVVM has been designed, are situation, when you are unable to modify either the model layer or view instances, or both.
P.S. Though, if you are using ViewModel instances as per ASP.NET MVC documentation, then you actually are NOT using MVVM. It is just Model2 MVC with different names for things (where "viewmodels" are actually views and "views" are templates). They kinda messed up when they marketed Rails-like architecture as "MVC".
I've always viewed Models as the "Building Blocks" of the application. They are usually self-contained classes with some properties and perhaps some rudimentary validation or logic for its own properties only.
View Models on the other hand are my actual application classes that end up using the "building blocks" (Models) when building and running the application. They do things like perform advanced validation, process commands, handle events, any kind of business logic, etc.
It should be noted that you don't have to expose your Model's properties in your ViewModel like you have in your example code. Doing so is the "MVVM purist" approach as it completely separates your Model layer from the View layer, however it's also perfectly acceptable to expose the entire Model to the View instead. This is what I typically use in most small projects due to it's simplicity and lack of code-duplication.
public MyModel CurrentModel
{
get { return _model; }
set
{
if (_model != value)
{
_model = value;
RaisePropertyChanged("CurrentModel");
}
}
}
However if there are cases where only a few properties from the Model is needed in the View, or if the project is large enough where I'll want to keep the layers totally separate, then I expose my Model's properties to the View through the ViewModel like you have in your example code.

MVC Object Change Tracking

I've currently got an issue where I need to see which fields have been changed on an Edit field for auditing purposes, in which I have code for, but I think my problem lies within my MVC View.
I have (test code):
[HttpPost]
public ActionResult Adjustment(GroupPolicy groupPolicy)
{
if (ModelState.IsValid)
{
_service.SaveGroupPolicy(groupPolicy);
return RedirectToAction("Index");
}
return View(groupPolicy);
}
Which is fine, the Policy saves. However, take this into consideration:
GroupPolicy has, say, 3 fields (in reality there are, maybe, 60):
bool IsPolicy
string Name
string Description
Name and Description are on the form, so that's fine. IsPolicy isn't used on the form, so that gets defaulted to false when posted back to the GroupPolicy object in the Adjustment method.
I can't really put IsPolicy in a Hidden field on the form, as that won't be elegant for 60+ fields in my actual solution, the HTML would be all over the place.
Now that the bool is defaulted to false, it completely abolishes the chance of me knowing if the field has changed or not. All I really want is a method for this data to be preserved, whilst keeping the new information on the Edit form.
Is this possible, am I missing something obvious?
Well first of all, GroupPolicy should be a view model and not an entity - and as such it should be tailored for the view e.g.
public class GroupPolicyViewModel
{
[HiddenInput]
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
...
}
Then in your action you don't need to worry about assigning values that have changed, you just map the view model directly across e.g.
public ActionList Adjustment(GroupPolicyViewModel viewModel)
{
if (ModelState.IsValid)
{
// pull actual entity from service
var groupPolicy = _service.GetGroupPolicy(viewModel.Id);
// update entity from view model
groupPolicy.Name = viewModel.Name;
groupPolicy.Description = viewModel.Description;
...
}
}
This keeps a clean separation between your view & business logic. Also, it allows you to add annotations for client-side validation without affecting your real model.
GroupPolicy has, say, 3 fields (in reality there are, maybe, 60)
I would recommend using AutoMapper for this e.g.
// call this once only e.g. Application_Start in the Global.asax
Mapper.CreateMap<GroupPolicyViewModel, GroupPolicy>();
...
// in your Adjustment action
var groupPolicy = _service.GetGroupPolicy(viewModel.Id);
groupPolicy = Mapper.Map<GroupPolicyViewModel, GroupPolicy>(viewModel, groupPolicy);
_service.SaveGroupPolicy(groupPolicy);
If IsPolicy not on the form then it shouldn't even be part of your model - this will prevent posting of this field into your model and so your check won't even be needed for IsPolicy.
Rather than accepting GroupPolicy as the parameter into the action, create a cut down object GroupPolicyInputModel with only fields that are on the form.
Then use your generic auditing to only compare all the posted fields, as per any other form.

Categories

Resources