ASP.NET MVC Dynamic List Binding - asp.net-mvc-2

I have a strongly typed mvc page which I wont to bind a unorder list to a list of objects. So in mvc view it might look something like
<% foreach (var item in Model.WhatYouDoL) { %>
<li><%: Html.Encode(item.Text) %><input type="hidden" name="WhatYouDoL[0].Reference" /></li>
<% } %>
My view model might look something like
public class ViewModelQuoteWhatYouDoInMotorTrade
{
public List<WhatYouDo> WhatYouDoL { get; set; }
}
and my list contains object like
public struct WhatYouDo
{
public decimal Percent { get; set; }
public string Reference { get; set; }
public string Text { get; set; }
}
This binds ok providing I use WhatYouDoL[0].Reference with the index ([0]) which when loading I can set with an index. The problem is I want to add and remove from this list on the client side. So I might have some js which adds and extra list item and removes the current. This means I have to somehow manage the indexes in the name and keep them in order and non duplicate on the client side. Does anyone know if there is a way to get around using the index in the name.
Thanks in advance.

There is, probably, a mistake:
<% foreach (var item in Model.WhatYouDoL) { %>
<li><%: Html.Encode(item.Text) %><input type="hidden" name="WhatYouDoL[0].Reference" /></li>
<% } %>
Maybe it should be:
<% foreach (var item in Model.WhatYouDoL) { %>
<li><%: item.Text %><input type="hidden" name="<%: item.Reference %>" /></li>
<% } %>
You don't need to encode as long as you use <: proof

Related

Binding List of KeyValuePair to checkboxes

I am using ASP.Net MVC with C#. I have a model which has a member for filter criteria. This member is a IList>. The key contains value to display and the value tells if this filter is selected or not. I want to bind this to bunch of checkboxes on my view. This is how I did it.
<% for(int i=0;i<Model.customers.filterCriteria.Count;i++) { %>
<%=Html.CheckBoxFor(Model.customers.filterCriteria[i].value)%>
<%=Model.customers.filterCriteria[i].key%>
<% } %>
This displays all checkboxes properly. But when I submit my form, in controller, I get null for filtercriteria no matter what I select on view.
From this post I got a hint for creating separate property. But how will this work for IList..? Any suggestions please?
The problem with the KeyValuePair<TKey, TValue> structure is that it has private setters meaning that the default model binder cannot set their values in the POST action. It has a special constructor that need to be used allowing to pass the key and the value but of course the default model binder has no knowledge of this constructor and it uses the default one, so unless you write a custom model binder for this type you won't be able to use it.
I would recommend you using a custom type instead of KeyValuePair<TKey, TValue>.
So as always we start with a view model:
public class Item
{
public string Name { get; set; }
public bool Value { get; set; }
}
public class MyViewModel
{
public IList<Item> FilterCriteria { get; set; }
}
then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel
{
FilterCriteria = new[]
{
new Item { Name = "Criteria 1", Value = true },
new Item { Name = "Criteria 2", Value = false },
new Item { Name = "Criteria 3", Value = true },
}
});
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// The model will be correctly bound here
return View(model);
}
}
and the corresponding ~/Views/Home/Index.aspx view:
<% using (Html.BeginForm()) { %>
<%= Html.EditorFor(x => x.FilterCriteria) %>
<input type="submit" value="OK" />
<% } %>
and finally we write a customized editor template for the Item type in ~/Views/Shared/EditorTemplates/Item.ascx or ~/Views/Home/EditorTemplates/Item.ascx (if this template is only specific to the Home controller and not reused):
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.Item>"
%>
<%= Html.CheckBoxFor(x => x.Value) %>
<%= Html.HiddenFor(x => x.Name) %>
<%= Html.Encode(Model.Name) %>
We have achieved two things: cleaned up the views from ugly for loops and made the model binder successfully bind the checkbox values in the POST action.

Data is empty in Post method asp.net mvc 2

I`m trying to display classes that have properties of type some custom class inside it.
Model:
public class ComplexParent
{
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
public class SimpleChild
{
public int Id { get; set; }
public string ChildName { get; set; }
public string ChildDescription { get; set; }
}
Controller:
public ActionResult Testify(int id)
{
ComplexParent par = new ComplexParent();
par.First = new SimpleChild() { Id = id };
par.Second = new SimpleChild()
{
Id = id + 1,
ChildName = "Bob",
ChildDescription = "Second"
};
return View("Testify", par);
}
[HttpPost]
public ActionResult Testify(ComplexParent pComplexParent)
{
return View("Testify", pComplexParent);
}
View:
<% using (Html.BeginForm())
{%>
<fieldset>
<legend>Fields</legend>
<%: Html.EditorFor(x => x.First) %>
<br />
<%: Html.EditorFor(x => x.Second.ChildName)%>
<br/>
<br/>
<br/>
<% Html.RenderPartial("SimpleChild", Model.First); %>
<p>
<input type="submit" value="Watch me :-)" />
</p>
</fieldset>
<% } %>
When it comes to Get it works just fine, I can see all the data. But on post pComplexParent parameter is empty (both properties of a complex class are nulls). Probably Im missing something here, but could not get this to work ...
Small addition: view part that only shows editor for name makes Second child not null and name is set to Bob. But I dont understand how to make it just with EditorFor or DisplayFor methods.
UPDATE: Thanks to Darin Dimitrov, who kindly went throught all my code and found what caused this problem. The exact problem was that if you are using display template, asp.net mvc 2 doesnt post any values back and if whole template is has nothing to post back object is null. I still thinking of the way how to get the data, even if you don`t want to edit it. But using editor template does the thing and I have all objects filled with proper data now.
Your view is a bit of a mess. You are using editor templates along with partials for the first child. It is not very clear what fields are included inside the form. I would recommend you using only editor templates:
Model:
public class ComplexParent
{
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
public class SimpleChild
{
public int Id { get; set; }
public string ChildName { get; set; }
public string ChildDescription { get; set; }
}
Controller:
[HandleError]
public class HomeController : Controller
{
public ActionResult Testify(int id)
{
var par = new ComplexParent();
par.First = new SimpleChild() { Id = id };
par.Second = new SimpleChild()
{
Id = id + 1,
ChildName = "Bob",
ChildDescription = "Second"
};
return View(par);
}
[HttpPost]
public ActionResult Testify(ComplexParent pComplexParent)
{
return View(pComplexParent);
}
}
View:
<% using (Html.BeginForm()) { %>
<%: Html.EditorFor(x => x.First) %>
<%: Html.EditorFor(x => x.Second) %>
<input type="submit" value="Watch me :-)" />
<% } %>
Editor template for SimpleChild (~/Views/Home/EditorTemplates/SimpleChild.ascx):
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<SomeNs.Models.SimpleChild>" %>
<%: Html.HiddenFor(x => x.Id) %>
<%: Html.EditorFor(x => x.ChildName) %>
<%: Html.EditorFor(x => x.ChildDescription) %>
Now if you want to have different editor templates for the two child properties you could either specify the editor template name when including it:
<%: Html.EditorFor(x => x.First, "FirstChildEditor") %>
which corresponds to ~/Views/Home/EditorTemplates/FirstChildEditor.ascx or use an [UIHint] attribute at your model:
public class ComplexParent
{
[UIHint("FirstChildEditor")]
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
My recommendation is not to use Html.RenderPartial for generating input fields because their names will be hardcoded and won't bind properly depending on your objects hierarchy.

Create a mailto anchor tag in MVC2

Is it possible in MVC2 to create an anchor tag that contains values from ViewData?
e.g.
Send Email
This code above doesn't render and just throws an exception?
Yes it is.
Moreover the default template will render that field exactly as you wrote if you use the Display Html extensions and an associated ViewModel. Just decorate the field in the model with the right DataType attribute
[DataType(DataType.EmailAddress)]
public string EmailAddress { get; set; }
Please see this post series for further informations.
EDIT:
Suppose you have the following ViewModel
public class CustomerModel {
public string CustomerName { get; set; }
[DataType(DataType.EmailAddress)]
public string EmailAddress { get; set; }
}
and inside your Controller the following Action
[HttpGet]
public ActionResult ViewCustomer( int id ) {
CustomerModel cm = LoadCustomerByID( id );
return View( cm );
}
you can have a view named Viewcustomer.aspx that is strong typed to an instance of CustomerModel and just have this code in the view
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MyApp.CustomerModel>" %>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<%= Html.DisplayForModel() %>
</asp:Content>
Please take a coffe and get time to read that article series. It's very easy and can address more than what I am trying to write in this small post. ;)
Hope it helps!
The answer here is not as complicated as many would think.. it's simply a Quote (") problem:
Try changing your outer quotes to single quotes.. It terminates the string when you use " quotes in your markup aswell as in the ["Email"]... :)
<a href='mailto:<%: ViewData["Email"] %>'>Send Email</a>

In MVC2, how do I validate fields that aren't in my data model?

I am playing with MVC2 in VS 2010 and am really getting to like it. In a sandbox application that I've started from scratch, my database is represented in an ADO.NET entity data model and have done much of the validation for fields in my data model using Scott Guthrie's "buddy class" approach which has worked very well.
However, in a user registration form that I have designed and am experimenting with, I'd like to add a 'confirm email address' or a 'confirm password' field. Since these fields obviously wouldn't exist in my data model, how would I validate these fields client side and server side?
I would like to implement something like 'Html.ValidationMessageFor', but these fields don't exist in the data model. Any help would be greatly appreciated.
I use view models. I don't create the data model instance to persist until the view model is valid.
Below is a simple example. Notice that some of the properties are data models, but the validation properties only exist on this view model.(the base isn't pertinent here)
public class ProblemAddToDepartmentProductView : ViewModel
{
public Problem Problem { get; set; }
public IList<Product> AllProducts { get; set; }
public IList<Department> AllDepartments { get; set; }
public string ProblemId { get; set; }
public string ProblemName { get; set; }
[DisplayName("Choose the product:")]
[Required(ErrorMessage = "Select the Product.")]
public string SelectedProduct { get; set; }
public SelectList GetProducts()
{
var selectList = new SelectList(AllProducts, "Id", "Name");
return selectList;
}
[DisplayName("Choose the department using this problem for that product:")]
[Required(ErrorMessage = "Select the Department.")]
public string SelectedDepartment { get; set; }
public SelectList GetDepartments()
{
var selectList = new SelectList(AllDepartments, "Id", "Name");
return selectList;
}
internal class ProductSelect
{
public Guid Id { get; set; }
public string Name { get; set; }
}
}
It will also help to see it wired on the page:
<fieldset>
<legend>Fields</legend>
<div class="editor-label">
<%= Html.LabelFor(x => x.SelectedProduct) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(x => x.SelectedProduct, Model.GetProducts(),"--Select One--") %>
<%= Html.ValidationMessageFor(x => x.SelectedProduct)%>
</div>
<div class="editor-label">
<%= Html.LabelFor(x => x.SelectedDepartment) %>
</div>
<div class="editor-field">
<%= Html.DropDownListFor(x => x.SelectedDepartment, Model.GetDepartments(),"--Select One--") %>
<%= Html.ValidationMessageFor(x => x.SelectedDepartment)%>
</div>
<p>
<input type="submit" value="Add Selected" />
</p>
</fieldset>
I also do this so the model will have these values if the validation fails, to pull back in the needed data for the drop downs:
p.ProblemId) %>
<%= Html.HiddenFor(p => p.ProblemName) %>
Client-Side:
Using Javascript Validation
and/or
Server-Side:
Validate in Controller (Using FormCollection) - or
Create "CustomViewModel" Class that encapsulates all validation strongly type your view - or
You could add two string properties to your Model. Doing so will allow you to populate an instance of your model in a Controller and validate appropriately in your Model/s...

Html.EditorFor does not render anything if I use a custom object?

It seems like it's not possible to edit custom object anymore after I upgraded to asp.net mvc 2 rc 2? I use this approach http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html with a custom object like this:
My model has just one property but inherits from an abstract base class
public class Page : ContentItem {
[DataType(DataType.MultilineText)]
public virtual string MainIntro { get;set; } // This property render correct
[DisplayFormat(NullDisplayText="(null value)")]
public virtual DetailCollection Tags { get; set; }
}
My controller looks like this
public ActionResult Edit(string pagePath) {
var page = _repository.GetByUrlSegment(pagePath);
return View(page.EditViewName, new DashboardModel(page, RootPages));
}
And my view looks like this
<% using (Html.BeginForm("update","Dashboard", FormMethod.Post, new { name = "editForm" } )) %>
<% { %>
<div>
<%=Html.EditorFor(model => model.CurrentItem) %>
<div class="editor-button">
<input type="submit" value="Save" />
</div>
</div>
<% } %>
Perhaps it would be better to expose this to the view as a space-separated string and exclude the collection from being displayed in the view. Alternatively, you might be able to define a specific template for how you want to display a collection. It's not clear to me how MVC would be able to determine what to display otherwise.
Try something like:
[ShowForDisplay(false)]
[ShowForEdit(false)]
public virtual DetailCollection Tags { get; set; }
public virtual string TagList
{
get
{
if (tags == null) return "(null value)";
// assumes DetailCollection implements IEnumerable<string>
return string.Join( " ", tags.Select( t => t).ToArray() );
}
set
{
tags = new DetailCollection( value.Split( new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries )
.Select( s => s.Trim() ) );
}
}