Model Binding binding at more than one level - asp.net

I have several forms, all which require a checkbox for each province/state. Therefore, I've made a partial view to render the checkboxes inside a form to promote code re-use. But when the user submits the form to a controller method, the RegionsViewModel does not get binded. The overall question is, how can I get multiple forms to share a partial view and view model?
Here's a sample code of my situation
Models
public class Form1ViewModel
{
/* Some properties */
public RegionsViewModel Regions {set; get;}
}
public class Form2ViewModel
{
/* Some properties */
public RegionsViewModel Regions {set; get;}
}
public class Form3ViewModel
{
/* Some properties */
public RegionsViewModel Regions {set; get;}
}
public class RegionsViewModel
{
public bool ON {set; get;}
public bool QC {set; get;}
/* this continues for all provinces and states */
}
Controller
[HttpPost]
public ActionResult Form(Form1VewModel model) {
//All properties except for model.RegionViewModel does not bind properly to the submitted form :(
}
Form1ViewModel.aspx
<% using (Html.BeginForm())
{%>
<!-- Binds some property -->
<% Html.RenderPartial("Controls/RegionSelector", Model.Regions); %>
<input type="submit" value="Submit Form!" />
<%}%>
Controls/RegionSelector.ascx
<%=Html.CheckBoxFor(x => x.AvailableProvince_ON> ON
<%=Html.CheckBoxFor(x => x.AvailableProvince_QC> QC
<!-- Binds to all provinces and states -->
Update
Replaced "Model.RegionSelectorVm" with "Model.Region". Thanks for finding the bug in my demo code Darin Dimitrov.

What is RegionSelectorVm? It seems that this is the type of your partial. Try with editor templates. It's cleaner:
<% using (Html.BeginForm()) { %>
<!-- Binds some property -->
<%= Html.EditorFor(x => x.Regions) %>
<input type="submit" value="Submit Form!" />
<% } %>
and inside ~/Views/Shared/EditorTemplates/RegionsViewModel.ascx:
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.RegionsViewModel>" %>
<%= Html.CheckBoxFor(x => x.ON) %> ON
<%= Html.CheckBoxFor(x => x.QC) %> QC
<!--
Continue with inputs for the provinces and states
which are part of the RegionsViewModel model
-->
Now everything should bind correctly.

Related

asp mvc 2 EditorFor strange behavior

ok i've defined a shared editor for string like the following
<%# Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl"
%>
<%= Html.LabelFor(model => model) %>
<%= Html.TextBoxFor(model => model) %>
<%= Html.ValidationMessageFor(model =>
model) %>
now i'm calling the custom editor like this in another control
<%= Html.EditorFor(model=>model.Username)%>
<%= Html.EditorFor(model=>model.Email)%>
<%= Html.EditorFor(model=>model.Password)%>
my model is like this
[Required(ErrorMessage="Le nom d'utilisateur est requis.")]
[DataType(DataType.Text)]
[DisplayName("Nom d'utilisateur")]
public string Username { get; set; }
[Required(ErrorMessage = "L'email est requis.")]
[DataType(DataType.EmailAddress)]
[DisplayName("Courriel")]
public string Email { get; set; }
[Required(ErrorMessage = "Le mot de passe est requis.")]
[ValidatePasswordLength]
[DataType(DataType.Password)]
[DisplayName("Mot de passe")]
public string Password { get; set; }
The only display that is rendered is the Email field.
The two others are not rendered ?
If i remove the DataType.Text and DataType.Password then all the display fields are rendered ??
Very strange behavior...
Someone knows why ?
You need a ValidationSummary to show the errors from all properties in a model. Otherwise, you will need a ValidationMessageFor for each property in the model.
This will work:
<%= Html.ValidationSummary() %>
Or this:
<%= Html.ValidationMessageFor(model.UserName) %>
<%= Html.ValidationMessageFor(model.Email) %>
<%= Html.ValidationMessageFor(model.Password) %>
DataType controls what type of template is used to render. When you specify Text or Password, then MVC will select the default templates for those (they're built-in) and ignore your template.
Email works because there is no built-in email template, and thus it falls back to string.
EDIT: I think i misunderstood. Are you saying they don't render at all? Do you have blank Password and Text templates in your EditorTemplates folder?

Custom ViewModel with MVC 2 Strongly Typed HTML Helpers return null object on Create?

I am having a trouble while trying to create an entity with a custom view modeled create form. Below is my custom view model for Category Creation form.
public class CategoryFormViewModel
{
public CategoryFormViewModel(Category category, string actionTitle)
{
Category = category;
ActionTitle = actionTitle;
}
public Category Category { get; private set; }
public string ActionTitle { get; private set; }
}
and this is my user control where the UI is
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<CategoryFormViewModel>" %>
<h2>
<span><%= Html.Encode(Model.ActionTitle) %></span>
</h2>
<%=Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>
<p>
<span class="bold block">Başlık:</span>
<%=Html.TextBoxFor(model => Model.Category.Title, new { #class = "width80 txt-base" })%>
</p>
<p>
<span class="bold block">Sıra Numarası:</span>
<%=Html.TextBoxFor(model => Model.Category.OrderNo, new { #class = "width10 txt-base" })%>
</p>
<p>
<input type="submit" class="btn-admin cursorPointer" value="Save" />
</p>
<% } %>
When i click on save button, it doesnt bind the category for me because of i am using custom view model and strongly typed html helpers like that
<%=Html.TextBoxFor(model => Model.Category.OrderNo) %>
My html source looks like this
<form action="/Admin/Categories/Create" method="post">
<p>
<span class="bold block">Başlık:</span>
<input class="width80 txt-base" id="Category_Title" name="Category.Title" type="text" value="" />
</p>
<p>
<span class="bold block">Sıra Numarası:</span>
<input class="width10 txt-base" id="Category_OrderNo" name="Category.OrderNo" type="text" value="" />
</p>
<p>
<input type="submit" class="btn-admin cursorPointer" value="Kaydet" />
</p>
</form>
How can i fix this?
Your View Model needs a default constructor without parameters and you need public set methods for each of the properties. The default model binder uses the public setters to populate the object.
The default model binder has some rules it follows. It chooses what data to bind to in the following order:
Form parameters from a post
Url route data defined by your route definitions in global.asax.cs
Query string parameters
The default model binder then uses several strategies to bind to models/parameters in your action methods:
Exact name matches
Matches with prefix.name where prefix is the parent class and name is the subclass/property
Name without prefix (as long as there are no collisions you don't have to worry about providing the prefix)
You can override the behavior with several options from the Bind attribute. These include:
[Bind(Prefix = "someprefix")] -- Forces a map to a specific parent class identified by the prefix.
[Bind(Include = "val1, val2")] -- Whitelist of names to bind to
[Bind(Exclude = "val1, val2")] -- Names to exclude from default behavior
You could use editor templates. Put your ascx control in ~/Views/Shared/EditorTemplates/SomeControl.ascx. Then inside your main View (aspx page) include the template like so (assuming your view is strongly typed to CategoryFormViewModel):
<%= Html.EditorForModel("SomeControl") %>
instead of
<% Html.RenderPartial("SomeControl", Model) %>
Make a default constructor for your viewmodel and initialize the Category there
public CategoryFormViewModel()
{
Category = new Category()
}
And at your controller action receive the viewmodel
public ActionResult ActionName(CategoryFormViewModel model)
{
//here you can access model.Category.Title
}

ASP.NET MVC 2 editor template for value types, int

I want to create a MVC 2 editor template for a value type i.e. int , has anyone done this with the preview 1 bits?
Many thanks
Will Nick Clarke's answer work when you submit the values on postback?
In MVC2 preview 2, calling Html.Textbox("abc", Model.ToString())
will render a textbox with ".abc" appended to the name, e.g.
<input id="StartDate_abc" name="StartDate.abc" type="text" value="02 Feb 09" />
which will cause problems when you postback and attempt to UpdateModel().
I did an editor template for a DateTime, the following works for me:
/Views/Shared/EditorTemplates/DateTime.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime>" %>
<%= Html.TextBox(String.Empty, Model.ToString("dd MMM yy")) %>
or, to use jQuery's DatePicker for all your DateTimes
add a reference to jQuery and jQueryUI to either your Masterpage or to the View containing the call to EditorFor.
/Views/Shared/EditorTemplates/DateTime.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime>" %>
<%= Html.TextBox("", Model.ToString("dd MMM yy")) %>
<script type="text/javascript">
$("#<%= ViewData.ModelMetadata.PropertyName %>").datepicker({ dateFormat: 'dd M y' });
</script>
Update: ASP.NET MVC3, using the Razor syntax:
#model System.DateTime
#Html.TextBox("", Model.ToString("dd MMM yy"))
<script type="text/javascript">
$("##ViewData.ModelMetadata.PropertyName").datepicker({ dateFormat: 'dd M y' });
</script>
And to use it all you need in your View is:
#Html.EditorFor(model => model.DueDate)
-Matt
I have not tried preview 1 yet but they did what you are asking for in this channel9 video:
http://channel9.msdn.com/posts/Glucose/Hanselminutes-on-9-ASPNET-MVC-2-Preview-1-with-Phil-Haack-and-Virtual-Scott/
They do both DisplayFor and EditorFor, starts around 2 minutes.
--Edit--
For value type i.e. int I was able to get it to work in the same way.
Create a model to pass to my view:
public class HomeController : Controller
{
public ActionResult Index()
{
HomeModel model = new HomeModel();
model.message = "Welcome to ASP.NET MVC!";
model.number = 526562262;
model.Date = DateTime.Now;
return View(model);
}
}
public class HomeModel
{
public string message { get; set; }
public int number { get; set; }
public DateTime Date { get; set; }
}
Link view to the model using the new template logic:
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<HomeModel>" %>
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
<p>
<% Html.EditorFor(c => c.message); %>
</p>
<p>
<% Html.EditorFor(c => c.number); %>
</p>
<p>
<% Html.EditorFor(c => c.Date); %>
</p>
Then create a template for each of the types e.g. Int32:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
Editor For My Int32: <%= Html.TextBox("abc", Model.ToString())%>
I put this in Views\Shared\EditorTemplates\Int32.ascx
I've written a blog post about how to do this by creating reusable templates in MVC 2.
My post also explains the relationship between TemplateInfo and templates.
I have found Brad Wilson's blog to have the best examples and explanations. Part-3 of the series talks specifically about value types (String, decimal, Int32).
Enjoy!

How to return Nested PartialViews (including their javascript) from an AJAX call in ASP.Net MVC

I have created a treeview of Categories using nested partial views:
my Index page (that displays the treeview):
<div>
Category Menu:
<input type="button" value="1" name='selectCat_btn' />
<input type="button" value="2" name='selectCat_btn' />
</div>
<!-- Treeview -->
<% Html.RenderPartial("ItemCats_UL", Model); %>
<div id="CatSelectorOutput">
</div>
ItemCats_UL:
<div>
<ul id="catsTree">
<% Html.RenderPartial("ItemCats_LI", Model); %>
</ul>
</div>
<script type="text/javascript" >
$(document).ready(function() {
$("#catsTree").treeview();
</script>
ItemCats_LI:
<%foreach (ItemCategory itemCat in Model)
{ %>
<li>
<%= itemCat.Name %>
<%if (itemCat.Children != null && itemCat.Children.Count() > 0)
{ %>
<ul>
<% Html.RenderPartial("ItemCats_LI", itemCat.Children); %>
</ul>
<%} %>
</li>
<%} %>
Now this treeview works perfectly when I return the basic View("Index", Model) from my controllers Index action on page load.
The trouble comes when I want to change the Categories Model displayed in my Treeview (the nested partialViews) from an AJAX call...
For example: I click one the 'Cats2' button and the page should display Categories with ParentID of 2 in the Treeview. I attempted this by returning a JsonResult of the html of the ItemCats_UL PartialView (using a RenderPartialToString method found here) from my Controller Action. As some of you might know Javascript won't run in your partial view when you use an AJAX form to return a PartialViewResult, and I need Javascript in my Treeview which is why I'm using the RenderPartialToString.
The category select button click handler:
<script type="text/javascript">
$("[name='selectCat_btn']").click(function() {
var CID = $(this).attr('value');
$.ajax({
type: "POST",
url: "SelectCat",
dataType: "json",
data: { "CID": CID },
success: function(result) { $("#CatSelectorOutput").html(result.output); }
});
return false;
});
</script>
My Controller Action:
[AcceptVerbs(HttpVerbs.Post)]
[UrlRoute(Name = "SelectCat", Path = "selectCat")]
public ActionResult SelectCat(int CID)
{
IQueryable<ItemCategory> cats;
cats = ItemRepo.GetItemCats().WithCID(CID);
JsonResult result = null;
result = new JsonResult
{
Data = new
{
success = true,
output =
Helpers.RenderHelper
.RenderPartialToString("~/Views/Admin/AdminItemCatsUL.ascx",
cats)
}
};
return result;
}
The result:
The ItemCats_UL partialView displays! BUT the nested PartialViews (ItemCats_LI) don't!
Error I receive when I step through the markup in the ItemCats_UL.ascx and hover over the 'Html' part of the following code:
<ul id="catsTree">
<% Html.RenderPartial("ItemCats_LI", Model); %>
</ul>
Value cannot be null.
Parameter name: viewContext
Html = 'Html' threw an exception of type 'System.ArgumentNullException'
I'm wondering if there's a clever guy out there who can extend the RenderPartialToString method to include nested partialviews? Or am I missing something simple?
You need to hook the newly returned HTML / JavaScript back into the DOM upon loading it.
I'm sure there are lots of ways to do this, but I found a nice jQuery add-on called LiveQuery (link)
that helps me do it.
To make it work in your case, you'd set up a jQuery document.ready function in the parent page that looks something like this:
$("#catsTree").livequery(function () { this.treeview(); }, function () { /* code to destroy the treeview here */ });

ASP.NET: User control with access to the controls that it wraps

I have a bunch of occurrences of this kind of boilerplate code in my ASP.NET project.
<div class="inputfield">
<div class="tl">
<span class="tr"><!-- --></span>
<span class="ll"><!-- --></span>
<div class="lr">
<div class="cntnt">
<asp:TextBox .../>
</div>
</div>
</div>
</div>
As you may have guessed, everything in that snippet is pure boilerplate except for the innermost text field.
What is the best way to avoid such boilerplate in ASP.NET? In e.g. Django I would make a custom tag for it, as such:
{% boiler %}
<input ... />
{% endboiler %}
I was thinking that maybe I can create a user control, but all the tutorials on ASP.NET user controls that I've found are very simplistic and "self-closing", i.e. they are not aware of the contents of the tag. I need something along the lines of:
<Hello:MyControl>
<asp:TextBox .../>
</Hello>
So my question is the following: what's the best way to avoid the boilerplate?
You can use an ITemplate property. Thus, you can inject different content in different situations.
[PersistChildren(false), ParseChildren(true, "ContentTemplate")]
public partial class WebUserControl1 : System.Web.UI.UserControl
{
[System.ComponentModel.Browsable(false), System.Web.UI.PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate ContentTemplate { get; set; }
protected override void CreateChildControls()
{
if (this.ContentTemplate != null)
this.ContentTemplate.InstantiateIn(this);
base.CreateChildControls();
}
}
Put the asp:TextBox in your user control, along with the other html tags. Provide properties on your user control that match the properties of the text box, so that you would do something like this:
<Hello:MyControl ID="myControl" runat="server" Width="300px" MaxLength="30" />
and then the width and maxlength properties would just get transferred to the internal textbox.
You could also provide access to the textbox from the usercontrol and set all the properties in the code behind.
Create a class like this:
[PersistChildren(false), ParseChildren(true, "ContentTemplate")]
public class CustomContent:WebControl
{
[System.ComponentModel.Browsable(false), System.Web.UI.PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate ContentTemplate { get; set; }
private PlaceHolder m_placeHolder;
protected override void CreateChildControls()
{
m_placeHolder = new PlaceHolder();
if (this.ContentTemplate != null)
this.ContentTemplate.InstantiateIn(m_placeHolder);
Controls.Add(m_placeHolder);
}
protected override void RenderContents(HtmlTextWriter writer)
{
writer.Write(#"<div class=""inputfield"">
<div class=""tl"">
<span class=""tr""><!-- --></span>
<span class=""ll""><!-- --></span>
<div class=""lr"">
<div class=""cntnt"">
");
base.RenderContents(writer);
writer.Write(#" </div>
</div>
</div>
</div>
");
}
}
This class isn't a "User Control" it's a "Server Control". You can do the same thing with a user control but you'll have issues with the designer. This will work in the designer.
And you can put markup like this in your ASPX:
<uc1:CustomContent runat="server" ID="content">
<asp:textbox runat="server"></asp:textbox>
</uc1:CustomContent>
don't forget the Register page declaration at the top of the aspx
<%# Register tagprefix="uc1" Assembly="Assembly where CustomContent is" Namespace="namespace where CustomContent is" %>
You can put whatever you want inside the uc1:CustomContent tags and it will render that boilerplate html around it. If you are curious about how ITemplate works, there are plenty of articles on msdn, etc.

Resources