MVC concatenated properties in View Model or in Controller - c#

I work with MVC and has one question, what is the best practice for building concatenated View Model properties? I can build concatenated field(FullName) in two places:
In Model View like this
public class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName { get {return FirstName+LastName;} }
}
public ActionResult Users()
{
var model = new User
{
FirstName = "Tomas",
LastName = "Saint",
});
return View(model);
}
In Controller
public class User
{
public string FullName { get; set; }
}
public ActionResult Users()
{
var model = new User
{
FullName = "Tomas" + "Saint";
});
return View(model);
}

If the calculations/functions are associated with formatting for the view then i would go ahead and put the functionality in the view model, like your full name property this is correct in the view model. However if you have any functions/calculations that require domain logic, then i dont think view models are the place for that and that can reside either in the domain model for reusability, or somewhere where else in your business logic layer.
So the short answer - formatting for the view in view models, any domain logic elsewhere.

Any methods or properties that calculate values for display purposes (like FullName in your example) belong in the ViewModel. Doing that gives you a clean, type-safe means of passing the data to the view and allows the view to access the property in a consistent way. This approach also has the advantage of making the property available wherever that particular ViewModel is used. That will not be the case if you add the property to your controller.
Example below using Razor syntax:
#model MvcApplication.Models.User
...
<div class="editor-label">
#Html.LabelFor(m => m.FullName)
</div>
<div class="editor-field">
#Html.DisplayFor(m => m.FullName)
</div>

Related

MVC Pass display data between views

I have a view model that is used to display a form on one view, and then is also used to represent the POST data to an action. The action then displays another view model that contains much of the same data from the first view model. However, the first view model has several "display only" properties that are also required on the second view model (for display only on the second view also).
I am wondering what the best way to pass this "display only" data to the second view would be. Currently, the best solution I have come up with is to have a bunch of hidden form fields that contain the display only property values, and then the model gets auto-populated for the action that handles the form POST. However, using hidden form fields seems very "hackish", and there seems like there should be a better solution to passing this data to another view The action doesn't need the display only information, it is only accessing it to populate the properties of the second view model that is passed to the second view.
Let me just explain my question with code, as what I am after is probably better understood through code than words.
Models:
public class SearchFilters
{
// ...
}
public class SearchResult
{
public int Id { get; set; }
public bool Selected { get; set; }
public string SomeDisplayValue1 { get; set; }
public string SomeDisplayValue2 { get; set; }
// ...
}
public class ResultsViewModel
{
public IList<SearchResult> Results { get; set; }
// ...
}
public class DoSomethingWithSelectedResultsViewModel
{
public IList<SearchResult> SelectedResults { get; set; }
public string SomeOtherProperty { get; set; }
// ...
}
Controller:
[HttpPost]
public ActionResult Results(SearchFilters filters)
{
ResultsViewModel results = new ResultsViewModel();
// ...
return new View(results);
}
[HttpPost]
public ActionResult DoSomethingWithSelectedResults(ResultsViewModel model)
{
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
View: Results.cshtml
#model ResultsViewModel
#using (Html.BeginForm("DoSomethingWithSelectedResults", "Search"))
{
<table>
for (int i = 0; i < Model.Results.Count; i++)
{
<tr>
<td>
#Html.CheckBoxFor(m => Model.Results[i].Selected)
#* I would like to eliminate these hidden inputs *#
#Html.HiddenFor(m => Model.Results[i].Id)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue1)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue2)
</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue1)</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue2)</td>
<tr>
}
</table>
<button type="submit">Do Something With Selected Results</button>
}
As far as I know, one of the best way to pass data from View to another View through a Controller is to use ViewBag, ViewData or TempData. As an example, you can pass the data retrieved from View I as shown below:
TempData[DataToBePassed] = model.CustomData;
And then retrieve this data in View II similar to that:
#if(TempData[DataToBePassed] != null)
{
var dataFromFirstView = TempData[DataToBePassed];
}
For more information take a look at When to use ViewBag, ViewData, or TempData in ASP.NET MVC 3 applications.
You could put the model in the TempData property of the controller, that way it's automatically available in the next request.
More here
Found what I was looking for, I just hadn't worked with MVC enough yet to know about it. The Controller.UpdateModel method does exactly what I was looking for.
Example (using the code from the question):
[HttpPost]
public ActionResult DoSomethingWithSelectedResults()
{
// Load initial model data here, in this case I had simply cached the results in
// temp data in the previous action as suggested by Emeka Awagu.
ResultsViewModel model = (ResultsViewModel)TempData["results"];
// Call UpdateModel and let it do it's magic.
UpdateModel(model);
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
Using this method I was able to eliminate all the hidden form fields and did not have to write any custom copy logic, since UpdateModel deals with it automatically.
Note: I did have to implement some custom model binders to get things to work correctly with dictionaries and collections (see here, here, and here).

Is my view model too "complex" to allow binding on post?

Using my model displaying a page works fine but the post does not return the bound model.
My classes:
public class ContactManager
{
public Contact Contact { get; set; }
public SelectList SalutationList { get; set; }
}
public class Contact
{
public int Id{get;set;}
public string FirstName{get; set;}
public SalutationType SalutationType{get; set;}
}
public class SalutationType
{
public int Id { get; set; }
public string Name { get; set; }
}
My View:
#model ViewModels.ContactManager
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Contact.Id)
#Html.DropDownListFor(model => model.Contact.SalutationType.Id, Model.SalutationList)
#Html.EditorFor(model => model.Contact.FirstName)
<input type="submit" value="Save" />
}
The issue seems to be in the DropDownListFor. The dropdown list displays correctly with the proper value but when I post this page the complete model is blank. If I simplify the DropDownListFor like this the values are posted as expected.
#html.DroDownListFor(model=>model.MyPlaceHolderProp, Model.SalutationList)
Is my model too complex? Am I not doing something correctly?
The models are based off of several tables using EF that I have created in a separate project. I am trying to avoid creating more classes/models then I have to.
You should post your controller action as well, as your model coming back as blank really has nothing to do with this. Changing the DropDownListFor definition one way or another should not effect the posting of any other values.
That said, you will run into another issue eventually here, so you need to regroup, anyways. You can't just post back the id value of a related item. Entity Framework will either complain that there's already an object with that id, or worse, if the object attaches, it will update the row with that id with the new posted value for Name, which in this case, is nothing, so it'll just clear it out.
When you create a relationship with a single item (a foreign key basically), if you don't specify a property to hold that foreign key value, Entity Framework creates one for you behind the scenes to track the relationship. In your case here, that means your Contacts table has a column named SalutationType_Id. However, there's no way from your class to directly access this value. This is why I recommend that you always provide an explicit property to handle the relationship:
[ForeignKey("SalutationType")]
public int SalutationTypeId { get; set; }
public SalutationType SalutationType { get; set; }
If you do that, then you can directly stuff the posted id there and Entity Framework will create the relationship.
#Html.DropDownListFor(m => m.Contact.SalutationTypeId, Model.SalutationList);
If you insist on keeping the key implicit, then you must create the relationship yourself, by creating a field on your view model to hold the posted value, then using that value to look up the SalutationType instance from the database, and then finally adding that to the Contact instance.
Add to your view model
public int SalutationTypeId { get; set; }
In your view
#Html.DropDownListFor(m => m.SalutationTypeId, Model.SalutationList)
In your POST action
var salutationType = db.SalutationTypes.Find(model.SalutationTypeId);
contact.SalutationType = salutationType;
You could do it this way. This may be the more "MVC best practice" way to handle it. Everything stays neatly in their models, and no manual IDs are required. The views are intended to be representations of the underlying models they are built on. If you are creating a view that has a form, then create a model that represents the form and use it in the view.
Revise your models like:
public class PostModel
{
public int ContactID { get; set; }
public int SalutationID { get; set; }
public string FirstName { get; set; }
}
public class PostView
{
public ContactManager contact { get; set; }
public PostModel post { get; set; }
}
Then create the PostView in the controller:
public ActionResult Index()
{
//create the PostView model
var pv = new PostView();
pv.ContactManager = contactManager;
pv.post = new PostView()
{
ContactID = contactManager.Contact.Id,
SalutationID = contactManager.SalutationType.Id,
FirstName = contactManager.Contact.FirstName
};
return View(pv);
}
Then the view could be like:
#model ViewModels.PostView
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.post.ContactID)
#Html.DropDownListFor(model => model.post.SalutationID, model.contact.SalutationList)
#Html.EditorFor(model => model.post.FirstName)
<input type="submit" value="Save" />
}
Then the post action in the controller:
[HttpPost]
public ActionResult Index(PostView pv)
{
//post code
//the posted data will be in pv.post
}
Have you considered using a custom model binder? Custom model binding isn't all that complicated for models that are still relatively simple, and you can handle the serialization/deserialization however you need to.
http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
http://ivonna.biz/blog/2012/2/2/custom-aspnet-model-binders-series,-part-3-subclassing-your-models.aspx
http://forums.asp.net/t/1944696.aspx?what+is+custom+model+binding+in+mvc
I am not sure this will help you... I wsa having a similar issue but I was using ajax to post back... anyway, I had forgotten to mark my binding class with the [Serializable] attribute.
so you might try
[Serializable]
public class Contract {
...
}
Again, I am using Json to post back to my controller so may not be related or help you. But, I guess could be worth a try.

MVC4 model binding - Passing Custom View Models & values from view to controller

I have a strongly typed view with the following model.
public class ProductViewModel
{
public Product Product { get; set; }
public List<ProductOptionWithValues> ProductOptionsWithValues { get; set; }
}
public class ProductOptionWithValues
{
public ProductOption ProductOption;
public List<AllowedOptionValue> AllowedOptionValues;
}
I'm using this Model To populate a form where a user can select the options they want for a product.
This is the view.
#model AsoRock.Entities.ViewModels.ProductViewModel
#{
ViewBag.Title = "Details";
}
#using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<h3>
#Html.DisplayFor(model => model.Product.ProductName)
----> #Html.DisplayFor(model => model.Product.Price)
</h3>
<br/>
foreach (var item in Model.ProductOptionsWithValues)
{
<b>#Html.DisplayFor(modelItem => item.ProductOption.Option.OptionName)</b>
<br/>
#Html.DropDownListFor(m => m.ProductOptionsWithValues,
new SelectList(item.AllowedOptionValues,
"Id", "DisplayString",
item.AllowedOptionValues.First().Id))
<br/>
}
<input type="submit" value="Add to cart" />
}
In my controller I am trying to pass the model back. When I set a break point in the controller it hits it but the Product view model is empty, any ideas how I can get the values that are selected in the view back in to my controller?
[HttpPost]
public ActionResult Details(ProductViewModel ProductViewModel)
{
return View();
//return View();
}
As mentioned in the comments, you need to change the name of the viewmodel parameter from ProductViewModel to something else e.g.
[HttpPost]
public ActionResult Details(ProductViewModel viewModel)
{
}
Now it's very odd that the viewModel param is not set to an instance of the class. The MVC model binder will still create an instance of ProductViewModel even if none of it's properties are set to anything. You're not using a custom model binder by any chance?
Also I would very strongly suggest that your viewmodel class does not have a Product property. Instead, create properties in the viewmodel specifically for the Product properties you intend to use e.g.
public class ProductViewModel
{
public string ProductName { get; set; }
public decimal ProductPrice { get; set; }
public List<ProductOptionWithValues> ProductOptionsWithValues { get; set; }
}
Using Product in the viewmodel sort of defeats the point of having a viewmodel. The viewmodel should contain only the bare minimum that the view needs. Including Product means the viewmodel is now bloated with extra data it does not use/need.
EDIT:
In your shoes, I would strip down the view itself, using only little bits of the viewmodel, and POST to the controller to see what happens. If the viewmodel calss is not NULL, go back to the view and add another bit back. Keep doing this until the viewmodel is NULL again. Doing this bit by bit should help.

Asp.Net MVC layout and partial views

let's consider two views that use the same layout composed of:
A left column containing a "body" (which is filled differently by both views)
A right column that displays general information (passed via the model)
Instead of defining the right part twice, I wondered if I could create a PartialView to link directly from the layout page.
The problem is that the partial views implicitely inherit their models from the view that is being rendered. And since each view has its own model, I end up with a model type mismatch in the partial view.
From here I see two solutions:
I could insert the common part of the view model in the ViewBag. Unfortunately this means that each view that uses this layout has to implement this "convention" but nothing warns the developer about it at compile time...
I could use polymorphism to make each view model inherit from the same base class (edit: or interface) that the Partial Views uses. This would work up to a certain extend but would potentially exponentially increase in complexity as soon as I have a second partial view in the same layout.
So here are the questions:
Am I right with the assumptions above?
Do you see any other possibility?
Any return on experience on this?
Thanks a lot,
TB.
Use an Interface and implement it on the two models, this is exactly the kind of thing they're used for.
Here is an example of two different Views using two different Models that both implement an interface. This is subtyping instead of ad-hoc polymorphism.
public class ViewModelOne : IReusableView
{
public string Name { get; set; }
public string Something { get; set; }
public int ANumber { get; set; }
}
public class ViewModelTwo : IReusableView
{
public string Name { get; set; }
public string Thing { get; set; }
public string SomethingElse { get; set; }
public int ANumber2 { get; set; }
}
public interface IReusableView
{
string Name { get; }
}
So we have the really simple partial view here that is 'InnerPartialView':
#model TestIntegration.Models.IReusableView
<div>
#Model.Name
</div>
Which is used in the home and about pages of this example controller:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View(new ViewModelOne() { Name = "hello", Something="sdfsdfs", ANumber = 1 });
}
public ActionResult About()
{
return View(new ViewModelTwo() { Name = "hello 2", SomethingElse = "aaaddd", ANumber2 = 10, Thing="rand" });
}
}
The home view:
#model TestIntegration.Models.ViewModelOne
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit http://asp.net/mvc.
#Html.Partial("InnerPartialView")
</p>
The about view:
#model TestIntegration.Models.ViewModelTwo
#{
ViewBag.Title = "About Us";
}
<h2>About</h2>
<p>
Put content here.
#Html.Partial("InnerPartialView")
</p>
When you render the partial view, you can send it a model:
#Html.RenderPartial(MVC.Partials.Views.Sidebar, Model.SideBarModel);
So you could send down data as part of the parent model that is the model for the partial sidebar.
In partial views, models are of type dynamic so you don't need to know what type they are. However, you just need to make sure the model has the property you need. In other words you can use Model.MyProperty or Model.MyProperty as MyPropertyType when using Html.Partial.

Handling MVC form submission to database

So I'm loosely following the Music Store tutorial. I created the StoreManagerController on pg. 54ish. And it created a view with the Create, Deleted, Edit, etc. It's saving some stuff to the database, namely my EditFor controls, but nothing else.
I have multiple DropDownListFor controls, populated by both tables in the database and also Active Directory user data. I'm not sure how to get these to save. Here is my abridged code. Thanks for the help.
View:
<div class="createTopInner">
<div class="editor-label">
#Html.LabelFor(model => model.test.Category)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.CategoryId, Model.CategoryItems, "")
#Html.ValidationMessageFor(model => model.test.Category)
</div>
</div>
Controller:
public ActionResult Create()
{
// These four lines get active directory users
ActiveDirectoryModel ads = new ActiveDirectoryModel();
ViewBag.assignedto = ads.FetchContacts();
ViewBag.coassignedto = ads.FetchContacts();
ViewBag.notifyto = ads.FetchContacts();
var model = Populate();
return View(model);
}
[HttpPost]
public ActionResult Create(CreateViewModel model)
{
if (ModelState.IsValid)
{
db.TestItems.AddObject(model.test);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
public CreateViewModel Populate()
{
var model = new CreateViewModel
{
CategoryItems =
from c in new IntraEntities().CategoryItems.ToList()
select new SelectListItem
{
Text = c.Name,
Value = c.ID.ToString()
}
};
return model;
}
Model:
public class CreateViewModel
{
public Intra.Models.TestItem test{ get; set; }
public int CategoryId { get; set; }
public IEnumerable<SelectListItem> CategoryItems { get; set; }
}
The problem seems to be that, while most of your inputs map to properties on test, the CategoryId doesn't. Not knowing anything about your entity models, it's difficult to say, but I'd hazard a guess that you need to retrieve the corresponding Category from the database and add that to your TestItem instance before you persist it. If you do have a CategoryId property on your TestItem instance, you could just set it, but I'm guessing that you don't because otherwise you would have used it directly (as you do for the Category label) instead of adding a property to the view model.
If you have access to it / know much about stored procedures, it is much better to use Store procedures inside the database and then call them within Entity. It's much more loosely coupled and easier to make changes to without recompiling code.

Categories