Binding to an object with properties and a list - model-view-controller

I've been able to bind a list of objects correctly. Works fine. Now when I change the item a complex object it stops working.
The complex object is the room name with the list of objects. When the 'postback' the name returns fine, but the list of objects comes back as null.
Any hints please?
Room Model:
public class Room
{
public string Name { get; set; }
public List<Option> Options { get; set; }
public Room() { }
public Room(string name, List<Option> options)
{
Name = name; Options = options;
}
}
Options Model
public class Option
{
public bool IsSelected { get; set; }
public string ImagePath { get; set; }
public int UniqueID { get; set; }
public Option() { }
public Option(bool isSelected, string imagePath, int uniqueID)
{ IsSelected = isSelected; ImagePath = imagePath; UniqueID = uniqueID; }
}
HomeController
public ActionResult Index()
{
List<Option> options = new List<Option>();
options.Add(new Option(true, "../Content/cars_2.jpg", 4));
options.Add(new Option(true, "../Content/vw_one_liter_concept01_2.jpg", 6));
options.Add(new Option(false, "../Content/00018578.jpg", 8));
//Get a list of selected options and union with all remaining
Room model = new Room("Room1", options);
return View(model);
}
[HttpPost]
public ActionResult Index(Room model)
{
ViewData["results"] = model.Options.Count();
return View(model);
}
Index View
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<MultiSelect.Models.Room>" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Index</title>
<script src="../../Scripts/jquery-1.4.1.js" type="text/javascript"></script>
<script src="../../Scripts/jquery-ui-1.8.10.custom.min.js" type="text/javascript"></script>
</head>
<body>
<% using (Html.BeginForm())
{%>
<%= Html.ValidationSummary(true)%>
<%= Html.TextBoxFor(m=> m.Name) %>
<% Html.RenderPartial("MultiSelect", Model.Options); %>
<% } %>
</body>
</html>
MultiSelect Partial View
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IList<MultiSelect.Models.Option>>" %>
<% for (int counter = 0;counter< Model.Count(); counter ++)
{ %>
<div class="opt">
<%= Html.HiddenFor(i=> i[counter].UniqueID)%>
<%= Html.HiddenFor(i=> i[counter].ImagePath) %>
<%= Html.CheckBoxFor(i => i[counter].IsSelected)%>
<img src="<%= Model.ElementAt(counter).ImagePath %>" alt="Image" width="128" height="128" />
</div>
<% } %>
<input id="Submit1" type="submit" value="submit" />

http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ provides a solution for what you're looking for.
Alternatively, an easier, but less flexible solution is to put a Room.cshtml file, and a Option.cshtml file in your Shared/EditorTemplates folder. Then you'd put
<%= Html.TextBoxFor(m=> m.Name) %>
<% Html.EditorFor(m => m.Options); %>
in Room.cshtml
<% Html.EditorFor(m => Model); %>
in index, and the contents of your partial view in Option.cshtml.

Related

How to make use of Viewbag in my View

I dont understand, I have my in my Controller:
[HttpGet]
public ActionResult Detail(int userId)
{
var user = ZincService.GetUserForId(userId);
if (user != null)
{
ViewBag.user = userId;
ViewBag.email = user.Email;
ViewBag.title = user.JobTitle;
ViewBag.node = user.Node;
}
return View(user);
}
then my view, Detail.aspx
<div id="user-details-view">
<div>
Title:
</div>
<div>
<%: Model.JobTitle %>
<%: Model.News %>
<%: Model.Node %>
</div>
<div>
<%: Html.ActionLink("Change Email Address", "ChangeEmailAddress", new { #id = Model.UserId })%>
</div>
</div>
when I run my app i get an error:
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /Areas/Admin/Views/User/Detail.aspx
I dont understand? Is it because of syntax errors?
Previous posts are correct as in logic but you assigned the viewbag names in your controller differently. It should be:
<div id="user-details-view">
<div>
Title:
</div>
<div>
<%: ViewBag.email %>
<%: ViewBag.title %>
<%: ViewBag.node %>
</div>
<div>
<%: Html.ActionLink("Change Email Address", "ChangeEmailAddress", new { #id = ViewBag.user })%>
</div>
</div>
Hope it helps..
It must be like this
<%: ViewBag.JobTitle %>
<%: ViewBag.News %>
<%: ViewBag.Node %>
Replace the Model. with ViewBag.
<%: ViewBag.title %>
<%: ViewBag.email %>
<%: ViewBag.node %>
and also change this
<%: Html.ActionLink("Change Email Address", "ChangeEmailAddress", new { id = ViewBag.user })%>
You must use that sintax:
<%: ViewBag.email %>
<%: ViewBag.title %>
<%: ViewBag.node %>
But would be better if you use Model:
public class UserInfo
{
public int UserId { get; set; }
public string Email { get; set; }
public string Title { get; set; }
public NodeType Node { get; set; }
}
[HttpGet]
public ActionResult Detail( int userId )
{
var data = ZincService.GetUserForId( userId );
var user = new UserInfo();
if ( data != null )
{
user.UserId = userId;
user.Email = data.Email;
user.Title = data.JobTitle;
user.Node = data.Node;
}
return View( user );
}
In view (MVC3) with razor sintax:
#model UserInfo
<div id="user-details-view">
<div>
Title:
</div>
<div>
#Model.Title
#Model.Node
</div>
<div>
#Html.ActionLink("Change Email Address", "ChangeEmailAddress", new { #id = Model.UserId })
</div>
</div>

Using Ajax gives stale results

EDIT
My Ajax form gets correct Id to update a content and a replace option.
Submitting is by clicking <input type="submit" value="submit!" />.
Problem: When I clicked on submit I didn't see any update. Second trial gave the expected result. When I refreshed the page the lost record (first hit was successful) was on spot.
Model
[Serializable]
public abstract class AbstractEntity {
public Guid Id { get; set; }
public DateTime LastModified { get; set; }
}
[Serializable]
public class Product : AbstractEntity {
public Product() {
this.Attachments = new HashSet<Attachment>();
}
public String Title { get; set; }
public String Commentary { get; set; }
public DateTime PlacedOn { get; set; }
public String User { get; set; }
public ICollection<Attachment> Attachments { get; set; }
}
[Serializable]
public class Attachment {
public String MimeType { get; set; }
public String Description { get; set; }
public String Filename { get; set; }
}
Controller
[HandleError]
public class ProductController : Controller {
private readonly IDocumentSession documentSession;
public ProductController(IDocumentSession documentSession) {
this.documentSession = documentSession;
}
public ActionResult ListRecent() {
return View(ListAll());
}
[Audit, HttpPost]
public ActionResult Delete(Guid id) {
documentSession.Delete<Product>(documentSession.Load<Product>(id));
documentSession.SaveChanges();
return PartialView("ProductsList", ListAll());
}
[Audit, HttpPost]
public ActionResult Create(Product product) {
if(ModelState.IsValid) {
documentSession.Store(product);
documentSession.SaveChanges();
}
return PartialView("ProductsList", ListAll());
}
private IEnumerable<Product> ListAll() {
return documentSession.Query<Product>().ToArray();
}
}
Views ('scriptless')
Layout
<head>
<title>#ViewBag.Title</title>
<link href="#Url.Content("~/Content/stylesheets/normalize.css")" rel="stylesheet" type="text/css" />
<link href="#Url.Content("~/Content/stylesheets/site.core.css")" rel="stylesheet" type="text/css" />
<script src="#Url.Content("~/Scripts/jquery-1.7.2.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
</head>
<body>
<div id="main-wrapper">
<div id="header">[#Html.ActionLink("List", "ListRecent", "Product")]</div>
<div id="content">#RenderBody()</div>
</div>
</body>
ListRecent.cshtml
#model IEnumerable<lamp.DomainLayer.Entities.Product>
#{
ViewBag.Title = "ListRecent";
var options = new AjaxOptions {
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "productsList"
};
}
<h2>List</h2>
#Html.Partial("ProductsList", Model)
#using(Ajax.BeginForm("Create", "Product", options)) {
<fieldset>
<p>
<label class="autoWidth">Title</label>
#Html.Editor("Title")
#Html.ValidationMessage("Title")
</p>
<p>
<label class="autoWidth">Commentary</label>
#Html.TextArea("Commentary")
#Html.ValidationMessage("Commentary")
</p>
#* Some fields I have omitted.. *#
<input type="submit" value="submit" />
<input type="reset" value="clear" />
</fieldset>
}
ProductsList.cshtml
#model IEnumerable<lamp.DomainLayer.Entities.Product>
#{
var options = new AjaxOptions {
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "productsList"
};
}
<div id="productsList">
#foreach(var p in Model) {
<div class="productCard">
<p class="title"><strong>Title</strong>: #p.Title</p>
<p class="author"><strong>User</strong>: #p.User</p>
<p class="date"><strong>Placed on</strong>: #idea.PlacedOn.ToShortDateString()</p>
<p class="link">#Html.ActionLink("details", "Details", "Product")</p>
<p class="link">
#using(Ajax.BeginForm("Delete", "Product", new { id = p.Id }, options)) {
<input type="submit" value="you!" />
}
</p>
</div>
}
</div>
I guess it is IE's fault (precisely the way,how IE cache AJAX requests). Look here-it could be solution:
http://viralpatel.net/blogs/ajax-cache-problem-in-ie/
Ok. Darin was right! I found out that changing my controller code to this one:
private IEnumerable<Product> ListAll() {
return documentSession
.Query<Product>()
.Customize((x => x.WaitForNonStaleResults()))
.ToArray();
}
fixes everything.
.Customize((x => x.WaitForNonStaleResults()))
is the solution!
Thanks everyone!

MVC3 Client Side Validation not working with an Ajax.BeginForm form

I have the following form in a partial view
#model PartDetail
#using (Ajax.BeginForm("Create", "Part", null, new AjaxOptions { UpdateTargetId = "update_panel"}))
{
<h3>New #Model.PartType</h3>
<p> Serial Number:
<strong>
#Html.EditorFor(model => model.SerialNumber)
#Html.ValidationMessageFor(model => model.SerialNumber)
</strong>
</p>
<p> Name:
<strong>
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</strong>
</p>
...... more relatively boiler plate code here......
<p>
<input type="submit" class="btn primary" value="Save Part"/>
</p>
}
With a model of
public class PartDetail
{
public string DateCreated { get; set; }
[StringLength(255, MinimumLength = 3)]
public string Description { get; set; }
public Guid ID { get; set; }
public string IsActive { get; set; }
public string Manufacturer { get; set; }
public IEnumerable<SelectListItem> Manufacturers { get; set; }
[StringLength(100, MinimumLength = 3)]
public string Name { get; set; }
public string PartType { get; set; }
[Required]
[StringLength(100, MinimumLength = 3)]
public string SerialNumber { get; set; }
}
And I reference (in parent views of my partial view)
<script src="#Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"> </script>
And nothing gets validated. If I type nothing into the Serial Number text box and press submit it saves it to the database with no problems.
Try adding an OnBegin callback to the AjaxOptions and this should work.
function validateForm() {
return $('form').validate().form();
}
#using (Ajax.BeginForm("Create", "Part", null, new AjaxOptions { UpdateTargetId = "update_panel", OnBegin = "validateForm" }))
...
If this doesn't work for you, an alternative solution may be to submit the form using jQuery. This would be my preferred solution.
<div id="result"></div>
#using (Html.BeginForm())
{
<h3>New #Model.PartType</h3>
<p>Serial Number:
<strong>
#Html.EditorFor(model => model.SerialNumber)
#Html.ValidationMessageFor(model => model.SerialNumber)
</strong>
</p>
<p> Name:
<strong>
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
</strong>
</p>
...... more relatively boiler plate code here......
<p>
<input type="submit" class="btn primary" value="Save Part"/>
</p>
}
jQuery/JS function to submit the form
$(function () {
$('form').submit(function () {
$.validator.unobtrusive.parse($('form')); //added
if ($(this).valid()) {
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
$('#result').html(result);
}
});
}
return false;
});
});
In this case order of javascript file including process is important. Your order of including these file should be
jquery.js
jquery.validate.js
jquery.validate.unobtrusive.js
When I tried with re-ordering it works fine for me. Try it.
Check your root Web.config of the solution/project. Does it contain the following lines?
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
</appSettings>
It not, add them.
you need to modified your controller action little bit to work validation in Ajax request
you catch RenderHtml of partial view and apply your validation on it for e.g
//In Controller
public string Create(PartDetail model)
{
string RenderHtml = RenderPartialViewToString("PartailViewName", model);
return RenderHtml;
}
protected string RenderPartialViewToString(string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = ControllerContext.RouteData.GetRequiredString("action");
ViewData.Model = model;
using (StringWriter sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
you must be pass ViewName and Model to RenderPartialViewToString method. it will return you view with validation which are you applied in model.

Correct way to load a partial view into a view

I am wanting to create a view that allows me to add phone numbers for a person.
public class PersonModel
{
public string Name { get; set; }
}
public class PhoneModel
{
public string PhoneNumber { get; set; }
}
public class PersonDetailViewModel
{
public PersonModel PersonDetails { get; set; }
public IList<PhoneModel> PhoneNumbers { get; set; }
}
I am binding my main view to the viewmodel like
#model DynamicPhoneNumber.Models.PersonDetailViewModel
#{
ViewBag.Title = "Add";
}
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Details</legend>Name #Html.TextBoxFor(a => a.PersonDetails.Name)
<input type="button" id="btnAdd" value="Press Me" />
<div id="mydiv">
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
My Partial view looks like
#model DynamicPhoneNumber.Models.PhoneModel
<p>
#Html.TextBoxFor(t => t.PhoneNumber)
</p>
Im using jquery to dynamically add the partial view.
On HttpPost I place a breakpoint and I can see the value from the PersonDetails.Name however none of the values from my loaded partialview are been bound to the PhoneModel.
What do I need to do to be able to return the data from the partial views into my viewmodel?
here is a good blog post that may help to resolve your problem
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

ModelState.IsValid returns false when it should be true

I have a pretty simple MVC 2 form. It has two dropdowns, user and role. The employee dropdown passes validation, and the role dropdown does not, regardless of what I select. There is no default "empty" option although I plan to implement one, which is why I need the validation to work. It fails both client and server validation. I just can't see why one would work and one does not!
The Form:
<% using (Html.BeginForm()) {%>
<%:Html.ValidationSummary(true) %>
<%:Html.EditorFor(model => model.User, new { AllEmployees = Model.AllEmployees, RoleList = Model.RoleList })%>
<p>
<input type="submit" value="Add New User" />
</p>
<% } %>
<% Html.EndForm(); %>
The Editor Template:
<tr>
<td>
<div class="editor-label">
<%: Html.LabelFor(model => model.UserId) %>
<%: Html.RequiredMarkFor(model => model.UserId) %>
</div>
</td>
<td>
<div class="editor-field">
<%: Html.DropDownListFor(model => model.UserId, new SelectList(ViewData["AllEmployees"] as IEnumerable, "UserId", "DisplayName", Model.UserId)) %>
<%: Html.ValidationMessageFor(model => model.UserId>
</div>
</td>
</tr>
<tr>
<td>
<div class="editor-label">
<%: Html.LabelFor(model => model.AccessLevel)%>
<%: Html.RequiredMarkFor(model => model.AccessLevel)%>
</div>
</td>
<td>
<div class="editor-field">
<%: Html.DropDownListFor(model => model.AccessLevel, new SelectList(ViewData["RoleList"] as IEnumerable, Model.AccessLevel))%>
<%: Html.ValidationMessageFor(model => model.AccessLevel)%>
</div>
</td>
</tr>
The Metadata:
[DisplayName("Employee")]
[Required(ErrorMessage = "Please select an employee.")]
[StringLength(8, ErrorMessage = "User Id must be less than 8 characters.")]
[DisplayFormat(ConvertEmptyStringToNull = false,
HtmlEncode = true)]
[DataType(DataType.Text)]
public object UserId { get; set; }
// Validation rules for Access Level
[DisplayName("Role")]
[Required(ErrorMessage = "Please select the role for this user.")]
[StringLength(15, ErrorMessage = "Role must be under 15 characters.")]
[DisplayFormat(ConvertEmptyStringToNull = false,
HtmlEncode = true)]
[DataType(DataType.Text)]
public object AccessLevel { get; set; }
The Get Action:
List<String> roles = (from o in txDB.Users
select o.AccessLevel).Distinct().ToList();
var viewModel = new UserViewModel
{
User = new User(),
AllEmployees = empList,
RoleList = roles
};
return View(viewModel);
The Post Action:
[HttpPost]
[AuthorizeAttribute(Roles="Administrator")]
public ActionResult Create(User user)
{
if(!ModelState.IsValid)
{
//ModelState is invalid
return View(new User());
}
try
{
//do stuff
}
}
The Required Helper Method (from Define markup for [Required] fields in View in ASP.NET MVC 2.0):
public static string RequiredMarkFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
if(ModelMetadata.FromLambdaExpression(expression, helper.ViewData).IsRequired)
return "*";
else
return string.Empty;
}
Post method should be as follows to get Server side validation...
[HttpPost]
[AuthorizeAttribute(Roles="Administrator")]
public ActionResult Create(User user)
{
if(!TryUpdateModel(user))
{
// Model is INVALID
return View(user);
}
else
{
// ModelState is VALID
// Do stuff
}
}
The else might be redundant depending on what you're doing but that should get you going.
In the view above your <% using Html.BeginForm() %> you need
<% Html.EnableClientValidation(); %>
You also need to reference the scripts, MicrosoftAjax and MicrosoftMvcValidation I think
First of all: You have two closing form tags
If you use
<% using (Html.BeginForm()) {%>
<% } %>
you dont need to use this
<% Html.EndForm(); %>
Regarding your validation problem you are using an editor only for your User property, which is the only one that get binded by the model binder
<%:Html.EditorFor(model => model.User, new { AllEmployees = Model.AllEmployees, RoleList = Model.RoleList })%>
Try to replace the previous code with an EditorForModel as your Editor Template is for a model class.
So your form should change in
<% using (Html.BeginForm()) {%>
<%:Html.ValidationSummary(true) %>
<table>
<%:Html.EditorForModel()%>
</table>
<p>
<input type="submit" value="Add New User" />
</p>
<% } %>
and you're done!

Resources