I migrated an API method from a standard MVC action to the new asp.net Web-API beta and suddenly a read only property is no longer serialized (both returning JSON). Is this expected behaviour?
Edit: Added code sample
I have both Newtonsoft.Json 4.0.8 and System.Json 4.0 referenced through nuget packages
public IQueryable<Car> Gets()
{
return _carRepository.GetCars();
}
public class Car
{
public IEnumerable<Photo> Photos
{
get { return _photos; }
}
public string PreviewImageUrl // No longer serialized
{
get
{
var mainImage = Photos.FirstOrDefault(o => o.IsMainPreview) Photos.FirstOrDefault();
return mainImage != null ? mainImage.Url : (string.Empty);
}
}
}
}
The JsonMediaTypeFormatter that ships with the Beta uses a serializer that does not support read-only properties (since they would not round-trip correctly). We are planning on addressing this for the next realese.
In the mean-time you could use a custom JSON MediaTypeFormatter implementation that uses Json.NET (there's one available here) instead of the built-in formatter.
Update: Also check out Henrik's blog about hooking up a JSON.NET formatter: http://blogs.msdn.com/b/henrikn/archive/2012/02/18/using-json-net-with-asp-net-web-api.aspx
I don't know if this is an expected behavior or not. I would say that this is expected for input parameters (because you cannot set their values) but not for output parameters. So I would say this is a bug for an output parameter. And here's an example illustrating the issue:
Model:
public class Product
{
public Product()
{
Prop1 = "prop1 value";
Prop2 = "prop2 value";
Prop3 = "prop3 value";
}
public string Prop1 { get; set; }
[ReadOnly(true)]
public string Prop2 { get; set; }
public string Prop3 { get; protected set; }
}
Controller:
public class ProductsController : ApiController
{
public Product Get(int id)
{
return new Product();
}
}
Request:
api/products/5
Result:
{"Prop1":"prop1 value","Prop2":"prop2 value"}
So if the property doesn't have a public setter it is not serialized which doesn't seem normal as the Product class is used as output in this case.
I would suggest opening a connect ticket so that Microsoft can fix this before the release or at least tell that this is by design.
Related
I have a standard breeze web-server which exposes the Project
[HttpGet]
public IQueryable<Project> Projects()
{
return _db.Context.Projects;
}
How can i prevent a Project with NoLongerExist=true from begin included in queries returned to the JavaScript client from here affecting the functionality of the breezejs client?.. um i would also like to hide this property also, people wont like to see that their projects are not actually deleted!
you can try this on your breeze controller...
public IQueryable<Project> Projects()
{
return _db.Context.Projects.Where(o => o.NoLongerExist == true);
}
prevent json serialization of the NoLongerExist property using data annotations on your model by doing this, i'm assuming you are using EF6 with JSON.NET on your backend...
[Table("Project")]
public partial class Project
{
public Project()
{
}
public int id { get; set; }
[JsonIgnore]
public bool NoLongerExist { get; set; }
}
Here is a problem.
We have a simple web api controller with one class in it
public class CheckApiController : ApiController
{
public class Test
{
public int Id { get; set; }
public string SomeString { get; set; }
/*public Test ComplexType
{
get
{
return new Test()
{
SomeString = "wtf"
};
}
}*/
}
public List<Test> Get()
{
return new List<Test>(){
new Test(){Id = 1,SomeString = "1st string"},
new Test(){Id=2,SomeString = "2nd string"}
};
}
public HttpResponseMessage Post(Test testclass)
{
//do something
var response = Request.CreateResponse(HttpStatusCode.Created);
return response;
}
}
When I check in fiddler Get and Post methods everything is OK. But when i uncomment the property ComplexType in Test and try again POST method will respond with huge 500 internal server error:
Insufficient stack to continue executing the program safely. This can happen from having too many functions on the call stack or function on the stack using too much stack space.
So its simple StackOverflow exception. So why this error happens with field that has no setter or how can i ignore it in binding?
Think about whats happening here. When you POST the object WebApi is building your Test class to build it it gets the ComplexType, which returns a new Test, which gets ComplexType, which returns a new Test and so on until you run out of stack.
Having any logic in you Model you send to/from your Api is generally not a great idea. Here is some info on DTOs, which explains why http://www.servicedesignpatterns.com/requestandresponsemanagement/datatransferobject
Id Recommend amending your Test class to look like:
public class Test
{
public int Id { get; set; }
public string SomeString { get; set; }
public Test ComplexType { get; set; }
}
And then assigning the 'SomeString' value afterwards.
C# Web.Api Odata APplication
I’m implementing Odata V4 for the first time and would like to use my own custom class instead of the data class from the designer.
Here is what I did:
I created a Data project in Visual Studios and added my SQL Linq data table “Video”
I created the data context as follows:
public class VideoDataContext : DbContext
{
public VideoDataContext(): base("name=VideoData")
{
DbSet<VideoEf> Videos { get; set; }
}
And my custom class as follows:
[Serializable]
[DataContract]
public class VideoEf : Repository
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Isrc { get; set; }
}
And model builder:
public Microsoft.OData.Edm.IEdmModel GetEdmModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
EntityTypeConfiguration<VideoEf> titleType = builder.EntityType<VideoEf>();
builder.EntitySet<VideoEf>("Video");
return builder.GetEdmModel();
}
And in my video controller:
public class VideoController : ODataController
{
VideoDataContext db = new VideoDataContext ();
[EnableQuery(PageSize = 20, MaxExpansionDepth = 5)]
public IHttpActionResult Get()
{
return Ok(db.Videos.AsQueryable());
}
When I make the call to get the video entities I keep getting a ” 406 Not Acceptable” error message
How can I ensure that the data returned from the database is mapped to my custom model ?
Is my model builder correct?
what could be causing the above error?
You don't need to return IQueryable because you have EnableQuery attribute, just return DbSet.
You also don't need any wcf attribute and EntityTypeConfiguration<VideoEf> titleType = builder.EntityType<VideoEf>();
Then it should just work.
Hope it helps.
Edit
My mistake for IQueryable, I also use it.
I am getting this error "Self referencing loop detected" while serializing using 'Json.NET'
I have a Book model
public class Book
{
public Book()
{
BookPersonMap = new List<BookPersonMap>();
}
public int BookId { get; set; }
public virtual ICollection<BookPersonMap> BookPersonMap { get; private set; }
(And many other virtual Icollections)
}
And this is the BookPerson Mapping class:
public class BookPersonMap
{
public int BookId { get; set; }
public string PersonName { get; set; }
public int PersonTypeId { get; set; }
public virtual Book Book { get; set; } // Foreign keys
public virtual PersonType PersonType { get; set; }
}
When I try to Serialize the Book object it throws:
"Self referencing loop detected for property 'Book' with type 'System.Data.Entity.DynamicProxies.Book_57F0FA206568374DD5A4CFF53C3B41CFDDC52DBBBA18007A896 08A96E7A783F8'. Path 'BookPersonMap[0]'."
I have tried the things suggested in some of the similar posts
Example:
PreserveReferencesHandling = PreserveReferencesHandling.Objects in Serializer settings returned a string with length 3 million!
ReferenceLoopHandling = ReferenceLoopHandling.Ignore in Serializer settings :
"An exception of type 'System.OutOfMemoryException' occurred in Newtonsoft.Json.dll but was not handled in user code"
^ Same luck with "ReferenceLoopHandling.Serialize"
MaxDepth = 1 : Infinite loop again.
Putting [JsonIgnore] on the virtual properties is working but it is a tedious task (because of numerous FK references) and not efficent, since if I miss one property and it will throw exception.
What is missing from above Json settings for them be not working?
services.AddMvc().AddJsonOptions(opt => {
opt.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
I have found the best way to solve this type of error is to flatten your model using a view model.
Put a break point on your object before it is serialized and start drilling into the child properties. You will probably find that you can go on indefinitely.
This is what the serializer is choking on.
Create a Constructor for your controller and put on it this line of code :
db.Configuration.ProxyCreationEnabled = false;
//db is the instance of the context.
For asp.net mvc 5 use this
Add the code below to your Application_Start method inside globax.asax file or startup file.
protected void Application_Start()
{
..
GlobalConfiguration.Configuration.Formatters.JsonFormatter
.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
}
Disable lazy loading and
ensure your controller does not return
Json(..obj)
rather it should return
Ok(..obj)
I've got a Knockout Model that gets posted via a save method:
self.save = function(form) {
ko.utils.postJson($("form")[0], self);
};
I check the request to make sure all the data is properly being posted (it is):
However, when I get to my action:
[HttpPost]
public ActionResult Create(EquipmentCreateModel equipmentCreateModel)
{
/stuff here
}
BuildingCode and Room contain escaped quotes, and identifiers is totally not null but has a count of 0:
And my ModelState is not valid, there is one error, for the Identifiers property which has an attempted value of :
and the Exception message is:
"The parameter conversion from type 'System.String' to type 'System.Collections.Generic.KeyValuePair`2[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' failed because no type converter can convert between these types."
My Model:
public class EquipmentCreateModel
{
//used to populate form drop downs
public ICollection<Building> Buildings { get; set; }
public ICollection<IdentifierType> IdentifierTypes { get; set; }
[Required]
[Display(Name = "Building")]
public string BuildingCode { get; set; }
[Required]
public string Room { get; set; }
[Required]
[Range(1, 100, ErrorMessage = "You must add at least one identifier.")]
public int IdentifiersCount { get; set; } //used as a hidden field to validate the list
public string IdentifierValue { get; set; } //used only for knockout viewmodel binding
public IDictionary<Guid, string> Identifiers { get; set; }
}
Now first I thought it was a problem with knockout, but then I found out the data wasn't being posted in the request correctly. I fixed that and still had the same problem. I thought MVC3 automatically converts Json now? Why are my simple properties appearing in escaped quotes and why can't my identities collection properly populate from the posted data?
Try this:
[HttpPost]
public ActionResult Create([FromJson] EquipmentCreateModel equipmentCreateModel)
{
//stuff here
}
where FromJson is:
public class FromJsonAttribute : CustomModelBinderAttribute
{
private readonly static JavaScriptSerializer serializer = new JavaScriptSerializer();
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
return serializer.Deserialize(stringified, bindingContext.ModelType);
}
}
}
This is taken from:
http://blog.stevensanderson.com/2010/07/12/editing-a-variable-length-list-knockout-style/
you should check the comments to as there are some modification for the FromJsonAttribute.
If you are using MVC3 you don't need to add JsonValueProviderFactory. For those of us who are still on MVC2 you can add JsonValueProviderFactory manually
http://haacked.com/archive/2010/04/15/sending-json-to-an-asp-net-mvc-action-method-argument.aspx
However JsonValueProvider only works with AJAX post. For the full postback it needs the extra processing. There's a thread describing how to handle full postback: groups.google.com/d/topic/knockoutjs/3FEpocpApA4/discussion
The easiest solution would be to use AJAX post. Change
ko.utils.postJson($("form")[0], self);
to
$.ajax({
url: $("form").action,
type: 'post',
data: ko.toJSON(self),
contentType: 'application/json',
success: function (result) {
alert(result);
}
});
You could try:
[HttpPost]
public ActionResult Create(string equipmentCreateModelString)
{
var equipmentCreateModel = JsonConvert.DeserializeObject<EquipmentCreateModel> equipmentCreateModelString, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
}
Otherwise you need to use a JsonValueProviderFactory. Here's an example
#DOTang, i have another approach. First, you need a clean js object from your view model. You can get it calling: ko.mapping.toJS(self), then pass your view model to postJson function as a property. Finally add [FromJson] attribute to your controller. Your controller argument name, must be equal to your js property name, in this case: model.
I hope it works for you as works for me.
server
[HttpPost]
public ActionResult RegisterUser([FromJson] EquipmentCreateModel model)
{
//...
}
client
self.save = function() {
var jsModel = ko.mapping.toJS(self);
ko.utils.postJson('#Url.Action("Create", "Equipment")', { model : jsModel });
}