Hi everyone so I am trying to create an application using asp.net mvc with a code first database that allows the users to be able to create a blog post with as many images as they wish. The application works perfect as long as I have the head,body and image fields full or if I take validation off the head and body.The issue is if I have validation on the head and the body the program crashs with this validation error:
DbEntityValidationException
An exception of type 'System.Data.Entity.Validation.DbEntityValidationException' occurred in Crud.dll but was not handled in user code
Additional information: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details. The validation errors are: Heading is Required; Body is Required
Thanks for your help with this issue.
View
#model Crud.Models.PostModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm("Edit", "Home", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-field">
#Html.LabelFor(model => model.Heading)
#Html.TextBoxFor(model => model.Heading)
#Html.ValidationMessageFor(model => model.Heading)
</div>
<div>
#Html.LabelFor(model => model.PostBody)
#Html.TextBoxFor(model => model.PostBody)
#Html.ValidationMessageFor(model => model.PostBody)
</div>
<div class="editor-label">
#*Temporary way to upload*#
#Html.LabelFor(model => model.ImagePath)
#Html.TextBoxFor(model => model.ImagePath, new { type = "file", multiple = "multiple", NAME = "files" })
</div>
<div class="editor-label">
#*Future Upload links*#
#*#Html.LabelFor(model => model.Images)
#Html.TextBoxFor(model => model.Images, new { type = "file", multiple = "multiple" })
#Html.ValidationMessageFor(model => model.Images)*#
</div>
<p><input type="submit" value="Create" /></p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Repository
public void Save(PostModel Post)
{
try
{
if (Post.PostID == 0)
{
context.Posts.Add(Post);
}
else
{
PostModel dbEntry = context.Posts.Find(Post.PostID);
if (dbEntry != null)
{
dbEntry.Heading = Post.Heading;
dbEntry.PostBody = Post.PostBody;
}
}
context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
// Retrieve the error messages as a list of strings.
var errorMessages = ex.EntityValidationErrors
.SelectMany(x => x.ValidationErrors)
.Select(x => x.ErrorMessage);
// Join the list to a single string.
var fullErrorMessage = string.Join("; ", errorMessages);
// Combine the original exception message with the new one.
var exceptionMessage = string.Concat(ex.Message, " The validation errors are: ", fullErrorMessage);
// Throw a new DbEntityValidationException with the improved exception message.
throw new DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors);
}
}
Model
public partial class PostModel
{
[Key]
[HiddenInput(DisplayValue = false)]
public int PostID { get; set; }
[Required(ErrorMessage = "Heading is Required")]
[Display(Name = "Heading")]
public string Heading { get; set; }
[Required(ErrorMessage = "Body is Required")]
[DataType(DataType.MultilineText)]
[Display(Name = "Body")]
public string PostBody { get; set; }
public string ImageDisplayName { get; set; }
public string ImagePath { get; set; } //Temporarly here until I can get the ImageModel Method Working
//public virtual ICollection<ImageModel> Images { get; set; }
}
Controller
public ViewResult Create()
{
return View("Edit", new PostModel());
}
public ViewResult Edit(int PostID)
{
PostModel editedItem = repository.Posts
.FirstOrDefault(p => p.PostID == PostID);
return View(editedItem);
}
[HttpPost]
public ActionResult Edit(PostModel Post, IEnumerable<HttpPostedFileBase> files)
{
if (ModelState.IsValid)
{
foreach (var file in files)
{
PostModel post = new PostModel();
if (file != null && file.ContentLength > 0)
{
string displayName = file.FileName;
string fileExtension = Path.GetExtension(displayName);
string fileName = string.Format("{0}.{1}", Guid.NewGuid(), fileExtension);
string path = Path.Combine(Server.MapPath("~/Img/"), fileName);
file.SaveAs(path);
post.ImageDisplayName = displayName;
post.ImagePath = fileName;
post.PostBody = Post.PostBody;
post.Heading = Post.Heading;
}
repository.Save(post);
}
}
return RedirectToAction("display");
}
Why do you want to add the [Required] attribute if the fields are not actually required?Please see here for how required attribute is handled for strings - Allow empty strings for fields marked with PhoneAttribute or UrlAttribute
Related
I am taking a beginner course in ASP.NET MVC for webapps and am having some issues with the API section. My GET request (using Postman) is pulling data in JSON fine, but my POST request is giving me an Invalid ModelState error. I'm not sure what in the model is invalid so am looking for assistance or guidance in how to debug
My GET request from postman spits out the customers in the database.
{
"Id": 1,
"Name": "Jimmy Steve",
"IsSubscribedToNewsleter": true,
"MembershipType": null,
"MembershipTypeId": 4,
"CustomerBirthday": "2018-01-01T00:00:00"
}
I then copy the above, remove the ID section and change the Name and submit as a POST request and I get a 400 bad request error from the ModelState not valid error.
I'm not sure what is wrong in my model that is not allowing me to POST.
Here is the controller code:
//GET
public IEnumerable<Customer> GetCustomers()
{
return _context.Customers.ToList();
}
//Post
[HttpPost]
public Customer CreateCustomer(Customer customer)
{
if (!ModelState.IsValid)
throw new HttpResponseException(HttpStatusCode.BadRequest);
_context.Customers.Add(customer);
_context.SaveChanges();
return customer;
}
Here is the model code:
public class Customer
{
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
public bool IsSubscribedToNewsleter { get; set; }
public MembershipType MembershipType { get; set; }
[Display(Name = "Membership Type")]
public byte MembershipTypeId { get; set; }
[Display(Name = "Date of Birth")]
[Min18YearsIfAMember]
public DateTime? CustomerBirthday { get; set;}
}
And here is the form code:
<h2>Create Customer</h2>
#using (Html.BeginForm("Save", "Customers"))
{
#Html.ValidationSummary(false)
<div class="form-group">
#Html.LabelFor(m => m.Customer.Name)
#Html.TextBoxFor(m => m.Customer.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Customer.Name)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Customer.MembershipTypeId)
#Html.DropDownListFor(m => m.Customer.MembershipTypeId, new SelectList(Model.MembershipTypes, "Id", "Name"), "", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Customer.MembershipTypeId)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Customer.CustomerBirthday)
#Html.TextBoxFor(m => m.Customer.CustomerBirthday, "{0:d MMM yyyy}", new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Customer.CustomerBirthday)
</div>
<div class="checkbox">
<label>
#Html.CheckBoxFor(m => m.Customer.IsSubscribedToNewsleter) Subscribed to Newsletter
</label>
</div>
#Html.HiddenFor(m => m.Customer.Id)
#Html.AntiForgeryToken()
<button type="submit" class="btn btn-primary"> Save </button>
}
I'm not sure what I'm doing wrong. I feel like it has something to do with a field being required - but I am submitting all fields in the post request so am not sure where I'm going wrong. Any ideas or tips on debugging would be greatly appreciated!
edit: Save code below
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Save(Customer customer)
{
if (!ModelState.IsValid)
{
var viewModel = new CustomerFormViewModel
{
Customer = customer,
MembershipTypes = _context.MembershipTypes.ToList()
};
return View("CustomerForm", viewModel);
}
if (customer.Id == 0)
_context.Customers.Add(customer);
else
{
var customerInDb = _context.Customers.Single(c => c.Id == customer.Id);
customerInDb.Name = customer.Name;
customerInDb.CustomerBirthday = customer.CustomerBirthday;
customerInDb.MembershipTypeId = customer.MembershipTypeId;
customerInDb.IsSubscribedToNewsleter = customer.IsSubscribedToNewsleter;
}
_context.SaveChanges();
return RedirectToAction("Index", "Customers");
}
I have a form with a submit button that should pass through the item to the actionlistener. I thought it might be similar to the question in #Html.HiddenFor does not work on Lists in ASP.NET MVC but none of the answers seem to work. You can even see my for-loop taken from one of the answers in there.
[
EDIT: I have gotten rid of the mass of hidden loops and replaced with #Html.EditorFor so that you can see, even if not hidden, the flags list does not get to the actionlistener. This is a problem because when someone edits the flags, there is no way to update the db as I cannot get the ID of the flag updated.
]
The ModelState in the controller is never valid, regardless whether I keep the "[Bind(Include =" there or not. That's just there because of the tutorial for
ASP.NET MVC Tutorial: Web application development with Azure Cosmos DB.
ItemController.cs:
[HttpPost]
[ActionName("ProductEdit")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditProductAsync( [Bind(Include = "Id, Name, Flags")] Item model)
{
Item product = await DocDBRepo<Item>.GetItem(model.Id);
model.Organisations = product.Organisations;
if (ModelState.IsValid) //Checks item validation via "required" set on properties
{
await DocDBRepo<Item>.UpdateItemAsync(model.Id, model);
return RedirectToAction("Index");
}
return View(model);
}
[HttpGet]
[ActionName("ProductEdit")]
public async Task<ActionResult> EditProductAsync(string id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Item item = await DocDBRepo<Item>.GetItem(id);
if (item == null)
{
return HttpNotFound();
}
return View(item);
}
ProductEdit.cs:
#model RRPortal.Models.Item
#{
ViewBag.Title = "ProductEdit";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>ProductEdit</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.Id)
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Flags, htmlAttributes: new { #class = "control-label col-md-2 " })
</div>
#*Flags list*#
#for (int i = 0; i < Model.Flags.Count; i++) //foreach (var flag in Model.Flags)
{
<div class="form-group">
//#Html.HiddenFor(modelItem => Model.Flags[i].Id)
#Html.Label(Model.Flags[i].Name, htmlAttributes: new { #class = "control-label col-md-3" })
#Html.LabelFor(modelItem => Model.Flags[i].Enabled, htmlAttributes: new { #class = "control-label col-md-1" })
<div class="col-md-8">
#Html.EditorFor(modelItem => Model.Flags[i].Enabled, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(modelItem => Model.Flags[i].Enabled, "", new { #class = "text-danger" })
</div>
</div>
}
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
Item.cs:
public class Item
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[Required]
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "flags")]
public List<Flag> Flags { get; set; }
[JsonProperty(PropertyName = "organisations")]
public List<Organisation> Organisations { get; set; }
}
public class Flag
{
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
[Required]
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[Required]
[JsonProperty(PropertyName = "enabled")]
public bool Enabled { get; set; }
}
public class Organisation
{
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "users")]
[Display(Name ="Users")]
public List<User> UserStore { get; set; }
}
public class User
{
[JsonProperty(PropertyName = "id")]
public int Id { get; set; }
[Required]
[JsonProperty(PropertyName = "fname")]
public string FName { get; set; }
[Required]
[JsonProperty(PropertyName = "lname")]
public string LName { get; set; }
[Required]
[Display(Name = "Admin?")]
[JsonProperty(PropertyName = "isadmin")]
public bool IsAdmin { get; set; }
}
The Item's Id and Name comes through and is not null when I debug the controller, but the Flags List is always empty. The ModelState shows the following exception: {"The parameter conversion from type 'System.String' to type 'RRPortal.Models.Flag' failed because no type converter can convert between these types."}
I have also been asked where the ModelState is showing the exception so below is a screenshot:
I will gladly update the question if anyone has any questions. I have been tweaking the view for 2 days now and still can't get the item to contain anything. The rendered HTML appears to contain the organisation and inner objects perfectly fine.
Any help is appreciated!
My guess is that in your HttpGet view you have something along the lines of:
[HttpGet]
public ActionResult EditProductAsync()
{
var model = new ProductViewModel()
{
Flags = _uow.Products.GetFlags(),
Organisations = _uow.Products.GetOrganisations()
};
return View(model);
}
Because these objects are not also returned as part of your form, they are returning to the server as empty which is throwing an error for you, thus invalidating the model. Before you check if the model is valid, you should first do something like this:
[HttpPost]
[ActionName("ProductEdit")]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditProductAsync( [Bind(Include = "Id, Name, Flags, Organisations")] Item model)
{
model.Organisations = _uow.Products.GetOrganisations();
model.Flags = _uow.Products.GetFlags();
if (ModelState.IsValid)
{
await DocDBRepo<Item>.UpdateItemAsync(model.Id, model);
return RedirectToAction("Index");
}
return View(model);
}
By populating those fields, any model errors you have are strictly your client's errors on submitting the form.
I'm trying to make very simple forum, but I have problem with DropDownList. I have two models:
ForumThread.cs
public partial class ForumThread
{
public ForumThread()
{
this.ForumCategory = new HashSet<ForumCategory>();
}
public int TH_ID { get; set; }
public System.DateTime DATE { get; set; }
public string TOPIC { get; set; }
public string USER { get; set; }
public virtual ICollection<ForumCategory> ForumCategory { get; set; }
}
ForumCategory.cs
public partial class ForumCategory
{
public ForumCategory()
{
this.ForumThread = new HashSet<ForumThread>();
}
public int CA_ID { get; set; }
public string CATEGORY { get; set; }
public bool isSelected { get; set; }
public virtual ICollection<ForumThread> ForumThread { get; set; }
}
I tried to make "Create" function with view:
Create
#model AnimeWeb.Models.ForumThread
#{
ViewBag.Title = "Create";
}
<h2>New Thread</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-field">
#Html.HiddenFor(model => model.TH_ID)
</div>
<div class="editor-label">
TOPIC
</div>
<div class="editor-field">
#Html.EditorFor(model => model.TOPIC)
#Html.ValidationMessageFor(model => model.TOPIC)
</div>
<div class="editor-label">
CATEGORY
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ForumCategory)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
And PartialView for ForumCategory:
ForumCategory
#model AnimeWeb.Models.FORUMCATEGORY
#Html.HiddenFor(model => model.CA_ID)
#Html.HiddenFor(model => model.CATEGORY)
<div>
#Html.DropDownListFor(item => Model.CA_ID, ViewBag.CA_ID as SelectList, "-- Select --")
</div>
ForumController
public ActionResult Create()
{
var db = new MainDatabaseEntities();
var viewModel = new ForumThread
{
ForumCategory = db.ForumCategory.Select(c => new { CA_ID = c.CA_ID, CATEGORY = c.CATEGORY, isSelected = false }).ToList().Select(g => new ForumCategory
{
CA_ID = g.CA_ID,
CATEGORY = g.CATEGORY,
isSelected = false
}).ToList(),
};
return View(viewModel);
}
//
// POST: /Forum/Create
[HttpPost]
public ActionResult Create(ForumThread forumthread, String user, int id)
{
var db = new MainDatabaseEntities();
var newthread = new ForumThread
{
TH_ID = forumthread.TH_ID,
DATE = DateTime.Now,
TOPIC = forumthread.TOPIC,
USER = forumthread.USER,
ForumCategory = new List<ForumCategory>()
};
foreach (var selectedCategory in forumthread.FORUMCATEGORY.Where(c => c.isSelected))
{
var category = new ForumCategory { CA_ID = selectedCategory.CA_ID };
db.ForumCategory.Attach(category);
newthread.ForumCategory.Add(category);
}
db.ForumThread.Add(newthread);
db.SaveChanges();
return RedirectToAction("Index");
}
And it obviously doesn't work. I tried to use other threads on this forum but nothing helped. Could someone explain me how to make this work?
The error is in partial view of ForumCategory:
The ViewData item that has the key 'CA_ID' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'.
In your PartialView for ForumCategory, your cast is not correct:
#Html.DropDownListFor(item => Model.CA_ID, ViewBag.CA_ID as SelectList, "-- Select --")
You have to use a SelectList (List of SelectListItem) that you can implement for example in a method in your model:
public List<SelectListItem> GetCategories()
{
var db = new MainDatabaseEntities();
List<SelectListItem> list = new List<SelectListItem>();
// Add empty item if needed
SelectListItem commonItem = new SelectListItem();
commonItem.Text = "--- Select ---";
commonItem.Value = "-1";
commonItem.Selected = true;
list.Add(commonItem);
// Add items from Database
foreach (ForumCategory fc in db.ForumCategory)
{
SelectListItem i = new SelectListItem();
i.Text = fc.CATEGORY;
i.Value = fc.CA_ID.ToString();
list.Add(i);
}
return list;
}
And then you can have you dropdown like that:
#Html.DropDownList("DropName", Model.GetCategories())
There may be other errors in some parts of your code, I just answered to the one you quoted
In your editortemplate, you have:
ViewBag.CA_ID as SelectList
But you don't show where you fill the ViewBag. Instead you might want to do something like this:
#Html.DropDownListFor(m => m.CA_ID,
new SelectList(Model.ForumCategory,
"CA_ID", "CATEGORY", Model.CA_ID))
As also explained in MVC3 DropDownListFor - a simple example?.
I'm having trouble getting my model to bind to an input box created by a custom helper method. I wrapped a jquery ajax-driven autocomplete box with a helper method called "Html.AutoCompleteBoxAjax." This helper basically just creates an element with javascript autocomplete functionality.
The property in the model is a string called "formatName". I have verified that, in the generated html for the view, both the name and id of the input element are "formatName", and that there are no other elements with those identities. I've also checked that the model has a default constructor, and that the "formatName" property is publicly accessible. Lastly, I've verified that when the Channel model is passed into the view, Channel.formatName has the correct value. Yet despite all this, I can't get the value to bind to the element, and the input box remains blank. There is also no binding when going the other way, from view to controller, and Channel.formatName remains blank.
What am I missing? Is it somehow because I'm using a custom helper method?
Model:
namespace WebApp.Models
{
public class ChannelModel
{
XYZ.DataAccess.ODS.DB db = Config.DB();
public string id { get; set; }
// Parent Project
[Display(Name = "Project")]
public string projectID { get; set; }
// Format Name
[Required(ErrorMessage = "Format is required.")]
[RegularExpression(Constants.username_regex, ErrorMessage = Constants.username_regexErrorMsg)]
[Display(Name = "Format")]
public string formatName { get; set; }
// Channel Name
[Required(ErrorMessage="Channel name is required.")]
[StringLength(100, ErrorMessage = Constants.minLengthErrorMsg, MinimumLength = Constants.username_minLength)]
[RegularExpression(Constants.username_regex, ErrorMessage = Constants.username_regexErrorMsg)]
[Display(Name = "Channel name")]
public string name { get; set; }
// Sequence
[Display(Name = "Sequence")]
public string sequenceID { get; set; }
public ChannelModel()
{
id = Guid.NewGuid().ToString();
}
public ChannelModel(XYZ.DataAccess.ODS.Channel channel_db)
{
id = channel_db.id;
projectID = channel_db.project_id;
name = channel_db.name;
formatName = channel_db.format_name;
sequenceID = channel_db.sequence_id;
}
public XYZ.DataAccess.ODS.Channel buildDBObject()
{
XYZ.DataAccess.ODS.Channel channel = new XYZ.DataAccess.ODS.Channel();
channel.id = id;
channel.project_id = projectID;
channel.name = name;
channel.format_name = formatName;
channel.sequence_id = sequenceID;
return channel;
}
}
}
View
#model WebApp.Models.ChannelModel
#using HelperMethods.Infrastructure
#{
ViewBag.Title = "Edit";
var sequences = ViewData["sequences"] as List<SelectListItem>;
}
<h2>Edit</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#section header {
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Channel</legend>
#Html.HiddenFor(model => model.id)
#Html.HiddenFor(model => model.projectID)
#Html.HiddenFor(model => model.name)
<div class="editor-field">
#Html.LabelFor(model => model.name):
#Html.DisplayFor(model => model.name)
</div>
<div class="editor-label">
#Html.Label("Format")
</div>
<div class="editor-field">
#Html.AutoCompleteBoxAjax("formatName", Url.Action("GetFormatsBeginningWith"))
#Html.ValidationMessageFor(model => model.formatName)
</div>
<!-- SEQUENCE -->
<div class="editor-label">
#Html.Label("Sequence")
</div>
<div class="editor-field">
#Html.SlaveDropdownList("sequenceID", "groupID", Url.Action("GetSequencesInGroup"), WebApp.Util.makeSelectList(sequences, Model.sequenceID))
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Controller
namespace WebApp.Controllers
{
public class ChannelController : Infrastructure.NoCacheController
{
XYZ.DataAccess.ODS.DB db = Config.DB();
-- stuff --
[HttpGet]
public ActionResult GetFormatsBeginningWith(string term)
{
var formats = db.getFormatsBeginningWith(term);
List<CustomHelpers.AutocompleteItem> items = new List<CustomHelpers.AutocompleteItem>();
foreach (var format in formats)
items.Add(new CustomHelpers.AutocompleteItem { value = format.name, label = format.name });
var j = Json(items, JsonRequestBehavior.AllowGet);
return j;
}
public ActionResult Edit(string id)
{
ChannelModel channelModel = new ChannelModel(db.getChannel(id));
string groupID = db.getProject(channelModel.projectID).group_id;
var sequences = db.getSequencesInGroup(groupID);
ViewData["sequences"] = makeSelectListItems(sequences);
return View(channelModel);
}
//
// POST: /Channel/Edit/5
[HttpPost]
public ActionResult Edit(ChannelModel model)
{
if (ModelState.IsValid)
{
db.updateChannel(model.buildDBObject());
return RedirectToAction("Index");
}
string groupID = db.getProject(model.projectID).group_id;
var sequences = db.getSequencesInGroup(groupID);
ViewData["sequences"] = makeSelectListItems(sequences);
return View(model);
}
-- more stuff --
}
}
Helper method for AutoCompleteBox
public static MvcHtmlString AutoCompleteBoxAjax(this HtmlHelper html, string id, string actionUrl)
{
TagBuilder input = new TagBuilder("input");
input.Attributes.Add("id", id);
input.Attributes.Add("name", id);
input.Attributes.Add("type", "text");
input.AddCssClass("autocomplete_ajax");
input.Attributes.Add("value", "");
input.Attributes.Add("action", actionUrl);
var variables = new Dictionary<string, string>() {
{"AUTOCOMPLETE_ID", id}
};
var script = populateScriptTemplate("TEMPLATE_autocomplete_ajax.js", variables);
StringBuilder s = new StringBuilder();
s.AppendLine(input.ToString(TagRenderMode.SelfClosing));
s.AppendLine(script);
return new MvcHtmlString(s.ToString());
}
Javascript for the autocomplete
$('#AUTOCOMPLETE_ID').autocomplete({
source: $('#AUTOCOMPLETE_ID').attr('action')
});
Relevant portion of the view's html
<div class="editor-field">
<input action="/Channel/GetFormatsBeginningWith" class="autocomplete_ajax" id="formatName" name="formatName" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="formatName" data-valmsg-replace="true"></span>
</div>
Answer turned out to be simple with input from #Jakub. The helper itself has to populate the value; there's no automatic binding that takes place for html helpers. Once I took care of this, the auto binding of posts back to the controller worked as expected.
I have an MVC 3 project where I have 1 view LoginRegister which contains 2 views with forms for login and pre-register. The problem is after incorrectly completing the pre-register form and using PartialView("LoginRegister", loginRegisterViewModel) validation messages aren't displayed due the ModelState being lost. Before reading the next paragraph it's probably best to jump to the CODE.
Debugging the PartialView("LoginRegister", loginRegisterViewModel) and stepping into the PreRegister view to the following #Html.ErrorMessageFor(model => model.Email). Where the ModelState does not contain the Email key (see below) and hence doesn't display an error message.
private static MvcHtmlString ErrorMessageHelper(this HtmlHelper htmlHelper, ModelMetadata modelMetadata, string expression, string validationMessage, IDictionary<string, object> htmlAttributes)
{
string modelName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(expression);
if (!htmlHelper.ViewData.ModelState.ContainsKey(modelName)) // ModelState contains no keys, e.g. Email not found and a null is returned
{
return null;
}
// code continues here to render error message
}
CODE
ViewModels
LoginRegisterViewModel
namespace Site.ViewModels.Account
{
public class LoginRegisterViewModel
{
public bool IsDialog { get; set; }
public PreRegisterViewModel PreRegister { get; set; }
public QuickLoginViewModel QuickLogin { get; set; }
}
}
PreRegisterViewModel
using FluentValidation.Attributes;
using Site.ViewModels.Validators;
namespace Site.ViewModels.Account
{
[Validator(typeof(PreRegisterViewModelValidator))]
public class PreRegisterViewModel
{
public string Email { get; set; }
public string ConfirmEmail { get; set; }
}
}
Views
LoginRegister
#model Site.ViewModels.Account.LoginRegisterViewModel
#{
Layout = ViewBag.Layout;
}
<div id="loginReg" class="mod-loginReg">
<h2>Login and Registration</h2>
#{Html.RenderPartial("PreRegister", Model.PreRegister, new ViewDataDictionary());}
#{Html.RenderPartial("QuickLogin", Model.QuickLogin, new ViewDataDictionary());}
</div>
PreRegister
#using Site.Classes.MVC.Extensions;
#model Site.ViewModels.Account.PreRegisterViewModel
#using (Html.BeginForm("PreRegister", "Account", FormMethod.Post, new { #class = "form-errorContainer" }))
{
<div class="mod-loginReg-register">
<h3>Register</h3>
<p>Please enter your email address to register.<br /><br /></p>
<div class="mod-loginReg-form">
<div class="field-item">
#Html.LabelFor(model => model.Email)
#Html.TextBoxFor(model => model.Email, new { #maxlength = "255", #class = "Textbox required email" })
#Html.ErrorMessageFor(model => model.Email)
</div>
<div class="field-item">
#Html.LabelFor(model => model.ConfirmEmail)
#Html.TextBoxFor(model => model.ConfirmEmail, new { #maxlength = "255", #class = "Textbox required email" })
#Html.ErrorMessageFor(model => model.ConfirmEmail)
</div>
<div class="button-group">
#Html.Button("Register", "Register")
</div>
</div>
</div>
}
AccountController
[RequireHttps]
public ActionResult LoginRegister(int showDialog)
{
var loginRegisterViewModel = new LoginRegisterViewModel();
if (showDialog == 1)
{
ViewBag.Layout = "~/layouts/structure/dialog.cshtml";
loginRegisterViewModel.IsDialog = true;
}
else
{
ViewBag.Layout = "~/layouts/structure/2column.cshtml";
}
return PartialView(loginRegisterViewModel);
}
[RequireHttps]
public ActionResult PreRegister()
{
return PartialView();
}
[HttpPost]
[RequireHttps]
public ActionResult PreRegister(PreRegisterViewModel model)
{
if (ModelState.IsValid)
{
return PartialView("PreRegisterComplete");
}
var loginRegisterViewModel = new LoginRegisterViewModel { PreRegister = model, QuickLogin = new QuickLoginViewModel() };
return PartialView("LoginRegister", loginRegisterViewModel);
}
This is caused by the viewData being cleared out when new ViewDataDictionary() is called.
#{Html.RenderPartial("PreRegister", Model.PreRegister, new ViewDataDictionary());}
#{Html.RenderPartial("QuickLogin", Model.QuickLogin, new ViewDataDictionary());}
According to MSDN ViewDataDictionary is used for:
Represents a container that is used to pass data between a controller
and a view.
I assume you have a good reason for this otherwise you would not have taken the trouble to add the additional parameter.
If you change it to:
#Html.RenderPartial("PreRegister", Model.PreRegister)
#Html.RenderPartial("QuickLogin", Model.QuickLogin)
the ModelState built up in the controller should be available on your views.