Is it possible to auto update only selected properties on an existent entity object without touching the others - asp.net-mvc

Say I have a bunch of boolean properties on my entity class public bool isActive etc. Values which will be manipulated by setting check boxes in a web application. I will ONLY be posting back the one changed name/value pair and the primary key at a time, say { isActive : true , NewsPageID: 34 } and the default model binder will create a NewsPage object with only those two properties set. Now if I run the below code it will not only update the values for the properties that have been set on the NewsPage object created by the model binder but of course also attempt to null all the other non set values for the existent entity object because they are not set on NewsPage object created by the model binder.
Is it possible to somehow tell entity framework not to look at the properties that are set to null and attempt to persist those changes back to the retrieved entity object and hence database ? Perhaps there's some code I can write that will only utilize the non-null values and their property names on the NewsPage object created by model binder and only attempt to update those particular properties ?
[HttpPost]
public PartialViewResult SaveNews(NewsPage Np)
{
Np.ModifyDate = DateTime.Now;
_db.NewsPages.Attach(Np);
_db.ObjectStateManager.ChangeObjectState(Np, System.Data.EntityState.Modified);
_db.SaveChanges();
_db.Dispose();
return PartialView("MonthNewsData");
}
I can of course do something like below, but I have a feeling it's not the optimal solution. Especially considering that I have like 6 boolean properties that I need to set.
[HttpPost]
public PartialViewResult SaveNews(int NewsPageID, bool isActive, bool isOnFrontPage)
{
if (isActive != null) { //Get entity and update this property }
if (isOnFontPage != null) { //Get entity and update this property }
}

API is not strongly typed but you can do it as follows. DbContext API has better support for this.
[HttpPost]
public PartialViewResult SaveNews(NewsPage Np)
{
Np.ModifyDate = DateTime.Now;
_db.NewsPages.Attach(Np);
var entry = _db.ObjectStateManager.GetObjectStateEntry(Np);
var cv = entry.CurrentValues;
if (isActive)
{
cv.SetBoolean(cv.GetOrdinal("isActive"), true);
}
_db.SaveChanges();
_db.Dispose();
return PartialView("MonthNewsData");
}

You can go for two options
Register a custom model binder for that action. In the custom model binder you have to get the complete object from the database and only update the POSTed properties.
Use a view model. Instead of directly having the NewsPage model as the action parameter. You can create a custom view model that wraps the necessary properties. Inside the action you have to make a call to db to get the complete NewsPage instance and update only the corresponding properties from the view model.

Somewhat ugly, but did the trick in my case without having to create and register custom model binder or using multiple if statements.
[HttpPost]
public void SaveNews(string propname, bool propvalue, int PageID)
{
var prop = typeof(NewsPage).GetProperties().FirstOrDefault(x => x.Name.ToLower() == propname.ToLower());
var Np = _db.NewsPages.FirstOrDefault(x => x.PageID == PageID);
prop.SetValue(Np, propvalue, null);
Np.ModifyDate = DateTime.Now;
_db.SaveChanges();
_db.Dispose();
}

Related

How to add a query string from surface controller in umbraco mvc in order to persist model values

How to add a query string from surface controller in umbraco mvc . This is my current code.
Initially I wrote a code like
public ActionResult Registration(RegisterModel model)
{
//Code to insert register details
ViewBag.Success="Registered Successfully"
return CurrentUmbracoPage();
}
with this I could successful persist my ViewBag and model properties value but I could not add a query string with it.
For certain requirement I have to change the code that returns a url with querystring.
which I did as below
public ActionResult Registration(RegisterModel model)
{
//Code to insert register details
ViewBag.Success="Registered Successfully"
pageToRedirect = AppendQueryString("success");
return new RedirectResult(pageToRedirect);
}
public string AppendQueryString(string queryparam)
{
var pageToRedirect = new DynamicNode(Node.getCurrentNodeId()).Url;
pageToRedirect += "?reg=" + queryparam;
return pageToRedirect;
}
and with this my values of the properties in model could not persist and the ViewBag returned with null value.
Can any one suggest me how to add query string by persisting the values in the model and ViewBag.
Data in ViewBag will not be available on the View when it redirects. Hence you have to add message in TempData which will be available in the View after the redirect like TempData.Add("CustomMessage", "message");

Entity Framework ASP.NET MVC private model fields

There is a field in our database which really ought to be a boolean, but for some reason the original developers made it a CHAR which will either be set to "1" or "0".
[Column("CHARGEABLE")]
[StringLength(1)]
private string Chargeable { get; set; }
I want my model to represent this field as a boolean so I figured I could add a property to my model to wrap it:
[NotMapped]
public bool ChargeableTrue
{
get
{
return Chargeable == "1" ? true : false;
}
set
{
Chargeable = value ? "1" : "0";
}
}
Now on my View I just display the EditorFor ( ChargeableTrue ), but when I click save it doesn't actually update it.
I think what is happening is that when the model is being updated, it's still attempting to get the value of 'Chargeable' from the View, even though I haven't displayed it there. And since there is no input field, it just gets null and ends up saving that to the database.
if (ModelState.IsValid)
{
db.Entry(call).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
What is one expected to do in this situation?
Based on KMan's answer, here's the extended version just in case you're not familiar with creating view models.
The idea is that your domain object is not really what you want to be updating exactly from your views. Instead, you create a go-between that can also include view-specific items (like a list of objects to populate a drop-down).
public class MyViewModel {
public bool Chargeable { get; set; }
}
Now you can do this:
#* In view *#
Html.EditorFor(m => m.Chargeable)
// In controller
public ActionResult Save(MyViewModel model) {
if (ModelState.IsValid) {
var domainObject = new MyObject() {
Chargeable = model.Chargeable ? "1" : "0"
};
// the rest of your code using domainObject
}
}
I'd consider just creating an overload of your domain object's constructor that accepts your view model to keep the mapping in one place. I typically use a tool like AutoMapper to map objects or manual extension methods.
A view model typically contains a sub-set of your domain object's properties, but can contain all of them or more properties like lists, visbility states, etc. They come in incredibly useful and I've never done a MVC project where I haven't used them.
Use a view model and make your mapping on the controller.

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.

Calling UpdateModel with a collection of complex data types reset all non-bound values?

I'm not sure if this is a bug in the DefaultModelBinder class or what.
But UpdateModel usually doesn't change any values of the model except the ones it found a match for.
Take a look at the following:
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Edit(List<int> Ids)
{
// Load list of persons from the database
List<Person> people = GetFromDatabase(Ids);
// shouldn't this update only the Name & Age properties of each Person object
// in the collection and leave the rest of the properties (e.g. Id, Address)
// with their original value (whatever they were when retrieved from the db)
UpdateModel(people, "myPersonPrefix", new string[] { "Name", "Age" });
// ...
}
What happens is UpdateModel creates new Person objects, assign their Name & Age properties from the ValueProvider and put them in the argument List<>, which makes the rest of the properties set to their default initial value (e.g. Id = 0)
so what is going on here?
UPDATE:
I stepped through mvc source code (particularly DefaultModelBinder class) and here is what I found:
The class determines we are trying to bind a collection so it calls the method: UpdateCollection(...) which creates an inner ModelBindingContext that has a null Model property. Afterwards, that context is sent to the method BindComplexModel(...) which checks the Model property for null and creates a new instance of the model type if that is the case.
That's what causes the values to be reset.
And so, only the values that are coming through the form/query string/route data are populated, the rest remains in its initialized state.
I was able to make very few changes to UpdateCollection(...) to fix this problem.
Here is the method with my changes:
internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType) {
IModelBinder elementBinder = Binders.GetBinder(elementType);
// build up a list of items from the request
List<object> modelList = new List<object>();
for (int currentIndex = 0; ; currentIndex++) {
string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
if (!DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, subIndexKey)) {
// we ran out of elements to pull
break;
}
// **********************************************************
// The DefaultModelBinder shouldn't always create a new
// instance of elementType in the collection we are updating here.
// If an instance already exists, then we should update it, not create a new one.
// **********************************************************
IList containerModel = bindingContext.Model as IList;
object elementModel = null;
if (containerModel != null && currentIndex < containerModel.Count)
{
elementModel = containerModel[currentIndex];
}
//*****************************************************
ModelBindingContext innerContext = new ModelBindingContext() {
Model = elementModel, // assign the Model property
ModelName = subIndexKey,
ModelState = bindingContext.ModelState,
ModelType = elementType,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object thisElement = elementBinder.BindModel(controllerContext, innerContext);
// we need to merge model errors up
VerifyValueUsability(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement);
modelList.Add(thisElement);
}
// if there weren't any elements at all in the request, just return
if (modelList.Count == 0) {
return null;
}
// replace the original collection
object collection = bindingContext.Model;
CollectionHelpers.ReplaceCollection(elementType, collection, modelList);
return collection;
}
Rudi Breedenraed just wrote an excellent post describing this problem and a very helpful solution. He overrides the DefaultModelBinder and then when it comes across a collection to update, it actually updates the item instead of creating it new like the default MVC behavior. With this, UpdateModel() and TryUpdateModel() behavior is consistent with both the root model and any collections.
You just gave me an idea to dig into ASP.NET MVC 2 source code.
I have been struggling with this for two weeks now. I found out that your solution will not work with nested lists. I put a breakpoint in the UpdateCollection method ,and it never gets hit. It seems like the root level of model needs to be a list for this method to be called
This is in short the model I have..I also have one more level of generic lists, but this is just a quick sample..
public class Borrowers
{
public string FirstName{get;set;}
public string LastName{get;set;}
public List<Address> Addresses{get;set;}
}
I guess that, I will need to dig deeper to find out what is going on.
UPDATE:
The UpdateCollection still gets called in asp.net mvc 2, but the problem with the fix above is related to this HERE

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