Model CheckBoxFor Properties being posted twice Asp Net MVC 3 - asp.net-mvc-3

So I have a partial view...
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<NewsletterUnsubscribe_MVC3v2.Models.IntegraRecord>" %>
<% if (!String.IsNullOrEmpty(Model.ErrorMessage))
{%>
<div class="input-validation-error">
<%:Model.ErrorMessage %>
</div>
<% }
else
{%>
<% using (Html.BeginForm())
{%>
<%:Html.ValidationSummary(true)%>
<fieldset>
<legend>IntegraRecord</legend>
<div class="editor-field">
<%:Html.LabelFor(m => m.EmailAddress)%>: <strong><%:Model.EmailAddress%></strong>
</div>
<%:Html.HiddenFor(m=>m.EmailAddress) %>
<div class="editor-field">
Unsubscribe from Area mailings: <%:Html.CheckBoxFor(m => m.AreaUnsubscribe)%>
</div>
<div class="editor-field">
Unsubscribe from Monthly newsletters: <%:Html.CheckBoxFor(m => m.MonthlyUnsubscribe)%>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
<% }
}%>
When I hit submit and look what's in the posted data I see
EmailAddress:someone#somewhere.co.uk
AreaUnsubscribe:true
AreaUnsubscribe:false
MonthlyUnsubscribe:true
MonthlyUnsubscribe:false
As a result TryUpdateModel returns true but doesn't populate any fields
This gets posted to the controller...
[HttpPost]
public ActionResult GetRecord(IntegraRecord model)
{
if (TryUpdateModel(model))
{
try
{
BusinessLayer.UpdateEmailAddress(model);
}
catch (ArgumentException)
{
return View("Error", ViewBag.Message = "Could Not Update Email Address.");
}
}
return PartialView("GetRecord", model);
}
Any help massively appreciated...
Update: So following the clarification below (Thanks!)
I'm not using a custom model binder so I guess I'm missing some other convention too...
Here's my model...
public class IntegraRecord
{
private const string EmailRegex = #"[snip]";
[Required(ErrorMessage = "Email Address is required")]
[RegularExpression(EmailRegex, ErrorMessage = "This does not appear to be an email address")]
public string EmailAddress;
public bool AreaUnsubscribe;
public bool MonthlyUnsubscribe;
public string ErrorMessage;
public IntegraRecord()
{
}
public IntegraRecord(string email, bool area, bool monthly)
{
EmailAddress = email;
AreaUnsubscribe = area;
MonthlyUnsubscribe = monthly;
}
}

That's how MVC handles checkboxes: asp.net mvc: why is Html.CheckBox generating an additional hidden input (and many other places)
The problem is onthe server side (default model binder is aware of that and doesn't have a problem). Are you using custom model binder?

Related

Data annotation for model validation is not working as desired in mvc4

I am using [Required] for field validation. Scenario is, there are two check boxes and based on one selection, user has to add value in textbox. If it is empty then show validation error. Issue is, it validates it for first time, but shows validation message on both check box selection afterwards. What am I missing here?
Model:
[DisplayName("Flat Fee Amount")]
[Required(ErrorMessage = "Enter flat fee amount.")]
public string FlatFeeAmount { get; set; }
View:
<div id="show-delivery-fee">
<div>
<%= Html.RadioButtonFor(m => m.DeliveryFee, "All", new {id = "All"}) %>
<%= Html.Label("All", new {#for = "All"}) %>
</div>
<div>
<%-- BASED ON THIS CHECKBOX SELECTION DISPLAY VALIDATION MESSAGE--%>
<%= Html.RadioButtonFor(m => m.DeliveryFee, "FlatFee", new {id = "FlatFee"}) %>
<%= Html.Label("Flat Fee", new {#for = "FlatFee"}) %>
</div>
<div>
<%= Html.LabelFor(m => m.FlatFeeAmount) %>
<%= Html.TextBoxFor(m => m.FlatFeeAmount, new {maxlength = "5"}) %>
<%= Html.ValidationMessageFor(m => m.FlatFeeAmount)%>
</div>
</div>
Controller:
[HttpPost]
[RequiredAccessRights(AllowRightsOr = new[] { SystemRight.EditDelivery })]
[AuditActionFilter("Save store delivery. Store id: {model.StoreId.Value}")]
public ActionResult Delivery(StoreDeliveryModel model)
{
if(this.ModelState.IsValid)
{
try
{
this.StoreManager.SaveDeliveryModel(model);
model.Submitted = true;
}
catch (ValidationException exc)
{
this.ModelState.AddModelError("", exc.Message);
}
}
return View(model);
}
Output:
I got my desired output if I use RequiredIf annotation in model.
Model:
[DisplayName("Flat Fee Amount")]
[RequiredIf("DeliveryFee", "FlatFee", ErrorMessage = "Please enter flat fee amount.")]
public string FlatFeeAmount { get; set; }

MVC3 Master-Details Validation not Displaying

I have an MVC3 page with an object (Header) that has data and a list of objects (Details) that I want to update on a single page. On the details object I have custom validation (IValidatableObject) that also needs to run.
This appears to generally be working as expected, validations are running and returning ValidationResults and if I put an #Html.ValidationSummary(false); on the page it displays those validations. However I don't want a list of validations at the top, but rather next to the item being validated i.e. Html.ValidationMessageFor which is on the page, but not displaying the relevant message. Is there something I'm missing? This is working on other pages (that don't have this Master-Details situation), so i'm thinking it is something about how I'm going about setting up the list of items to be updated or the editor template for the item?
Edit.cshtml (the Header-Details edit view)
#foreach (var d in Model.Details.OrderBy(d => d.DetailId))
{
#Html.EditorFor(item => d, "Detail")
}
Detail.ascx (the Details Editor Template)
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Detail>" %>
<tr>
<td>
<%= Model.Name %>
<%= Html.HiddenFor(model => model.DetailId) %>
</td>
<td class="colDescription">
<%= Html.EditorFor(model => model.Description) %>
<%= Html.ValidationMessageFor(model => model.Description) %>
</td>
<td class="colAmount">
<%= Html.EditorFor(model => model.Amount) %>
<%= Html.ValidationMessageFor(model => model.Amount) %>
</td>
</tr>
Model is Entity Framework with Header that has Name and HeaderId and Detail has DetailId, HeaderId, Description and Amount
Controller Code:
public ActionResult Edit(Header header, FormCollection formCollection)
{
if (formCollection["saveButton"] != null)
{
header = this.ProcessFormCollectionHeader(header, formCollection);
if (ModelState.IsValid)
{
return new RedirectResult("~/saveNotification");
}
else
{
return View("Edit", header);
}
}
else
{
return View("Edit", header);
}
}
[I know controller code can be cleaned up a bit, just at this state as a result of trying to determine what is occuring here]
IValidatableObject implementation:
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this.Name.Length < 5) && (this.Amount > 10))
{
yield return new ValidationResult("Item must have sensible name to have Amount larger than 10.", new[] { "Amount" });
}
}
I would recommend you to use real editor templates. The problem with your code is that you are writing a foreach loop inside your view to render the template which generates wrong names for the corresponding input fields. I guess that's the reason why you are doing some workarounds in your controller action to populate the model (header = this.ProcessFormCollectionHeader(header, formCollection);) instead of simply using the model binder to do the job.
So let me show you the correct way to achieve that.
Model:
public class Header
{
public IEnumerable<Detail> Details { get; set; }
}
public class Detail : IValidatableObject
{
public int DetailId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int Amount { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if ((this.Name ?? string.Empty).Length < 5 && this.Amount > 10)
{
yield return new ValidationResult(
"Item must have sensible name to have Amount larger than 10.",
new[] { "Amount" }
);
}
}
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new Header
{
Details = Enumerable.Range(1, 5).Select(x => new Detail
{
DetailId = x,
Name = "n" + x,
Amount = 50
}).OrderBy(d => d.DetailId)
};
return View(model);
}
[HttpPost]
public ActionResult Index(Header model)
{
if (ModelState.IsValid)
{
return Redirect("~/saveNotification");
}
return View(model);
}
}
View (~/Views/Home/Index.cshtml):
#model Header
#using (Html.BeginForm())
{
<table>
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
#Html.EditorFor(x => x.Details)
</tbody>
</table>
<button type="submit">OK</button>
}
Editor template for the Detail type (~/Views/Shared/EditorTemplates/Detail.ascx or ~/Views/Shared/EditorTemplates/Detail.cshtml for Razor):
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<MvcApplication1.Controllers.Detail>"
%>
<tr>
<td>
<%= Html.DisplayFor(model => model.Name) %>
<%= Html.HiddenFor(model => model.DetailId) %>
<%= Html.HiddenFor(model => model.Name) %>
</td>
<td class="colDescription">
<%= Html.EditorFor(model => model.Description) %>
<%= Html.ValidationMessageFor(model => model.Description) %>
</td>
<td class="colAmount">
<%= Html.EditorFor(model => model.Amount) %>
<%= Html.ValidationMessageFor(model => model.Amount) %>
</td>
</tr>
Here are a couple of things that I did to improve your code:
I performed the ordering of the Details collection by DetailId at the controller level. It's the controller's responsibility to prepare the view model for display. The view should not be doing this ordering. All that the view should do is display the data
Thanks to the previous improvement I git rid of the foreach loop in the view that you were using to render the editor template and replaced it with a single #Html.EditorFor(x => x.Details) call. The way this works is that ASP.NET MVC detects that Details is a collection property (of type IEnumerable<Detail>) and it will automatically look for a custom editor templated inside the ~/Views/SomeController/EditorTemplates or ~/Views/Shared/EditorTemplates folders called Detail.ascx or Detail.cshtml (same name as the type of the collection). It will then render this template for each element of the collection so that you don't need to worry about it
Thanks to the previous improvement, inside the [HttpPost] action you no longer need any ProcessFormCollectionHeader hacks. The header action argument will be correctly bound from the request data by the model binder
Inside the Detail.ascx template I have replaced <%= Model.Name %> with <%= Html.DisplayFor(model => model.Name) %> in order to properly HTML encode the output and fill the XSS hole that was open on your site.
Inside the Validate method I ensured that the Name property is not null before testing against its length. By the way in your example you only had an input field for the Description field inside the template and didn't have a corresponding input field for the Name property, so when the form is submitted this property will always be null. As a consequence I have added a corresponding hidden input field for it.

MVC3 - The model item passed into the dictionary is of type ERROR

EDIT FOR SOLUTION:
The answer can be found here:
http://forums.asp.net/p/1794394/4941748.aspx/1?p=True&t=634704481730535356
BUT please see the below from Ricardo as well.
I've got a Controller/View called LedgerUser. I have a ViewModel called LedgerViewModel which contains an instance of LedgerUser and SelectList for instances of UserType and a property called UniqueId which i use for Images.
Now when i POST the form back from my Create View I get the following error:
The model item passed into the dictionary is of type 'Accounts.Models.LedgerUser', but this dictionary requires a model item of type 'Accounts.ViewModels.LedgerUserViewModel'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The model item passed into the dictionary is of type 'Accounts.Models.LedgerUser', but this dictionary requires a model item of type 'Accounts.ViewModels.LedgerUserViewModel'.
Now my understanding is that you pass the Model back to the Action Method and not the ViewModel? Im using the following technologies:
ASP.NET MVC3
Entity Framework 4 Database First
My code is:
LedgerUserViewModel:
/// <summary>
/// ViewModel to represent the LedgerUser & its required fields.
/// </summary>
public class LedgerUserViewModel
{
public SelectList UserTypes { get; set; }
public string UserType { get; set; }
public LedgerUser LedgerUser { get; set; }
public string UniqueKey { get; set; } //--Used for the Images.
public bool Thumbnail { get; set; }
}
I have extended the LedgerUser Model to decorate with Data Annotations:
[MetadataType(typeof(LedgerUserMetaData))]
public partial class LedgerUser
{
public class LedgerUserMetaData
{
[Required(ErrorMessage = "Date Of Birth Required")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = " {0:dd/MM/yyyy}")]
[DataType(DataType.Date)]
public object DateOfBirth { get; set; }
}
}
My GET Action Method for LedgerUser:
// GET: /LedgerUser/Create
/// <summary>
/// Action Method to create the LedgerUser. Usually this will be once a user has registered
/// and will be directed from the AccountController.
/// </summary>
/// <param name="id">id</param>
/// <returns></returns>
public ActionResult Create(string id)
{
var uniqueKey = new Guid(id);
var userTypes = new SelectList(db.UserTypes, "id", "Description");
var ledgerUser = new LedgerUser()
{
id = uniqueKey,
RecordStatus = " ",
CreatedDate = DateTime.Now,
DateOfBirth = DateTime.Today
};
var viewModel = new LedgerUserViewModel()
{
UserTypes = userTypes,
LedgerUser = ledgerUser
};
return View(viewModel);
}
My POST Action Method for LedgerUser:
[HttpPost]
public ActionResult Create(bool Thumbnail,LedgerUser ledgeruser, HttpPostedFileBase imageLoad2)
{
///---code to do stuff..
}
My Create View:
#model Accounts.ViewModels.LedgerUserViewModel
#using Accounts.Models
#using (Html.BeginForm("Create", "LedgerUser", new { Thumbnail = true}, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Ledger User</legend>
<div class="editor-field">
#Html.HiddenFor(model => model.LedgerUser.id)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LedgerUser.AccountNumber,"Account Number")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LedgerUser.AccountNumber)
#Html.ValidationMessageFor(model => model.LedgerUser.AccountNumber)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LedgerUser.FirstName,"First Name")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LedgerUser.FirstName)
#Html.ValidationMessageFor(model => model.LedgerUser.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LedgerUser.LastName,"Last Name")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LedgerUser.LastName)
#Html.ValidationMessageFor(model => model.LedgerUser.LastName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LedgerUser.DateOfBirth,"D.O.B.")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LedgerUser.DateOfBirth)
#Html.ValidationMessageFor(model => model.LedgerUser.DateOfBirth)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LedgerUser.UserType, "User Type")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.LedgerUser.UserType,Model.UserTypes)
</div>
<div class="editor-label">
#Html.Label("Avatar")
</div>
<div class="editor-field">
#Html.UploadImageFor(model => model.UniqueKey,thumbnail:true)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Now i have interrogated the POST using Fiddler and i have found that name is correctly being set to "LedgerUser."
Content-Disposition: form-data; name="LedgerUser.id"
d1cd8e85-700d-4462-aa95-7428dbf58deb
-----------------------------7dc963b2304b4 Content-Disposition: form-data; name="LedgerUser.AccountNumber"
1
-----------------------------7dc963b2304b4 Content-Disposition: form-data; name="LedgerUser.FirstName"
Gareth
-----------------------------7dc963b2304b4 Content-Disposition: form-data; name="LedgerUser.LastName"
Bradley
-----------------------------7dc963b2304b4 Content-Disposition: form-data; name="LedgerUser.DateOfBirth"
12 April 2012
-----------------------------7dc963b2304b4 Content-Disposition: form-data; name="LedgerUser.UserType"
b8502da9-3baa-4727-9143-49e33edc910c
-----------------------------7dc963b2304b4 Content-Disposition: form-data; name="imageLoad2"; filename="001.jpg" Content-Type:
image/jpeg
Im at a loss. Thanks guys
In your post action method for LedgerUser you are returning to the same view with an incorrect model, your code probably looks like this:
return View();
If you are returning to the same page after creating a new record, you need to make sure you do the same as in your get method:
var ledgerUser = new LedgerUser()
{
id = uniqueKey,
RecordStatus = " ",
CreatedDate = DateTime.Now,
DateOfBirth = DateTime.Today
};
var viewModel = new LedgerUserViewModel()
{
UserTypes = userTypes,
LedgerUser = ledgerUser
};
return View(viewModel);
If you don't want to do this, then just redirect after the post action method to another view such as (assuming you have an Index action):
return View("Index")
Better yet, IF you do need to post to the same view, then just use AJAX/jQuery post to call your Create action instead of the form post.
Good luck.
The error you're getting sounds like the error you'd receive if you passed the wrong type of viewmodel to your viewpage. Does your post method look like this?
[HttpPost]
public ActionResult Create(bool Thumbnail,LedgerUser ledgeruser, HttpPostedFileBase imageLoad2)
{
///---code to do stuff..
return View(ledgeruser);
}
If so, you're issue is that you're recreating the view with the wrong model type. That said, yes, you should be taking LedgerViewModel as your post parameter. With MVC, typically whatever model you passed into your view with your get, will be your parameter on the Post.
Suggested structure for your post. Uses the Post Redirect Get (PRG) pattern.
[HttpPost]
public ActionResult Create(LedgerViewModel model)
{
///---code to do stuff..
return RedirectToAction("Create", new {id = model.LedgerUser.id.ToString()});
}

ASP.NET MVC 2 - Some validation errors are not appearing

I'm testing my view/edit model's Data Annotations, and some of the errors aren't showing up. They're all property-level, but they're not showing up as either property-level or model-level. They're simply not showing up at all.
My view/edit model:
public class AdminGameEditModel
{
[Required]
public int GameID { get; set; }
[Required(ErrorMessage="A game must have a title")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string GameTitle { get; set; }
[Required(ErrorMessage="A short URL must be supplied")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string Slug { get; set; }
[Required(ErrorMessage="A box art image must be supplied")]
public HttpPostedFileBase BoxArt { get; set; }
[Required(ErrorMessage="A large image for the index page is required")]
public HttpPostedFileBase IndexImage { get; set; }
[Required(ErrorMessage="A game must have a review")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string ReviewText { get; set; }
[Required(ErrorMessage="A game must have a score")]
public int ReviewScore { get; set; }
[Required(ErrorMessage="A game must have at least one Pro listed")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string[] Pros { get; set; }
[Required(ErrorMessage="A game must have at least one Con listed")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string[] Cons { get; set; }
[Required(ErrorMessage="A game must belong to a genre")]
public int GenreID { get; set; }
[Required(ErrorMessage="A game must be associated with at least one platform")]
public int[] PlatformIDs { get; set; }
}
The properties whose validation doesn't seem to be working properly are Pros, Cons, and GenreID. Here's how I'm trying to invoke them in my view:
<p>
<%: Html.Label("Genre") %>
<%: Html.ValidationMessageFor(model => Model.GameData.GenreID) %>
<%: Html.DropDownListFor(m => Model.GameData.GenreID, new SelectList(Model.AllGenres, "GenreID", "Name", Model.GameData.GenreID)) %>
</p>
<p>
<%: Html.LabelFor(model => Model.GameData.Pros) %><br />
<% for (var i = 0; i < 5; ++i)
{ %>
<input type="text" name="GameData.Pros" value="<%: (Model.GameData.Pros[i] != null && String.IsNullOrEmpty(Model.GameData.Pros[i])) ? "" : Model.GameData.Pros[i] %>" /><br />
<% } %>
<%: Html.ValidationMessageFor(model => Model.GameData.Pros) %>
</p>
<p>
<%: Html.LabelFor(model => Model.GameData.Cons) %><br />
<% for (var i = 0; i < 5; ++i)
{ %>
<input type="text" name="GameData.Cons" value="<%: (Model.GameData.Cons[i] != null && String.IsNullOrEmpty(Model.GameData.Cons[i])) ? "" : Model.GameData.Cons[i] %>" /><br />
<% } %>
<%: Html.ValidationMessageFor(model => Model.GameData.Cons) %>
</p>
The rest all show up fine. I'm stumped as to why those three aren't appearing. I don't see anything that jumps out to me as the cause. I'm using the default model binder and validation service.
Any ideas?
Well, for starters.. your input fields have no id's. Model validation doesn't work with names, only id's. But that's only part of the problem. The Model Binder is unlikely to be able to bind to arrays because arrays are immutable, this makes it hard to do iterative assignment to them. You're going to have to rethink this part of your application.
Second, your DropDownList has no default value. It will, in most cases just select the first item so there is no way for it to not be valid.
You may find this article interesting.

client side validation for asp.net mvc dropdown?

i just wanted to know how to enable client side validations for dropdowns in asp.net mvc 2.
The scenario would be that the dropdown will contain a "Select" item and the list of other items..,The user should select other items... the validation should fire when the user does not select the other items
public class FacilityBulletinModel
{
[DisplayName("Select a Facility")]
public List<SelectListItem> ListFacility { get; set; }
[DisplayName("Facility Bulletin")]
[Required(ErrorMessage = "Please create a Bulletin")]
public string FacilityBulletin { get; set; }
[DisplayName("Active")]
public bool Active { get; set; }
[HiddenInput(DisplayValue = false)]
public int SiteId { get;set;}
}
in my view
Select Facility <span class="err">*</span><br />
<%=Html.DropDownListFor(model => model.ListFacility, null, new {onChange="updateSiteId()" })%>
<span class="err"> <%= Html.ValidationMessageFor(model => model.ListFacility) %></span>
First, if a dropdown is required, add the [Required] attribute to your model property.
Then, enable client side validation somewhere at the top of your view:
<% Html.EnableClientValidation() %>
Then just add a validation message:
<div class="inputField">
<%= Html.LabelFor(model => model.property)%>
<%= Html.DropDownListFor(model => model.property, (SelectList)ViewData["myselelectlist"])%>
<%= Html.ValidationMessageFor(model => model.property)%>
</div>
(this requries MicrosoftMvcValidation.js to be loaded)

Resources