Related
I am working with ASP.NET MVC 5 Web Api. I want consult all my users.
I wrote api/users and I receive this:
"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'"
In WebApiConfig, already I added these lines:
HttpConfiguration config = new HttpConfiguration();
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType);
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
But it still doesn't work.
My function for return data is this:
public IEnumerable<User> GetAll()
{
using (Database db = new Database())
{
return db.Users.ToList();
}
}
If you are working with EF, besides adding the code below on Global.asax
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
GlobalConfiguration.Configuration.Formatters
.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
Dont`t forget to import
using System.Data.Entity;
Then you can return your own EF Models
Simple as that!
When it comes to returning data back to the consumer from Web Api (or any other web service for that matter), I highly recommend not passing back entities that come from a database. It is much more reliable and maintainable to use Models in which you have control of what the data looks like and not the database. That way you don't have to mess around with the formatters so much in the WebApiConfig. You can just create a UserModel that has child Models as properties and get rid of the reference loops in the return objects. That makes the serializer much happier.
Also, it isn't necessary to remove formatters or supported media types typically if you are just specifying the "Accepts" header in the request. Playing around with that stuff can sometimes make things more confusing.
Example:
public class UserModel {
public string Name {get;set;}
public string Age {get;set;}
// Other properties here that do not reference another UserModel class.
}
Given right answer is one way to go, however it is an overkill when you can fix it by one config settings.
Better to use it in the dbcontext constructor
public DbContext() // dbcontext constructor
: base("name=ConnectionStringNameFromWebConfig")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
Asp.Net Web API Error: The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'
Add this code to global.asax below on Application_Start:
Update from .Ignore to .Serialize . It must work.
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
public class UserController : ApiController
{
Database db = new Database();
// construction
public UserController()
{
// Add the following code
// problem will be solved
db.Configuration.ProxyCreationEnabled = false;
}
public IEnumerable<User> GetAll()
{
return db.Users.ToList();
}
}
I resolved it using this code to WebApiConfig.cs file
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
I don't like this code:
foreach(var user in db.Users)
As an alternative, one might do something like this, which worked for me:
var listOfUsers = db.Users.Select(r => new UserModel
{
userModel.FirstName = r.FirstName;
userModel.LastName = r.LastName;
});
return listOfUsers.ToList();
However, I ended up using Lucas Roselli's solution.
Update: Simplified by returning an anonymous object:
var listOfUsers = db.Users.Select(r => new
{
FirstName = r.FirstName;
LastName = r.LastName;
});
return listOfUsers.ToList();
Adding this in your Application_Start() method of Global.asax file should solve the problem
protected void Application_Start()
{
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
GlobalConfiguration.Configuration.Formatters
.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
// ...
}
METHOD 2: [Not recommended]
If you are working with EntityFramework, you can disable proxy in your DbContext class constructor. NOTE: this code wll be removed if you update the model
public class MyDbContext : DbContext
{
public MyDbContext()
{
this.Configuration.ProxyCreationEnabled = false;
}
}
There's also this scenario that generate same error:
In case of the return being a List<dynamic> to web api method
Example:
public HttpResponseMessage Get()
{
var item = new List<dynamic> { new TestClass { Name = "Ale", Age = 30 } };
return Request.CreateResponse(HttpStatusCode.OK, item);
}
public class TestClass
{
public string Name { get; set; }
public int Age { get; set; }
}
So, for this scenario use the [KnownTypeAttribute] in the return class (all of them) like this:
[KnownTypeAttribute(typeof(TestClass))]
public class TestClass
{
public string Name { get; set; }
public int Age { get; set; }
}
This works for me!
My personal favorite: Just add the code below to App_Start/WebApiConfig.cs. This will return json instead of XML by default and also prevent the error you had. No need to edit Global.asax to remove XmlFormatter etc.
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
Just put following lines in global.asax:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
Import
using System.Data.Entity;
Use AutoMapper...
public IEnumerable<User> GetAll()
{
using (Database db = new Database())
{
var users = AutoMapper.Mapper.DynamicMap<List<User>>(db.Users);
return users;
}
}
Use the following namespace:
using System.Web.OData;
Instead of :
using System.Web.Http.OData;
It worked for me
Add the below line
this.Configuration.ProxyCreationEnabled = false;
Two way to use ProxyCreationEnabled as false.
Add it inside of DBContext Constructor
public ProductEntities() : base("name=ProductEntities")
{
this.Configuration.ProxyCreationEnabled = false;
}
OR
Add the line inside of Get method
public IEnumerable<Brand_Details> Get()
{
using (ProductEntities obj = new ProductEntities())
{
this.Configuration.ProxyCreationEnabled = false;
return obj.Brand_Details.ToList();
}
}
Use [Serializable] for class:
Example:
[Serializable]
public class UserModel {
public string Name {get;set;}
public string Age {get;set;}
}
It worked for me!
Solution that worked for me:
Use [DataContract] for class and [DataMember] attributes for each property to serialize. This is enough to get Json result (for ex. from fiddler).
To get xml serialization write in Global.asax this code:
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
xml.UseXmlSerializer = true;
Read this article, it helped me to understand serialization:
https://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization
To add to jensendp's answer:
I would pass the entity to a user created model and use the values from that entity to set the values in your newly created model. For example:
public class UserInformation {
public string Name { get; set; }
public int Age { get; set; }
public UserInformation(UserEntity user) {
this.Name = user.name;
this.Age = user.age;
}
}
Then change your return type to: IEnumerable<UserInformation>
While all these answers above are correct, one may want to check the InnerException > ExceptionMessage.
If it says something like this "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.". This could be an issue because of default behavior of the EF.
By assigning LazyLoadingEnabled = false in your DbContext constructor will do the trick.
public class MyDbContext : DbContext
{
public MyDbContext()
{
this.Configuration.LazyLoadingEnabled = false;
}
}
For more detailed reading about EagerLoading and LazyLoading behavior of EF refer this MSDN Article.
In my case I have had similar error message:
The 'ObjectContent`1' type failed to serialize the response body for
content type 'application/xml; charset=utf-8'.
But when I dig deeper in it, the issue was:
Type 'name.SomeSubRootType'
with data contract name
'SomeSubRootType://schemas.datacontract.org/2004/07/WhatEverService'
is not expected. Consider using a DataContractResolver if you are
using DataContractSerializer or add any types not known statically to
the list of known types - for example, by using the KnownTypeAttribute
attribute or by adding them to the list of known types passed to the
serializer.
The way I solved by adding KnownType.
[KnownType(typeof(SomeSubRootType))]
public partial class SomeRootStructureType
This was solved inspired from this answer.
Reference: https://msdn.microsoft.com/en-us/library/ms730167(v=vs.100).aspx
I basically add one line which they are
entities.Configuration.ProxyCreationEnabled = false;
to UsersController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using UserDataAccess;
namespace SBPMS.Controllers
{
public class UsersController : ApiController
{
public IEnumerable<User> Get() {
using (SBPMSystemEntities entities = new SBPMSystemEntities()) {
entities.Configuration.ProxyCreationEnabled = false;
return entities.Users.ToList();
}
}
public User Get(int id) {
using (SBPMSystemEntities entities = new SBPMSystemEntities()) {
entities.Configuration.ProxyCreationEnabled = false;
return entities.Users.FirstOrDefault(e => e.user_ID == id);
}
}
}
}
You will have to define Serializer Formatter within WebApiConfig.cs available in App_Start Folder like
Adding config.Formatters.Remove(config.Formatters.XmlFormatter);
// which will provide you data in JSON Format
Adding config.Formatters.Remove(config.Formatters.JsonFormatter);
// which will provide you data in XML Format
Another case where I received this error was when my database query returned a null value but my user/view model type was set as non-nullable. For example, changing my UserModel field from int to int? resolved.
This also happens when the Response-Type is not public!
I returned an internal class as I used Visual Studio to generate me the type.
internal class --> public class
Visual Studio 2017 or 2019 is totally unthoughtful on this, because Visual Studio itself requires the output to be in json format, while Visual Studio's default format is "XmlFormat" (config.Formatters.XmlFormatter).
Visual Studio should do this automatically instead of giving developers so much trouble.
To correct this problem, go to the WebApiConfig.cs file, and add
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
after "config.MapHttpAttributeRoutes();" in the Register(HttpConfiguration config) method. This would allow your project to produce json output.
In my case I solved recreating the database.
I made some changes in a model and launching Update-Database in Package Manager Console I got the following Error:
"The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Activities_dbo.Projects_ProjectId". The conflict occurred in database "TrackEmAllContext-20190530144302", table "dbo.Projects", column 'Id'."
In case: If adding code to WebApiConfig.cs or Global.asax.cs doesn't work for you:
.ToList();
Add .ToList() function.
I tried out every solution but following worked for me:
var allShops = context.shops.Where(s => s.city_id == id)**.ToList()**;
return allShops;
I hope, it helps.
in my case, it was fixed when I removed the virtual keyword before my navigation properties,
I mean the reference tables.
so I changed
public virtual MembershipType MembershipType { get; set; }
to:
public MembershipType MembershipType { get; set; }
I don't understand why this happens. I'm consuming REST service via WCF. In case of error the service sends this kind of message body (I see in Fiddler):
{"Errors":["Some text"],"StatusCode":500}
I have created a class for this, and getting it via Message.GetBody(). StatusCode is filled, but Errors are always empty (not null, but empty). I have tried String[], List of String, also tried initialized backing field. What is wrong here?
Here is the class:
[DataContract(Name="root", Namespace="")]
public class ErrorResult
{
[DataMember]
public string[] Errors { get; set; }
[DataMember]
public int StatusCode { get; set; }
public override String ToString()
{
return String.Join(Environment.NewLine, Errors);
}
}
Here is the parsing:
public void AfterReceiveReply(ref Message reply, object correlationState)
{
var resp = reply.Properties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
if (resp != null && resp.StatusCode != HttpStatusCode.OK)
{
String message = null;
try
{
ErrorResult res = reply.GetBody<ErrorResult>();
if (res != null)
{
message = res.ToString();
}
}
catch { }
if (!String.IsNullOrEmpty(message))
{
throw new Exception(message);
}
}
}
I'm using [DataContract(Name="root", Namespace="")] because I was getting error "expecting element ErrorResult with namespace 'bla-bla-bla' but got element 'root' with namespace ''".
UPDATE. I have noticed the same problem with another entity, the one which was service result type. It contained 2 integer fields, and on return they were 0s, even though I could see in Fiddler and in Message.ToString() values there. I have fixed this by adding Name="..." to DataMember attributes, even though those names are the same as property names, except casing (JSON camel vs C# pascal). I was sure that C# parsing is case insensitive! Ok, but adding names to ErrorResult in subject still didn't fix the problem with array.
UPDATE 2. Ok, after not finding any solution, I did a workaround in an "ugly way". At least this started working at once.
XmlDocument doc = new XmlDocument();
doc.LoadXml(reply.ToString());
String message = String.Join(Environment.NewLine,
doc.SelectNodes("/root/Errors/item").OfType<XmlNode>().Select(n => n.InnerText));
I have also tried XMlSerialzer with Message.GetReaderAtBodyContents(), but got "error in XML" and gave it up.
Here is explained what to do to make List work. I usually use arrays (eg: string[] not Array class) to avoid problems or additional configuration and cast on client side.
I have Create a webApi. When i run this webapi i got expected data in my browser.Here is my api get method
public IEnumerable GetAllProducts()
{
return db.products.ToList();
}
Now i want to consume this api service in my project.To do so i coded in my controller as
public ViewResult Index()
{
var responseStream = requestMethod.GetResponseStream(requestMethod.getRequest("GET", "application/json", string.Format("{0}/api/product/", restService)).GetResponse());
var products = deSerializeProduct<List<product>>(responseStream) as List<product>;
return View( products);
}
To serilization i have a custome serializer as:
public class DefaultSerialization : ISerialization
{
public string Serialize<T>(object o)
{
String json;
using (var stream = new MemoryStream())
{
var serializer = new DataContractJsonSerializer(typeof(T));
serializer.WriteObject(stream, (T)o);
json = Encoding.UTF8.GetString(stream.ToArray());
}
return json;
}
public object DeSerialize<T>(System.IO.Stream stream)
{
var serializer = new DataContractJsonSerializer(typeof(T));
return serializer.ReadObject(stream);
}
}
}
but while Deserializing i got error on return serializer.ReadObject(stream); as:
The type 'Product' cannot be serialized to JSON because its IsReference setting is 'True'. The JSON format does not support references because there is no standardized format for representing references. To enable serialization, disable the IsReference setting on the type or an appropriate parent class of the type.
There are a few possible thinks you can try to work around this issue, the main reason is that EntityFramework (or ur ORM) is conflicting with serialization.
Btw, if you provide code, try to provide code which compiles and makes sence. I could not reproduce your problem since there's code missing. If you could include all the code (or even a visual studio project) I would try to fix your problem instead of demonstrating a solution based on a totally different context.
In my opinion, you shouldn't be returning your ORM objects from your API. Instead return DataTransferObjects. You remove Entity Framework from the serialized objects, this way the error could disappear.
Use RestSharp to make REST API Calls
What you can do is mark your Product entity with the following attribute
[DataContract(IsReference=false)]
Try using JSON.NET ?
http://json.codeplex.com/ I'd use JSON.NET together with DTO's to achieve this.
But what I do not understand is what you are trying to achieve. Why not simply do a client side call to your API, I do not see the benefits of your server side API-call in this case. Unless your WebApi is hosted on a different environment, calling it from server side wouldn't make sense in any case. Your MVC projects seems to have reference to your ORM objects layer aswell, which again is abit strange (or is the Product class not a object mapped to ur database ?)
If they are on the same server, there is no benefit in using WebApi this way, instead create a layer which provides the information you need, and include it in both your Api and Controller, this way your controller does not need to consume the API, it can call the other layer directly. It also remove the serverside JSON deserialization.
I also notice your API returns "cities", and you are casting them to products, doesn't that sounds odd ?
EDIT: Working example:
I've created a small application which is working for me. The example can be downloaded at: https://dl.dropboxusercontent.com/u/87239305/WebApi.Example.zip
I've used restSharp to create Api Calls from within .NET for both DTO and EF related objects.
Non-DTO example:
ApiController looks like this:
public interface IApiExampleController
{
IEnumerable<User> GetAllUsers();
}
public class ApiExampleController : ApiController, IApiExampleController
{
private readonly IEFContext _context;
public ApiExampleController()
{
_context = new EFContext();
}
public IEnumerable<User> GetAllUsers()
{
return _context.Users.ToList();
}
}
The MVC Controller to call the API looks like this:
public ActionResult Index()
{
var users = GetApiUsers();
return View(users);
}
private IList<User> GetApiUsers()
{
const string apiBaseUrl = "http://localhost:52812/Api/";
const string apiSuffix = "ApiExample/GetAllUsers";
var client = new RestClient(apiBaseUrl);
var request = new RestRequest(apiSuffix, Method.GET);
var response = (RestResponse<List<User>>)client.Execute<List<User>>(request);
return response.Data;
}
DTO example:
The API controller:
public interface IApiDTOExampleController
{
IEnumerable<UserDTO> GetAllUsers();
}
public class ApiDTOExampleController : ApiController, IApiDTOExampleController
{
private readonly IEFContext _context;
public ApiDTOExampleController()
{
_context = new EFContext();
}
public IEnumerable<UserDTO> GetAllUsers()
{
return _context.Users.Select(x => new UserDTO { Id = x.Id, Name = x.Name }).ToList();
}
}
The code to call this API is the following:
public ActionResult IndexDTO()
{
var dtoUsers = GetApiDTOUsers();
return View(dtoUsers);
}
private IList<UserDTO> GetApiDTOUsers()
{
const string apiBaseUrl = "http://localhost:52812/Api/";
const string apiSuffix = "ApiDTOExample/GetAllUsers";
var client = new RestClient(apiBaseUrl);
var request = new RestRequest(apiSuffix, Method.GET);
var response = (RestResponse<List<UserDTO>>)client.Execute<List<UserDTO>>(request);
return response.Data;
}
As you can see, there is no big difference between the DTO and EF implementation... Nevertheless, using a DTO allows you to control which data is needed on the calling side.
Both work fine (in my case), so depending on your product or city object there may not be any need to implement DTO to get rid of the error (nevertheless I advice you to do so).
I have inherited a project from my predecessor which uses OpenRasta to host a webservice for my OpenSource colleagues to access for their applications. This is my first foray into OpenRasta
I've added a lot of additional features all of which is working via manual browser requests, although not 100% reliably but that's perhaps another question later. So I have embarked on creating a set of Unit Tests to test the functionality, which I should be doing anyway. I have successfully created a unit test or two for each GET request all of which are passing, but I am stuck on the test for the single POST I have in the project.
I'm getting a HTTP 415 Error '8-[2012-12-07 11:23:19Z] Information(0) Executing OperationResult OperationResult: type=RequestM ediaTypeUnsupported, statusCode=415.' from the output window. I've taken inspiration from a post by Nate Taylor http://taylonr.com/integration-testing-openrasta and have asked him the same question, which he has kindly replied to. I'm still trying to decipher his answer, and perhaps someone might be able to expand and fill in the gaps in my understanding?
Here is the code which I have been trying:
[Test]
public void AuthenticateUserJSONPOSTTest()
{
object content = new AuthenticationStructure { Username = "matthew.radford", Password = "obviously not going to tell you that bit and will replace with a domain test user when the time comes", AppId = 4 };
OpenRastaJSONTestMehods.POST<AuthenticationResult, AuthenticationStructure>("http://localhost/user", content);
}
[Test]
public static void POST<T, U>(string uri, object content)
{
const string LocalHost = "http://localhost/";
if (uri.Contains(LocalHost))
POST<T, U>(new Uri(uri), content);
else
throw new UriFormatException(string.Format("The uri doesn't contain {0}", LocalHost));
}
[Test, WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
public static void POST<T,U>(Uri serviceuri, object content)
{
using (var host = new InMemoryHost(new Configuration()))
{
var request = new InMemoryRequest()
{
Uri = serviceuri,
HttpMethod = "POST"
};
request.Entity.ContentType = MediaType.Json;
request.Entity.Headers["Accept"] = "application/json";
var serializer = new DataContractJsonSerializer(typeof(T), new [] { typeof(AuthenticationStructure) });
serializer.WriteObject(request.Entity.Stream, content);
request.Entity.Stream.Seek(0, SeekOrigin.Begin);
request.Entity.ContentLength = request.Entity.Stream.Length;
//Just a read test, not necessary for the output
byte[] readbyte = new byte[(int)request.Entity.ContentLength];
request.Entity.Stream.Read(readbyte, 0, (int)request.Entity.ContentLength);
request.Entity.Stream.Seek(0, SeekOrigin.Begin);
U readObject = (U)serializer.ReadObject(request.Entity.Stream);
request.Entity.Stream.Seek(0, SeekOrigin.Begin);
NUnit.Framework.Assert.AreEqual(content, readObject);
var response = new InMemoryResponse();
response.Entity.ContentType = MediaType.Json;
response.Entity.Headers["Accept"] = "application/json";
response = (InMemoryResponse)host.ProcessRequest(request);
int statusCode = response.StatusCode;
//this is where the test fails because the above response isn't correct and gives the 415 statusCode
NUnit.Framework.Assert.AreEqual(201, statusCode, string.Format("Http StatusCode Error: {0}", statusCode));
object returnedObject;
if (response.Entity.ContentLength > 0)
{
response.Entity.Stream.Seek(0, SeekOrigin.Begin);
//Just a read test, not necessary for the output
readbyte = new byte[(int)response.Entity.ContentLength];
response.Entity.Stream.Read(readbyte, 0, (int)response.Entity.ContentLength);
response.Entity.Stream.Seek(0, SeekOrigin.Begin);
returnedObject = serializer.ReadObject(response.Entity.Stream);
//return returnedObject;
}
}
}
Thanks in advance.
I've tried so many different things this morning to try and get this working. The first good step forward I made by trying to read the JSON stream as a string to actually see what the object was being serialized as.
To do that I found How to convert an Stream into a byte[] in C#? this set me off in the right direction to read the stream out to a string. I therefore came up with this line to write it to the output window:
Debug.WriteLine(Encoding.UTF8.GetString(StreamHelper.ReadToEnd(request.Entity.Stream), 0, (int)request.Entity.Stream.Length).ToString());
This was the result:
{"__type":"AuthenticationStructure","username":"matthew.radford","appid":4,"password":"###########"}
I realised there were two problems in this output. Firstly and simply, the password should be before the appid, which was easily fixed in the AuthenticationStructure class, where I had made a mistake. DataMember Order needed to equal 3 for AppId
[DataMember(Name="appid", Order=3)]
public int AppId { get; set; }
Secondly though, the default serialization includes a '__type' member at the beginning of the notation. This obviously then doesn't match my parameters on the POST method of my Handler:
[HttpOperation(HttpMethod.POST)]
public OperationResult Post(string username, string password, int appid)
At this point I looked at trying to remove the type notation from the JSON string. I found a good sites Deserialize array values to .NET properties using DataContractJsonSerializer which showed me both how to write the constructor to include alwaysEmitTypeInformation which they had set to false, but I wanted to Emit the type information, so changed it to true. And it also showed me how to create an Surrogate based on IDataContractSurrogate, which I called AuthenticationTypeSurrogate.
public class AuthenticationTypeSurrogate : IDataContractSurrogate
{
public Type GetDataContractType(Type type)
{
// "Book" will be serialized as an object array
// This method is called during serialization, deserialization, and schema export.
if (typeof(AuthenticationStructure).IsAssignableFrom(type))
{
return typeof(object[]);
}
return type;
}
public object GetObjectToSerialize(object obj, Type targetType)
{
// This method is called on serialization.
if (obj is AuthenticationStructure)
{
AuthenticationStructure authenticationStructure = (AuthenticationStructure)obj;
return new object[] { authenticationStructure.Username, authenticationStructure.Password, authenticationStructure.AppId };
}
return obj;
}
public object GetDeserializedObject(object obj, Type targetType)
{
// This method is called on deserialization.
if (obj is object[])
{
object[] arr = (object[])obj;
AuthenticationStructure authenticationStructure = new AuthenticationStructure { Username = (string)arr[0], Password = (string)arr[1], AppId = (int)arr[2] };
return authenticationStructure;
}
return obj;
}
public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
{
return null; // not used
}
public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
{
return typeDeclaration; // Not used
}
public object GetCustomDataToExport(Type clrType, Type dataContractType)
{
return null; // not used
}
public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
{
return null; // not used
}
public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
{
return; // not used
}
}
The serialization worked for this but now the Deserialization, which I never bothered to debug and get right because it still wasn't working because instead of creating a JSON it was serializing a object array, which you would expected because that was what the method GetObjectoSerialize method is returning. So I hit another brick wall unless I could work out how to change this into JSON.
So finally I thought well I'll just add the __type parameter into an overloaded POST method and pass on the other parameters to the original POST method.
[HttpOperation(HttpMethod.POST)]
public OperationResult Post(Type __type, string username, string password, int appid)
{
return Post(username, password, appid);
}
This was so nearly right but it still didn't work, so finally I created another overloaded method and passed the whole type:
[HttpOperation(HttpMethod.POST)]
public OperationResult Post(AuthenticationStructure astruct)
{
return Post(astruct.Username, astruct.Password, astruct.AppId);
}
And finally this worked. I'm not perfectly happy with it and would have liked to link straight into my existing method, but it works.
Thanks to everyone who looked and especially Nate, thanks for your original responses, and hopefully this will help people in the future.
I have been using Telerik MVC Grid for quite a while now. It is a great control, however, one annoying thing keeps showing up related to using the grid with Ajax Binding to objects created and returned from the Entity Framework. Entity objects have circular references, and when you return an IEnumerable<T> from an Ajax callback, it generates an exception from the JavascriptSerializer if there are circular references. This happens because the MVC Grid uses a JsonResult, which in turn uses JavaScriptSerializer which does not support serializing circular references.
My solution to this problem has been to use LINQ to create view objects that do not have the Related Entities. This works for all cases, but requires the creation of new objects and the copying of data to / from entity objects to these view objects. Not a lot of work, but it is work.
I have finally figured out how to generically make the grid not serialize the circular references (ignore them) and I wanted to share my solution for the general public, as I think it is generic, and plugs into the environment nicely.
The solution has a couple of parts
Swap the default grid serializer with a custom serializer
Install the Json.Net plug-in available from Newtonsoft (this is a great library)
Implement the grid serializer using Json.Net
Modify the Model.tt files to insert [JsonIgnore] attributes in front of the navigation properties
Override the DefaultContractResolver of Json.Net and look for the _entityWrapper attribute name to ensure this is also ignored (injected wrapper by the POCO classes or entity framework)
All of these steps are easy in and of themselves, but without all of them you cannot take advantage of this technique.
Once implemented correctly I can now easily send any entity framework object directly to the client without creating new View objects. I don't recommend this for every object, but sometimes it is the best option. It is also important to note that any related entities are not available on the client side, so don't use them.
Here are the Steps required
Create the following class in your application somewhere. This class is a factory object that the grid uses to obtain JSON results. This will be added to the telerik library in the global.asax file shortly.
public class CustomGridActionResultFactory : IGridActionResultFactory
{
public System.Web.Mvc.ActionResult Create(object model)
{
//return a custom JSON result which will use the Json.Net library
return new CustomJsonResult
{
Data = model
};
}
}
Implement the Custom ActionResult. This code is boilerplate for the most part. The only interesting part is at the bottom where it calls JsonConvert.SerilaizeObject passing in a ContractResolver. The ContactResolver looks for properties called _entityWrapper by name and sets them to be ignored. I am not exactly sure who injects this property, but it is part of the entity wrapper objects and it has circular references.
public class CustomJsonResult : ActionResult
{
const string JsonRequest_GetNotAllowed = "This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.";
public string ContentType { get; set; }
public System.Text.Encoding ContentEncoding { get; set; }
public object Data { get; set; }
public JsonRequestBehavior JsonRequestBehavior { get; set; }
public int MaxJsonLength { get; set; }
public CustomJsonResult()
{
JsonRequestBehavior = JsonRequestBehavior.DenyGet;
MaxJsonLength = int.MaxValue; // by default limit is set to int.maxValue
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if ((JsonRequestBehavior == JsonRequestBehavior.DenyGet) && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(JsonRequest_GetNotAllowed);
}
var response = context.HttpContext.Response;
if (!string.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
response.Write(JsonConvert.SerializeObject(Data, Formatting.None,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new PropertyNameIgnoreContractResolver()
}));
}
}
}
Add the factory object to the telerik grid. I do this in the global.asax Application_Start() method, but realistically it can be done anywhere that makes sense.
DI.Current.Register<IGridActionResultFactory>(() => new CustomGridActionResultFactory());
Create the DefaultContractResolver class that checks for _entityWrapper and ignores that attribute. The resolver is passed into the SerializeObject() call in step 2.
public class PropertyNameIgnoreContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(System.Reflection.MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (member.Name == "_entityWrapper")
property.Ignored = true;
return property;
}
}
Modify the Model1.tt file to inject attributes that ignore the related entity properties of the POCO Objects. The attribute that must be injected is [JsonIgnore]. This is the hardest part to add to this post but not hard to do in the Model1.tt (or whatever filename it is in your project). Also if you are using code first then you can manually place the [JsonIgnore] attributes in front of any attribute that creates a circular reference.
Search for the region.Begin("Navigation Properties") in the .tt file. This is where all of the navigation properties are code generated. There are two cases that have to be taken care of the many to XXX and the Singular reference. There is an if statement that checks if the property is
RelationshipMultiplicity.Many
Just after that code block you need to insert the [JSonIgnore] attribute prior to the line
<#=PropertyVirtualModifier(Accessibility.ForReadOnlyProperty(navProperty))#> ICollection<<#=code.Escape(navProperty.ToEndMember.GetEntityType())#>> <#=code.Escape(navProperty)#>
Which injects the property name into the generated code file.
Now look for this line which handles the Relationship.One and Relationship.ZeroOrOne relationships.
<#=PropertyVirtualModifier(Accessibility.ForProperty(navProperty))#> <#=code.Escape(navProperty.ToEndMember.GetEntityType())#> <#=code.Escape(navProperty)#>
Add the [JsonIgnore] attribute just before this line.
Now the only thing left is to make sure the NewtonSoft.Json library is "Used" at the top of each generated file. Search for the call to WriteHeader() in the Model.tt file. This method takes a string array parameter that adds extra usings (extraUsings). Instead of passing null, construct an array of strings and send in the "Newtonsoft.Json" string as the first element of the array. The call should now look like:
WriteHeader(fileManager, new [] {"Newtonsoft.Json"});
That's all there is to do, and everything starts working, for every object.
Now for the disclaimers
I have never used Json.Net so my implementation of it might not be
optimal.
I have been testing for about two days now and haven't found any cases where this technique fails.
I also have not found any incompatibilities between the JavascriptSerializer and the JSon.Net serializer but that doesn't mean
there aren't any
The only other caveat is that the I am testing for a property called "_entityWrapper" by name to set its ignored property to true. This is obviously not optimal.
I would welcome any feedback on how to improve this solution. I hope it helps someone else.
The first solution works with the grid editing mode, but we have the same problem with the load of the grid that already has rows of objects with circular reference, and to resolve this we need to create a new IClientSideObjectWriterFactory and a new IClientSideObjectWriter.
This is what I do:
1- Create a new IClientSideObjectWriterFactory:
public class JsonClientSideObjectWriterFactory : IClientSideObjectWriterFactory
{
public IClientSideObjectWriter Create(string id, string type, TextWriter textWriter)
{
return new JsonClientSideObjectWriter(id, type, textWriter);
}
}
2- Create a new IClientSideObjectWriter, this time I do not implement the interface, I've inherited the ClientSideObjectWriter and overrided the AppendObject and AppendCollection methods:
public class JsonClientSideObjectWriter : ClientSideObjectWriter
{
public JsonClientSideObjectWriter(string id, string type, TextWriter textWriter)
: base(id, type, textWriter)
{
}
public override IClientSideObjectWriter AppendObject(string name, object value)
{
Guard.IsNotNullOrEmpty(name, "name");
var data = JsonConvert.SerializeObject(value,
Formatting.None,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new PropertyNameIgnoreContractResolver()
});
return Append("{0}:{1}".FormatWith(name, data));
}
public override IClientSideObjectWriter AppendCollection(string name, System.Collections.IEnumerable value)
{
public override IClientSideObjectWriter AppendCollection(string name, System.Collections.IEnumerable value)
{
Guard.IsNotNullOrEmpty(name, "name");
var data = JsonConvert.SerializeObject(value,
Formatting.Indented,
new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new PropertyNameIgnoreContractResolver()
});
data = data.Replace("<", #"\u003c").Replace(">", #"\u003e");
return Append("{0}:{1}".FormatWith((object)name, (object)data));
}
}
NOTE: The replace its because the grid renders html tags for the client template in edit mode and if we don't encode then the browser will render the tags. I didn't find a workarround yet if not using a Replace from string object.
3- On my Application_Start on Global.asax.cs I registered my new factory like this:
DI.Current.Register<IClientSideObjectWriterFactory>(() => new JsonClientSideObjectWriterFactory());
And it worked for all components that Telerik has. The only thing that I do not changed was the PropertyNameIgnoreContractResolver that was the same for the EntityFramework classes.
I put the new call into my Application_Start for implement the CustomGridActionResultFactory but the create method never called...
I have taken a slightly different approach which I believe might be a little easier to implement.
All I do is apply an extended [Grid] attribute to the grid json returning method instead of the normal [GridAction] attribute
public class GridAttribute : GridActionAttribute, IActionFilter
{
/// <summary>
/// Determines the depth that the serializer will traverse
/// </summary>
public int SerializationDepth { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="GridActionAttribute"/> class.
/// </summary>
public GridAttribute()
: base()
{
ActionParameterName = "command";
SerializationDepth = 1;
}
protected override ActionResult CreateActionResult(object model)
{
return new EFJsonResult
{
Data = model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
MaxSerializationDepth = SerializationDepth
};
}
}
and
public class EFJsonResult : JsonResult
{
const string JsonRequest_GetNotAllowed = "This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.";
public EFJsonResult()
{
MaxJsonLength = 1024000000;
RecursionLimit = 10;
MaxSerializationDepth = 1;
}
public int MaxJsonLength { get; set; }
public int RecursionLimit { get; set; }
public int MaxSerializationDepth { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(JsonRequest_GetNotAllowed);
}
var response = context.HttpContext.Response;
if (!String.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
}
else
{
response.ContentType = "application/json";
}
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data != null)
{
var serializer = new JavaScriptSerializer
{
MaxJsonLength = MaxJsonLength,
RecursionLimit = RecursionLimit
};
serializer.RegisterConverters(new List<JavaScriptConverter> { new EFJsonConverter(MaxSerializationDepth) });
response.Write(serializer.Serialize(Data));
}
}
Combine this with my serializer Serializing Entity Framework problems and you have a simple way of avoiding circular references but also optionally serializing multiple levels (which I need)
Note: Telerik added this virtual CreateActionResult very recently for me so you may have to download the latest version (not sure but I think maybe 1.3+)
Another good pattern is to simply not avoid creating a ViewModel from the Model.
It is a good pattern to include a ViewModel. It gives you the opportunity to make last minute UI related tweaks to the model. For example, you can tweak a bool to have an associated string Y or N to help make the UI look nice, or vice versa.
Sometimes the ViewModel is exactly like the Model and the code to copy the properties seems unnecessary, but the pattern is a good one and sticking to it is the best practice.