How do you validate a class using Validation attributes when validating strongly typed view models.
Suppose you have a view model like so:
[PropertiesMustMatch("Admin.Password", "Admin.ConfirmPassword")]
public class AdminsEditViewModel
{
public AdminsEditViewModel()
{
this.Admin = new Admin(); // this is an Admin class
}
public IEnumerable<SelectListItem> SelectAdminsInGroup { get; set; }
public IEnumerable<SelectListItem> SelectAdminsNotInGroup { get; set; }
public Admin Admin { get; set; }
}
I get null exception when on this line of PropertiesMustMatchAttribute
object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
since Password field is a property of Admin class and NOT AdminsEditViewModel. How do I make it so that it will go so many levels deep until it does find property of Admin in the ViewModel AdminsEditViewModel?
thanks
You need to modify the PropertiesMustMatchAttribute class to parse the property name and search deeply.
This attribute is not part of the framework; it's included in the default MVC template (in AccountModels.cs)
You can therefore modify it to suit your needs.
Specifically, you would call name.Split('.'), then loop through splitted names and get the property values.
It would look something like
object GetValue(object obj, string properties) {
foreach(strong prop in properties)
obj = TypeDescriptor.GetProperties(obj)
.Find(prop, ignoreCase: true)
.GetValue(obj);
}
return obj;
}
Related
In my applicantion, I browse to the URL by supplying the parameters through query string. Based on the URI, the respective controller's action is triggered, and the parameters supplied are auto-mapped to my model.
URL: http://{host}:{port}/{website}/{controller}/{action}?{querystring}
URI:
/{controller}/{Action}?{QueryString}
My URI: Employee/Add?EmployeeCode=Code3&EmployeeId=103
EmployeeModel
public class EmployeeModel
{
public Employee()
{
}
public string EmployeeId { get; set; }
public string EmployeeCode { get; set; }
//Some more properties here
}
EmployeeController
[HttpGet]
[Route("Add")]
public IActionResult Add([FromUri] EmployeeModel model)
{
//Some code here
}
While this all works fabulous, when I browse through, below is the order in which break-points hit,
Add method of EmployeeController
Default constructor of EmployeeModel
set method of EmployeeId property of EmployeeModel
set method of EmployeeCode property of EmployeeModel
I suspect the order in which the properties get initialized is based on the order they are declared in the class.
But, to create an instance and initialize the properties the framework must be using reflection. And as per the MSDN documentation for Type.GetProperties the order is not guarateed.
The GetProperties method does not return properties in a particular
order, such as alphabetical or declaration order. Your code must not
depend on the order in which properties are returned, because that
order varies.
I basically want the initialization to take place in a specific order, is this possible?
You can't get the model binding mechanism to do things in a specific order, but you can make sure that the order is applied where it has to be.
Presumably, EmployeeModel is a domain model object on which the order actually matters, and you're now model binding directly to this type. Instead, introduce an edit model1 which you model bind to, and then map that to your model type:
public class EmployeeEditModel
{
public string EmployeeId { get; set; }
public string EmployeeCode { get; set; }
}
// and change your action signature to this:
[HttpGet]
[Route("Add")]
public IActionResult Add([FromUri] EmployeeEditModel model)
1 For an explanation of what an edit model is, see the final remarks on this old answer of mine.
To perform the mapping you have numerous alternatives, some better than others. Pick one that suits you - however, since the reason the order matters is probably something inherent in the domain model object, I'd advice you to put the logic inside it (e.g. in a constructor), to make it easier to remember to change it if the requirements change.
Map via a constructor on the model object
public class EmployeeModel
{
public EmployeeModel(string employeeId, string employeeCode /* , ... */)
{
// do stuff in whatever order you need
EmployeeId = employeeId;
EmployeeCode = employeeCode;
}
// Now your properties can be get-only
public string EmployeeId { get; }
public string EmployeeCode { get; }
}
Map via an extension method that does everything in the right order
public static class EmployeeEditModelExtensions
{
public EmployeeModel AsDomainModel(this EmployeeEditModel editModel)
{
// do stuff in whatever order you need
var model = new EmployeeModel();
model.EmployeeId = editModel.EmployeeId;
model.EmployeeCode = editModel.EmployeeCode;
// ...
}
// Now your properties can be get-only
public string EmployeeId { get; }
public string EmployeeCode { get; }
}
Use an external framework such as AutoMapper, with custom configuration to make sure that the ordering is correct
Do something else. The only purpose is to get you from an EmployeeEditModel instance to an EmployeeModel instance, assigning to the properties of the EmployeeModel in the correct order. Since you write this code yourself, you can do what you want.
I have a class which looks like this:
public class ApplicationFormModel
{
protected ApplicationFormModel()
{
CurrentStep = ApplicationSteps.PersonalInfo;
PersonalInfoStep = new PersonalInfo();
}
public PersonalInfo PersonalInfoStep { get; set; }
public IEducationalBackground EducationalBackgroundStep { get; set; }
public IAboutYou AboutYouStep { get; set; }
public IOther OtherStep { get; set; }
}
where IEducationalBackground, IAboutYou, and IOther are interfaces. I do not use this class directly, but I use derived classes of this one which upon instantiation create the proper instances of EducationalBackgroundStep, AboutYouStep, and OtherStep.
In my view, I am using Razor Helpers such as
#Html.TextBoxFor(model => (model.EducationalBackgroundStep as ApplicationFormModels.EducationalBackgroundAA).University, new {#class = "form-control", type = "text", autocomplete = "off"})
The field 'University', for example, is NOT part of the Interface and I therefore need the cast to access it. Everything is fine for properties of the interface itself, but those which I need to cast for do not end up having the correct ID and Name properties.
For example, instead of EducationalBackgroundStep_University as ID, I only get University. This causes the form to not include this value when submitting it.
I did not have this issue before when I used a base class instead of an interface, but then I had to include the EducationalBackgroundStep, AboutYouStep, and OtherStep in each derived class (and have it then of the correct derived type), but that is what I wanted to avoid.
Is there any way around this? Thank you very much!
The issue with the ID generation is because you are using casting (x as y) and the TextBoxFor expression handler can't determine what the original model property was (more to the point, it doesn't make sense to use the original model property as you're not using it any more, you're using the cast property)
Example fiddle: https://dotnetfiddle.net/jQOSZA
public class c1
{
public c2 c2 { get; set; }
}
public class c2
{
public string Name { get; set; }
}
public ActionResult View(string page, bool pre = false)
{
var model = new c1 { c2 = new c2 { Name = "xx" } };
return View(model);
}
View
#model HomeController.c1
#Html.TextBoxFor(x=>Model.c2.Name)
#Html.TextBoxFor(x=>(Model.c2 as HomeController.c2).Name)
The first textboxfor has ID c2_Name while the second has just Name
You have two options:
1) use concrete classes rather than interfaces for your viewmodel
2) don't use TextBoxFor and instead use TextBox and specify the ID manually (but then you'll lose refactoring)
#Html.TextBox("c2_Name", (Model.c2 as HomeController.c2).Name)
This will give you the ID you're expecting, but as #StephenMuecke rightly points out, this might not bind correctly when you do the POST - so you may still be stuck... but at least it answers the question.
#freedomn-m explained to me why my code wouldn't work and he put me on the right track to find a solution, so he gets the accepted answer.
The workaround I used is the following - so I now have the following classes:
public class ApplicationFormViewModel {
public PersonalInfo PersonalInfoStep { get; set; }
// constructors which take the other classes and
// initialize these fields in an appropriate manner
public IEducationalBackground EducationalBackgroundStep { get; set; }
public IAboutYou AboutYouStep { get; set; }
public IOther OtherStep { get; set; }
}
// in our case, XX can be one of 3 values, so we have 3 classes
public class ApplicationFormXX {
public PersonalInfo PersonalInfoStep { get; set; }
// constructor which take the ApplicationFormViewModel and
// initialize these fields in an appropriate manner
public EducationalBackgroundXX EducationalBackgroundStep { get; set; }
public AboutYouXX AboutYouStep { get; set; }
public OtherXX OtherStep { get; set; }
}
To the main View I send the ApplicationFormViewModel and for each of the fields, I call a separate Partial View.
The Partial views render the common fields which are present in the Interfaces and then, depending on the type of the object held by the interface, it calls a different partial view which accepts the correct Model.
Example:
In the main View I have (NOTE: The actions return a partial view):
#model Applications.Models.ApplicationFormModels.ApplicationFormViewModel
// CODE, CODE, CODE
#Html.Action("RenderEducationalBackgroundStep", "ApplicationFormsLogic", routeValues: new {model = Model})
In the Partial View of for the EducationalBackgroundStep, I have:
#model ApplicationFormModels.ApplicationFormViewModel
// CODE, CODE, CODE
#{
var educationalBackgroundType = Model.EducationalBackgroundStep.GetType();
if (educationalBackgroundType == typeof(EducationalBackgroundXX))
{
<text>#Html.Partial("~\\Views\\Partials\\ApplicationForm\\Partials\\ApplicationSteps\\EducationalBackground\\_EducationalBackgroundXX.cshtml", new ApplicationFormModels.ApplicationFormModelXX { EducationalBackgroundStep = Model.EducationalBackgroundStep as EducationalBackgroundXX })</text>
}
// OTHER ELSE IF CASES
}
And then, the _EducationalBackgroundXX.cshtml partial view expects a model like this:
#model ApplicationFormModels.ApplicationFormModelXX
This way, no casting is required and everything works fine with the ModelBinder. Again, thank you #freedomn-m for setting me on the right track.
NOTE: In practice I need more fields than the ones presented here (for navigation and some custom logic), so actually all of these classes inherit an abstract base class (this makes it redundant to have the PersonalInfoStep declared in each of the classes, for example, because it can be inherited from the abstract base class). But for the intents and purposes of this method, what's present here suffices.
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();
Can you create custom data annotations for the model that can be read inside the T4 template for the View like property.Scaffold is read? I would like to add data annotation parameters like Scaffold based on which I would build the view.
Thank you
I wrote a blog post on the solution I came up with for MVC5. I'm posting it here for anyone who comes along:
https://johniekarr.wordpress.com/2015/05/16/mvc-5-t4-templates-and-view-model-property-attributes/
Edit: In your entities, decorate property with custom Attribute
namespace CustomViewTemplate.Models
{
[Table("Person")]
public class Person
{
[Key]
public int PersonId { get; set;}
[MaxLength(5)]
public string Salutation { get; set; }
[MaxLength(50)]
public string FirstName { get; set; }
[MaxLength(50)]
public string LastName { get; set; }
[MaxLength(50)]
public string Title { get; set; }
[DataType(DataType.EmailAddress)]
[MaxLength(254)]
public string EmailAddress { get; set; }
[DataType(DataType.MultilineText)]
public string Biography { get; set; }
}
}
With this Custom Attribute
namespace CustomViewTemplate
{
[AttributeUsage(AttributeTargets.Property)]
public class RichTextAttribute : Attribute
{
public RichTextAttribute() { }
}
}
Then create a T4Helper that we'll reference in our template
using System;
namespace CustomViewTemplate
{
public static class T4Helpers
{
public static bool IsRichText(string viewDataTypeName, string propertyName)
{
bool isRichText = false;
Attribute richText = null;
Type typeModel = Type.GetType(viewDataTypeName);
if (typeModel != null)
{
richText = (RichTextAttribute)Attribute.GetCustomAttribute(typeModel.GetProperty(propertyName), typeof(RichTextAttribute));
return richText != null;
}
return isRichText;
}
}
}
So, this is how you do it.
Follow this tutorial on how to create a custom attribute http://origin1tech.wordpress.com/2011/07/20/mvc-data-annotations-and-custom-attributes/
To read this attribute values in the T4 scaffolding templates, first add the template files as described here http://www.hanselman.com/blog/ModifyingTheDefaultCodeGenerationscaffoldingTemplatesInASPNETMVC.aspx
Then, for example, open List.tt from the AddView folder. This template creates the Index view.
Go to the end of the template file and find the definition for class ModelProperty. Add your property value to it ( public string MyAttributeValue { get; set; }
Now go a bit down in the List.tt and find bool Scaffold(PropertyInfo property) method. You will need to add your own attribute property reader. This method, for the above mentioned tutorial, would be:
string OptionalAttributesValueReader(PropertyInfo property){
foreach (object attribute in property.GetCustomAttributes(true)) {
var attr = attribute as OptionalAttributes ;
if (attr != null) {
return attr.style;
}
}
return String.Empty;
}
Then find the method List GetEligibleProperties(Type type) at the bottom of the file. Add your reader to it like this:
...
IsForeignKey = IsForeignKey(prop),
IsReadOnly = prop.GetSetMethod() == null,
Scaffold = Scaffold(prop),
MyAttributeValue = OptionalAttributesValueReader(prop)
When you want to use and read this attribute you can do it like the Scaffold property is used in the List.tt
List<ModelProperty> properties = GetModelProperties(mvcHost.ViewDataType);
foreach (ModelProperty property in properties) {
if (property.MyAttributeValue != String.Empty) {
//read the value
<#= property.MyAttributeValue #>
}
}
Since these classes are defined in my project, I had to add my project dll and namespace to the top of the List.tt:
<## assembly name="C:\myProjectPath\bin\myMVCproject.dll" #>
<## import namespace="myMVCproject.CustomAttributes" #>
If your model changes and you need to find these new changes in the scaffolding, you need to rebuild your project.
Hope anyone looking for the solution will find this useful. Ask if there is anything unclear.
This is how I did it in MVC 5. I did this a long time ago and I may be forgetting stuff, I'm just copy/pasting what I see in my modified templates.
I needed a way to set the order of properties in (for example) the create/edit views or in the list view table. So I created a custom attribute OrderAttribute with an integer property Order.
To access this attribute in the T4 templates I modified the file ModelMetadataFunctions.cs.include.t4. At the top I added one method that retrieves the Order value set in the attribute from a PropertyMetadata object, and another method to simply order a list of PropertyMetadata items by that order:
List<PropertyMetadata> GetOrderedProperties(List<PropertyMetadata> properties, Type modelType) {
return properties.OrderBy<PropertyMetadata, int>(p => GetPropertyOrder(modelType, p)).ToList();
}
int GetPropertyOrder(Type type, PropertyMetadata property) {
var info = type.GetProperty(property.PropertyName);
if (info != null)
{
var attr = info.GetCustomAttribute<OrderAttribute>();
if (attr != null)
{
return attr.Order;
}
}
return int.MaxValue;
}
Finally, in the List template for example, I have added a part where I call the GetOrderedProperties method:
var typeName = Assembly.CreateQualifiedName("AcCtc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", ViewDataTypeName);
var modelType = Type.GetType(typeName);
var properties = ModelMetadata.Properties.Where(p => p.Scaffold && !p.IsPrimaryKey && !p.IsForeignKey && !(p.IsAssociation && GetRelatedModelMetadata(p) == null)).ToList();
properties = GetOrderedProperties(properties, modelType);
foreach (var property in properties)
{
//...
}
Unfortunately I needed the name of the project to be able to create a Type object which I needed to get the attributes from. Not ideal, perhaps you can get it some other way but I couldn't manage it without this string including all the version stuff.
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?