Entity Framework and MVC 3: The relationship could not be changed because one or more of the foreign-key properties is non-nullable - asp.net

I have been trying to use one View for updating an object and all its child collections (based on one-to-many relationships in an SQL Server database with an Entity Framework model).
It was suggested I should use AutoMapper, and I tried that and got it to work. (see Trying to use AutoMapper for model with child collections, getting null error in Asp.Net MVC 3 ).
But the solution is really hard to maintain. And when I try the simple one I had to begin with, using an entity object directly as the model (a "Consultant" object, the parent of all the child collections), I am able to get all the correct changed data back in the POST, and I can use UpdateModel to get them, including child collections. Simple. Granted, UpdateModel only worked after creating a custom model binder from a tip here at SO:
From my custom model binder:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
return base.BindModel(controllerContext, bindingContext);
}
protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
{
ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
propertyMetadata.Model = value;
string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyMetadata.PropertyName);
// Try to set a value into the property unless we know it will fail (read-only
// properties and null values with non-nullable types)
if (!propertyDescriptor.IsReadOnly)
{
try
{
if (value == null)
{
propertyDescriptor.SetValue(bindingContext.Model, value);
}
else
{
Type valueType = value.GetType();
if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(EntityCollection<>))
{
IListSource ls = (IListSource)propertyDescriptor.GetValue(bindingContext.Model);
IList list = ls.GetList();
foreach (var item in (IEnumerable)value)
{
list.Add(item);
}
}
else
{
propertyDescriptor.SetValue(bindingContext.Model, value);
}
}
}
catch (Exception ex)
{
// Only add if we're not already invalid
if (bindingContext.ModelState.IsValidField(modelStateKey))
{
bindingContext.ModelState.AddModelError(modelStateKey, ex);
}
}
}
}
Here's my simple Edit POST method:
[HttpPost]
[ValidateInput(false)] //To allow HTML in description box
public ActionResult Edit(int id, FormCollection collection)
{
Consultant consultant = _repository.GetConsultant(id);
UpdateModel(consultant);
_repository.Save();
return RedirectToAction("Index");
}
But after that UpdateModel worked. The problem is, at the next stage, when trying to call SaveChanges on the context, that fails. I'm getting this error:
The operation failed: The relationship
could not be changed because one or
more of the foreign-key properties is
non-nullable. When a change is made to
a relationship, the related
foreign-key property is set to a null
value. If the foreign-key does not
support null values, a new
relationship must be defined, the
foreign-key property must be assigned
another non-null value, or the
unrelated object must be deleted.
I don't understand what is wrong. I'm seeing all the correct values in the Consultant object posted, I just can't save it to database. The route of AutoMapper in this case (although an interesting tool) is not working well, it's complicating my code immensely and making the application, which should be rather simple, a nightmare to maintain.
Can anyone offer any insight into why I'm getting this error and how to overcome it?
UPDATE:
Reading some posts here, I found one that seemed slightly related: How to update model in the database, from asp.net MVC2, using Entity Framework? . I don't know if it relates to this, but when I inspected the Consultant object after POST it seems this object itself has entitykey, but the individual items in a collection do not (EntityKeySet = null). Each item however does have the correct id. I don't pretend to understand any of this with the EntityKey, so please explain if it has any bearings on my issue, and if so, how to resolve it...
UPDATE 2:
I thought of something that might have something to do with my problems: The View is using a technique described by Steven Sanderson (see http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ ), and when debugging it seems to me as if UpdateModel has trouble matching the items in a collection in the View with the ones in the actual Consultant object. I'm wondering if this has to do with the indexing in this technique. Here's the helper from that code (I can't follow it very well myself, but it uses a Guid to create indexes, which might be the problem):
public static class HtmlPrefixScopeExtensions
{
private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
string key = idsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
But then again, I wouldn't have thought this should be the problem since the hidden input contains the id in the value attribute, and I thought UpdateModel just looked at the name of the field to get Programs (the collection) and Name (the property), and then the value to the the id...? And then again there's seems to be some mismatch during update. Anyway, here's the generated html from FireBug also:
<td>
<input type="hidden" value="1" name="Programs[cabac7d3-855f-45d8-81b8-c31fcaa8bd3d].Id" id="Programs_cabac7d3-855f-45d8-81b8-c31fcaa8bd3d__Id" data-val-required="The Id field is required." data-val-number="The field Id must be a number." data-val="true">
<input type="text" value="Visual Studio" name="Programs[cabac7d3-855f-45d8-81b8-c31fcaa8bd3d].Name" id="Programs_cabac7d3-855f-45d8-81b8-c31fcaa8bd3d__Name">
<span data-valmsg-replace="true" data-valmsg-for="Programs[cabac7d3-855f-45d8-81b8-c31fcaa8bd3d].Name" class="field-validation-valid"></span>
</td>
Anyone know if this is the problem? And if so, how can I work around it to be able to easily update the collections with UpdateModel? (While still being able to add or remove items in the View before POST, which was the purpose of this technique to begin with).

It looks like there is a Parent entity that has a one to many relationship with your Consultant entity. When you change an attribute of the Consultant entity that is used as the ForeignKey for that relationship, Entity Framework sets the relevant field in the Parent entity to null to decouple the relationship. When that field is not nullable you'll get this error. Actually that error definition is surprisingly good, I've seen this problem with far more cryptic errors.
So, I recommend that you check the parent entity in the database, and proceed to a remedy from there (if you can change it to nullable all is well, if it is part of a different constraint -pk or suchlike- you'll have to fiddle with your object models). I'd ask you to post your entity models, but the chunk of text is intimidating as it is.

I think the error you are getting is related to: EF 4: Removing child object from collection does not delete it - why? You have created an orphan somewhere.

Yes it is related to HtmlPrefixScopeExtensions, but only because you are using Mvc Futures model binders.
In global.asax.cs comment out the line
Microsoft.Web.Mvc.ModelBinding.ModelBinderConfig.Initialize();
and retry: it will work ok !
The problem happens because the MVC futures model binder does not handle correctly this case. It converts ok the form data into your model when you submit the form, but it has a problem when filling the ModelState object when you use HtmlPrefixScopeExtensions to generate non incremental ids.
The model itself is correctly created from the form data. The problem lies inside ModelState which contains only the last value of the collection instead of all elements of the collection.
The strongly typed helper method - which renders the list - only select items which are in your Model property list AND in the matching ModelState entry which is converted into a list. So because there is only one item in the matching ModelState entry other list items get deselected.
This method called by the strongly typed helper code:
htmlHelper.GetModelStateValue(fullName, typeof(string[]))
returns only the last element of the list, because ModelState["Programs[cabac7d3-855f-45d8-81b8-c31fcaa8bd3d].List"].Value contains only the last element of the list.
This is a bug (or non supported scenario) in MVC3 Futures extensible model binders.

Related

Magnolia - Cross field validation

We have below requirement.
Field 1 : comboBoxField and it is mandatory.
Field 2: comboBoxField
Case 1. If editor choose field 1 value is "value1", then field 2 is mandatory
Case 2: if editor choose Field 1 value is "value2", then filed 2 is optional.
How to do this validation either in form/field validation before commit action.
Or how do we validate in commit action.
Any lead will help me a lot. I am stuck in this issue for longtime.
I tried with cross-field, dynamic filed , switchable and composite field as suggested in documentation, but I got null pointer exceptions. If you know the solution please share code snippet also.
I also suffered with this type of validation. The problem is in the isolation of the fields. I couldn't find a way to validate one field depending on another field value.
Anyway I found a workaround solution. I aggregate two dependent fields with complex field and create a validator for it.
Here's my code for the Magnolia 5.7 and vaadin validator (it's deprecated 7th version, nevertheless the logic will be the same with the 8th version).
public class CompanyRequiredFieldsValidator implements Validator {
private final ConfiguredFieldValidatorDefinition definition;
public CompanyRequiredFieldsValidator(ConfiguredFieldValidatorDefinition definition) {
this.definition = definition;
}
#Override
public void validate(Object value) throws InvalidValueException {
Optional<String> companyName = getString(value, "companyName");
if (companyName.isPresent()) {
getString(value, "companyAddress")
.orElseThrow(() -> new InvalidValueException(definition.getErrorMessage()));
}
}
private Optional<String> getString(Object value, String property) {
return Optional.of(value)
.filter(Item.class::isInstance).map(Item.class::cast)
.map(_v -> _v.getItemProperty(property))
.map(Property::getValue)
.filter(String.class::isInstance).map(String.class::cast)
.filter(StringUtils::isNotEmpty);
}
}
For complex fields validated value has com.vaadin.v7.data.Item type, so it's possible to get all properties from it.
The only problem, that the error message does not highlight the inner field, only whole complex field.

Default value for ViewModel

I'm making a simple Search page in MVC with some filters in it. The filters are represented by properties in my ViewModel. My ViewModel is binded to a GET form in the cshtml so my filter will appears in the querystrings and the user will be able to bookmark his search.
What I want to do is to assign a default value to some of my filters.
My (simplified) ViewModel :
public class SearchViewModel
{
//Filter I want to set a default value to
public OrganizationType? OrganizationType {get; set;}
//Results of the search
public IEnumerable<ItemViewModel> Items {get; set;}
}
I'd like to set a default value for OrganizationType. I can't simply set it in the constructor of SearchViewModel because it depends on the current user :
public void InitViewModel(SearchViewModel vm)
{
vm.OrganizationType = _someLogic.GetDefaultValue(_currentUser);
}
First solution was simply to check if OrganizationType is null, then assign a default value :
public ActionResult Search(SearchViewModel vm)
{
if(vm.OrganizationType == null)
vm.OrganizationType = _someLogic.GetDefaultValue(_currentUser);
return View(vm);
}
But this solution doesn't work as a null value corresponds to an empty filter and it's a choice that the user can make. So I can't override it.
The second solution I tried was to specify that the default value of the controller should be null in the Search action :
public ActionResult Search(SearchViewModel vm = null)
{
if (vm == null)
{
vm = new SearchViewModel();
InitViewModel(vm);
}
...
return View(vm);
}
But in practice, the variable vm is never null, so the default values are never setted.
I also tried having two Action, one wihout a ViewModel where I instanciate a new ViewModel with the default values and then call the second action :
public ActionResult Search()
{
var vm = new SearchViewModel();
InitViewModel(vm);
//Simply call the second action with the initizalied ViewModel
return Search(vm);
}
public ActionResult Search(SearchViewModel vm)
{
...
return View(vm);
}
But it doesn't work because there is now an ambiguity between the two action, and asp.net doesn't know which one to choose.
So in summary, I'd like to find a way to set a default value for a ViewModel, without setting it in the constructor and overriding user choices.
Another way to say it, how can I distinguish an "empty" ViewModel from one where some values are binded from the form.
Any idea ?
Ok I think I found a solution to my own problem...
I can use the ModelState property of the controler to check it the ViewModel is empty or was binded from the form :
public ActionResult Search(SearchViewModel vm = null)
{
if (ModelState.Count == 0)
{
InitViewModel(vm);
}
...
return View(vm);
}
So if ModelState.Count equals to 0 it means that user didn't change any filters. So the form is empty and we can bind our default values. As soon as the user will change one of the filters or submit the request, the ModelState.Count will be greater than 0 so we shouldn't set the default value. Otherwise we would override an user choice.
The logic of what you're doing is a little iffy. Generally speaking, if a value is nullable then null is the default value. However, it seems that you're trying to make a distinction here between whether the value is null because it's not set or null because the user explicitly set it to null. This type of semantic variance is usually a bad idea. If null has a meaning, then it should always carry that meaning. Otherwise, your code becomes more confusing and bugs are generally introduced as a result.
That said, you can't count on ModelState having no items. I've honestly never played around with ModelState enough in scenarios where there's not post data, but it's possible there's some scenario where there's no post data and yet ModelState may have items. Even if there isn't, this is an implementation detail. What if Microsoft does an update that adds items to ModelState in situations where it previously had none. Then, your code breaks with no obvious reason why.
The only thing you can really count on here is whether the request method is GET or POST. In the GET version of your action, you can reasonably assume that the user has made no modifications. Therefore, in this scenario, you can simply set the value to whatever you like without concern.
In the POST version of your action, the user has made some sort of modification. However, at this point, there is no way to distinguish any more whether the value is null because it is or because the user explicitly wanted it to be. Therefore, you must respect the value as-is.

Required attribute in Buddy Class not working with Entity Framework 5 and ASP.NET

My database has a field that is not nullable, but can contain an empty string. When I try to save the record using connection.SaveChanges(), I get an exception saying "The MyField field is required."
I have created a BuddyClass as follows, but I still get the message:
namespace MyNamespace {
[MetadataType(typeof(QuesT_Metadata))] public partial class QuesT { }
public class QuesT_Metadata {
[Required(AllowEmptyStrings = true)
public string MyField { get; set; }
}
}
I can use the ErrorMessage attribute to change the message in the error that is thrown, so I know the Buddy Class is working properly, but apparently the Required attribute is not.
I also tried including attribute DisplayFormat(ConvertEmptyStringToNull = false), but got the same result.
I have done this before, and also the first reference below seems to say it should work, so I'm stumped. Can anyone help?
References (Only the first two seem directly relevant, but the others may still be helpful):
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.requiredattribute.allowemptystrings.aspx
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayformatattribute.convertemptystringtonull.aspx
How to make an Entity Framework property NOT NULL, but not required in form submission
Data annotation attributes not working using buddy class metadata in an MVC app
Data validation with custom attributes (AttributeTargets.Class) on EF buddy classes
I'm in the same boat here... I've got several instances of your exact behavior which are working just fine....
and now, one particular field won't behave...
But, if I leave off the "Required(AllowEmptyStrings = true)" attribute, things go back to working just fine. Which, I guess is what I'm really looking for, as the attribute in question doesn't really make all that much sense (Required, but allow the user not to answer).....
For me the bigger question is why it sometimes works, and sometimes doesn't ?
But at a miminum, deleting the one like of code should solve the problem for you.
I have worked around this by trapping the error:
public static class ExtensionMethods {
public static void SaveChangesWithEmptyStrings(this DbContext context) {
try {
context.SaveChanges();
}
catch (DbEntityValidationException ex) {
foreach (DbEntityValidationResult result in ex.EntityValidationErrors)
foreach (DbValidationError error in result.ValidationErrors) {
Type t = result.Entry.Entity.GetType();
PropertyInfo pi = t.GetProperty(error.PropertyName);
pi.SetValue(result.Entry.Entity, "");
}
context.SaveChanges(); // Try again
}
}
}

Updating object with related entities from detached state

When I query from the entity framework I always query in a detached state so that the records retrieved can be stored in cache for subsequent requests.
Right now I have a form that the user can edit which contains a parent record, and then two lists of parent records.
When the data is POSTed to the server, I take my view models and map them into the entity framework objects using AutoMapper. The data looks fine; AutoMapper is mapping the data correctly.
When I attach the object so that I can update it, an exception is thrown: A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship.
public static void UpdateOrder(ShippingOrder shippingOrder) {
using (OrderEntity orderContext = new OrderEntity()) {
//Exception happens here
orderContext.ShippingOrders.Attach(shippingOrder);
//Update the order itself; mark the order has being modified so the EF will update it.
orderContext.ObjectStateManager.ChangeObjectState(shippingOrder, System.Data.EntityState.Modified);
//Perform the update.
orderContext.SaveChanges();
}
}
The EntityFramework (EF) seems to think that my keys aren't lining up, but I'm not sure what isn't correct. The foreign key property does have the correct value, so I'm not sure what it's checking. Does anyone have any ideas?
You might try something like this:
ShippingOrder existingShippingOrder = orderContext.ShippingOrders.Find(shippingOrder.ID);
orderContext.Entry(existingShippingOrder ).CurrentValues.SetValues(shippingOrder);
Instead of
orderContext.ObjectStateManager.ChangeObjectState(shippingOrder, System.Data.EntityState.Modified);
try this
orderContext.Entry(ShippingOrder).State = EntityState.Modified;
as explained here
Insert or update pattern A common pattern for some applications is to
either Add an entity as new (resulting in a database insert) or Attach
an entity as existing and mark it as modified (resulting in a database
update) depending on the value of the primary key. For example, when
using database generated integer primary keys it is common to treat an
entity with a zero key as new and an entity with a non-zero key as
existing. This pattern can be achieved by setting the entity state
based on a check of the primary key value. For example:
public void InsertOrUpdate(DbContext context, Unicorn unicorn)
{
context.Entry(unicorn).State = unicorn.Id == 0 ?
EntityState.Added :
EntityState.Modified;
context.SaveChanges();
}
you can try
public static void UpdateOrder(ShippingOrder shippingOrder) {
using (OrderEntity orderContext = new OrderEntity()) {
orderContext.Entry(shippingOrder).State = shippingOrder.Id==0?
EntityState.Added :
EntityState.Modified;
orderContext.SaveChanges();
}
}
UPDATE:
for ObjectContext class you can try
public static void UpdateOrder(ShippingOrder shippingOrder) {
using (OrderEntity orderContext = new OrderEntity()) {
orderContext.ObjectStateManager.ChangeObjectState(shippingOrder, EntityState.Modified);
orderContext.SaveChanges();
}
}

ASP.NET MVC model binding and validation question

I'm trying to use MVC for a new project after having been around the block with all the samples and tutorials and such. However, I'm having a hard time figuring out where certain things should take place.
As an example, I have an entity called Profile. This entity contains the normal profile type stuff along with a DateOfBirth property that is of type DateTime. On the HTML form, the date of birth field is split into 3 fields. Now, I know I can use a custom model binder to handle this, but what if the date entered is not a valid date? Should I be checking for that in the model binder? Should all my validation go in the model binder? Is it ok to have only a few things validated in the model binder and validate the rest in the controller or the model itself?
Here's the code I have now, but it just doesn't look right to me. Seems dirty or smelly.
namespace WebSite.Models
{
public class ProfileModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
DateTime birthDate;
var form = controllerContext.HttpContext.Request.Form;
var state = controllerContext.Controller.ViewData.ModelState;
var profile = new Profile();
profile.FirstName = form["FirstName"];
profile.LastName = form["LastName"];
profile.Address = form["Address"];
profile.Address2 = form["Address2"];
profile.City = form["City"];
profile.State = form["State"];
profile.Zip = form["Zip"];
profile.Phone = form["Phone"];
profile.Email = form["Email"];
profile.Created = DateTime.UtcNow;
profile.IpAddress = controllerContext.HttpContext.Request.UserHostAddress;
var dateTemp = string.Format("{0}/{1}/{2}",
form["BirthMonth"], form["BirthDay"], form["BirthYear"]);
if (string.IsNullOrEmpty(dateTemp))
state.AddModelError("BirthDate", "Required");
else if (!DateTime.TryParse(dateTemp, out birthDate))
state.AddModelError("BirthDate", "Invalid");
else
profile.BirthDate = birthDate;
return profile;
}
}
}
Building on the sample code above, how would you do the validation message for a 3 part field? In the case above, I'm using a completely separate key that doesn't actually correspond to a field in the form, because I don't want an error message to appear beside all 3 fields. I only want it to appear to the right of the Year field.
I think it is reasonable to do validation in the model binder. As Craig points out, the validation is mostly the property of your business domain, however:
Sometimes your model is just a dumb presentation model, not a business object
There are various mechanisms you can use to surface the validation knowledge into the model binder.
Thomas gives you an exmaple of #1.
An example of #2 is when you declaratively desribe validation knowlege using attributes (like the DataAnnotation attribute [Required]), or inject some business layer validation service into a custom model binder. In these situations the model binder is an ideal place to take care of validation.
That being said, model binding (finding, converting, and shuffling data into an object) and validation (data meets our specifications) are two seperate concerns. You could argue that they should be seperate phases/components/extensibility points, but we have what we have, although the DefaultModelBinder makes some distinction between these two responsibilities. If all you want to do is provide some validation for a specific type of object you can derive from the DefaultModelBinder and override the OnPropertyValidating method for property level validations or OnModelUpdated if you need the holistic view.
Here's the code I have now, but it
just doesn't look right to me. Seems
dirty or smelly.
For your specific code I would try to write a model binder for DateTime only. The default model binder can take care of binding firstname, lastname, etc., and delegate to your custom model binder when it reaches a DateTime property on the Profile. In addition, try using the valueProvider in the bindingContext instead of going directly to the form. These things can give you more flexibility.
More thoughts here: 6 Tips for ASP.NET MVC Model Binding.
Sometimes the model is a view-model, not a domain model. In this case you could benefit from separating those two and design the view model to match your view.
Now you can let the view model validate the input and parse the three fields into a DateTime. Then it can update the domain model:
public ActionResult SomeAction(ViewModel vm)
{
if (vm.IsValid)
{
var dm = repositoryOrSomething.GetDomainModel();
vm.Update(dm);
}
// more code...
}
I had the same exact situation the other day...below is my model binding code. Basically it binds all the DateTime? fields of a model to month/day/year fields from a form (if possible) So, yes, I do add in the validation here, since it does seem appropriate to do so.
public class DateModelBinder : DefaultModelBinder
{
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType == typeof(DateTime?))
{
string DateMonth = _GetDateValue(bindingContext, propertyDescriptor.Name + "Month");
string DateDay = _GetDateValue(bindingContext, propertyDescriptor.Name + "Day");
string DateYear = _GetDateValue(bindingContext, propertyDescriptor.Name + "Year");
// Try to parse the date if we have at least a month, day or year
if (!String.IsNullOrEmpty(DateMonth) || !String.IsNullOrEmpty(DateDay) || !String.IsNullOrEmpty(DateYear))
{
DateTime fullDate;
CultureInfo enUS = new CultureInfo("en-US");
// If we can parse it, set the model property
if (DateTime.TryParse(DateMonth + "/" + DateDay + "/" + DateYear,
enUS,
DateTimeStyles.None, out fullDate))
{
SetProperty(controllerContext, bindingContext, propertyDescriptor, (DateTime?)fullDate);
}
// The date is invalid, so we need to add a model error
else
{
string ModelPropertyName = bindingContext.ModelName;
if(ModelPropertyName != "")
{
ModelPropertyName += ".";
}
ModelPropertyName += propertyDescriptor.Name;
bindingContext.ModelState.AddModelError(ModelPropertyName, "Invalid date supplied for " + propertyDescriptor.Name);
}
}
return;
}
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
// Get a property from binding context
private string _GetDateValue(ModelBindingContext bindingContext, string key)
{
ValueProviderResult valueResult;
bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName + "." + key, out valueResult);
//Didn't work? Try without the prefix if needed...
// && bindingContext.FallbackToEmptyPrefix == true
if (valueResult == null)
{
bindingContext.ValueProvider.TryGetValue(key, out valueResult);
}
if (valueResult == null)
{
return null;
}
return (string)valueResult.ConvertTo(typeof(string));
}
}
Note: I had some problems with bindingContext.FallbackToEmptyPrefix always being false...can't find any useful info on that, but you get the idea.
Validation should be done in multiple places, according to the functionality of each place. For example, if your model binder cannot find the submitted values into a proper DateTime value, then the binder can add a model state error. If, on the other hand, your business logic requires the date to be within a certain range, this would not be appropriate to do and the model binder; it should be in the business logic layer. Controllers can potentially add validation errors as well if, for example, the edit model cannot be transformed into an entity model.
A validation framework such as xVal makes this much simpler.
The Contact Manager sample application on the http://www.asp.net/mvc site has an excellent description of separating out your validation logic into a service layer from your controller and model.
It's well work a read
I tired of creating little small-purpose ViewModels that only touched parts of my mile-wide domain model.
Thus, I cooked up my own method for addressing this. My ViewModel is a typeOf DomainModel, and I use a custom model binder to ensure its identity properties load first - once identity is set - it triggers a DomainModel.Load, and the remainder of the binding activity essentially performs a 'merge'.
Again, when my ViewModel is bound (e.g. on a form POST), after essential fields comprising the ID are set - it immediately loads up the domain model from the database. I just had to come up with a replacment for the DefaultModelBinder. My custom model binder posted here on StackOverflow allows you to control the binding order of the properties.
Once I can guarantee that identity properties are bound, (the internals of my viewmodel listen for the completion of identity setters) I trigger a load of my domain model, as the rest of the properties are bound, they are overwriting, i.e. 'merging' into the loaded domain model.
Basically, I can have all my various razor views, whether they expose 5 form fields or 50 fields of the model.. all submit to a controller action that looks like this (granted, I still make separate actions where needed to do appropriate custom business stuff.. but the point is, my controller actions are focused and succinct)
<HttpPost()>
<Authorize(Roles:="MYCOMPANY\activeDirRoleForEditing")>
Function Edit(<Http.FromBody()> ByVal mergedModel As OrderModel) As ActionResult
'notice: NO loading logic here - it already happened during model binding
'just do the right thing based upon resulting model state
If Me.ModelState.IsValid Then
mergedModel.SaveAndReload("MyServiceWebConfigKey")
ViewBag.SuccessMessage = String.Format("You have successfully edited the order {0}", mergedModel.Id)
Return View("Edit", mergedModel)
Else
ViewBag.ErrorText = String.Format("Order {0} not saved. Check for errors and correct.", mergedModel.Id)
Return View("Edit", mergedModel)
End If
End Function

Resources