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.
Related
I made an MVC project which includes CRUD operations to all my classes, using default controllers and view pages which support those operations.
My new task was to make few API controllers to specific classes, which I have done correctly so far.
The problem is when a get request is requested, an entire object is returned with all its connections to other classes (which is correct!), but say I have this class:
public class VM{
public int Id { get; set; }
public string MacAddress { get; set; }
public string IpAddress { get; set; }
public DateTime? CreateDate { get; set; }
public string PrivateKey { get; set; }
public int AppId { get; set; }
public int CompanyId { get; set; }
}
I don't want the user to get the privateKey for instance, or ID. I would like the user to get all the rest, but certain information should NOT be sent.
What is the best practice to achieve that? Will making a new class which does not have those specific class members be the right answer?
Say tomorrow I would like to add another data member which will not be sent, will I have to make ANOTHER class?
I assume changing those specific data members' data to null just before sending the object back to the client is not the right answer, is it?
Just setting them to null is not the right answer indeed.
But generally you want your application to be as consistent as possible, and thus, for similar request you should return about the same types of fields/objects.
Thus a few (2 or 3) DTO (data transfer objects) should be sufficient.
If the project is of a small scale, or you just feel like being crazy you can always convert them to anonymous objects as follows:
List<VM> VMs = VMRepo.GetAll();
vms.ConvertAll(vm => new {vm.MacAddress, vm.IpAddress});
Or even give then custom names:
vms.ConvertAll(vm=> new {MAC= vm.MacAddress, IP= vm.IpAddress});
I'm trying to serialize an object to JSON string as shown below:
new JavaScriptSerializer().Serialize(person)
Here, person is the object which is having lot of attributes such as name, address, city, state, etc but I have decorated the class as shown below so that it'll serialize only name and address.
using System;
using System.Runtime.Serialization;
namespace DataAccess.Models
{
[Serializable]
[DataContract]
public class Person
{
public string Id { get; set; }
[DataMember(Name = "full-name")]
public string Name { get; set; }
[DataMember(Name = "address")]
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
}
But when I run the program, the new JavaScriptSerializer().Serialize(person) gives me JSON with all the data including Id, City, State, Zip.
Why is it not giving me only full-name & address? It seems like it is completely ignoring these DataMember attributes.
When I use JsonConvert.SerializeObject(person) from Newtonsoft, everything works perfect & it serializes only Name & Address but JavascriptSerializer is giving all data.
Can any one tell me what could be the issue?
Consider using the [ScriptIgnore()] Attribute on those properties you don't want to be serialized.
http://msdn.microsoft.com/en-us/library/system.web.script.serialization.scriptignoreattribute(v=vs.100).aspx
See here for a detailed list of how the JavaScriptSerializer will handle different types: http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer(v=vs.110).aspx
Edit:
Note that the JavascriptSerializer is not aware of the DataMember attributes and they therefore will not be used by the JavascriptSerializer (JSON.net will use them, but JSON.Net also defines it's own constructs for this: [JsonIgnore()] [JsonObject] and many other attributes support custom-naming). To accomplish this, try using the DataContractJsonSerializer in the System.Runtime.Serialization.Json namespace with some draw-backs.
See this question and answer for additional information:
JavaScriptSerializer.Deserialize - how to change field names
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.
Assume I have an ASP.NET MVC 3 Controller that looks like this
public class MyController : Contoller
{
public ActionResult Edit(MyModel model)
{
/* doing some stuff with the model */
}
}
The model looks like this
public class MyModel
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public ThatModel Property1 { get; set; }
public List<ThisModel> BunchOfThisModel { get; set; }
}
public class ThatModel
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
public string Property4 { get; set; }
public string Property5 { get; set; }
public string Property6 { get; set; }
}
public class ThisModel
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
Does ASP.NET MVC or .NET (v4 is ok, v4.5 not) provide any built-in means to encode a model (say MyModel in this case) so that it can be sent to an action as form url encoded (aka x-www-form-urlencoded)? An example would be "property1=abc&property2=def". However, ASP.NET MVC has its own way to deal with nested models etc when decoding the request back to the model. Assume I'm simulating a browser using the WebRequest/WebResponse APIs provided since .NET 1.1.
In essence, I'd like to build up requests in tests to verify that
some data is excluded through binding, if needed
the anti forgery token is set, if needed
malicious data is handled accordingly
Note: ASP.NET Web API is not being used at this stage. Because I'm writing (integration) tests for an existing application, sending the model as JSON, XML or another alternative format isn't applicabile to the question.
I hope I've understood this question correctly, but provided you are 'POST'ing that data (from JSON?) then it is able to build up the model using a best-guess process.
Property names are matched, so if you sent (guessing the duplicate Property1 is actually Property3 here)
{
Property1="this",
Property2="that",
Property3={Property1="this3", ....},
BunchOfThisModel=[{Property1="bunchthis"},{....}]
}
This would populate your POCO with whatever names matched. If you left out a property (i.e. Property2) it would take on it's default(T) value.
Sending your object model in a GET request would be much more complicated, you could base64 the JSON string, and then parse it on server which is popular approach, but given it's a complex model POST might work best for your intentions.
You can also use a CustomBinder (a good article is here). You can also control which properties are bound to your POCO object by using the BindAttribute with the Exclude/Include options.
Hope I haven't missed the point and this proves useful :)
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.