Asp.Net Razor View Passing Expression To Partial - asp.net-mvc

I find myself writing this a whole lot in my views:
<div class="form-group">
#Html.LabelFor(x => x.City)
#Html.EditorFor(x => x.City)
#Html.ValidationMessageFor(x => x.City)
</div>
I'd really like to put this in a Partial _Field.cshtml, something like this:
#model //what model type???
<div class="form-group">
#Html.LabelFor(Model)
#Html.EditorFor(Model)
#Html.ValidationMessageFor(Model)
</div>
That could then be called by:
#Html.Partial("_Field", x => x.City)
What would the #model type in my partial be if I wanted to accomplish something like this?
UPDATE This works, but I'd rather use a partial for ease of changing the template:
public static MvcHtmlString Field<TModel, TItem>(this HtmlHelper<TModel> html, Expression<Func<TModel, TItem>> expr)
{
var h = "<div class='form-group'>";
h += $"{html.LabelFor(expr)}{html.EditorFor(expr)}{html.ValidationMessageFor(expr)}";
h += "</div>";
return MvcHtmlString.Create(h);
}

That's not possible. However, what you want is very similar to editor templates. Essentially, you just create a view in Views/Shared/EditorTemplates named after one of the following conventions:
A system or custom type (String.cshtml, Int32.cshtml, MyAwesomeClass.cshtml, etc.)
One of the members of the DataType enum (EmailAddress.cshtml, Html.cshtml, PhoneNumber.cshtml, etc.). You would then apply the appropriate DataType attributes to your properties:
[DataType(DataType.EmailAdress)]
public string Email { get; set; }
Any thing you want, in conjunction with the UIHint attribute:
[UIHint("Foo")]
public string Foo { get; set; }
Which would then correspond to a Foo.cshtml editor template
In your views, then, you simply use Html.EditorFor:
#Html.EditorFor(x => x.City)
Then, for example, you could have Views/Shared/EditorTemplates/String.cshtml as:
<div class="form-group">
#Html.Label("", new { #class = "control-label" })
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { #class = "form-control" })
#Html.ValidationMessage("")
</div>
(The empty quotes are placeholders. Razor will automatically fill in the appropriate property name, thankfully.)
Calling EditorFor, then, will print all of this, rather than just the default text input. You can take this much further, as well. I have some articles on my blog that goes into greater detail, if you're interested.
UPDATE
It's worth mentioning a few features of EditorFor:
You can pass a template directly to the call, meaning you can customize what template is used on the fly and per instance:
#Html.EditorFor(x => x.City, "MyCustomEditorTemplate")
You can pass additionalViewData. The members of this anonymous object are added to the ViewData dynamic dictionary. Potentially, you could use this to branch within your editor template to cover additional scenarios. For example:
#Html.EditorFor(x => x.City, new { formGroup = false })
Then in your editor template:
#{ var formGroup = ViewData["formGroup"] as bool? ?? true; }
#if (formGroup)
{
<!-- Bootstrap form group -->
}
else
{
<!-- Just the input -->
}

Related

Unable to Post Dropdown Values Added at Runtime in ASP.NET MVC

For some reason I'm having trouble getting option values I add to an ASP.NET MVC dropdown at runtime to post.
In this case, I'm adding options to the dropdown at runtime using jquery.
This is what I have so far:
The Razor HTML:
#Html.DropDownListFor(sc => sc.SelectedComponents, Enumerable.Empty<SelectListItem>(), new { #class = "form-control", #id = "SelectedComponents", #name= "SelectedComponents", size = "5", multiple = "multiple" })
The relevant portion of the model:
public IEnumerable<string> SelectedComponents { get; set; }
Also, I can't get the posted values added at runtime to appear in the Forms Collection when I look in the controller action.
Can anyone suggest the best way to handle this particular situation?
Thanks much,
Pete
More information:
I'm trying to implement a simple solution using "BeginCollectionItem" to post added items at runtime when I submit the Razor Form.
In the main view I have this to display the partial view:
#foreach (var components in Model.selectedComponents)
{
#Html.Partial("SelectedComponents", Model)
}
The Partial View looks like this:
#model blah.blah.RequestViewModel
#using(Html.BeginCollectionItem("selectedComponents"))
{
<div class="form-group">
<label for="Component" class="control-label col-xs-12 col-sm-3">Selected Components:</label>
<div class="col-xs-12 col-sm-6">
#Html.DropDownListFor(model => model.selectedComponents, Enumerable.Empty<SelectListItem>(), new { #class = "form-control", #id = "SelectedComponents", #name = "SelectedComponents", size = "5", multiple = "multiple" })
</div>
<div class="col-xs-0 col-sm-3">
</div>
</div>
}
The relevant portion of the viewmodel looks like this:
public class RequestViewModel
{
public IEnumerable<string> selectedComponents { get; set; }
}
I simply want to post a list of strings that are added at runtime using jquery. This part is working and is not shown.
The controller that I'm posting to looks like this:
public ActionResult Create(HttpPostedFileBase[] files, RequestViewModel data)
{
}

Working with dynamic editor template

I am trying to build a web site that lets user to create new violation.Each violation has its own input area.For instance, if user wants to create a new Ramp Width Violation, he/she should provide width or for Ramp Slope Violation slope should be provided as input.
Taking into account those requirements , I decided to have a dynamic property inside my view.
My dynamic object property can be boolean , integer or double according to violation type.
Here is my class that includes dynamic property.Other properties are removed for brevity.
public class CreateViolationViewModel
{
public string DynamicName { get; set; }
public object DynamicValue { get; set; }
}
Main View
#Html.LabelFor(model => model.DynamicName, #Model.DynamicName, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(x => x.DynamicValue, "violationDynamics_" + Model.DynamicValue.GetType().Name)
</div>
#Html.HiddenFor(model => model.DynamicName, new {#value=Model.DynamicName })
EditorTemplate For Integer(violationDynamics_Int32.cshtml)
#{
Layout = null;
}
#model int
#Html.TextBoxFor(x => x, new { #class = "form-control", #type = "number" })
#Html.Hidden("ModelType", Model.GetType())
EditorTemplate For Double(violationDynamics_Double.cshtml)
#{
Layout = null;
}
#model double
#Html.TextBoxFor(x => x, new { #class = "form-control", #type = "number" })
#Html.Hidden("ModelType", Model.GetType())
Controller
[HttpPost]
public ActionResult Create(CreateViolationViewModel createViolationViewModel)
{
}
When the page is loaded it renders the related editor templates.The problem is that when the form is posted back dynamic value comes as a array of string as shown below.
I know I need something like model binder but haven't found the solution yet.By the way I take this approach from the previous answer here
So questions are ;
1- How can I make this approach work for post backs?
2- Is there any other recommend or easy approach to accomplish my task ?
Thanks

Adding a New Item in a List Rendered by Editor Template

I have an ASP.NET MVC page that uses a lot of AJAX. At the most basic level, it contains a list of questions, with each question having a list of answers inside it. My view models (vastly simplified for the sake of this question):
View Models:
public class QuestionViewModel
{
....
public string QuestionText { get; set; }
public List<AnswerViewModel> AnswerList { get; set; }
public bool EditMode { get; set; }
....
}
public class AnswerViewModel
{
....
public string Answer { get; set; }
....
}
_View.cshtml:
#model QuestionViewModel
#if(Model.EditMode)
{
#Html.EditorFor(x => x)
}
else
{
...
}
QuestionViewModel.cshtml (Editor Template):
#model Project.Models.QuestionViewModel
....
#Html.EditorFor(x => x.AnswerList)
....
AnswerViewModel.cshtml (Editor Template):
#model KSUAdvising.Models.AnswerViewModel
....
#Html.TextBoxFor(x => x.Answer)
....
^^^This EditorFor call renders my answer list fine (along with the appropriate indices so the model binder will bind back to the list appropriately. i.e.:
<input id="AnswerList_0__Answer" name="AnswerList[0].Answer" type="text" value="Yes">
<input id="AnswerList_1__Answer" name="AnswerList[1].Answer" type="text" value="No">
However, on the client side I need to add items to this list when the user clicks a button. This needs to happen client side so no changes are made to the database until the user saves the question.
I see a few direction to go to solve this problem:
Make an AJAX call to the server to render back a new blank answer and add it to the DOM. However, with this being out of the context of a question it doesn't render the proper name (index) and it doesn't get picked up by the model binder.
Render a hidden HTML "template" to the client including the proper input names, then clone this template and modify the name indices to satisfy the model binder. This seems a bit hacky, and is hard to properly render this template.
This seems like it would be a common scenario, but I'm having trouble coming up with a good solution to this. What is the recommended approach for this case?
I vote for option 2.
Render a hidden HTML Item Template. Then on a client button click, clone the template and modify the index.
In the cshtml page, after the foreach, add a new hidden div for the template. Give it an index place holder (I use _ x0x_). Create a new empty item and then render it the same way as you did in the foreach. (You could also have the item render as a partial view and then call it inside the foreach and outside.)
Here is sample cshtml page:
#foreach (var role in roles)
{
int roleIndex = roles.IndexOf(role);
string rolePrefix = "CasePartyRoles[" + roleIndex + "].";
<div id="CasePartyRoleIndex_#roleIndex" class="row brdr-bttm mrgn-bttm-sm">
<div class="col-md-10 mrgn-bttm-sm">
#Html.Hidden(rolePrefix + "SequenceNo", role.SequenceNo)
#Html.Hidden(rolePrefix + "RowVersion", role.RowVersion)
#Html.DropDownListFormGroupFor(modelItem => role.PartyRoleCode, (SelectList)ViewBag.PartyRoleSelectList, null, "col-md-3", "col-md-9", null, role.PartyRoleCode, rolePrefix + "PartyRoleCode")
#Html.DropDownListFormGroupFor(modelItem => role.PartyStatusCode, (SelectList)ViewBag.PartyStatusSelectList, null, "col-md-3", "col-md-9", null, role.PartyStatusCode, rolePrefix + "PartyStatusCode")
#Html.EditorFormGroupFor(modelItem => role.SubFileNo, "col-md-3", "col-md-9", null, null, rolePrefix + "SubFileNo")
#Html.EditorFormGroupFor(modelItem => role.PartyRank, "col-md-3", "col-md-9", null, null, rolePrefix + "PartyRank")
</div>
</div>
}
<div id="CasePartyRoleTemplate" class="hidden">
#*Template for new Role*#
#{
var newRole = new CasePartyRole();
string newRolePrefix = "CasePartyRoles[_x0x_].";
}
<div id="CasePartyRoleIndex__x0x_" class="row brdr-bttm mrgn-bttm-sm">
<div class="col-md-10 mrgn-bttm-sm">
#Html.Hidden(newRolePrefix + "SequenceNo", newRole.SequenceNo)
#Html.Hidden(newRolePrefix + "RowVersion", newRole.RowVersion)
#Html.DropDownListFormGroupFor(modelItem => newRole.PartyRoleCode, (SelectList)ViewBag.PartyRoleSelectList, null, "col-md-3", "col-md-9", null, newRole.PartyRoleCode, newRolePrefix + "PartyRoleCode")
#Html.DropDownListFormGroupFor(modelItem => newRole.PartyStatusCode, (SelectList)ViewBag.PartyStatusSelectList, null, "col-md-3", "col-md-9", null, newRole.PartyStatusCode, newRolePrefix + "PartyStatusCode")
#Html.EditorFormGroupFor(modelItem => newRole.SubFileNo, "col-md-3", "col-md-9", null, null, newRolePrefix + "SubFileNo")
#Html.EditorFormGroupFor(modelItem => newRole.PartyRank, "col-md-3", "col-md-9", null, null, newRolePrefix + "PartyRank")
</div>
</div>
</div>
Here is a little jQuery function to add the item based on the template:
function createItemFromTemplate(templateId, indexNo, insertBeforeId) {
// Copy the template Element, replaces all the _x0x_ with the index no and add the new element
$(insertBeforeId).before($.parseHTML($(templateId).clone().prop('outerHTML').replace(/_x0x_/g, indexNo)));
}
It depends... For the sake of performance I wouldn't go with the first option. This slight delay to load up a new item via Ajax could be annoying.
Personally most times I would just build an HTML element completely on client side. I know it complicates maintanance as you have to remember to change your client side logic any time you make changes to your model collection but it's fast and straightforward.
Also have an idea how to improve your second option. Basicly you don't need to have a real object to build it.
#Html.TextBoxFor(model => model.Items[int.MaxValue], new { style= "display:none" })
the input rendered
<input name="Items[2147483647]" id="Items_2147483647_" style="display: none;" type="text" value="">
Please note the Items property is null in my case but it doesn't matter if you just want to build a template as xxxFor html helpers just use expressions to build html. Also it's very easy then to replace int.MaxValue while cloning the template with a real index. It's safe to use int.MaxValue as a placeholder, it's very unlickely you will have that much items on your page.
Hope it helps!

ASP.NET MVC 3 diplay label + data in readonly scenario using templated HtmlHelpers

I need to display short description like label and near it data associated with this description.
For editing data it looks like
<div class="field">
#Html.LabelFor(m => m.CustomerTaxId)
#Html.EditorFor(m => m.CustomerTaxId)
</div>
Now I want to do the same thing for readonly data. I've written next code
<div>
#Html.LabelFor(m => m.TotalAmount)
#Html.DisplayFor(m => m.TotalAmount)
</div>
It seems working but it breaks semantic meaning of label tag which must be used for input tags only.
Is there a way in MVC 3 to get something like
<div>
<span class="label">Total amount</span>
<span class="value">1500.00 $</span>
</div>
with minimal efforts (think it can be done with heavy template usage)
It seems like I can overwrite template for Html.DisplayFor how to deal with Html.LabelFor?
You can create custom html helper as below,
public static MvcHtmlString Span<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData, null);
string spanInnerText = metadata.DisplayName ?? metadata.PropertyName;
TagBuilder tag = new TagBuilder("span");
tag.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
tag.SetInnerText(spanInnerText);
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
You could use in a view as,
#Html.Span(m => m.Name, new { #class = "label" })

How to pass data to the view in mvc asp.net?

let me ask the question first.
Where is the correct place to call a function that load a list of values to be display on a view?
I create a controller like this
public ActionResult Create()
{
SeaModel newSea = new SeaModel();
return View("Season/CreateSea", newSea);
}
//I not quite sure if this should go here or in another place
partial class seaDataContext
{
public List<string> getSeaSettings()
{
var seaSettings = from p in settings
where p.setting == "periods"
select p.value;
return seaSettings.ToList<string>();
}
}
The model is like
public class SeaModel
{
[Required(ErrorMessage="*")]
[Display(Name = "Period Name")]
public string periods { get; set; }
}
Which create a view like
#using (Html.BeginForm()) {
#Html.ValidationSummary(true, "Please correct the following errors.")
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
#Html.LabelFor(model => model.periods)
</div>
<div class="editor-field">
#Html.Select(model => model.periods, ****My doubt comes here****)
#Html.ValidationMessageFor(model => model.periods)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
so, How and where do I pass the return of getSeaSettings() to the view?
Thanks
best practice is to make a Selectlist in your Model for this dropdown.
however you also can use the more easy option: using ViewData
public ActionResult Create()
{
SeaModel newSea = new SeaModel();
ViewData["myDropDown"] = new SelectList(listOfObjects, "valueOfTheObjectLikeID", "NameYouWantToShowInDropdown");
return View("Season/CreateSea", newSea);
}
then:
#Html.Select(model => model.periods, ViewData["myDropDown"] as SelectList)
dont forget in your [HttpPost] method to also fill in the viewdata if you'r validation fails, so the dropdown can be rebuilt.
You need to look at repository pattern. Have a look at this tutorial at asp.net site
http://www.asp.net/mvc/tutorials/creating-model-classes-with-linq-to-sql-cs
Stefanvds's approach was what I used to do.
But I found out there is a better way using additionalViewData.
Use this EditorFor HTML Helper extension method.
http://msdn.microsoft.com/en-us/library/ff406462.aspx
Instead of passing Select List Items into ViewData in the Controller, you do this in your View.
Pass in your list items as an anonymous object for the additionalViewData parameter.
Important thing is to use the same name as your Property Name.
#Html.EditorFor(
m => m.MyPropertyName,
new { MyPropertyName = Model.ListItemsForMyPropertyName }
);
Of course, you are passing in a View Model object.
public class MyViewModel
{
public int MyPropertyName;
public IList<SelectListItem> ListItemsForMyPropertyName;
}
EditorFor method uses your existing Editor View Templates.
So you don't need to specify CSS class names and HTML attributes again like when you use the Html.DropDown( ) method.
For example,
//------------------------------
// additionalViewData
//------------------------------
#Html.EditorFor(
m => m.MyPropertyName,
new { MyPropertyName = Model.ListItemsForMyPropertyName }
)
//------------------------------
// traditional approach requires to pass your own HTML attributes
//------------------------------
#Html.DropDown(
"MyPropertyName",
Model.ListItemsForMyPropertyName,
new Dictionary<string, object> {
{ "class", "myDropDownCssClass" }
}
);
//------------------------------
// DropDownListFor still requires you to pass in your own HTML attributes
//------------------------------
#Html.DropDownListFor(
m => m.MyPropertyName,
Model.ListItemsForMyPropertyName,
new Dictionary<string, object> {
{ "class", "myDropDownCssClass" }
}
);
That is why I like the additionalViewData approach more.
Because, the HTML code rendered relies on the Editor Templates completely.
Also, using specialized View Models make your code cleaner and easier to maintain.
Hope it helps.

Resources