I have a strong typed view model and a MetaData partial class which has annotation attributes on required fields and field type. The Create.aspx view page has a form when submitted will execute Create method in the controller. When the user submit the form without all the required fields entered, upon reaching UpdateModel() line an exception is thrown. However, none of the error messages specified in the annotated fields is shown. Instead, the execution iterate through the RuleViolation() and landed at the most generic exception message. Thus, the user does not know that some required fields are not entered. If I define the checking if empty of required fields in the RuleVilolation() method then it is not DRY. Does anyone know why the error messages are not shown from the MetaClass? Thank you.
///Controller method
[AcceptVerbs(HttpVerbs.Post)]
[ValidateInput(false)]
public ActionResult Create(string id, [Bind(Prefix = "Transfer")]TransferFormViewModel newTransferViewModel, string cancel)
{
....
if (ModelState.IsValid)
{
Transfer newTransfer = new Transfer();
if (ModelState.IsValid)
{
try
{
Person person = base.ApplicaitonRepository.GetPerson(intID);
UpdateModel<Transfer>(newTransfer, "Transfer");
.....
}
catch (Exception ex)
{
newTransfer.MiscException = ex;
HelpersLib.ModelStateHelpers.AddModelErrors(this.ModelState, newTransfer.GetRuleViolations());
}
}
}
return View(new TransferFormViewModel(base.ApplicaitonRepository, newTransfer));
}
///partial domain objec class
[MetadataType(typeof(TransferMetaData))]
public partial class Transfer
{
public IEnumerable<RuleViolation> GetRuleViolations()
{
....
}
}
///MetaData class
class TransferMetaData
{
[Display(Name="List Type")]
public int ListType { get; set; }
[Required(ErrorMessage = "Notification Date/Time is required."), Display(Name = "Notification Date/Time")]
public DateTime AddedToListDate { get; set; }
[Required(ErrorMessage="Admit Date/Time is required."), Display(Name="Admit Date/Time")]
...
}
Do you have <%= Html.ValidationSummary() %> somewhere in your view?
What entries are in your ModelState?
Related
I have a view that is using a model and I am using that information to create a form.
I have three steps of the form that are optional or may not be shown.
The problem is that these hidden sections get posted along with the form data and break the business logic. (I have no control over the business logic)
So is there a way to tell the framework not to pass certain sections or fields? Perhaps VIA a class or something?
I know I could use AJAX to send certain sections as they are needed, but the site spec is to have them hidden and displayed as needed.
Although you could do this client-side, it won't stop malicious over-posting/mass assignment.
I suggest reading 6 Ways To Avoid Mass Assignment in ASP.NET MVC.
Excerpts:
Specify Included Properties only:
[HttpPost]
public ViewResult Edit([Bind(Include = "FirstName")] User user)
{
// ...
}
Specify Excluded Properties only:
[HttpPost]
public ViewResult Edit([Bind(Exclude = "IsAdmin")] User user)
{
// ...
}
Use TryUpdateModel()
[HttpPost]
public ViewResult Edit()
{
var user = new User();
TryUpdateModel(user, includeProperties: new[] { "FirstName" });
// ...
}
Using an Interface
public interface IUserInputModel
{
string FirstName { get; set; }
}
public class User : IUserInputModel
{
public string FirstName { get; set; }
public bool IsAdmin { get; set; }
}
[HttpPost]
public ViewResult Edit()
{
var user = new User();
TryUpdateModel<IUserInputModel>(user);
// ...
}
Use the ReadOnlyAttribute
public class User
{
public string FirstName { get; set; }
[ReadOnly(true)]
public bool IsAdmin { get; set; }
}
Lastly, and the most recommended approach is to use a real ViewModel, instead a domain Model:
public class UserInputViewModel
{
public string FirstName { get; set; }
}
Show/Hide will not allow/disallow the value from being sent to the Controller.
Elements that are Disabled or just not editable will (99% of the time) be returned as null / minVal.
You can set the elements in the View as Disabled by using JQuery in the script:
$('#elementID').attr("disabled", true);
OR you could use a DOM command:
document.getElementById('elementID').disabled = "true";
So you can set the fields as both Disabled AND Hidden, so that it is neither displayed, nor populated. Then in your Controller you can just base the Business Logic on whether or not certain fields (preferable Mandatory fields, if you have any) are null.
You can check this in C# like this:
For a string:
if (string.IsNullOrWhiteSpace(Model.stringField))
{
ModelState.AddModelError("stringField", "This is an error.");
}
For a DateTime:
if (Model.dateTimeField == DateTime.MinValue)
{
ModelState.AddModelError("dateTimeField ", "This is an error.");
}
Just for interest sake, here is how you can Hide/Show elements on the View using JQuery:
$('#elementID').hide();
$('#elementID').show();
I have a model class with [MetadataType(typeof(ThisEntityMetaData))] and [Bind(...)] annotations. I need to validate post back combined property values and a route parameter(viewType). The viewType is not a property of the entity class. So far I can only do this validation in [post] of the action. I'd like to do this validation in the entity class or the ThisEntityMetaData class. How can I do that? Thanks.
[HttpPost]
[ActionName("Create")]
[AcceptParameter(Name = "Save", Value = "Save")]
[ValidateInput(false)]
public ActionResult Create(int id, thisViewModel newViewModel,
string cancel, enumViewType viewType)
{
/* code omitted */
switch(viewType)
{
case enumViewType.OutAndNoReturn:
case enumViewType.OutAndReturn:
if(!thisEntity.Source.HasValue || !thisEntity.Reason.HasValue)
ViewData["Message"] = "Source, Reason are required.";
break;
case enumViewType.DirectOut:
case enumViewType.IndirectOut:
if ((!thisEntity.Source.HasValue || !thisEntity.Reason.HasValue ||
!thisEntity.Desired.HasValue))
{
thisEntity.ShowOutBlock = true;
ViewData["Message"] = "Source, Reason, Desired are required.";
return View(thisEntity);
}
break;
}
/* code omitted */
}
The viewType is not a property of the entity class.
You could use a real view model, not something that you have named view model but actually is not a view model at all:
[HttpPost]
[ActionName("Create")]
[AcceptParameter(Name = "Save", Value = "Save")]
[ValidateInput(false)]
public ActionResult Create(thisViewModel newViewModel)
{
...
}
where thisViewModel obviously contains everything you need:
[MetadataType(typeof(ThisEntityMetaData))]
public class thisViewModel
{
public int Id { get; set; }
public string Cancel { get; set; }
public enumViewType ViewType { get; set; }
...
}
Now inside your ThisEntityMetaData feel free to validate whatever you want in this view model.
I can't seem to get the edit function of my view to work..i have a page that lists, a page that shows specific detail and on that page, i should be able to edit the information of the form..PROBLEM: when i run the application it says:No parameterless constructor defined for this object. What am i doing wrong...?
In the Home Controller i have:
Edit Functions:
[HttpGet]
public ViewResult EditSchoolDetails(int id)
{
var institution = _educationRepository.GetInstititionById(id);
var model = (Mapper.Map<Institution, InstitutionModel>(institution));
return View(model);
}
post
[HttpPost]
public ActionResult EditSchoolDetails( InstitutionModel institutionModel, int id)
{
if (ModelState.IsValid) {
//_get from repository and add to instituion
var institution = _educationRepository.GetInstititionById(institutionModel.Id);
// Map from the view model back to the domain model
var model = Mapper.Map<Institution, InstitutionModel>(institution);
//UpdateModel(model);
SaveChanges();
return RedirectToAction("ViewSchoolDetails", new {institutionModel = institutionModel, id = id});
}
return View(institutionModel);
}
InstitutionModel
public class InstitutionModel {
public InstitutionModel() {
NAABAccreditations = new List<AccreditationModel>();
}
public int Id { get; set; }
public string Name { get; set; }
public bool IsNAAB { get { return NAABAccreditations.Any(); } }
public string Website { get; set; }
public AddressModel Address { get; set; }
public IEnumerable<AccreditationModel> NAABAccreditations { get; set; }
}
Does the Institution class have a parameterless constructor? If not, that will be the problem. You are passing an InstitutionModel to the the edit view, so the post action should probably take an InstitutionModel too, then you can map back to the original Institution model:
public ActionResult EditSchoolDetails(int id, InstitutionModel institutionModel)
{
if (ModelState.IsValid)
{
//add to database and save changes
Institution institutionEntity = _educationRepository.GetInstititionById(institution.Id);
// Map from the view model back to the domain model
Mapper.Map<InstitutionModel, Institution>(institutionModel, institutionEntity);
SaveChanges();
return RedirectToAction("ViewSchoolDetails",);
}
return View(institutionModel);
}
Notice also how it returns the view model back to the view if the model state isn't valid, otherwise you will lose all your form values!
Here's a similar question too which might help: ASP.NET MVC: No parameterless constructor defined for this object
Is it possible you need to pass a parameter to ViewSchoolDetails? I notice in the return statement you commented out that you were passing it an id, but in the return statement you're using, you're not passing in anything.
EDIT
This (from your comment below):
parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult ViewSchoolDetails(Int32)
...tells me you need to pass a parameter to ViewSchoolDetails
EDIT 2
I saw your edit, and would say this: if the method you are calling is
public ActionResult ViewSchoolDetails(InstitutionModel institutionModel, int id)
Then you MUST pass it an object of type InstitutionModel and an int as parameters or you will get an exception. Meaning, you need
RedirectToAction("ViewSchoolDetails", new {institutionModel = institutionModel, id = id});
Whenever i get this, i have forgotten to create a parameter-less constructor on my view-model. I always add one now just in case it's needed and i forget.
Does InstitutionModel have one?
I have a viewmodel that contains a product and SelectList of categories.
public class AdFormViewModel
{
public AmericanAds.Model.Ad Ad { get; set; }
public SelectList Categories { get; set; }
public AdFormViewModel(AmericanAds.Model.Ad ad, SelectList categories)
{
Ad = ad;
Categories = categories;
}
}
When adding a new product, if validation fails for category dropdown I get below error message.
The model item passed into the dictionary is of type 'AmericanAds.Model.Ad' but this dictionary requires a model item of type 'AmericanAds.Controllers.AdFormViewModel'.
Here is the controller for create action.
public ActionResult Create()
{
AdFormViewModel data = new AdFormViewModel(
null,
new SelectList(_repository.CategoryList().ToList(), "CategoryId", "CategoryName")
);
return View(data);
}
//
// POST: /Ad/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Ad ad)
{
if (ModelState.IsValid)
{
try
{
_repository.AddAd(ad);
return RedirectToAction("Index");
}
catch
{
return View(ad);
}
}
else
{
return View(ad);
}
}
What am I missing?
As you can tell, I am very new to ASP.Net MVC.
Thanks!
It's because your Create view requires a model of type AdFormViewModel but in your Create action (the one with the [AcceptVerbs(HttpVerbs.Post)] attribute) you return a model of type Ad (see the lines where it says return View(ad)).
Like the exception message says ; It requires an AmericanAds.Controllers.AdFormViewModel but you are sending an AmericanAds.Model.Ad.
And no, I don't think this has anything to do with the validation.
This is what my data model classes look like:
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Position Position { get; set; }
}
public class Position
{
public string Title { get; set; }
}
I have a Create view where I want to have two text boxes for first name and last name, and then a dropdown box that has the position title. I tried doing it this way:
View (only the relevant part):
<p>
<label for="Position">Position:</label>
<%= Html.DropDownList("Positions") %>
</p>
Controller:
//
// GET: /Employees/Create
public ActionResult Create()
{
ViewData["Positions"] = new SelectList(from p in _positions.GetAllPositions() select p.Title);
return View();
}
//
// POST: /Employees/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Employee employeeToAdd)
{
try
{
employeeToAdd.Position = new Position {Title = (string)ViewData["Positions"]};
_employees.AddEmployee(employeeToAdd);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
However, when I click submit, I get this exception:
System.InvalidOperationException was unhandled by user code
Message="There is no ViewData item of type 'IEnumerable<SelectListItem>' that has the key 'Positions'."
I'm pretty sure I'm doing this wrong. What is the correct way of populating the dropdown box?
You can store:
(string)ViewData["Positions"]};
in a hiddn tag on the page then call it like this
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Employee employeeToAdd, string Positions)
{
In the Create() (WITH POST ATTRIBUTE) employee since the ViewData["Positions"] is not set you are getting this error. This value should form part of your post request and on rebinding after post should fetch it from store or get it from session/cache if you need to rebind this..
Remember ViewData is only available for the current request, so for post request ViewData["Positions"] is not yet created and hence this exception.
You can do one quick test... override the OnActionExecuting method of the controller and put the logic to fetch positions there so that its always availlable. This should be done for data that is required for each action... This is only for test purpose in this case...
// add the required namespace for the model...
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// add your logic to populate positions here...
ViewData["Positions"] = new SelectList(from p in _positions.GetAllPositions() select p.Title);
}
There may be other clean solutions to this as well probably using a custom model binder...
I believe that ViewData is for passing information to your View, but it doesn't work in reverse. That is, ViewData won't be set from Request.Form. I think you might want to change your code as follows:
// change following
employeeToAdd.Position = new Position {Title = (string)ViewData["Positions"]};
// to this?
employeeToAdd.Position = new Position {Title = (string)Request.Form["Positions"]};
Good luck!