ASP.NET MVC posted entity not mapping to LINQ model - c#

I have a page which is strongly typed to my "User" class. When it is loaded, I load it by Id from the database and pass it to the view.
When the edit form is posted, the object gets posted to the controller method fine, with some other parameters. The object has its properties filled from the form, but it's ID (which obviously isnt on the form) doesnt get posted.
Even when I manually set it to an ID in code and try and save my context, nothing happens on the database.
Here is a rough view of the code with stuff taken out for brevity.
public ActionResult MyProfile()
{
ViewData["Countries"] = new SelectList(userService.GetCountries(), "id", "name");
return View(userService.GetById(CurrentUser.id));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MyProfile(MSD_AIDS_Images_Data.LINQRepositories.User user, string password2)
{
user.id = CurrentUser.id; //user id isn't posted, so need to reassign it
userService.SaveChanges();
}
I have written code like this a dozen times and it has worked, what is going wrong?
EDIT
When I debug the user object, it's PropertyChanged and PropertyChanging properties are set to NULL

The User object coming into the MyProfile method is not associated with a LINQ context. You need to use explicit binding using UpdateModel, e.g.:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MyProfile(int id, string password2)
{
MSD_AIDS_Images_Data.LINQRepositories.User user = <LINQ query to load user by id>;
UpdateModel(user); // updates the model with form values
userService.SaveChanges();
}
Note you can implement a custom model binder that does this before calling your controller method so you can accept User as a parameter, but I'm assuming you haven't done this.

I fixed the Model binding issues by using an Update Model overload which allows you to specifiy which properties in the model you wish to update:
string[] includeProperties = {"password", "firstname", "lastname", "email", "affiliation", "countryId"};
UpdateModel(user, includeProperties);

Related

Prefilling a form with long request data

I am trying to prefill some HTML form fields with data contained in the request.
My original setup, before any changes, looks like this:
Inside TicketController:
//GET: Retrieve the form
[Route("createticket")]
public ActionResult CreateTicket()
{
//Irrelevant code ommited
return View("CreateTicket");
}
//POST: Submit the form
[HttpPost]
[Route("createticket")]
public ActionResult CreateTicket(CreateTicketModel createTicketModel)
{
//Irrelevant code ommited (saving the submitted data)
return RedirectToAction("ViewTicket", new { ticketId = ticket.TicketId });
}
I could pass the data as a GET parameter, but the problem is often the data will be too long to be contained in the URL (2000+ characters).
The only solution I can currently think of is to make retrieving the form a POST instead of a GET, so I can use POST parameters to prefill the form. I have two problems with this solution:
I will have two possible POST requests at the /createticket path, which will be conflicting
It just doesn't feel right to use a POST request to retrieve a form
How could I tackle this?
Why cant you just instantiate a CreateTicket model and set the properties with the data you need to be prefilled? Via model binding the values of your properties will be set into the HTML formfields.
[Route("createticket")]
public ActionResult CreateTicket()
{
//Irrelevant code ommited
var data = GetSomeDataYouWantToPrefill();
var model = new CreateTicketModel(data);
return View("CreateTicket", model);
}
Then in the constructor of the model you can assign the values in the data object to the properties. In your view with the Html.TextboxFor method you can bind to the properties in the model

What does ModelState.IsValid do?

When I do a create method i bind my object in the parameter and then I check if ModelState is valid so I add to the database:
But when I need to change something before I add to the database (before I change it the ModelState couldn't be valid so I have to do it)
why the model state still non valid.
What does this function check exactly?
This is my example:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EncaissementID,libelle,DateEncaissement,Montant,ProjetID,Description")] Encaissement encaissement) {
encaissement.Montant = Convert.ToDecimal(encaissement.Montant);
ViewBag.montant = encaissement.Montant;
if (ModelState.IsValid) {
db.Encaissements.Add(encaissement);
db.SaveChanges();
return RedirectToAction("Index", "Encaissement");
};
ViewBag.ProjetID = new SelectList(db.Projets, "ProjetId", "nomP");
return View(encaissement);
}
ModelState.IsValid indicates if it was possible to bind the incoming values from the request to the model correctly and whether any explicitly specified validation rules were broken during the model binding process.
In your example, the model that is being bound is of class type Encaissement. Validation rules are those specified on the model by the use of attributes, logic and errors added within the IValidatableObject's Validate() method - or simply within the code of the action method.
The IsValid property will be true if the values were able to bind correctly to the model AND no validation rules were broken in the process.
Here's an example of how a validation attribute and IValidatableObject might be implemented on your model class:
public class Encaissement : IValidatableObject
{
// A required attribute, validates that this value was submitted
[Required(ErrorMessage = "The Encaissment ID must be submitted")]
public int EncaissementID { get; set; }
public DateTime? DateEncaissement { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
// Validate the DateEncaissment
if (!this.DateEncaissement.HasValue)
{
results.Add(new ValidationResult("The DateEncaissement must be set", new string[] { "DateEncaissement" });
}
return results;
}
}
Here's an example of how the same validation rule may be applied within the action method of your example:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EncaissementID,libelle,DateEncaissement,Montant,ProjetID,Description")] Encaissement encaissement) {
// Perform validation
if (!encaissement.DateEncaissement.HasValue)
{
this.ModelState.AddModelError("DateEncaissement", "The DateEncaissement must be set");
}
encaissement.Montant = Convert.ToDecimal(encaissement.Montant);
ViewBag.montant = encaissement.Montant;
if (ModelState.IsValid) {
db.Encaissements.Add(encaissement);
db.SaveChanges();
return RedirectToAction("Index", "Encaissement");
};
ViewBag.ProjetID = new SelectList(db.Projets, "ProjetId", "nomP");
return View(encaissement);
}
It's worth bearing in mind that the value types of the properties of your model will also be validated. For example, you can't assign a string value to an int property. If you do, it won't be bound and the error will be added to your ModelState too.
In your example, the EncaissementID value could not have a value of "Hello" posted to it, this would cause a model validation error to be added and IsValid will be false.
It is for any of the above reasons (and possibly more) that the IsValid bool value of the model state will be false.
ModelState.IsValid will basically tell you if there is any issues with your data posted to the server, based on the data annotations added to the properties of your model.
If, for instance, you have a [Required(ErrorMessage = "Please fill")], and that property is empty when you post your form to the server, ModelState will be invalid.
The ModelBinder also checks some basic stuff for you. If, for instance, you have a BirthDate datepicker, and the property that this picker is binding to, is not a nullable DateTime type, your ModelState will also be invalid if you have left the date empty.
Here, and here are some useful posts to read.
You can find a great write-up on ModelState and its uses here.
Specifically, the IsValid property is a quick way to check if there are any field validation errors in ModelState.Errors. If you're not sure what's causing your Model to be invalid by the time it POST's to your controller method, you can inspect the ModelState["Property"].Errors property, which should yield at least one form validation error.
Edit: Updated with proper dictionary syntax from #ChrisPratt
This is not meant to be the best answer, but I find my errors by stepping through the ModelState Values to find the one with the error in Visual Studio's debugger:
My guess is that everyone with a question about why their ModelState is not valid could benefit from placing a breakpoint in the code, inspecting the values, and finding the one (or more) that is invalid.
This is not the best way to run a production website, but this is how a developer finds out what is wrong with the code.

MVC Object Change Tracking

I've currently got an issue where I need to see which fields have been changed on an Edit field for auditing purposes, in which I have code for, but I think my problem lies within my MVC View.
I have (test code):
[HttpPost]
public ActionResult Adjustment(GroupPolicy groupPolicy)
{
if (ModelState.IsValid)
{
_service.SaveGroupPolicy(groupPolicy);
return RedirectToAction("Index");
}
return View(groupPolicy);
}
Which is fine, the Policy saves. However, take this into consideration:
GroupPolicy has, say, 3 fields (in reality there are, maybe, 60):
bool IsPolicy
string Name
string Description
Name and Description are on the form, so that's fine. IsPolicy isn't used on the form, so that gets defaulted to false when posted back to the GroupPolicy object in the Adjustment method.
I can't really put IsPolicy in a Hidden field on the form, as that won't be elegant for 60+ fields in my actual solution, the HTML would be all over the place.
Now that the bool is defaulted to false, it completely abolishes the chance of me knowing if the field has changed or not. All I really want is a method for this data to be preserved, whilst keeping the new information on the Edit form.
Is this possible, am I missing something obvious?
Well first of all, GroupPolicy should be a view model and not an entity - and as such it should be tailored for the view e.g.
public class GroupPolicyViewModel
{
[HiddenInput]
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
...
}
Then in your action you don't need to worry about assigning values that have changed, you just map the view model directly across e.g.
public ActionList Adjustment(GroupPolicyViewModel viewModel)
{
if (ModelState.IsValid)
{
// pull actual entity from service
var groupPolicy = _service.GetGroupPolicy(viewModel.Id);
// update entity from view model
groupPolicy.Name = viewModel.Name;
groupPolicy.Description = viewModel.Description;
...
}
}
This keeps a clean separation between your view & business logic. Also, it allows you to add annotations for client-side validation without affecting your real model.
GroupPolicy has, say, 3 fields (in reality there are, maybe, 60)
I would recommend using AutoMapper for this e.g.
// call this once only e.g. Application_Start in the Global.asax
Mapper.CreateMap<GroupPolicyViewModel, GroupPolicy>();
...
// in your Adjustment action
var groupPolicy = _service.GetGroupPolicy(viewModel.Id);
groupPolicy = Mapper.Map<GroupPolicyViewModel, GroupPolicy>(viewModel, groupPolicy);
_service.SaveGroupPolicy(groupPolicy);
If IsPolicy not on the form then it shouldn't even be part of your model - this will prevent posting of this field into your model and so your check won't even be needed for IsPolicy.
Rather than accepting GroupPolicy as the parameter into the action, create a cut down object GroupPolicyInputModel with only fields that are on the form.
Then use your generic auditing to only compare all the posted fields, as per any other form.

Editing form fields via ajax and .net mvc

I have a number of inputs on my page. I would like to save the changes to the model on the input blur, so as I change the value of each input it gets saved back to server, like Google contacts.
<input id="FirstName" name="FirstName">Jack</input>
I create a blur event using jquery to post the value back to the server. It posts a structure with the name of the input, the value and an id of the entity.
$.post(url, { id: "2", key: "FirstName", value: "Jack" }, successFuction);
In my controller I have:
public ActionResult EditField(int id, string key, string value)
I then retrieve the entity using EntityFramework with the id. I then wanted to update the property on the model for the field.
var entity = _db.Get(id);
entity[key] = value;
return Content "Success";
Which I obviously can't do! The only way I can think off is multiple methods for each field so EditName, EditAddress etc. which seems wrong. I want this method to be able to handle each property of the model.
What is a better way to structure the controller instead of writing multiple methods for each individual field?
You could post your entire form (e.g. first name, last name, etc.) on each blur for any of your fields (this should be fine since you're saving all changes as the user progresses on the form anyway). Unless you're really trying to save bytes, posting the whole form seems fine.
You could just post the field name and then use reflection to look up the property of your object and set the value.
I think that you can do it if you are willing to model the entity in a general way:
public class FieldEntity {
public int Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
Then use it inside the context like:
var fieldEntity = db.Find(id);
fieldEntity.Key = key;
fieldEntity.Value = value;
db.SaveChanges();
However, it is usually better to structure data in a way that is meaningful. In the example you describe it looks like you might have a Person and Address entity. So why not have a Person entity that has a property Address?

MVC2 DataAnnotations on ViewModel - Don't understand using it with MVVM pattern

I have an MVC2 Application that uses MVVM pattern. I am trying use Data Annotations to validate form input.
In my ThingsController I have two methods:
[HttpGet]
public ActionResult Index()
{
return View();
}
public ActionResult Details(ThingsViewModel tvm)
{
if (!ModelState.IsValid) return View(tvm);
try
{
Query q = new Query(tvm.Query);
ThingRepository repository = new ThingRepository(q);
tvm.Things = repository.All();
return View(tvm);
}
catch (Exception)
{
return View();
}
}
My Details.aspx view is strongly typed to the ThingsViewModel:
<%# Page Title=""
Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<Config.Web.Models.ThingsViewModel>" %>
The ViewModel is a class consisting of a IList of returned Thing objects and the Query string (which is submitted on the form) and has the Required data annotation:
public class ThingsViewModel
{
public IList<Thing> Things{ get; set; }
[Required(ErrorMessage="You must enter a query")]
public string Query { get; set; }
}
When I run this, and click the submit button on the form without entering a value I get a YSOD with the following error:
The model item passed into the dictionary is of type
'Config.Web.Models.ThingsViewModel', but this dictionary
requires a model item of type
System.Collections.Generic.IEnumerable`1[Config.Domain.Entities.Thing]'.
How can I get Data Annotations to work with a ViewModel? I cannot see what I'm missing or where I'm going wrong - the VM was working just fine before I started mucking around with validation.
I don't think the problem is with the validation.
Change this line;
tvm.Things = repository.All(); //Is this the Linq extension method 'All()'?
to this
tvm.Things = repository.ToList();
I don't know what this is or what it does;
new ThingRepository(q);
It takes a string parameter and returns some kind of Linq IQueriable or List? If that's returning something else it could be causing the problem.
Do you have client-side validation enabled? It might even be a quick hacky-fix, but regarding the error message - it's tough to say without extra info. Could you post your View and the rendered Html?
What does your route for Details look like?
If you set a breakpoint at the start of the Details method, does it get hit when you click on the submit button?
It looks like you could just declare your ThingsViewModel like so:
public class ThingsViewModel: IEnumerable<Thing>
and then implement the interface as appropriate to access the Things list.
I think that ASP.NET MVC might be trying to map your view to the wrong controller. When you return the view you might need to specify the view file name you're trying to use.
return View("ViewName")

Categories

Resources