using MVC Model Binder, how to prevent binding inner complex object properties? - model-view-controller

i have the following model
public class Person
{
public int Id {get;set;}
[Required()]
public string Name {get;set;}
[Required()]
public Address Address {get;set;}
}
public class Address
{
public int Id {get;set;}
[Required()]
public string City {get;set;}
[Required()]
public string Street {get;set;}
}
in the controller:
[HttpPost]
public ActionResult Create(Person entity)
{
if (ViewData.ModelState.IsValid)
{
///Some code
return this.RedirectToAction("Browse");
}
else
{
return View("Edit", ViewModel);
}
}
the problem is that the binder try to validate even the inner address class, but all i care for, is the AddressID
but the ModelBinder insist to validate even the City and Street properties.
how can i simply override the original ModelBinder just to validate the ID of the inner object (which is in my situation is AddressID)??
is there a simple way ?

It sounds like your entity and your model have two different requirements. If that is the case, then they should be two different classes. Write a separate Person and address class for MVC to bind to and don't have city or street be required.
Another possible, but less elegant solution, is to not rely on MVC doing the model binding. If you only have a handful of values that may be acceptable, but if you have a lot, then I would use my first suggestion.

Related

Using a viewmodel which ignores the properties from the model

I'm using entity framework and MVC (both CORE if that matters) to make a site. Using the model and tying it directly to the view is fine all the CRUD actions work, everything is lovely.
I wanted to use a couple of pages to access the model so the site looked better, so split the controls out onto separate views and added a corresponding viewmodel for each, so my project looks like this
-Model
--CustomerModel
-ViewModel
--CustomerNameVM
--CustomerAddressVM
-View
--CustomerNameView
--CustomerAddressView
The CustomerModel has a number of properties
Forename
Surname
Address
Postcode
with Forename and Surname in the CustomerNameVM and Address and Postcode in CustomerAddressVM. Surname is defined as [Required] in the model but not in CustomerNameVM which I believe is the correct way to do it.
I'm struggling to get the model loaded into the viewmodel and then trying to save it when I'm editing the address details in CustomerAddressView because it errors when I try and save as the viewmodel doesn't contain Surname (from the model), so it's null and therefore the [Required] criteria isn't being met.
I've tried a few methods of trying to get past this like :-
Jeffrey Palermo's Onion Architecture
Repositories
domain models
amongst others which all end up with the same problem, I can't save the Address as the Surname is null.
How do I ignore validation criteria for the properties of the model that aren't being referenced in the viewmodel?
Or how do I load and reference only those properties of the model that are present in viewmodel?
Edit
For those who keep asking for code, which codeset? I've tried 30 of them now, none of which do the job. Which one of these do you want? I'm trying to get a general idea of how this is supposed to work as none of the methods, documentation and associated examples function.
Here's a starter for 10, it's unlike the other 29 codesets but it's code and probably the shortest.
The controller
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Step2Address(int? id, [Bind("CustomerID,txtAddress,txtPostcode")] VMAddress VMAddress) {
if (ModelState.IsValid) {
//the saving code
}
return View(VMAddress);
}
the model
public class clsCustomer {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CustomerID { get; set; }
public string Forename { get; set; }
[Required]
public string Surname { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
the viewmodel
public class VMAddress {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CustomerID { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
}
the view
#model theProject.Models.VMStep2Contact
<form asp-action="Step2Address">
<input type="hidden" asp-for="ComplaintID" />
<input asp-for="txtAddress"/>
<input asp-for="txtPostcode"/>
<input type="submit" value="Save" />
the context
public class ContextCustomer : DbContext {
public ContextCustomer(DbContextOptions<ContextCustomer> options) : base(options) {
}
public DbSet<clsCustomer> Customer{ get; set; }
}
Clicking "Save" on the webpage calls the controller straight away, which hits the first line if (ModelState.IsValid) and as the Surname isn't set and is [Required] the ModelState is not valid so no save is attempted.
I don't actually understand what the problem is, and without code, it's impossible to say what you might be doing wrong. Generally speaking, you shouldn't have any issues since you're using view models.
A view model is, of course, not saved directly to the database, so it has to be mapped over to an actual entity class that you will be saving. In the case of an edit, you should retrieve all relevant entities from the database, map any posted values onto the appropriate properties on those entities, and then save those entities back to the database. If you're doing this, presumably, the customer model should already contain the Surname and other required properties, and you'd only be modifying/setting the address properties.
If you're doing a create, then, simply you can't just take address information. You need the name as well, so for this, you'd need to pass a view model that contains at least all required fields, such that you have all the information you need to actually save the entity.
If you're trying to do a multi-step form, where you collect all the information over multiple posts, then you simply must persist the posted data somewhere other than the database until you have enough of it to actually save an entity to the database. This is usually accomplished via Session/TempData.

How to make single controller for two database classes - MVC3

I have two database classes as defined below:
public class TopDate
{
[Key]
public int DateId { get; set; }
public DateTime Date { get; set; }
}
public class TopSong
{
[Key]
public int SongId { get; set; }
public string Title { get; set; }
public int DateId { get; set; }
}
where DateId is foreign key to TopSong
I am creating a controller through which i can create, delete or edit these database values.
When i right click on controller class and add controller i can only select one of the two classes defined above. Is there a way to make 1 controller to handle database updates to both these tables on one page?
Error Image:
Your controller should not be dealing directly with domain objects (meaning those things that are directly associated with your database). Create a ViewModel that contains the properties that you need, use your service layer to populate the ViewModel and your controller will use that as the Model for its base. An example of your ViewModel could be something like the following given your description above:
public class MusicViewModel
{
public int SongId {get;set;}
public string Title {get;set;}
public IEnumerable<DateTime> TopDates {get;set;}
}
This view model would contain a list of all dates that a specific song was a Top Song.
The objects you showing (code) are database classes (so called domain objects).
What you need to do is to define a view model, a standard ASP MVC practice:
you define a class, that is tailored for specific view and only containing data relevant to that particular view. So you will have a view model for a view that will create a song, another that will update it etc.
Actually situation you describing is classical situation to use view models. Using domain objects in the views, however, is really really bad practice and prone to more problems than you want to deal with.
Hope this helps.

Ignoring properties when serializing

I'm pulling my hair out on this one.
I am trying to implement a multi-step wizard, and i'm using the Html.Serialize html helper in MVC3 Futures. This works well, except one of the properties in my model is a SelectList. I don't want this property serialized (and it blows up when it tries anyways).
I can't use [NonSerialized] because that only works on fields, not properties. I've even tried some of the other normal ways such as [XmlIgnore] (which I didn't think would work anyways).
Can anyone suggest an attribute that will ignore a property in a model when using Html.Serialize?
EDIT:
The error I get when I try to serialize is a InvalidDataContractException. There is this message:
Type 'System.Web.Mvc.SelectList' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. If the type is a collection, consider marking it with the CollectionDataContractAttribute. See the Microsoft .NET Framework documentation for other supported types.
However, if I do this then I have to mark all the members with [DataMember] just to exclude 1 property, which seems kind of stupid.
UPDATE:
A quick example of this is this bit of code (make sure to add reference to System.Runtime.Serialization.dll):
Test.cs
[Serializable]
public class Test
{
public int ID { get; set; }
[IgnoreDataMember]
public SelectList TestList { get; set; }
}
HomeController.cs
public ActionResult About()
{
return View(new Test() { ID = 0, TestList = new SelectList(new [] {""})});
}
Home/About.cshtml
#using Microsoft.Web.Mvc
#model MvcApplication3.Models.Test
#Html.Serialize("Test", Model)
This generates the InvalidDataContractException
public class MyViewModel
{
[IgnoreDataMember]
public SelectList Items { get; set; }
...
}
or simply:
public class MyViewModel
{
public IEnumerable<SelectListItem> Items { get; set; }
...
}

MVC ASP.NET MVC3 AllowHtml Attribute Not Working?

The question is very simple:
Say you have a model called Person
public class Person
{
public int PersonID {get; set;}
public string Name {get; set;}
[AllowHtml] // Allow html in Intro property
public string Intro {get; set;}
[ScaffoldColumn(false)]
public string ComplicatedValue {get; set;}
}
In controller's Create action
[HttpPost]
public ActionResult Create(Person o, FormCollection collection)
{
// whatever code here;
}
If you run it,
input plain text for Intro, no
problem happens.
input html content for Intro, no matter how you set
your configuration file, it will
tells "A potential dangerous ..."
I DO find the reason of this problem.
If you change the function to
public ActionResult Create(Person o) // Get rid of the *FormCollection collection*
{
// whatever code here;
}
This will eliminate the "potential dangerous" error.
But my problem is that for my application, I have to use the secondary parameter FormCollection collection in the Create Action method, because I need to use some other control values and server variable to assign a calculated value to the ComplicatedValue property.
If any expert of ASP.NET MVC3 have met the same problem as me, and found a solution, please kindly let me know.
This forum at this link discusses this issue at length and gives some workarounds.
http://forums.asp.net/p/1621677/4161637.aspx
Here is one solution from that thread that may or may not work for you:
public ActionResult Create(Person o) // Get rid of the *FormCollection collection*
{
FormCollection form = new FormCollection(Request.Unvalidated().Form);
// whatever code here;
}
or my own recommendation:
public ActionResult Create(Person o, int otherControlValue1, int otherControlValue2, ...)
{
o.ComplicatedValue = CalculateComplicatedValue(otherControlValue1, otherControlValue2, ...);
// whatever code here.
}
In my case, I am not using the FormCollection, but it was there so that I had a different footprint on my [HttpPost] method. I did this hack and put in a bogus parameter:
public virtual ActionResult Edit(int id)
{
return View(this.repository.GetById(id));
}
[HttpPost]
public virtual ActionResult Edit(int id, int? bogusID)
{
var d = repository.GetById(id);
if (TryUpdateModel(d))
{
repository.Save();
return RedirectToAction("Index");
}
return View();
}
Might I suggest using a custom model binder instead of pulling the complex data from a FormCollection. Scott Hanselman has a blog post on creating a custom model binder that would serve as a good template. In his post he puts together a DateTimeModelBinder that allows a DateTime property to be set either by a single input containing the date or a pair of inputs containing a date and a time.
have you tried
[Bind(Exclude="ComplicatedValue")]
:
[HttpPost]
public ActionResult Create([Bind(Exclude="ComplicatedValue")]Person o)
{
}
?
with that it allows you to exclude setting ComplicatedValue property on the form and still submit the object as a Person class.
hope that helps

Custom model validation of dependent properties using Data Annotations

Since now I've used the excellent FluentValidation
library to validate my model classes. In web applications I use it in conjunction with the jquery.validate plugin to perform client side validation as well.
One drawback is that much of the validation logic is repeated on the client side and is no longer centralized at a single place.
For this reason I'm looking for an alternative. There are many examples out there showing the usage of data annotations to perform model validation. It looks very promising.
One thing I couldn't find out is how to validate a property that depends on another property value.
Let's take for example the following model:
public class Event
{
[Required]
public DateTime? StartDate { get; set; }
[Required]
public DateTime? EndDate { get; set; }
}
I would like to ensure that EndDate is greater than StartDate. I could write a custom
validation attribute extending ValidationAttribute in order to perform custom validation logic. Unfortunately I couldn't find a way to obtain the
model instance:
public class CustomValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
// value represents the property value on which this attribute is applied
// but how to obtain the object instance to which this property belongs?
return true;
}
}
I found that the CustomValidationAttribute seems to do the job because it has this ValidationContext property that contains the object instance being validated. Unfortunately this attribute has been added only in .NET 4.0. So my question is: can I achieve the same functionality in .NET 3.5 SP1?
UPDATE:
It seems that FluentValidation already supports clientside validation and metadata in ASP.NET MVC 2.
Still it would be good to know though if data annotations could be used to validate dependent properties.
MVC2 comes with a sample "PropertiesMustMatchAttribute" that shows how to get DataAnnotations to work for you and it should work in both .NET 3.5 and .NET 4.0. That sample code looks like this:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute
{
private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";
private readonly object _typeId = new object();
public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
: base(_defaultErrorMessage)
{
OriginalProperty = originalProperty;
ConfirmProperty = confirmProperty;
}
public string ConfirmProperty
{
get;
private set;
}
public string OriginalProperty
{
get;
private set;
}
public override object TypeId
{
get
{
return _typeId;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
OriginalProperty, ConfirmProperty);
}
public override bool IsValid(object value)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
return Object.Equals(originalValue, confirmValue);
}
}
When you use that attribute, rather than put it on a property of your model class, you put it on the class itself:
[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public class ChangePasswordModel
{
public string NewPassword { get; set; }
public string ConfirmPassword { get; set; }
}
When "IsValid" gets called on your custom attribute, the whole model instance is passed to it so you can get the dependent property values that way. You could easily follow this pattern to create a date comparison attribute, or even a more general comparison attribute.
Brad Wilson has a good example on his blog showing how to add the client-side portion of the validation as well, though I'm not sure if that example will work in both .NET 3.5 and .NET 4.0.
I had this very problem and recently open sourced my solution:
http://foolproof.codeplex.com/
Foolproof's solution to the example above would be:
public class Event
{
[Required]
public DateTime? StartDate { get; set; }
[Required]
[GreaterThan("StartDate")]
public DateTime? EndDate { get; set; }
}
Instead of the PropertiesMustMatch the CompareAttribute that can be used in MVC3. According to this link http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1:
public class RegisterModel
{
// skipped
[Required]
[ValidatePasswordLength]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
public string ConfirmPassword { get; set; }
}
CompareAttribute is a new, very useful validator that is not actually
part of
System.ComponentModel.DataAnnotations,
but has been added to the
System.Web.Mvc DLL by the team. Whilst
not particularly well named (the only
comparison it makes is to check for
equality, so perhaps EqualTo would be
more obvious), it is easy to see from
the usage that this validator checks
that the value of one property equals
the value of another property. You can
see from the code, that the attribute
takes in a string property which is
the name of the other property that
you are comparing. The classic usage
of this type of validator is what we
are using it for here: password
confirmation.
It took a little while since your question was asked, but if you still like metadata (at least sometimes), below there is yet another alternative solution, which allows you provide various logical expressions to the attributes:
[Required]
public DateTime? StartDate { get; set; }
[Required]
[AssertThat("StartDate != null && EndDate > StartDate")]
public DateTime? EndDate { get; set; }
It works for server as well as for client side. More details can be found here.
Because the methods of the DataAnnotations of .NET 3.5 don't allow you to supply the actual object validated or a validation context, you will have to do a bit of trickery to accomplish this. I must admit I'm not familiar with ASP.NET MVC, so I can't say how to do this exactly in conjunction with MCV, but you can try using a thread-static value to pass the argument itself. Here is an example with something that might work.
First create some sort of 'object scope' that allows you to pass objects around without having to pass them through the call stack:
public sealed class ContextScope : IDisposable
{
[ThreadStatic]
private static object currentContext;
public ContextScope(object context)
{
currentContext = context;
}
public static object CurrentContext
{
get { return context; }
}
public void Dispose()
{
currentContext = null;
}
}
Next, create your validator to use the ContextScope:
public class CustomValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
Event e = (Event)ObjectContext.CurrentContext;
// validate event here.
}
}
And last but not least, ensure that the object is past around through the ContextScope:
Event eventToValidate = [....];
using (var scope new ContextScope(eventToValidate))
{
DataAnnotations.Validator.Validate(eventToValidate);
}
Is this useful?

Resources