Passing a partial model to a Display Template in ASP.Net MVC? - asp.net-mvc

I have a view model derived from a base view model. The base VM has two properties, bool IsDisplayable & int MyValue.
I would like to create a display template that would take those two values and either display MyValue or not depending on the value of IsDisplayable.
This pair of values is part of all my view models and I would like to use one Display Template for all my views that use models derived from my base model.
My question is how would I pass this part of each view model to my Display Template?
#Html.DisplayFor(modelItem => item.MyValue,"MyDisplayTemplate")
My Display Template:
#model WhatModelGoesHere
#if (Model.IsDisplayable)
{
<td>
#Html.DisplayFor(model => model.MyValue)
</td>
}
The difficulty is trying to make the display template generic enough to work with any of my view models.

I suggest you create a common base class for other models to inherit.
Put the two fields IsDisplayable and MyValue that you need to display in the public template in the common base class, so that you only need to reference the common base class in the template.
And I suggest you use partial view to implement Display Template.
Common base class:
public class GenricModel
{
public bool IsDisplayable { get; set; }
public int MyValue { get; set; }
}
View Model to inhert common base class:
public class ViewModel1 : GenricModel
{
public int Id { get; set; }
public string Name { get; set; }
}
In the view to show ViewModel1 data, you can use #Html.Partial("_TemplateView", item) to call Display Template, here is an example based on ViewModel1:
#model IEnumerable<WebApplication_core.Models.ViewModel1>
#{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Index</h1>
<table class="table-bordered">
<tr>
<th>
Id
</th>
<th>
Name
</th>
<th>
MyValue
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#item.Id
</td>
<td>
#item.Name
</td>
#Html.Partial("_TemplateView", item)
</tr>
}
</table>
Then, create a partial view named _TemplateView.cshtml in Views folder.
Generally speaking, partial views are created under Views/Shared path in the project, you can refer to this.
#model WebApplication_core.Models.GenricModel
#if (Model.IsDisplayable)
{
<td>
#Html.DisplayFor(model => Model.MyValue)
</td>
}

Related

MVC - Best/cleanest way to use data from multiple view models in a single view?

I'm still learning the ropes of MVC. I know this question is probably MVC 101, but I'm having trouble figuring out the best way to go about it:
What's the best way to take the data from 2 view models & use it in a single view?
I inherited an application which has a bunch of email templates setup via Views. These templates all utilize the same view model:
#model MyCompany.Mvc.MyApplication.Models.Emails.EmailTemplateViewModel
So far, this has worked for all templates because the template data is rather generic. It's really more a matter of setting up the html design of the different views around the generic email data.
Now however, they want an email template that sort of breaks that mold. It's still a template & includes the data within the "EmailTemplates" model, but it ALSO needs to include add'l information, which is all already contained within a separate view model:
#model CustomerViewModel
What's the best way to go about pulling from both of these models for my new email template view?
So, as a quick example, here would be my models:
EmailTemplateViewModel
public class EmailTemplateViewModel
{
public string TodaysDateLongFormat
{
get
{
DateTime value = DateTime.Now;
return value.ToString("MMMM") + " " + value.Day + ToOrdinal(value.Day) + ", " + value.Year;
}
}
}
CustomerViewModel
public class CustomerViewModel
{
public string CustomerFirstName { get; set; }
public string CustomerLastName { get;set; }
public string HasMissingOrderField { get; set; }
}
NewEmailTemplateView.cshtml (current multiple model declaration would obviously fail)
#model MyCompany.Mvc.MyApplication.Models.Emails.EmailTemplateViewModel
#model CustomerViewModel
<table style="border-top: 5px; padding-bottom: 5px;">
<tr>
<td>
Date: #Model.TodaysDateLongFormat
</td>
</tr>
<tr>
<td>
Customer First Name: #Model.CustomerFirstName
</td>
</tr>
<tr>
<td>
Customer Last Name: #Model.CustomerLastName
</td>
</tr>
</table>
What's the best way to go about this?
Thanks
Nothing wrong with having multiple viewmodels under a parent viewmodel.
public class ParentViewModel
{
public ChildViewModel1 ChildViewModel1 { get; set; }
public ChildViewModel2 ChildViewModel2 { get; set; }
}
You can then set the model in your view to #model ParentViewModel, after which you can access each child viewmodel (and it's respective properties) using the usual Razor syntax:
#Model.ChildViewModel1.Property1, #Model.ChildViewModel2.Property3, etc.
I think the best answer here is to build just one proper View Model and fill it with required data with a controller.
NewEmailTemplateViewModel
public class NewEmailTemplateViewModel
{
public CustomerViewModel Customer { get; set; }
public EmailTemplateViewModel Email { get; set; }
}
NewEmailTemplateView.cshtml
#model MyCompany.Mvc.MyApplication.Models.Emails.NewEmailTemplateViewModel
<table style="border-top: 5px; padding-bottom: 5px;">
<tr>
<td>
Date: #Model.Email.TodaysDateLongFormat
</td>
</tr>
<tr>
<td>
Customer First Name: #Model.Customer.CustomerFirstName
</td>
</tr>
<tr>
<td>
Customer Last Name: #Model.Customer.CustomerLastName
</td>
</tr>
</table>
and in controller just create your view model and assign properties:
var model = new NewEmailTemplateViewModel();
model.Customer = your customerViewModel;
model.Email = your modelViewModel;
return model;

mvc razor view nested foreach model post null subobject

In my code I cannot read a nested object value on form post.
Wrog way to edit List property in one Object:
#{
var contatore = 0;
foreach (var item in Model.Movimenti)
{
var movimento = item;
<tr>
<td align="left">
#*Imposto gli Hidden per tutte le proprietà che devo recuperare al post*#
#Html.HiddenFor(x => movimento.Prodotto.Descrizione, "Movimenti[" + contatore + "].Prodotto.Descrizione")
#Html.DisplayFor(x => movimento.Prodotto.Descrizione, "Movimenti[" + contatore + "].Prodotto.Descrizione")
</td>
<td>#Html.EditorFor(x => movimento.Aum, "CurrencyDisabled", "Movimenti[" + contatore + "].AUM")</td>
</tr>
contatore++;
}
}
This is the correct way to edit List property in one Object:
The code:
#using AI.Business.Models
#model Operazione
#{ ViewBag.Title = "Simulatore"; }
#using (Html.BeginForm("CreaOperazione", "Operativita", FormMethod.Post))
{
// Imposto gli Hidden per tutte le proprietà che devo recuperare al post
#Html.HiddenFor(x => x.RapportoModel.TipoRapportoId)
<table width="100%" class="display" id="Simulatore" cellspacing="0">
<thead>
<tr>
<th class="dt-head-left">Linea</th>
<th>AUM</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(x => x.Movimenti)
</tbody>
</table>
<button id="btnSalva" name="btnSalva" type="submit" style="float: right;">Salva Operazione</button>
}
With the editor assuggested:
#model AI.Business.Models.Movimento
<tr>
<td align="left">
#Html.HiddenFor(x => x.Prodotto.Descrizione)
#Html.DisplayFor(x => x.Prodotto.Descrizione)</td>
<td>#Html.EditorFor(x => x.Aum, "CurrencyDisabled")</td>
And this is my object:
public class Movimento
{
public int Id { get; set; }
public ProdottoModel Prodotto { get; set; }
public decimal Aum { get; set; }
}
And the Object Prodotto:
public class ProdottoModel
{
[Key]
public int ID { get; set; }
public string Descrizione { get; set; }
}
In my Actionresult the property Descrizione is null:
[HttpPost]
public ActionResult CreaOperazione(Operazione operazione)
{
if (ModelState.IsValid)
{
// Do something
}
else
ImpostaErrore(ModelState);
return View("PaginaSimulatore", operazione);
}
Open the images:
At my first access to the page the property Prodotto.Descrizione is populated
When i raise the form post event this property was sent with a null value
I'm not sure how you're getting any of this to work, but it's a total fluke. HiddenFor, for example, has no parameter that let's you specify the name value for the field. Instead, where you're trying to do that, the parameter is actually for htmlAttributes, which expects either an anonymous object or IDictionary. The only reason you aren't getting errors is because string is technically an object, but it will never do anything in this context.
The same goes for the rest of your helper calls. With EditorFor, in particular, the second param where you're passing "CurrencyDisabled", is for specifying the editor template that should be used, and the third param is for additionalViewData, which just appends items to ViewData within the context of the editor template.
Long and short, none of this works how you think it does. Plainly and simply, if you need to work with a collection, you need to use for rather than foreach. The expression that you pass to the *For family of helpers is not just about identifying a property however you can get to it; it must be a bindable expression, i.e. something Razor can use to create a name for the form field that will line up to something on your model on post. In order for that to happen, the names must be something like Movimenti[N].Prodotto.Descrizione, and the only way to get that is to call the helper like:
#Html.HiddenFor(m => m.Movimenti[i].Prodotto.Descrizione)
Where i would be the iterator from your for loop.

Binding input model in partial view help needed

I want to pass an input model from a partial view to a controller. I'm rather new to MVC so still trying to understand how the default model binder works.
Via AJAX (listBox) a controller passes back a partial view and inserts into table id=searchResults.
#model ViewModels.LocationViewModel
#using (Ajax.BeginForm("ProcessSearch", "SearchR", new AjaxOptions
{
HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "searchResults",
}))
{
<div>#Html.ListBoxFor(xxx)</div>
<input id="Search" type="submit" value="Search" />
}
Here is the controller and ViewModel that populates the partial view
public class OrderViewModel
{
public string Description { get; set; }
public string Status { get; set; }
}
public ActionResult ProcessSearch(SearchViewModel search)
{
select new OrderViewModel{
Status=f.STATUS,
Description=f.DESCRIPTION}).ToList();
return PartialView(model);
}
In the same main view I have this form that I want I want to bind to yet another view model. I simply don't understand how to implement the default binder from the model of the partial view. I apologize if I didn't explain this correctly. I hope it makes sense.
#using (Html.BeginForm("Testing", "SearchR", FormMethod.Post))
{
<div>#Html.DropDownListFor(yyy)</div>
<input id="Reshop" type="submit" value="Reshop" />
}
<table id="searchResults"></table>
public ActionResult Testing(RSOrderViewModel rOrder)
{
return Content("hey");
}
public class RSOrderViewModel
{
public string yyy { get; set; }
public IEnumerable<OrderViewModel> sovm { get; set; }
}
#model List<ViewModels.OrderViewModel>
#{ViewData.TemplateInfo.HtmlFieldPrefix = "sovm";
}
<table id="searchResults">
<tr>
<th>Order Id</th>
<th>Order Detail</tr>
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(x => x[i].OrderId)
</td>
<td>
#Html.DisplayFor(x => x[i].OrderDetail)
</td>
</tr>
}
</table>
The table is outside the second form. So when you POST to the Testing action all that is sent to the controller is the value of the dropdown list. If you want to send the collection that's stored in this table you will have to either use AJAX or put the table inside the form:
#using (Html.BeginForm("Testing", "SearchR", FormMethod.Post))
{
<div>#Html.DropDownListFor(yyy)</div>
<table id="searchResults"></table>
<input id="Reshop" type="submit" value="Reshop" />
}
Now of course putting the table inside the form doesn't mean that it will send anything to the server when you submit the form. You need to put input fields (hidden if you don't want them to be visible to the user) that will contain the values that will be POSTed back. Also those input field names must follow the standard convention for binding to a list.
You haven't actually shown how does the partial view look like but here's an example of how it might look so that the convention is respected. For example let's suppose that you have 2 properties in your OrderViewModel that you want to be bound: OrderId and OrderDetail:
#model IEnumerable<OrderViewModel>
#{
// We set the name prefix for input fields to "sovm"
// because inside your RSOrderViewModel the collection
// property you want to bind to this table is called sovm
// and in order to respect the convention the input names
// must be in the following form "sovm[0].OrderId", "sovm[1].OrderId", ...
ViewData.TemplateInfo.HtmlFieldPrefix = "sovm";
}
<thead>
<tr>
<th>Order Id</th>
<th>Order detail</th>
</tr>
</thead>
<tbody>
#Html.EditorForModel()
</tbody>
and then you could have an editor template which will be rendered for each element of the model (~/Views/Shared/EditorTemplates/OrderViewModel.cshtml):
#model OrderViewModel
<tr>
<td>#Html.EditorFor(x => x.OrderId)</td>
<td>#Html.EditorFor(x => x.OrderDetail)</td>
</tr>
The name of the template is the name of the type used in the collection you want to bind to (IEnumerable<OrderViewModel> sovm { get; set; } => OrderViewModel.cshtml). It must also be placed inside the ~/Views/Shared/EditorTemplates folder if it can be reused between multiple controllers or if it is specific to the current controller inside the ~/Views/XXX/EditorTemplates folder where XXX is the current controller.

MVC 3 - Passing list of check boxes to and from a controller

I have a list of information sources in a database that I need to pass to a view in MVC. I need an end user to be able to tick the sources of information that apply to their course.
I am able to successfully pass the view a list of information sources alongside check boxes using the following code.
public ViewResult CreateUpdateInfoSource(int ProgrammeId)
{
List<ProgInfoSourceModel> viewmodel = new List<ProgInfoSourceModel>();
List<ProgInfoSourceDTO> myProgInfoDTOList = progInfoSourceService.AllInfoSources();
if (myProgInfoDTOList.Count != 0)
{
foreach (var x in myProgInfoDTOList)
{
ProgInfoSourceModel insert = new ProgInfoSourceModel();
insert.Selected = false;
insert.ProgrammeId = ProgrammeId;
insert.InfoSourceId = x.InfoSourceId;
insert.InfoSource = x.InfoSource;
insert.InfoReference = x.InfoReference;
insert.Rank = x.Rank;
viewmodel.Add(insert);
}
}
return View(viewmodel);
}
I am able to unpack this in the view just fine, however I am having real difficulty passing a the list back to my controller. I need to be able to loop through the list in my controller and see which ones do or don't apply so I can update the database.
My model looks like this:
namespace ProgrammeSpec.MVC.Models
{
public class ProgInfoSourceModel
{
[DisplayName("Selected")]
public bool Selected { get; set; }
[DisplayName("Programme Id")]
public int ProgrammeId { get; set; }
[DisplayName("Info Source Id")]
public int InfoSourceId { get; set; }
[DisplayName("Info Source")]
public String InfoSource { get; set; }
[DisplayName("Reference")]
public String InfoReference { get; set; }
[DisplayName("Rank")]
public int? Rank { get; set; }
}
}
My View looks like this:
<html>
<head>
<title>CreateUpdateInfoSource</title>
</head>
<body>
#using (Html.BeginForm())
{
<table>
<tr>
<th>
Selected
</th>
<th>
ProgrammeId
</th>
<th>
InfoSourceId
</th>
<th>
InfoSource
</th>
<th>
InfoReference
</th>
<th>
Rank
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.CheckBoxFor(modelItem => item.Selected)
</td>
<td>
#Html.DisplayFor(modelItem => item.ProgrammeId)
</td>
<td>
#Html.DisplayFor(modelItem => item.InfoSourceId)
</td>
<td>
#Html.DisplayFor(modelItem => item.InfoSource)
</td>
<td>
#Html.DisplayFor(modelItem => item.InfoReference)
</td>
<td>
#Html.DisplayFor(modelItem => item.Rank)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
</tr>
}
</table>
<input type="submit" value="Update" />
}
</body>
</html>
and the controller that the view gets passed to looks like this: (snippet)
[HttpPost]
public ActionResult CreateUpdateInfoSource(List<ProgInfoSourceModel> viewmodel)
{
if (ModelState.IsValid)
{
try
{
The problem is the viewmodel is null. I understand this is probably because I've unpacked the list in the view so it is no longer a list but how can I access the values of the check boxes then?
The added complication is that the number of info sources will vary so I can't use a static form or list and give each one an Id...
This must be a fairly common problem with a simple solution, but I'm an MVC novice and I don't know how to get round this.
Try using an editor template (Here's another SO that answers that question How to create custom editor/display templates in ASP.NET MVC 3?), for your ProgInfoSourceModel and then simply use Html.EditorFor(m => m.Model) on the View.
When you use the foreach loop, each checkbox is getting the same input name - and so is not actually submitting the correct data back.
If you go the editor template route, and making MVC do the hard work of iterating through the IEnumerable - it will create inputs with names like 'item[0].Selected' - which the model binder then correctly deserialized back into a list.
Phil Haack also blogged a fantastic walkthrough of exactly this scenario way back in 2008: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

How to expose a collection of items from MVC model to a MVC view (*.cshtml) in ASP.NET MVC

I have a MVC model oject that contains a collection of object. I want to expose the MVC model as user input fields on a MVC view.
Below is MVC model and domain model
public class BookModel
{
public BookModel(Book book)
{
this.Authors = book.Authors;
}
public List<Author> Authors { get; set; }
}
public class Book
{
public List<Author> Authors = new List<Author>();
}
public class Author
{
public string Name { get; set; }
}
Below is the Action method in controller:
public ActionResult Edit(int id)
{
BookModel model = GetBookModel(id);
return View(model);
}
The problem is that, the generated view (*.cshtml) does NOT have the input fields for the collection authors.
Any idea would be very much appreciated.
The problem is that, the generated view (*.cshtml) does NOT have the
input fields for the collection authors.
The ASP.NET MVC new View wizard does its best. From there its up to you.
So inside Edit.cshtml you could use editor templates:
#model BookModel
#using (Html.BeginForm())
{
<table>
<thead>
<tr>
<th>Name</th>
<th>Text</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(x => x.Authors)
</tbody>
</table>
<input type="submit" value="Edit" />
}
and then define a editor template for an Author (~/Views/Shared/EditorTemplates/Author.cshtml) which will be rendered automatically for each element of your collection:
#model Author
<tr>
<td>#Html.EditorFor(x => x.Name)</td>
<td>#Html.EditorFor(x => x.Text)</td>
</tr>

Resources