"Required" attribute not working when "AllowHtml" attribute is used. MVC 5 - asp.net-mvc

I want to post a html with a wysiwyg editor so I used [AllowHtml] attribute on my property. It works, but when I use it with [Required] and [StringLength] attributes, it stops working. ModelState.IsValid returns true even if prop is empty.
Yes, I know I can manually control it if it is null and add an error to ModelState, but why?
Why this happens? Is there a better way to post some HTML code to back-end?
My dto:
public int Id { get; set; }
[Required(ErrorMessage = CErr.RequiredField)]
[StringLength(100, ErrorMessage = CErr.Min5Max100)]
[MinLength(5, ErrorMessage = CErr.Min5Max100)]
public string Name { get; set; }
[AllowHtml]
[Required(ErrorMessage = CErr.RequiredField)]
[StringLength(4000, ErrorMessage = CErr.TooLongValue)]
public string HtmlBody { get; set; }
My action:
[Route("new")]
[HttpPost]
public ActionResult NewMessageLayout(ManageMessageLayoutDto model)
{
if (ModelState.IsValid)
{
var response = Repositories.MessageLayoutRepository.SaveMessageLayout(model, CurrentUser.Id);
if (response.Status == ResultStatus.Success)
{
return RedirectToAction("MessageManagement");
}
else
{
ModelState.AddModelError("error", response.Message);
return View("ManageMessageLayout", model);
}
}
return View("ManageMessageLayout", model);
}
And some HTML:
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.Id)
<label>Name <span class="req">*</span></label>
#Html.TextBoxFor(m => m.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Name, null, new { #class = "label label-danger" })
<label>Content <span class="req">*</span></label>
#Html.TextBoxFor(m => m.HtmlBody)
#Html.ValidationMessageFor(m => m.HtmlBody, null, new { #class = "label label-danger" })
<input class="btn btn-success btn-block btn-lg" type="submit" value="#(editing ? "Save" : "Add")" />
}

I am unable to replicate your error. However, I did notice a few things. First, according to the ckEditor documentation, textbox is not a valid attaching point for the editor. You should use textarea.
CkEditor Developer Documentation
At this point any textarea, p, or div element can be transformed into
a rich text editor by using the ckeditor() method.
Next, notice that I added AllowEmptyStrings=false to the [Required( attribute. This may be the important piece you are missing (will test without) - Testing seems to indicate that not setting AllowEmptyStrings does not impact the results of this test setup as the default value for this property is false. The model was still invalid.
Setup
VS 2015, MVC 5.2.2, .NET 4.5.1, jQuery 1.10.2, ckEditor 3.6.4
View Model
public class TestViewModel
{
[AllowHtml]
[Required(AllowEmptyStrings = false, ErrorMessage = "This should be filled out")]
[StringLength(4000, ErrorMessage="Its too big")]
public string HtmlBody { get; set; }
}
Controller Actions
public ActionResult About()
{
return View(new TestViewModel());
}
[HttpPost]
public ActionResult About(TestViewModel model)
{
if (!ModelState.IsValid) throw new Exception();
var test = model.HtmlBody;
return RedirectToAction("Contact");
}
View
#model WebApplication6.Models.TestViewModel
#{
ViewBag.Title = "About";
}
<h2>Test of HTML</h2>
#using (Html.BeginForm())
{
<label>Content <span class="req">*</span></label>
#Html.TextAreaFor(m => m.HtmlBody)
#Html.ValidationMessageFor(m => m.HtmlBody, null, new { #class = "label label-danger" })
<input type ="submit" value="test on server"/>
}
#section scripts
{
<script type="text/javascript">
$(function () {
$('#HtmlBody').ckeditor();
});
</script>
}
Results:
Basically, the exception was thrown because the model state was invalid

I found the problem.
My presentation layer was using 5.2.2.0 version of System.Web.Mvc but repository layer was using 5.2.3.0. I downgraded this to 5.2.2.0. And now it is working normally.

Related

MVC model validation

So, im currently building an application that needs the user model validating, and if the incorrect properties are filled in to the user it will tell them.
I have the data annotations set up, but im not sure how i relay the error message back to the user?
I have this set up so far on my model and view.
Model
public class DatabaseModel
{
[Required(ErrorMessage = ("A first name is required"))]
public string FirstName { get; set; }
[Required(ErrorMessage = ("A last name is required"))]
public string LastName { get; set; }
[Required(ErrorMessage = ("A valid role is required"))]
public string Role { get; set; }
// TODO - Validate rank to only b 1 - 10
//
[Range(1,10, ErrorMessage = ("A rank between 1 and 10 is required"))]
public int Rank { get; set; }
}
And View
#model RoleCreatorAndEditor.Models.DatabaseModel
#{
ViewData["Title"] = "Index";
}
<h2>User Information</h2>
<p>This is your user information!</p>
#using (Html.BeginForm("Index", "Home", FormMethod.Post)) {
#Html.Label("First Name")
<br>
#Html.TextBoxFor(m => m.FirstName)
<br>
#Html.Label("Last Name")
<br>
#Html.TextBoxFor(m=>m.LastName)
<br>
#Html.Label("Role")
<br>
#Html.TextBoxFor(m => m.Role)
<br>
#Html.Label("Rank")
<br>
#Html.TextBoxFor(m => m.Rank)
<br><br>
<input type="submit" value="Save">
}
My Controller
public class HomeController : Controller
{
// GET: Home
[HttpGet]
public ActionResult Index()
{
DatabaseModel model = new DatabaseModel();
return View(model);
}
[HttpPost]
public ActionResult Index(DatabaseModel model)
{
if (ModelState.IsValid)
{
ListToDatatable convert = new ListToDatatable();
DataTable user = convert.Convert(model);
DatabaseRepository dbRepo = new DatabaseRepository();
dbRepo.Upload(user);
}
return View();
}
}
I believe the model needs to be passed back to the view in order to display the error message, and although i have read through the documentation on asp.net i cannot understand how they just add the error message and the form knows how to display the errors to the user.
I am extremely confused.
You need to use ModelState.IsValid in your Controller and also #Html.ValidationMessageFor(model => model.FirstName) in your view:
public ActionResult Index(ViewModel _Model)
{
// Checking whether the Form posted is valid one.
if(ModelState.IsValid)
{
// your model is valid here.
// perform any actions you need to, like database actions,
// and/or redirecting to other controllers and actions.
}
else
{
// redirect to same action
return View(_Model);
}
}
For your example:
#model RoleCreatorAndEditor.Models.DatabaseModel
#{
ViewData["Title"] = "Index";
}
<h2>User Information</h2>
<p>This is your user information!</p>
#using (Html.BeginForm("Index", "Home", FormMethod.Post)) {
#Html.LabelFor(m=>m.FirstName)
<br>
#Html.TextBoxFor(m => m.FirstName)
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
<br>
#Html.LabelFor(m=>m.LastName)
<br>
#Html.TextBoxFor(m=>m.LastName)
#Html.ValidationMessageFor(model => model.LastName, "", new { #class = "text-danger" })
. . .
<input type="submit" value="Save">
}
Controller:
[HttpPost]
public ActionResult Index(DatabaseModel model)
{
if (ModelState.IsValid)
{
ListToDatatable convert = new ListToDatatable();
DataTable user = convert.Convert(model);
DatabaseRepository dbRepo = new DatabaseRepository();
dbRepo.Upload(user);
}
return View(model);
}

Losing form values after validation

Using a ViewModel for validation:
public class CCvm
{
[Required(ErrorMessage = "Please enter your Name")]
public string cardHolderName { get; set; }
}
My controller calls a task on post:
public async Task<ActionResult> Pay(FormCollection form, CCvm model)
{
if (!ModelState.IsValid)
{
return View(model);
}
}
And the View:
#model GCwholesale.Models.CCvm
#{
Layout = "~/Views/Shared/_HomeSubPageLayout.cshtml";
ViewBag.Title = "Secure Checkout";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="Payment">
<label>Name on Card: </label>
#Html.EditorFor(model => model.cardHolderName, new { htmlAttributes = new { #placeholder = "Cardholder Name Please", #Value = ViewBag.Name } })<br />
#Html.ValidationMessageFor(model => model.cardHolderName)
<button class="submitCheckout">SUBMIT NOW</button>
</div>
}
But when validation fails the data in the form goes away.
Thanks for taking a look.
You do not need to set #Value = ViewBag.Name inside EditorFor.
#Html.EditorFor(model => model.cardHolderName,
new { htmlAttributes = new { #placeholder = "Cardholder Name Please" } })
Besides, you do not need FormCollection as a parameter because you already have CCvm Model.
public async Task<ActionResult> Pay(CCvm model){
{
//...
}
#Value = ViewBag.Name
You're not setting the ViewBag.Name, so it wouldn't have a value and would result in a blank input. Remove that and let the HtmlHelper set it based off the value in the posted model.

The ViewData item that has the key 'ShelfId' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'

Problem
I use the following code very similarily somewhere else in my application, but it is not working. I am completely stumped.
The ViewData item that has the key 'ShelfId' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
This is thrown during the post method. My model state is invalid.
Code
Models
Shelf
public class Shelf
{
[Key]
public int ShelfId
[Display(Name = "Shelf Id")]
[Required]
public string ShelfName
public virtual List<Book> Books {get; set;}
}
Book
public class Book
{
public int BookId
[Required]
[StrengthLength(160, MinimumLength = 8)]
public string BookName
public int ShelfId
public Shelf shelf {get; set;}
}
Controller
// GET: Units/Create
public async Task<IActionResult> Create()
{
var shelves = await _db.Shelves.OrderBy(q => q.Name).ToListAsync();
ViewBag.SelectedShelves = new SelectList(shelves, "ShelfId", "Name");
return View();
}
// POST: Units/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Book book)
{
book.CreatedBy = User.Identity.GetUserName();
book.Created = DateTime.UtcNow;
book.UpdatedBy = User.Identity.GetUserName();
book.Updated = DateTime.UtcNow;
if (ModelState.IsValid)
{
db.Units.Add(unit);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(book);
}
view
#model AgentInventory.Models.Book
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Create Unit</title>
</head>
<body>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal well bs-component" style="margin-top:20px">
<h4>Unit</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="control-label col-md-2">Room</div>
<div class="col-md-10">
#Html.DropDownListFor(model => model.ShelfId, (SelectList)ViewBag.SelectedShelves, "All", new { #class = "form-control" })
</div>
</div>
<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.BookName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.BookName, "", new { #class = "text-danger" }
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Attempts
I tried:
Adding #Html.HiddenFor(model=>model.ShelfId) in the create view, but that didn't work.
I have looked at similar issues on stackoverflow, but none of the fixes worked for me. (IE - hiddenfor, different kinds of selectlists)
Since I am new to MVC framework, I would be grateful for any assistance. I don't understand why this code works for two other kinds of models (Building and room), but not my current two models? It's weird.
PS - Is there a way to do this easily without using viewbag as well?
The reason for the error is that in the POST method when you return the view, the value of ViewBag.SelectedShelves is null because you have not set it (as you did in the get method. I recommend you refactor this in a private method that can be called from both the GET and POST methods
private void ConfigureViewModel(Book book)
{
var shelves = await _db.Shelves.OrderBy(q => q.Name).ToListAsync();
// Better to have a view model with a property for the SelectList
ViewBag.SelectedShelves = new SelectList(shelves, "ShelfId", "Name");
}
then in the controller
public async Task<IActionResult> Create()
{
// Always better to initialize a new object and pass to the view
Book model = new Book();
ConfigureViewModel(model)
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Book book)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(book)
return View(book);
}
// No point setting these if the model is invalid
book.CreatedBy = User.Identity.GetUserName();
book.Created = DateTime.UtcNow;
book.UpdatedBy = User.Identity.GetUserName();
book.Updated = DateTime.UtcNow;
// Save and redirect
db.Units.Add(unit);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
Note your Book class contains only fields, not properties (no { get; set; }) so no properties will be set and the model will always be invalid because BookName has Required and StringLength attributes.
Also you have not shown all the properties in your model (for example you have CreatedBy, Created etc. and its likely that ModelState will also be invalid because you only generate controls for only a few properties. If any other properties contain validation attributes, then ModelState will be invalid. To handle this you need to create a view model containing only the properties you want to display edit.
public class BookVM
{
public int Id { get; set; }
[Required]
[StrengthLength(160, MinimumLength = 8)]
public string Name { get; set; }
public int SelectedShelf { get; set; }
public SelectList ShelfList { get; set; }
}
Then modify the private method to assign the SelectList to the view model (not ViewBag, and in the controller methods, pass a new instance of BookVM to the view, and post back to
public async Task<IActionResult> Create(BookVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model)
return View(model);
}
// Initialize a new Book and set the properties from the view model
}

MVC Model not posting

When I click the login button I never get my model posted to the server. However if I accept a FormCollection I will see the values. How can I make this automatically bind to my model instead of searching the Form Collection?
From what I have read there are a few common problems for this:
1 - your view does not specify what model you are using (#model myApp.Models.name)
2 - Your model does not use properties
3 - Any of the required fields are missing
Controller
[HttpGet]
public ActionResult Password()
{
return View(new AuthViewModel());
}
[HttpPost]
public ActionResult Password(AuthViewModel password)
{
if (password == null || string.IsNullOrEmpty(password.Password))
{
ViewBag.Error = Constants.ErrorMessages.UserPassword_PassBlank;
return View(new AuthViewModel());
}
//success
return Redirect("/");
}
Model
public class AuthViewModel
{
public string Password { get; set; }
}
View
#model MvcApplication1.Models.AuthViewModel
#{
ViewBag.Title = "Password";
}
<h2>Password</h2>
#using (Html.BeginForm())
{
<div>#Html.TextBoxFor(m => m.Password,new{placeholder="Password",type="password",autofocus=""})</div>
<div><button id="btnLogin" type="submit">Login</button></div>
<div class="error">#ViewBag.Error</div>
}
Not sure why Dan's answer isn't working without trying it, looks like it should.
I took a look at some of my code for a login form, similar to yours.
Here's mine :
public class SignInModel
{
[Required]
[Display(Name = "Enter your email address")]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
[Display(Name = "Enter your password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
The main difference I see is that mine has the [DataType(DataType.Password)] attribute on the password. Not sure if this makes that much difference though.
The other thing I noticed is different is that in my form I specify that the form method is POST. Also I've used the EditorFor() helper instead of textbox or password:
#using (Html.BeginForm("SignIn", "Account", "POST"))
{
<div class="form-field">
#Html.LabelFor(x => x.Email)
#Html.EditorFor(m => m.Email)
</div>
<div class="form-field">
#Html.LabelFor(x => x.Password)
#Html.EditorFor(m => m.Password)
</div>
<div class="form-remember">
#Html.CheckBoxFor(m => m.RememberMe)
#Html.LabelFor(x => x.RememberMe)
</div>
<button type="submit">
Sign In</button>
}
use the following
#using (Html.BeginForm())
{
<div>#Html.PasswordFor(model => model.Password)</div>
<div><input id="btnLogin" type="submit" value="Login"/></div>
<div class="error">#ViewBag.Error</div>
}

Model returning null value

This is my code for my model
public class ManufacturerModel
{
public int ProductTYpeId { get; set; }
public int Id { get; set; }
public string Name { get; set; }
public List<SelectListItem> manf { get; set; }
public Manufacturer manufacturer { get; set; }
}
This is my code in cshtml file
#using (Html.BeginForm("addmanufacturer", "Admin", FormMethod.Post, new { id = "formPageID" }))
{
<div class="row">
<label>Select Existing Manufacturer<span style="color: Red">*</span>:</label>
<div class="formRight">
#Html.DropDownList("Id", Model.manf)
</div>
</div>
<div class="row">
<label>Manufacturer Name<span style="color: Red">*</span>:</label>
<div class="formRight">
#Html.TextBoxFor(m => m.manufacturer.name)
#Html.ValidationMessageFor(m => m.manufacturer.name)
</div>
</div>
}
I am posting this form and when I am trying to fetch the value from the manfacurer ie - ManufacturerModel manufacturer = new ManufacturerModel();
using a model object all the value are coming out null.
in the text box If I replace it with m => m.Name then I am able to get proper value of Name.
can any one suggest what the problem is
I am using the manf to bind a dropdown. If In case I post back the form and the if it is return the value becomes blank, I need to refill the value..
public ActionResult addmanufacturer(string id)
{
if (id == null)
id = "0";
ManufacturerModel manufacturer = new ManufacturerModel();
manufacturer.ProductTYpeId = Convert.ToInt32(id);
manufacturer.manf = GetManf();
manufacturer.Id = -1;
return View(manufacturer);
}
I think problem will be becouse of:
#using (Html.BeginForm("Action", "Controller", FormMethod, HTML ATTRIBUTES )) { }
You probably want this overload:
#using (Html.BeginForm("Action", "Controller", Route Values, FormMethod, html attributes )) { }
Important is say him that you want route values and not a html atrributes, so try this
#using (Html.BeginForm("addmanufacturer", "Admin", new { id = "formPageID" }, FormMethod.Post, null )) { }
Hope, it helps.
M.

Resources