I have a scenario where I need to update an object from information that was posted to the action. As long as the information is in the page this works fine. However, it requires that I put information into hidden fields if I don't want the modelstate to complain.
As an example, lets say I am using the class below as the model:
public class Client
{
[Required]
public string Name { get; set; }
public string Email { get; set; }
public int Id { get; set; }
}
If I don't want the user to edit the name, I need to include it in a hidden field so that it get bound to the model and the validation passes.
The problem I have is that is obviously not secure if used with more sensitive information. So I tried this:
public virtual ActionResult Save(Client model, int clientId)
{
var client = datasource.LoadEntity(adapter, clientId); // clientId is passed as a querystring to the action
if (!TryUpdateModel(client))
return new RedirectResult('The edit page URL');
}
The problem is that the modelstate still complains about the "Name" value not being available even though it was loaded to the client object from the database.
Obviously I am doing something wrong but I can't figure out what.
The view model is just for information coming from the client.
So you have to remove the Name property and get it from somewhere else.
If this is a view model also used by the administrator for example (who is able to enter/change the name) then the best would be a derived view model like this:
public class Client
{
public string Email { get; set; }
public int Id { get; set; }
}
public class ClientWithName : Client
{
[Required]
public string Name { get; set; }
}
You can use the overload TryUpdateModel(TModel, string\[\]); if my understanding is correct, this should allow to specify the property to include in the update, like this:
public virtual ActionResult Save(Client model, int clientId)
{
var client = datasource.LoadEntity(adapter, clientId);
if (!TryUpdateModel(client, new string[] { "Email" }))
return new RedirectResult('The edit page URL');
}
I never tried it though, can you let us know if it works as expected?
I've gone with the solution outlined here: Asp.net MVC 3 Validation exclude some field validation in TryUpdateModel
Essentially, it removes the validation from the Modelstate if those fields aren't present which works for me as those values are retrieved from the database.
Related
I am working on a basic CRUD .net MVC application, with a requirement to validate the same data differently depending on if it is being created or edited.
As per the answer to the following question, it seems a different model for create and edit is required.
I have tried adding a class to the current model, with the following error:
"Multiple object sets per type are not supported. The object sets
'ReportDetails' and 'ReportDetailsEdit' can both contain instances
of type 'Test.Models.ReportDetails'."
I have also tried creating a new model, with the following error:
The namespace 'Test.Models' already contains a definition for 'TestDBContext'
What is the correct way to achieve this?
Here is a current basic outline of the model and controller:
Model:
public class ReportDetails {
[Key]
public int ReportID { get; set; }
[Required]
public string Email { get; set; }
[NotMapped]
[Compare("Email", ErrorMessage = "Confirmation field must match")]
public string ConfirmEmail { get; set; }
//Further fields here
}
Controller:
[HttpPost]
public ActionResult Edit(ReportDetails reportdetails) {
if (ModelState.IsValid) {
db.Entry(reportdetails).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(reportdetails);
}
The model is currently used for both create and edit, although I do not want the confirm email field included in addition to different validation rules for further fields.
I was hoping to understand the behavior of Bind Exclude in the following situation:
Here is my model class:
public class Client
{
[Required]
public int? ClientId { get; set; }
[Required]
public string Name { get; set; }
public string Comments { get; set; }
}
Here is the Post method in my controller:
public int PostClient([Bind(Exclude = "ClientId")]Client client)
Using fiddler, I send ClientId and Name in the Json string. When I debug PostClient, I see that the client.ClientId is set to the value I send through fiddler. But because I have used ClientId in the bind exclude category, I expected null or default value for ClientId. Can somebody help me understand why the value is set for ClientId?
PS: I understand that ViewModels work better in this case - creating a new model class only with the parameters I want to bind. Although I might go with that, this question is only to understand Bind Exclude well.
I wonder if there is a way to validate just one of my models in the viewmodel send it to my action? I use the DataAnnotations as validate rules.
Like the if (!ModelState.IsValid)
let me know if the question is unclear and I will edit for a better explination
EDIT
my viewmodel looks like this
public class CompaniesViewModel
{
public Core.Model.Customer Company { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
public Core.Model.Meeting Meeting { get; set; }
}
What I want to do in this particular situation is to validate just Customer. I cant do the ModelState.IsValid then all get validated. So how can I do to just validate one of them like customer in this case. Hope this was more clear
There are a number of different ways you can do this. The first is to add a property called IsValid that checks the property. So something like:
public class Company
{
public bool IsValid
{
get { return GetValid() }
}
private bool IsValid()
{
if ( Some check here )
return false;
}
}
[HttpPost]
public ActionResult SomeAction(CompaniesViewModel model)
{
if (model.Company.IsValid)
{
}
}
However a better solution IMO would be just to post the Company to your controller rather than your entire view model. Just because your passing a view model to a view it doesn't mean that you need to post the entire view model back. When you create your HTML form specify only the properties you want to post back to your controller. So for example your controller would become:
[HttpPost]
public ActionResult SomeAction(Company company)
{
if (Model.IsValid)
{
}
}
Now when you check if Model.IsValid it just check company as that is all you've passed back to the controller.
At server side, you can try the ValidateModel(object) method, like in TryValidateModel(CompaniesViewModel.Company).
If you have enabled client sided validation, you need to post only the relevant entity. If you want to post all entities, but you need to validate only one, you can consider the following:
either removing the rules, using javascript ASP .NET MVC Disable Client Side Validation at Per-Field Level
or creating a Data-Transfer-Object, ie a View Model which has NO link to the Model, but reproduces the entities you want with the validation rules you want having applied in this scenario. Of course, then, you'll need in your controller or a model binder some way to bind from your ViewModel to your Model entities.
You can separate Customer Model to another class in your ViewModel and map that in Controller to a existing/new Customer:
public class CompaniesViewModel
{
public Company Company { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
public Core.Model.Meeting Meeting { get; set; }
}
//Validations for Company go here:
public class Company
{
public string CompanyId { get; set; }
[Required]
public string CompanyName { get; set; }
}
Once again I'm having trouble with Linq to Sql and the MVC Model Binder.
I have Linq to Sql generated classes, to illustrate them they look similar to this:
public class Client
{
public int ClientID { get; set; }
public string Name { get; set; }
}
public class Site
{
public int SiteID { get; set; }
public string Name { get; set; }
}
public class User
{
public int UserID { get; set; }
public string Name { get; set; }
public int? ClientID { get; set; }
public EntityRef<Client> Client { get; set; }
public int? SiteID { get; set; }
public EntityRef<Site> Site { get; set; }
}
The 'User' has a relationship with the 'Client' and 'Site
. The User class has nullable ClientIDs and SiteIDs because the admin users are not bound to a Client or Site.
Now I have a view where a user can edit a 'User' object, the view has fields for all the 'User' properties. When the form is submitted, the appropiate 'Save' action is called in my UserController:
public ActionResult Save(User user, FormCollection form)
{
//form['SiteID'] == 1
//user.SiteID == 1
//form['ClientID'] == 1
//user.ClientID == null
}
The problem here is that the ClientID is never set, it is always null, even though the value is in the FormCollection.
To figure out whats going wrong I set breakpoints for the ClientID and SiteID getters and setters in the Linq to Sql designer generated classes. I noticed the following:
SiteID is being set, then ClientID is being set, but then the Client EntityRef property is being set with a null value which in turn is setting the ClientID to null too! I don't know why and what is trying to set the Client property, because the Site property setter is never beeing called, only the Client setter is being called.
Manually setting the ClientID from the FormCollection like this:
user.ClientID = int.Parse(form["ClientID"].ToString());
throws a 'ForeignKeyReferenceAlreadyHasValueException', because it was already set to null before. The only workaround I have found is to extend the generated partial User class with a custom method:
Client = default(EntityRef<Client>)
but this is not a satisfying solution. I don't think it should work like this?
Please enlighten me someone. So far Linq to Sql is driving me crazy!
Best regards
I've just run in the same issue with MVC 3. I think this happens because model binder sets all public properties of the model class while creating it. So it sets user.ClientID to 1 and then user.Client to null (because it doesn't exist in form collection). And after that the value of user.ClientID becomes null too.
So you just need to exclude the Client property of the User class from binding like this:
public ActionResult Save([Bind(Exclude="Client")]User user, FormCollection form)
This worked for me.
This is somewhat a two-part question (please let me know if they should be split up).
1) I have a model class with an array of objects contained inside it. I would like to be able to bind this automatically so I can accept a single pollModel argument in my controllers.
public class pollResponseModel
{
public long id { get; set; }
public long pollID { get; set; }
public string text { get; set; }
public long count { get; set; }
}
public class pollModel
{
public long id;
public long entID { get; set; }
public string question { get; set; }
public DateTime posted { get; set; }
public DateTime expiration { get; set; }
public pollResponseModel[] responses { get; set; }
}
The problem is that I'm not sure how to bind the responses field, seeing as it can be any arbitrary size. Well, I can bind it properly when displaying the edit view, but that's about it. That leads me to the second part of my question:
2) What's an acceptable way of dynamically creating and removing data in a list on the client, so that it can be bound to a model and accessed in its modified form on the server? I envision the creation/removal process working like the iPhone list GUI: a single + button will add a new element, and a - button on each row of data will remove it from the list. I would imagine jQuery is an appropriate starting point but my JS skills are very limited.
Check out this article by Phil Haack : Model Binding To a List. It explains exactly what you need to do to bind to list properties, or properties that are complex objects.
Essentially you just have to construct your POST data in the correct way for the model binder to parse it. The article explains how to add hidden index fields and represent your complex properties in your form.