Prevent ModelState.IsValid from validating attached entities? - asp.net-mvc

Is there a way to override ModelState.IsValid?
Some of the entities to be validated are just attached entities, so all the fields except the ID are not to be validate as the entity is in state Unchanged.
Is there a way to do this?
Has anyone faced this issue before?
Update
Say I have the following action:
[HttpPost]
public ActionResult SaveEntity(MyEntity entity)
{
var isValid = ModelState.IsValid; //false
}
Since the model validates all properties and all descendant properties of entity, there has to be a way to check on each entity of those descendants, whether it's attached to the context, and if it is, remove error from ModelState, something like the following:
public ActionResult TryValidateDetachedModel(MyEntity entity, DbContext context)
{
foreach (var ms in ModelState.Where(ms => ms.Value.Errors.Any()).ToArray())
// should iterate over something like GetAllEntityTypesMetadata()
{
var entity = GetEntityFromMetadata(ms);
if (context.Entry(entity).State == EntityState.Unchanged)
{
ms.Value.Errors.Clear();
}
}
}
What I'm trying to do in the above pseudo code is to check the entities in the validation chain, and if one of them is attached as Unchanged, skip validation / remove its errors.
Right now I have to do it hard-coded manually by checking ModelState.Key, I'm looking for a more generic and efficient way.

To clear all errors use next
ModelState.Clear();
regards

Here's what I do to ensure the validation only applies to the current entity:
foreach (var key in ModelState.Keys)
if (key.Split('.').Length > 2)
ModelState[key].Errors.Clear();
if (!ModelState.IsValid)
return BadRequest(ModelState);
The check for the occurrences of . means: if the modelstate key is something like currentDTO.relatedDTO.field then that validation error is ignored (cleared). If it's just id or currentDTO.validateThisField, then it doesn't get cleared.

Related

Error copying a record from one entity to another using Entity Framework and Automapper

I am trying to move a record from one table into another matching (almost) table using EF5, MVC, and Automapper.
This code is what I am using:
In My Global Application_Start
//Create Map and manually map StatusCode to Status
Mapper.CreateMap<InstitutionStaging, InstitutionStaging_Archive>()
.ForMember(dest => dest.Status,o =>o.MapFrom(src=>src.StatusCode));
In my Controller
private MyContext db = new MyContext();
Public ActionResult ArchiveMe(int id = 0){
var institutionstaging = db.InstitutionStagings.Find(id);
if (institutionstaging == null)
{
return HttpNotFound();
}
if (ModelState.IsValid)
{
var institutionArchive = Mapper.Map<InstitutionStaging, InstitutionStaging_Archive>(institutionstaging);
//Set Archive date to now.
institutionArchive.ArchiveDate = DateTime.Now;
//Error happens on the next line
db.InstitutionStaging_Archives.Add(institutionArchive);
db.InstitutionStagings.Remove(institutionstaging);
db.Entry(institutionArchive).State = EntityState.Added;
//Commit the changes
var result = db.SaveChanges();
}
}
When it hits the line marked "Error happens here==>" I get the following error message.
{"The entity type InstitutionStaging_Archive is not part of the model for the current context."}
The MyContext contains DbSets for both InstitutionStaging and InstitutionStaging_Archive.
Any idea what is happening?
TIA
J
This error isn't typically a problem with AutoMapper, but rather a problem with your Entity Framework model setup.
It can be because you are using the wrong connection string, or it can be because you don't have the model mapped correctly.
Since we don't know what your model is, what your database looks like, or how your mappings are.. can't help much beyond that.
To prove it to yourself, just comment out the automapper stuff and do it by hand, and I'm pretty sure you'll get the same error.

InvalidOperationException when using updatemodel with EF4.3.1

When I update my model I get an error on a child relation which I also try to update.
My model, say Order has a releationship with OrderItem. In my view I have the details of the order together with an editortemplate for the orderitems. When I update the data the link to Order is null but the orderid is filled, so it should be able to link it, TryUpdateModel returns true, the save however fails with:
InvalidOperationException: 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.]
My update method:
public ActionResult ChangeOrder(Order model)
{
var order = this.orderRepository.GetOrder(model.OrderId);
if (ModelState.IsValid)
{
var success = this.TryUpdateModel(order);
}
this.orderRepository.Save();
return this.View(order);
}
I tried all solutions I saw on SO and other sources, none succeeded.
I use .Net MVC 3, EF 4.3.1 together with DBContext.
There are a number of code smells here, which I'll try to be elegant with when correcting :)
I can only assume that "Order" is your EF entity? If so, I would highly recommend keeping it separate from the view by creating a view model for your form and copying the data in to it. Your view model should really only contain properties that your form will be using or manipulating.
I also presume orderRepository.GetOrder() is a data layer call that retrieves an order from a data store?
You are also declaring potentially unused variables. "var order =" will be loaded even if your model is invalid, and "var success =" is never used.
TryUpdateModel and UpdateModel aren't very robust for real-world programming. I'm not entirely convinced they should be there at all, if I'm honest. I generally use a more abstracted approach, such as the service / factory pattern. It's more work, but gives you a lot more control.
In your case, I would recommend the following pattern. There's minimal abstraction, but it still gives you more control than using TryUpdateModel / UpdateModel:
public ActionResult ChangeOrder(OrderViewModel model) {
if(ModelState.IsValid) {
// Retrieve original order
var order = orderRepository.GetOrder(model.OrderId);
// Update primitive properties
order.Property1 = model.Property1;
order.Property2 = model.Property2;
order.Property3 = model.Property3;
order.Property4 = model.Property4;
// Update collections manually
order.Collection1 = model.Collection1.Select(x => new Collection1Item {
Prop1 = x.Prop1,
Prop2 = x.Prop2
});
try {
// Save to repository
orderRepository.SaveOrder(order);
} catch (Exception ex) {
ModelState.AddModelError("", ex.Message);
return View(model);
}
return RedirectToAction("SuccessAction");
}
return View(model);
}
Not ideal, but it should serve you a bit better...
I refer you to this post, which is similar.
I assume that the user can perform the following actions in your view:
Modify order (header) data
Delete an existing order item
Modify order item data
Add a new order item
To do a correct update of the changed object graph (order + list of order items) you need to deal with all four cases. TryUpdateModel won't be able to perform a correct update of the object graph in the database.
I write the following code directly using a context. You can abstract the use of the context away into your repository. Make sure that you use the same context instance in every repository that is involved in the following code.
public ActionResult ChangeOrder(Order model)
{
if (ModelState.IsValid)
{
// load the order from DB INCLUDING the current order items in the DB
var orderInDB = context.Orders.Include(o => o.OrderItems)
.Single(o => o.OrderId == model.OrderId);
// (1) Update modified order header properties
context.Entry(orderInDB).CurrentValues.SetValues(model);
// (2) Delete the order items from the DB
// that have been removed in the view
foreach (var item in orderInDB.OrderItems.ToList())
{
if (!model.OrderItems.Any(oi => oi.OrderItemId == item.OrderItemId))
context.OrderItems.Remove(item);
// Omitting this call "Remove from context/DB" causes
// the exception you are having
}
foreach (var item in model.OrderItems)
{
var orderItem = orderInDB.OrderItems
.SingleOrDefault(oi => oi.OrderItemId == item.OrderItemId);
if (orderItem != null)
{
// (3) Existing order item: Update modified item properties
context.Entry(orderItem).CurrentValues.SetValues(item);
}
else
{
// (4) New order item: Add it
orderInDB.OrderItems.Add(item);
}
}
context.SaveChanges();
return RedirectToAction("Index"); // or some other view
}
return View(model);
}

Using Data Annotations with similar models and the same view to have different validation

I have two separate classes derived from the same interface, but have different validation/data annotations assigned. The requirement is that the same data needs to be collected, but on one screen nothing is required (a save screen), but on the other there are some required fields (a submit/finalize screen.)
I've made a PartialView that is to be used in two separate View, one for save, one for final submit.
I've tried using the parent Interface as the View's model, however my validators don't fire (as I expect, I'm guessing that because the Interface itself doesn't have any annotations, nothing will fire.) Is there a way to have the page dynamically choose one class or the other depending on which page I'm using instead of the Interface?
As a side-note, this is being done in ASP.net MVC 3 with Razor.
You can achieve what you want with one class, and a little lateral thinking.
First, create your class, with the validation baked in. Next, create a custom ModelValidatorProvider inheriting from DataAnnotationsModelValidatorProvider, like so:
public class MyMetadataValidatorProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
var vals = base.GetValidators(metadata, context, attributes);
// check to see if any keys have been inserted
if (context.Controller.ViewData.Keys.Count > 0)
{
// check if we have a key named "NoValidate" with a value of true
// do not return the validtors if we do
if ((bool)context.Controller.ViewData.FirstOrDefault(k => k.Key == "NoValidate").Value)
{
// we do not want to return our validators, return an empty list
return new List<ModelValidator>();
}
}
else
{
// check if the form has a key named "NoValidate" with a value of true
// do not return the validtors if we do
if (context.HttpContext.Request.Form["NoValidate"].ToLowerInvariant() == "true")
{
// we do not want to return our validators, return an empty list
return new List<ModelValidator>();
}
}
// we want to return our validators
return vals;
}
}
Next, register the custom ModelValidatorProvider in Application_Start in Global.asax.cs, like so:
ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new MyMetadataValidatorProvider());
Then, add the following to your view (this will govern whether the validators are returned when the form is POSTed):
#Html.Hidden("NoValidate", ViewData.FirstOrDefault(k => k.Key == "NoValidate").Value)
Finally, add actions like the following:
public ActionResult Index()
{
var model = new MyModel();
// this will set validation to appear
ViewData.Add("NoValidate", false);
// this will suppress validation
ViewData.Add("NoValidate", true);
return View(model);
}
[HttpPost]
public ActionResult Index(MyModel model)
{
// we DO want validation, so let's test for it in addition
// to testing if the ModelState is valid
if (Request.Form["NoValidate"].ToLowerInvariant() != "true" && ModelState.IsValid)
{
ModelState.Clear();
var newmodel = new MyModel();
ViewData.Add("NoValidate", true);
return View(newmodel);
}
ViewData.Add("NoValidate", false);
return View(model);
}
Note that you can control whether the validation appears in your GET action by setting the NoValidate key in ViewData as you want. On the POST, the validation is governed by the form value for NoValidate.
IMPORTANT NOTE: In your action which requires validation, you need to add a test to confirm that the Form does not have the key NoValidate, or its value is not True, in order to enforce that a user cannot avoid the validation.
UPDATE
At first, I had validation only appearing when certain conditions were true. Idecided this was a BAD IDEA, so now validation will only be suppressed if the conditions are true.
Each view should be strongly typed to a separate view model. Each viewmodel then has the validation logic on it (annotations) or inherits from a base that has the required validation on it.
Any logic that cannot be inherited is simply set on your ViewModel itself. If its a small moderl I would consider just copy/paste and two separate viewmodels with their own set of attributes.
You can use AutoMapper to easily map between some concrete object that implements your interface and your ViewModels.
Could you use one class? You can create a filter that allows you to manage the validation errors for an action. In your case you can add an attribute to the Save action and ignore the required errors, but the validations will run for the submit/finalize action. This example will discard all the errors.
public class DontValidateEmailAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext) {
var modelState = filterContext.Controller.ViewData.ModelState;
var incomingValues = filterContext.Controller.ValueProvider;
foreach (var key in modelState.Keys)
modelState[key].Errors.Clear();
}
}
I learnt this technique from Steve Sanderson's Pro ASP NET MVC 3. He uses the technique to validate a model that has required fields but the data entry is a multistep wizard. If the value has not been returned in the form post, he removes the errors for that property.

Iterating over an unknown IQueryable's properties?

Forgive me if this has been asked before; I couldn't find anything close after a few searches:
I'm trying to write an ActionFilter in MVC that will "intercept" an IQueryable and nullify all the parent-child relationships at runtime. I'm doing this because Linq does not serialize objects properly if they have parent-child relationships (it throws a circular reference error because the parent refers to the child, which refers back to the parent and so on), and I need the object serialized to Json for an Ajax call. I have tried marking the child relationship in the DBML file with a privacy status of internal, and while this fixes the serialization problem, it also hides the child members from the view engine when the page renders, causing another error to be thrown. So, by fixing one problem, I cause another.
The only thing that fixes both problems is to manually set the child members to null just before returning the serialization, but I'm trying to avoid doing that because it's cumbersome, not reusable, etc. I'd rather use an ActionFilter to inspect the IQueryable that is being serialized and nullify any members with a Type of EntitySet (how Foreign Keys/Associations are represented). However, I don't have much experience with Reflection and can't find any examples that illustrate how to do something like this. So... is this possible with Reflection? Is there a better way to accomplish the same thing? I'll post the relevant code tomorrow when I'm back at my work computer.
Thanks,
Daniel
As promised, the code:
[GridAction]
public ActionResult _GetGrid()
{
IQueryable<Form> result = formRepository.GetAll();
foreach (Form f in result)
{
f.LineItems = null;
f.Notes = null;
}
return View(new GridModel<Form> { Data = result });
}
An added wrinkle is that I'm using the new Telerik MVC Extensions, so I'm not actually serializing the Json myself -- I'm just returning the IQueryable in an IGridModel, and the action filter [GridAction] does the rest.
So, just in case anyone's curious, here's how I finally solved this problem: I modified Damien Guard's T4 template to include the attribute [ScriptIgnore] above entities of type Association. This lets the JSON serializer know to not bother serializing these, thus preventing the circular reference problem I was getting. The generated code ends up looking like this:
private EntitySet<LineItem> _LineItems;
[ScriptIgnore]
[Association(Name=#"Form_LineItem", Storage=#"_LineItems", ThisKey=#"Id", OtherKey=#"FormId")]
public EntitySet<LineItem> LineItems
{
get {
return _LineItems;
}
set {
_LineItems.Assign(value);
}
}
This fixes the serialization problem I was having without disabling the use of child tables through LINQ. The grid action on the controller ends up looking like this:
[GridAction]
public ActionResult _GetGrid()
{
return View(new GridModel<Form> { Data = formRepository.GetAll() });
}
There are two options, one is to ignore those properties during serialization using [XmlIgnore]. The other one is to nullify the properties using reflection.
Ignore in serialization, simple usage sample that shows how to use default value in serialization:
[Serializable]
public class MyClass
{
[XmlIgnore]
public int IgnoredVal { get; set; }
public int Val { get; set; }
}
public void UsageSample()
{
var xmlSerializer = new XmlSerializer(typeof(MyClass));
var memoryStream = new MemoryStream();
var toSerialize = new MyClass { IgnoredVal = 1, Val = 2 };
xmlSerializer.Serialize(memoryStream, toSerialize);
memoryStream.Position = 0;
var deserialize = (MyClass)xmlSerializer.Deserialize(memoryStream);
Assert.AreEqual(0, deserialize.IgnoredVal);
Assert.AreEqual(2, deserialize.Val);
}
Nullify with reflection, code sample:
public void NullifyEntitySetProperties(object obj)
{
var entitySetProperties = obj.GetType().GetProperties()
.Where(property => property.PropertyType == typeof(EntitySet));
foreach (var property in entitySetProperties)
{
property.SetValue(obj, null, null);
}
}
In my opinion, if the first option can be done used in your code it's better. This option is more direct and economic.

Modelbinding database entities in ASPNET MVC

I'm having trouble trying to think what the best way is to recreate a database object in a controller Action.
I want to make use of ModelBinders so in my action I have access to the object via a parameter, rather than having to repeat code to get an object from the database based on an identifier parameter. So I was thinking of having a ModelBinder that performs a call to the dataaccess layer to obtain the original object (or creates a new one if it doesn't exist in the database), then binds any properties to the database object to update it. However I've read that the ModelBinders shouldn't make database queries (first comment of this article).
If the ModelBinder shouldn't perform a database query (so just using the DefaultModelBinder) then what about database objects that have properties that are other db objects? These would never get assigned.
Saving an object after the user has edited it (1 or 2 properties are editable in the view) the ModelBinded object would be missing data, so saving it as it is would result in data in the database being overwritten with invalid values, or NOT-NULL constraints failing.
So, whats the best way to get an object in a controller action from the database bound with the form data posted back from the view?
Note im using NHibernate.
I get the model object from the database, then use UpdateModel (or TryUpdateModel) on the object to update values from the form parameters.
public ActionResult Update( int id )
{
DataContext dc = new DataContext();
MyModel model = dc.MyModels.Where( m => m.ID == id ).SingleOrDefault();
string[] whitelist = new string[] { "Name", "Property1", "Property2" };
if (!TryUpdateModel( model, whitelist )) {
... model error handling...
return View("Edit");
}
ViewData.Model = model;
return View("Show");
}
Unfortunately you don't have control over the construction of the model binder, so you can't inject any repository implementation.
You can reach out directly into a service locator to pull in your repository & fetch the item:
public class ProductBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext, Type modelType)
{
if(modelType != typeof(Product))
return null;
var form = controllerContext.HttpContext.Request.Form;
int id = Int32.Parse(form["Id"]);
if(id == 0)
return base.CreateModel(controllerContext, bindingContext, modelType);
IProductRepository repository = ServiceLocator.Resolve<IProductRepository>();
return repository.Fetch(id);
}
}
You might even make this work for all of your entities if you can use a base class or interface that provides the Id of the class.
You'll have to set this up in Global.asax:
ModelBinders.Binders.Add(typeof(Product), new ProductBinder());
and then you can do this:
public ActionResult Save([Bind] Product product)
{
....
_repository.Save(product);
}
Let me first state that I don't recommend to access database from ModelBinders, as from perspective of Separation Of Concern ModelBinders should only be responsible of interpretting client request, obviously database is not.
If you dont want to repeat your self (DRY), use repositories/services
However if u really want to do it like that, then
In global.asax.cs Register a custom MyModelBinderProvider to MVC
ModelBinderProviders.BinderProviders.Add(new EntityModelBinderProvider
{
ConnectionString = "my connection string"
));
Cunstruct the custom ModelBinderProvider to contain database settings
public class EntityBinderProvider: IModelBinderProvider
{
public string ConnectionString { get; set; }
public IModelBinder GetBinder(Type modelType)
{
if (Is known entity)
return new EntityBinder(ConnectionString);
else
return null;
}
}
Follow further instructions from Ben Scheirman
You don't actually have to hit the database. Simply setting the Id of the objects will be enough to set the relationship up, but watch your cascades. Make sure your cascde settings won't update the related object as it will clear the values.

Resources