I am designing an API wrapper in C# for Asana, a project management solution. During the design process, I ran into a few roadblocks. I am wondering what a good way to design the API wrapper would be.
The Asana API I am integrating with works with REST. The requests return JSON.
There will be 6 data classes (User, Task, Project, etc), each containing a bunch of strings to hold the data returned from the REST requests. My first idea with these classes is to give them each factory Parse() constructors so I can easily pass in json and get a data object in return. I realize I can't extract the static factory methods into an interface.
I will have a REST request class that will manage sending and receiving data from the REST server. It will always return a JSON string.
Finally, I would like a AsanaAPI class that will contain methods to wrap those exposed on the REST server (i.e GetUser, GetAllUsers, GetTask). Every method either returns a specific data class or an array of data classes. Here are the two cases:
public User GetSingleUser(string userID = "me")
{
if(userID == "") throw new ArgumentException("UserID cannot be blank");
string url = string.Format("{0}/{1}{2}", userUrl, userID, "?opt_fields=id,name,email,workspaces,workspaces.id,workspaces.name");
JSONNode root = JSON.Parse(GetResponse(url))["data"];
return User.Parse(root);
}
public List<User> GetAllUsers()
{
List<User> users = new List<User>();
string url = string.Format("{0}{1}", userUrl, "?opt_fields=id,name,email,workspaces,workspaces.id,workspaces.name");
JSONArray root = JSON.Parse(GetResponse(url))["data"].AsArray;
foreach(JSONNode userRoot in root)
{
users.Add(User.Parse(userRoot));
}
return users;
}
Each method will have that same format, but the User type will be replaced with Project, Task, etc. I want to extract the logic in these two methods because there will be many more methods with almost the exact same format.
In summary, the roadblocks I ran into were the fact that
I can't extract the factory constructor method from the data class.
I can't extract the parsing logic from the request methods
Is there something I can do with generics or is there just a better way of designing this project?
So I created a Parsable interface containing only a Parse method. Each data type implements Parsable. I was able to extract the parsing logic using generic types. It isn't the prettiest solution, but it does work.
public User GetSingleUser(string userID = "me")
{
if(userID == "") throw new ArgumentException("UserID cannot be blank");
string url = "{baseUrl}/users/{userID}?{opt_fields}".FormatWith(
new { baseUrl = BASE_URL, userID = userID, opt_fields = "opt_fields=id,name,email,workspaces,workspaces.id,workspaces.name" });
return (User)ParseJson<User>(AsanaRequest.GetResponse(url));
}
public User[] GetAllUsers()
{
string url = "{baseUrl}/users?{opt_fields}".FormatWith(
new { baseUrl = BASE_URL, opt_fields = "opt_fields=id,name,email,workspaces,workspaces.id,workspaces.name" });
return (User[])ParseJsonArray<User>(AsanaRequest.GetResponse(url));
}
public T ParseJson<T>(string json) where T : Parsable, new()
{
JSONNode root = JSON.Parse(json)["data"];
T ret = new T();
ret.Parse(root);
return ret;
}
public T[] ParseJsonArray<T>(string json) where T : Parsable, new()
{
JSONArray root = JSON.Parse(json)["data"].AsArray;
T[] nodes = new T[root.Count];
for(int i = 0; i < root.Count; i++)
{
T newParsable = new T();
newParsable.Parse(root[i]);
nodes[i] = newParsable;
}
return nodes;
}
Related
Is there another way to deserialize the response content of an api call into a generic object in .Net without having to create a model representing the json object? In other words, is there or does .Net provide a generic object or model that i can use to deserialize the response into. The reason being is because every api call response is not the same which means i have to create a new model for every api call. And i have about 20 different endpoints that return different responses, which means i would have to create 20 models representing the response.
My recommendation would be to create request and response models for this, even though it feels like extra work. This is because you'll need to eventually pass the parameters to functions in your business layer anyway, and you'll want to take advantage of the type safety without doing a bunch of Int32.TryParse's, which at that point, you're creating variables and doing extra work anyway. Actually, you're not going to be able to outrun the type safety the language not only provides, but generally requires. What I've done, is just copy/paste my DTO or EDMX table model into the new class, then decorate it. Pretty fast.
You may be looking for dynamic object which is late bound with unknown compile time properties.
A handy implementation of this is the JObject available with Newtonsoft.Json package and it offers similar functionality to the typeless elements of Javascript in that the properties can be of any type, with any nesting, etc. as long as it was parsed from well formed Json.
Usage is super simple, but watch out for null values, etc as default behavior for dynamic (aka ExpandoObject under the hood) is to throw exceptions for properties (aka Keys) not found...
public static void Run()
{
string apiJsonResponse = #"{
Name: 'Luke Skywalker',
Title: 'Jedi',
Skills : [
'Lightsaber',
'The Force'
]
}";
dynamic json = JObject.Parse(apiJsonResponse);
Console.WriteLine($"Name: {json.Name}");
Console.WriteLine($"Title: {json.Name}");
Console.WriteLine($"Skills: {String.Join(", ", json.Skills)}");
}
The result will be the Json dynamically parsed and rendered without any strongly typed model:
You can use generic method to have list of JObject. Based on your need you can extract model from jobject.
private static async Task<List<JObject>> CallApi(Uri uri, HttpContent data = null, string headerKey = null, string headerKeyVal = null)
{
string res = string.Empty;
try
{
var handler = new HttpClientHandler
{
//UseDefaultCredentials = true,
};
using var client = new HttpClient(handler);
if (!string.IsNullOrWhiteSpace(headerKey))
client.DefaultRequestHeaders.Add(headerKey, headerKeyVal);
var post = await client.GetAsync(uri);
//var post = await client.PostAsync(uri);
if (post.StatusCode != HttpStatusCode.InternalServerError)
{
res = await post.Content.ReadAsStringAsync();
}
return JsonConvert.DeserializeObject<List<JObject>>(res);
}
catch (Exception ex)
{
throw ex;
}
}
in .Net 3.1 after version, can use using System.Text.Json
api response data
{
"Status" : "0"
"Data" : {
"FirstName" : "firstName",
"LastName" : "lastName"
}
}
Response Model
public class ResponseModel
{
public string Status { get; set; }
public JsonElement Data { get; set; }
}
Deserialize
public void Deserialize()
{
var jsonString = "{ \"Status\": \"0\", \"Data\": { \"FirstName\": \"firstName\", \"LastName\" : \"last_name\"} }";
var response = JsonSerializer.Deserialize<ResponseModel>(jsonString);
//get first_name
var firstName = response.Data.GetProperty("FirstName").GetString();
Console.WriteLine(firstName);
}
I'm fairly new to WEB API so please forgive the ignorance. I'm trying to return a list of events that a specific user will be attending and nothing more. My code works but it returns more info than I need. This is what is returned when I call the API : [{"$id":"1","eventID":"1"},{"$id":"2","eventID":"2"}]
My controller code is as follows:
public HttpResponseMessage Get(string id)
{
List<GetEventAttend> events = null;
events = db.userattends.Where(x => x.userID == id).Select(s => new GetEventAttend()
{ eventID = s.eventID }).ToList<GetEventAttend>();
return Request.CreateResponse(HttpStatusCode.OK, events);
}
This is the code for GetEventAttend:
public class GetEventAttend
{
public string eventID { get; set; }
}
Is there any way I can return in the format of {"1","2"}?
You're almost there but instead of selecting a new GetEventAttend you can just select the eventID fields and return those:
public HttpResponseMessage Get(string id)
{
var events = db.userattends.Where(x => x.userID == id).Select(s => s.eventID).ToList();
return Request.CreateResponse(HttpStatusCode.OK, events);
}
Is the GetEventAttend class really that small or is that just for demo purposes? If it is just a container for the web api result than you do not need that class, as the answer shows you.
EDIT: CodeCaster has a point. This answer will return an array of eventIds. This might suffice for now but in later stages you might want to return an array of events, even if they consist of just the identifier. Because now if you want to include additional information about the event you have to create a new api or introduce breaking changes.
In the original code you have probably configured reference handling, see the docs on how to disable that:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.None;
I'm used to doing this in Django (similar to Ruby on Rails) where in some cases I need to hard code a JSON response object for the client to be able to interpret, but I've been searching everywhere online on figuring out how to do this with ASP.NET web API and I can't find anything on this, ASP.NET web API seems to be forcing me to create a class to represent a JSON response for every URI controller.
For example, here's the only way I know for manually creating a JSON response:
1.) I first need to create the class to represent the response object
public class XYZ_JSON
{
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
}
2.) Then I need to properly write up the URI controller that'll return an "XYZ_JSON" that I've just defined above:
// GET: api/ReturnJSON
public XYZ_JSON Get()
{
XYZ_JSON test = new XYZ_JSON { PropertyName = "Romulus", PropertyValue = "123123" };
return test;
}
Will result with an http response of something like:
200 OK
{"PropertyName":"Romulus", "PropertyValue":"123123"}
This whole class to JSON design pattern is cool and all, but it's not helpful and actually makes things much worse when trying to return a class as a JSON object with many classes within it such as:
public class XYZ_JSON
{
public string PropertyName { get; set; }
public string PropertyValue { get; set; }
public List<ComplexObject> objects { get; set; } // <- do not want
}
The JSON response object above isn't that complex, but for what I'm trying to accomplish I'll have to put a list of classes within a list of classes within a list of classes, and I can't develop it in this awkward way unless I spend a week on it which is just ridiculous.
I need to be able to return a JSON response in this kind of fashion:
// GET: api/ReturnJSON
public JSON_Response Get(string id)
{
// do some SQL querying here to grab the model or what have you.
if (somethingGoesWrong = true)
return {"result":"fail"}
else
return {"result":"success","value":"some value goes here"}
}
The design pattern above is what I'm trying to accomplish with ASP.NET web API, a very simply way to return a semi-hard coded JSON response object which would allow me to return very unique and dynamic responses from a single URI. There's going to be many use cases where a list of up to 8 completely unique Class objects will be returned.
Also, If what I'm trying to accomplish is the backwards way of doing things than that's fine. I've released a very successful and stable iOS application with a flawless Django backend server handling things this way perfectly without any issues.
Can someone explain to me how I can return a simple hard coded JSON response using the ASP.NET web API?
Thanks!
You can create anonymous types in C#, so you can use one of these to produce your hard-coded result. For example:
return new JsonResult
{
Data = new
{
result = "success",
value = "some value"
}
};
To clarify, the above code is for ASP.NET MVC. If you're using Web API, then you can just return the data object, or use an IHttpActionResult. The anonymous type part (the new {}) stays the same.
Use an anonymous object.
public object Get(string id)
{
// do some SQL querying here to grab the model or what have you.
if (somethingGoesWrong = true)
return new {result = "fail"}
else
return new {result = "success", value= "some value goes here"}
}
You can use a generic JObject to return your values without constructing a complete class structure as shown below
public JObject Get(int id)
{
return JsonConvert.DeserializeObject<JObject>(#"{""result"":""success"",""value"":""some value goes here""}");
}
For hard coded response, why not just do something like below. The JSON content will be returned without being surrounded by quotation marks.
public HttpResponseMessage Get()
{
string content = "Your JSON content";
return BuildResponseWithoutQuotationMarks(content);
}
private HttpResponseMessage BuildResponseWithoutQuotationMarks(string content)
{
var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(content);
return response;
}
private HttpResponseMessage BuildResponseWithQuotationMarks(string content)
{
var response = Request.CreateResponse(HttpStatusCode.OK, content);
return response;
}
// GET: api/ReturnJSON
public JsonResult Get()
{
return Json(new { Property1 = "Value1", Property2 = "Value2" });
}
You can return json using JsonResult class. and the Json() method takes anonymous object so you don't need to create a class.
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 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.