I'm trying to make a custom template for a basket item list. I need a few different templates, as I have different ways of displaying the item, depending on if it's on the webpage or in a mail. Now my problem is, that when I use the default name it works flawlessly.
#Html.DisplayFor(b => b.Items)
But when I try to add a template name, I get an expection that my templates needs to be of a list type IEnumerable and not BasketItem.
#Html.DisplayFor(i => basket.Items, "CustomerItemBaseList")
Any ideas where my mistake is, or why it's not possible are appreciated. Thanks.
Unfortunately that's a limitation of templated helpers. If you specify a template name for a collection property the template no longer applies automatically for each item of the collection. Possible workaround:
#for (int i = 0; i < Model.Items.Length; i++)
{
#Html.DisplayFor(x => x.Items[i], "CustomerItemBaseList")
}
That's a good idea, Darin. I'm lazy though, so I'd like to take it one step further and make an actual helper that wraps this. I also took out the lambda expression to simplify it for my case, but you can easily add that functionality back in.
public static class DisplayTextListExtension
{
public static MvcHtmlString DisplayForList<TModel>(this HtmlHelper<TModel> html, IEnumerable<string> model, string templateName)
{
var tempResult = new StringBuilder();
foreach (var item in model)
{
tempResult.Append(html.DisplayFor(m => item, templateName));
}
return MvcHtmlString.Create(tempResult.ToString());
}
}
Then the actual usage looks like:
#Html.DisplayForList(Model.Organizations, "infoBtn")
I liked Dan's answer but just adjusted slightly as it can work for any IEnumerable:
using System.Collections;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace YourProject.Whatever
{
public static class DisplayExtensions
{
public static MvcHtmlString DisplayForIEnumerable<TModel>(this HtmlHelper<TModel> html, IEnumerable model, string templateName)
{
var tempResult = new StringBuilder();
foreach (var item in model)
{
var item1 = item;
tempResult.Append(html.DisplayFor(m => item1, templateName));
}
return MvcHtmlString.Create(tempResult.ToString());
}
}
}
And of course:
#Html.DisplayForIEnumerable(Model.Organizations, "NameOfYourDisplayTemplate")
I suggest another solution useful even more with lists of heterogeneous objects (i.e. BasketItem subclasses), using the additionalViewData parameter of the DisplayFor method, like:
#DisplayFor(b=>b.Items, new { layout="row" })
in this way the helper works fine with IEnumerable<T>, calling for each item (subclass of T) the relative DisplayTemplate, passing it the additionalViewData values in the ViewData dictionary.
The template could so output different code for different layout values.
In the example above the template named View\Shared\DisplayTemplates\BasketItem (or the name of the subclass) should be like this:
#model MyProject.BasketItem // or BasketItem subclass
#{
string layout = ViewData["layout"] as string ?? "default";
switch(layout)
{
case "row":
<div class="row">
...
</div>
break;
// other layouts
...
default: // strongly recommended a default case
<div class="default-view>
...
</div>
break;
}
}
It is strongly recommended to provide always a default code.
I hope this suggestion could help.
Related
I'm trying to render a Model in MVC that takes a list of Content objects List<Content>()
A Content is a base class and can be one of many different derived types eg: (TextBox, Schedule etc...)
In my View, while iterating through the list of contents I want to address a view that matches the type of the Content's derived class. (so that TextBox uses it's own view, Schedule it's own view and so on...)
How can I achieve this? Maybe I need to do some binding in the ViewModel?
Any help will be greatly appreciated,
thanks in advance for your answer(s).
You can iterate your list, and on each iteration call a partial view which matches the iterated object type (what you called "Content").
You can encapsulate a public property in you content class, to hold the view name. Or you can also use the object's method GetType instead.
Something like this (where your list of objects resides in Model.ContentList):
foreach (var _content in Model.ContentList) {
#Html.Partial(_content.ViewName, _content); // the "ViewName" would hold different names, such as: "TextBox", "Schedule" from your question.
}
Lets say your model -
public class Content
{
public int Weight { get; set; }
}
public class TextContent : Content
{
public string Text { get; set; }
}
Then create a controller action -
public ActionResult GetContents()
{
var contents = new List<TextContent>()
{
new TextContent() {Text = "Sample Text"},
new TextContent() {Text = "Second Sample Content"}
};
return View(contents);
}
now create a folder called EditorTemplates in Shared folder of Views folder and place following cshtml with name TextContent.cshtml (Note: name of the cshtml file should be matching with the model name, otherwise custom editor template will not be rendered).
#model myc.Models.TextContent
#Html.TextBoxFor(m => m.Text)
Now create your view with IEnumerable<myc.Models.TextContent> -
model IEnumerable<MvcApplication1.Controllers.TextContent>
#{
ViewBag.Title = "GetContents";
}
<h2>GetContents</h2>
#foreach (var item in Model) {
#Html.EditorFor(m => item)
}
When you run the application and go to the view, you will get -
This is my view model:
public class TaskViewModel{
public int TaskID{get;set;}
public IEnumerable<TaskExecutor> Executors{get;set;}
}
public class TaskExecutor{
public int ExecutorID{get;set;}
public string LastName{get;set;}
public string FirstName{get;set;}
}
In my view I have done something like this:
<table>
#foreach(var item in Model.Executors)
{
<tr>
<td>item.ExecutorID</td>
<td>#string.Format("{0} {1}",item.FirstName,item.LastName)</td>
</tr>
}
</table>
Now, when loading the view, there won't be any problem, but I might need to edit the table and I want the changes to persist when submitting the form. The only way I can think of is an HtmlHelper extension method that will properly bind an IEnumerable to a table but I have no idea how to do that. I'd be happy to see some code. Or is there any other way to achieve this?
One option could be as follows:
namespace System.Web.Mvc
{
public static class ExecutorsExtensions
{
public static MvcHtmlString Executors(this HtmlHelper helper, List<TaskExecutor> executors)
{
var sb = new StringBuilder();
sb.Append("<table>");
for (var i = 0; i < executors.Count; i++)
{
sb.Append("<tr>");
sb.Append(string.Format("<td><input name=\"Executors[{0}].FirstName\" value=\"{1}\"></td>", i, executors[i].FirstName));
// add other cells here
sb.Append("<tr>");
}
sb.Append("</table>");
return new MvcHtmlString(sb.ToString());
}
}
}
Usage
#Html.Executors(Model.Executors)
Please note you would need to make the Executors a List<TaskExecutor> for the indexing to work properly.
The indexing of the loop and and name variable would keep the model binding happy. You could add further fields where I have commented above.
You could also use Html.TextBox or Html.TextBoxFor to generate the inputs if needed.
I have some Customer Details and I only want to show fields which have a value.
For example if Telephone is null don't show it.
I currently have in my view model
public string FormattedTelephone
{
get { return string.IsNullOrEmpty(this.Telephone) ? " " : this.Telephone; }
}
And in my view
#Html.DisplayFor(model => model.FormattedTelephone)
This is working correctly, however, I would like to show the Field Name if the field has a value e.g.
Telephone: 02890777654
If I use #Html.DisplayNameFor in my view it shows the field name even if the field is null.
I also want to style the field name in bold and unsure of where I style it - the view or the view model.
For the bold style you can use this bit of code in your view, but of course it's proper to use an external style sheet.
<style type="text/css">
.telephone{
font-weight: bold;
}
</style>
You can do the check for null in your view and conditionally display the data:
#if (Model.FomattedTelephone != null)
{
<div class="telephone">
#Html.DisplayFor(model => model.FormattedTelephone)</div>
}
For style add a class for to the span you can put around field name.
You could create your own HtmlHelper that will only write if string is not null or empty.
Or you could add a DisplayTemplates something like here:
How do I create a MVC Razor template for DisplayFor()
For more background on helpers in razor read the following
http://weblogs.asp.net/scottgu/archive/2011/05/12/asp-net-mvc-3-and-the-helper-syntax-within-razor.aspx
And if they're in your App_Code folder read the answer to this
Using MVC HtmlHelper extensions from Razor declarative views
You'll probably want to over the default helper page with this (and inherit in your helper classes in App_Code)
public class WorkaroundHelperPage : HelperPage
{
// Workaround - exposes the MVC HtmlHelper instead of the normal helper
public static new HtmlHelper Html
{
get { return ((WebViewPage)WebPageContext.Current.Page).Html; }
}
public static UrlHelper Url
{
get { return ((WebViewPage) WebPageContext.Current.Page).Url; }
}
}
I would make a helper for this, something like this:
using System.Web.Mvc.Html;
public static class HtmlHelpers
{
public static MvcHtmlString LabelDisplayFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
StringBuilder html = new StringBuilder();
string disp = helper.DisplayFor(expression).ToString();
if (!string.IsNullOrWhiteSpace(disp))
{
html.AppendLine(helper.DisplayNameFor(expression).ToString());
html.AppendLine(disp);
}
return MvcHtmlString.Create(html.ToString());
}
}
Now, when you are in your View, you can simply do this (given you include the namespace in your view or web.config):
#Html.LabelDisplayFor(model => model.FormattedTelephone)
All it really does is check to see if your display helper is not an empty string, if it is, it will simply append your LabelFor and DisplayFor, if not, it will return an empty string.
I usually prefer to use Display/Editor Templates instead of HtmlHelper. Here is template that I have used to perform exactly the same task, its designed for bootstrap data list but anyone can adjust it easily.
#if (Model == null)
{
#ViewData.ModelMetadata.NullDisplayText
}
else if (ViewData.TemplateInfo.TemplateDepth > 1)
{
#ViewData.ModelMetadata.SimpleDisplayText
}
else
{
<dl class="dl-horizontal">
#foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm)))
{
if(MvcHtmlString.IsNullOrEmpty(Html.Display(prop.PropertyName)))
{
continue;
}
if (prop.HideSurroundingHtml)
{
#Html.Display(prop.PropertyName)
}
else
{
<dt>#prop.GetDisplayName()</dt>
<dd>#Html.Display(prop.PropertyName)</dd>
}
}
</dl>
}
Key line is:
if(MvcHtmlString.IsNullOrEmpty(Html.Display(prop.PropertyName)))
Its based on object template so to use it you need use it on object or whole model like
#Html.DisplayForModel("TemplateName")
I'm fairly new to ASP.NET MVC and am looking for help with the following issue.
I have a DropDownList control in a view. The selected value will be stored in an integer field of my model.
My preference is to not hard code the text and values in markup. In addition, I'd like to be able to use this same list of text and values in DropDownList controls in other views.
I'm not sure if I need to create a class that initializes these values somewhere, or perhaps I need an extension method. I could use some guidance on the best way to approach this.
This is a sample:
var apps = _webAppRepo.GetAll();
IList<SelectListItem> appSelectListItems = new List<SelectListItem>();
foreach (var item in apps) {
appSelectListItems.Add(new SelectListItem {
Text = item.ApplicationName,
Value = item.WebApplicationID.ToString()
});
}
ViewBag.AppSelectListItems = appSelectListItems;
And this is how you use it on you view:
#Html.DropDownListFor(model =>
model.WebApplicationID,
(IEnumerable<SelectListItem>)ViewBag.AppSelectListItems
)
If you would like to use it without a model, this is the way:
#Html.DropDownList("Foo",
(IEnumerable<SelectListItem>)ViewBag.AppSelectListItems
)
There are a number of ways of doing this, but you could do worse than storing these values in a view model, and rendering them on the page. I'm just using a Tuple here as an example:
public class MyViewModel
{
List<Tuple<int, string>> items_ = new List<Tuple<int, string>>();
public MyViewModel()
{
items_.Add(new Tuple<int, string>(1, "Item1"));
items_.Add(new Tuple<int, string>(2, "Item2"));
// etc
}
public List<Tuple<int, string>> Items { get { return items_; } }
}
Your controller:
public class MyController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel());
}
}
Your view:
#model MyNameSpace.MyViewModel
<select id="mySelect" name="mySelect>
#{foreach(Tuple<int, string> item in Model.Items){
<option value="#item.Item1">#item.Item2</option>
}}
</select>
Obviously, you can also make an extention method to create combo boxes in more interesting ways :)
You should populate your DropDownList from a database table, or from memory o reading from a xml file.
The you should be able to use the same data to populate any other ddl on your views.
There has been many discussion on ASP.NET MVC and Codebehind-files, mostly where it has been pointed out that these Codebehind-files are evil.
So my question is, how do you handle page-specific logic?
What we don't want here is spaghetti-code in the inline-code and we don't want page-specific code scattered throughout helper-classes or on top of the HTML-helper-class.
An example would be:
<% for(int i = 0; i < companyList.Count; i++) { %>
RenderCompanyNameWithRightCapsIfNotEmpty(company, i)
<% } %>
With accompanying codebehind:
private string RenderCompanyNameWithRightCapsIfNotEmpty(string company, index)
{
if (index == 0) {
return string.Format("<div class=\"first\">{0}</div>", company);
}
// Add more conditional code here
// - page specific HTML, like render a certain icon
string divClass = (index % 2 == 0) ? "normal" : "alternate";
return string.Format("<div class=\"{1}\">{0}</div>", company, divClass);
}
This will only be used on one page and is most likely subject to change.
Update: A couple approaches I thought about where these:
1) Inline codebehind on page - with simple methods that returns strings.
<script runat="server">
private string RenderCompanyHtml(string companyName) ...
<script>
2) Putting a method which returns a string in the Controller. But that would be putting View-logic into the Controller.
public class SomeController : Controller
{
[NonAction]
private static string RenderCompanyHtml(string companyName) ...
public ActionResult Index() ...
}
You should put that code in the controlleraction where you prepare the viewdata.
I usually make a region "helper methods" in my controller class with a few [NonAction] methods to keep things clean.
So my (simplified) controller would look like this:
public class SomeController : Controller
{
#region Helper methods
[NonAction]
private static string CompanyNameWithRightCapsIfNotEmpty(string company)
{
if (string.IsNullOrEmpty(company)) {
return company;
}
return UpperCaseSpecificWords(company);
}
#endregion
public ActionResult Companies()
{
var companies = GetCompanies();
var companyNames = companies.Select(c => CompanyNameWithRightCapsIfNotEmpty(c.Name));
ViewData["companyNames"] = companyNames;
return view();
}
}
Helper methods are one good way of handling page specific code, but I think it is a;ways preferable to get your model to show the data you need.
If you're going to go for the helper option, you would be better served by making the operation it perfroms a bit less page specific. If your method RenderCompanyNameWithRightCapsIfNotEmpty has to be so specific it would be better if your model provided it. One way would be to have the model provide a list with the text already formatted, and expose it as a public property (say an IEnumerable of formatted company names).
Use Html Helpers.
Like so create the helper methods in a static class:
public static string Label(this HtmlHelper helper, string target, string text)
{
return String.Format("<label for='{0}'>{1}</label>", target, text);
}
.. then use in your view:
<span><% =Html.Label("FinishDateTime.LocalDatetime", "Finish Time:")%><br />
You could create a helper method called maybe RenderCompanyName(string[] companies) that checked for nulls, did the caps manipulation and rendered the html in between - all in the same helper if you like.
Also: controller action methods should be light - ie. only getting the data and returning views. You should delegate things like manipulation of data for presentation to views and Html helpers.
EDIT: Here is a helper that you might be after:
This helper renders an IList<> to html in the form of an unordered list <ul>...</ul>. The useful thing about it is that it gives you control over how the list is rendered thru css AND it allows you to render additional html/content for each item. Take a look - this is the helper:
public static string UnorderedList<TItem>(this HtmlHelper helper,
IList<TItem> items, Func<TItem, string> renderItemHtml,
string ulID, string ulClass, string liClass)
{
StringBuilder sb = new StringBuilder();
// header
if (!ulID.IsNullOrTrimEmpty()) sb.AppendFormat("<ul id='{0}'", helper.Encode(ulID.Trim()));
else sb.AppendFormat("<ul");
if (!ulClass.IsNullOrTrimEmpty()) sb.AppendFormat(" class='{0}'>", helper.Encode(ulClass.Trim()));
else sb.AppendFormat(">");
// items
foreach (TItem i in items)
{
if (!liClass.IsNullOrTrimEmpty())
sb.AppendFormat("<li class='{0}'>{1}</li>", helper.Encode(liClass.Trim()),
renderItemHtml(i));
else
sb.AppendFormat("<li>{0}</li>", renderItemHtml(i));
}
// footer
sb.AppendFormat("</ul>");
return sb.ToString();
}
..using it is easy. here is a simple example to render a list of tags:
<div id="tags">
<h2>Tags</h2>
<%=Html.UnorderedList<Tag>(Model.Tags.Tags,tag=>
{
return tag.Name;
},null,null,null) %>
</div>
..you can see in my usage example that i have chosen not to specify any css or id attribute and i simply return the name of the Tag item thru the use of the anonymous delegate. Anonymous delegates are way easy to use.. in your case maybe something like this would work:
<div id="tags">
<h2>Tags</h2>
<%=Html.UnorderedList<string>(ViewData["companies"],company=>
{
if (someCondition) return company.ToUpper();
else return company;
},null,null,null) %>
</div>
.. ViewData["companies"] is an IList<string> for simplicity.