With the built-in json converter I return multiple objects in my action like this:
return Json(new { success = true, data = units });
When I use the JSON.NET library how can I do the same?
This does obviously not compile:
return new { success = true, data = JsonConvert.SerializeObject(units) };
I do not want to create an extra viewmodel for this containing both properties.
Do I have a wrong understanding of the default Json javascript serializer maybe ?
If you want to use Newtonsoft.Json to serialise your objects, you can create a new ActionResult class and pass the data in the result.
For example:
public class NewtonsoftJsonResult : ContentResult
{
private readonly object _data;
public NewtonsoftJsonResult(object data)
{
_data = data;
}
public override void ExecuteResult(ControllerContext context)
{
Content = JsonConvert.SerializeObject(_data);
ContentType = "application/json";
base.ExecuteResult(context);
}
}
Just return your custom ActionResult with the anonymous object as data:
public ActionResult Index()
{
return new NewtonsoftJsonResult(new { success = true, data = units});
}
In your second example, JsonConvert.SerializeObject(units) will result in a string returned to JavaScript. JavaScript won't see data as containing some "real" data but rather a simple string, with curly parentheses inside.
Use your first sentence as usual. MVC's Json method will serialize the objects within.
For example:
class Units
{
public int Width { get; set; }
public int Height { get; set; }
}
...
Units u = new Units { Width = 34, Height = 20 };
return Json(new { success = true, data = units });
will result in a Json that looks similar to this:
{ "success" : "true", "data" : { "Height" : "20", "Width" : "34" } } }
Related
I have this component that displays generic messages:
<span>#message</span>
The messages are identified by an id and come from string tables in resources files (multiple languages). An example of a message would be:
"Hello {user}! Welcome to {site}!"
So in the basic case, I simply parse the string and replace {user} with, say, "John Doe" and {site} with "MySiteName". The result is set to message and is then properly (and safely) rendered.
But what I would like to do is actually replace {site} with a component that I created that displays the site name with special font and styling. I also have other cases where I want to replace special {markings} with components.
How would you approach this problem ? Is there a way to "insert" a component into a string and then insert the string "safely" to be rendered ? I say "safely" because portions of the final string may come from the DB and be inherently unsafe (like user's name) so inserting the string with something like #((MarkupString)message) does not seem safe.
EDIT:
Thanks to MrC aka Shaun Curtis from whom this final solution is greatly inspired. I marked his answer as the best one.
So I finally went with a scoped service that gets the strings from the resources files, parse them and return a list of RenderFragments that it gets from a component's static table. I use dynamic objects to send specific parameters to the RenderFragments when required.
I basically now get all the text of my app through this centralized mechanism.
Here is an example of an entry in a resource file string table:
Name: "welcome"; Value: "Welcome to {site:name} {0}!"
Here is how it is used in a component:
<h3><Localizer Key="notif:welcome" Data="#(new List<string>() { NotifModel.UserNames.First })"/></h3>
You can see the simplified component and service code below. I explicitely left out the validation and error checking code for simplicity.
#using MySite.Client.Services.Localizer
#inject ILocalizerService Loc
#foreach (var fragment in _fragments)
{
#fragment.Renderer(fragment.Item)
}
#code
{
private List<ILocalizerService.Fragment> _fragments;
public enum RendererTypes
{
Default,
SiteName,
SiteLink,
}
public static Dictionary<RendererTypes, RenderFragment<dynamic>> Renderers = new Dictionary<RendererTypes, RenderFragment<dynamic>>()
{
// NOTE: For each of the following items, do NOT insert a space between the end of the markup and the closing curly brace otherwise it will be rendered !!!
// Like here ↓↓
{ RendererTypes.Default, (model) => #<span>#(model as string)</span>},
{ RendererTypes.SiteName, (model) => #<MySiteNameComponent />},
{ RendererTypes.SiteLink, (model) => ##model.LinkTxt}
};
[Parameter]
public string Key { get; set; }
[Parameter]
public List<string> Data { get; set; }
protected override void OnParametersSet()
{
_fragments = Loc.GetFragments(Key, Data);
}
}
interface ILocalizerService
{
public struct Fragment
{
public Fragment(RenderFragment<dynamic> renderer)
: this(renderer, default)
{
}
public Fragment(RenderFragment<dynamic> renderer, dynamic item)
{
Renderer = renderer;
Item = item;
}
public RenderFragment<dynamic> Renderer { get; set; }
public dynamic Item { get; set; }
}
List<Fragment> GetFragments(string key, List<string> parameters);
}
internal sealed class LocalizerService : ILocalizerService
{
private readonly Dictionary<string, IStringLocalizer> _strLoc = new Dictionary<string, IStringLocalizer>();
public LocalizerService(IStringLocalizer<MySite.Shared.Resources.App> appLoc,
IStringLocalizer<MySite.Shared.Resources.Connection> connLoc,
IStringLocalizer<MySite.Shared.Resources.Notifications> notifLoc)
{
// Keep string localizers
_strLoc.Add("app", appLoc);
_strLoc.Add("conn", connLoc);
_strLoc.Add("notif", notifLoc);
}
public List<Fragment> GetFragments(string key, List<string> parameters)
{
var list = new List<Fragment>();
GetFragments(list, key, parameters);
return list;
}
private void GetFragments(List<Fragment> list, string key, List<string> parameters)
{
// First, get key tokens
var tokens = key.Split(':');
// Analyze first token
switch (tokens[0])
{
case "site":
// Format : {site:...}
ProcessSite(list, tokens, parameters);
break;
default:
// Format : {0|1|2|...}
if (uint.TryParse(tokens[0], out var paramIndex))
{
ProcessParam(list, paramIndex, parameters);
}
// Format : {app|conn|notif|...}
else if (_strLoc.ContainsKey(tokens[0]))
{
ProcessStringLocalizer(list, tokens, parameters);
}
break;
}
}
private void ProcessSite(List<Fragment> list, string[] tokens, List<string> parameters)
{
// Analyze second token
switch (tokens[1])
{
case "name":
// Format {site:name}
// Add name component
list.Add(new Fragment(Shared.Localizer.Renderers[Shared.Localizer.RendererTypes.SiteName]));
break;
case "link":
// Format {site:link:...}
ProcessLink(list, tokens, parameters);
break;
}
}
private void ProcessLink(List<Fragment> list, string[] tokens, List<string> parameters)
{
// Analyze third token
switch (tokens[2])
{
case "user":
// Format: {site:link:user:...}
ProcessLinkUser(list, tokens, parameters);
break;
}
}
private void ProcessLinkUser(List<Fragment> list, string[] tokens, List<string> parameters)
{
// Check length
var length = tokens.Length;
if (length >= 4)
{
string linkUrl;
string linkTxt;
// URL
// Format: {site:link:user:0|1|2|...}
// Retrieve handle from param
if (!uint.TryParse(tokens[3], out var paramIndex))
{
throw new ApplicationException("Invalid token!");
}
var userHandle = GetParam(paramIndex, parameters);
linkUrl = $"/user/{userHandle}";
// Text
if (length >= 5)
{
if (tokens[4].Equals("t"))
{
// Format: {site:link:user:0|1|2|...:t}
// Use token directly as text
linkTxt = tokens[4];
}
else if (uint.TryParse(tokens[4], out paramIndex))
{
// Format: {site:link:user:0|1|2|...:0|1|2|...}
// Use specified param as text
linkTxt = GetParam(paramIndex, parameters);
}
}
else
{
// Format: {site:link:user:0|1|2|...}
// Use handle as text
linkTxt = userHandle;
}
// Add link component
list.Add(new Fragment(Shared.Localizer.Renderers[Shared.Localizer.RendererTypes.SiteLink], new { LinkUrl = linkUrl, LinkTxt = linkTxt }));
}
}
private void ProcessParam(List<Fragment> list, uint paramIndex, List<string> parameters)
{
// Add text component
list.Add(new Fragment(Shared.Localizer.Renderers[Shared.Localizer.RendererTypes.Default], GetParam(paramIndex, parameters)));
}
private string GetParam(uint paramIndex, List<string> parameters)
{
// Proceed
if (paramIndex < parameters.Length)
{
return parameters[paramIndex];
}
}
private void ProcessStringLocalizer(List<Fragment> list, string[] tokens, List<string> parameters)
{
// Format {loc:str}
// Retrieve string localizer
var strLoc = _strLoc[tokens[0]];
// Retrieve string
var str = strLoc[tokens[1]].Value;
// Split the string in parts to see if it needs formatting
// NOTE: str is in the form "...xxx {key0} yyy {key1} zzz...".
// This means that once split, the keys are always at odd indexes (even if {key} starts or ends the string)
var strParts = str.Split('{', '}');
for (int i = 0; i < strParts.Length; i += 2)
{
// Get parts
var evenPart = strParts[i];
var oddPart = ((i + 1) < strParts.Length) ? strParts[i + 1] : null;
// Even parts are always regular text. If not null or empty, we add directly
if (!string.IsNullOrEmpty(evenPart))
{
list.Add(new Fragment(Shared.Localizer.Renderers[Shared.Localizer.RendererTypes.Default], evenPart));
}
// Odd parts are always keys. If not null or empty, get fragments recursively
if (!string.IsNullOrEmpty(oddPart))
{
GetFragments(list, oddPart, parameters);
}
}
}
}
You don't necessarily need to build components. A component is a c# class that emits a RenderFragment.
You could simply build RenderFragments for {site},... Here's a simple static class that shows two ways to do this:
namespace StackOverflowAnswers;
public static class RenderFragements
{
public static RenderFragment SiteName => (builder) =>
{
// Get the content from a service that's accessing a database and checking the culture info for language
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "p-2 bg-primary text-white");
builder.AddContent(2, "My Site");
builder.CloseElement();
};
public static RenderFragment GetSiteName(string sitename) => (builder) =>
{
// parse to make sure you're happy with the string
builder.OpenElement(0, "span");
builder.AddAttribute(1, "class", "p-2 bg-dark text-white");
builder.AddContent(2, sitename);
builder.CloseElement();
};
}
And here's an index page using them:
#page "/"
#using StackOverflowAnswers
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<div class=m-2>
The site name for this site is #(RenderFragements.GetSiteName("this site"))
</div>
#(RenderFragements.SiteName)
With the RenderFragment your writing c# code. You can run a parser to check the string before rendering it.
You could have a scoped service that gets the info from the database for the user and exposes a set of RenderFragments you then use in your pages/components.
I used regex to split the source at the tokens configured in TokenMappings. Token mappings could easily be loaded from a json source for example. To configure more "{markings}" just add more lines to the TokenMappings.
<StringParser Source="Hello {user}! Welcome to {site}!" />
StringParser.razor
#foreach (var subString in substrings)
{
if (tokens.Contains(subString))
{
var key = StripCurlyBrackets(subString);
<DynamicComponent Type=#(TokenMappings[key].Item1)
Parameters=#(TokenMappings[key].Item2) />
}
else
{
#subString
}
}
#code {
private Dictionary<string, (Type, Dictionary<string, object>?)> TokenMappings;
private string[] substrings;
private string[] tokens;
[Parameter]
public string Source { get; set; }
protected override void OnParametersSet()
{
var user = "John Doe"; // I would expect these are supplied via a signin context.
var site = "MySiteName"; //
TokenMappings = new Dictionary<string, (Type, Dictionary<string, object>?)>
{
{ "user", ( typeof(UserComponent), new Dictionary<string, object>{ { "User", user } } ) },
{ "site", ( typeof(SiteComponent), new Dictionary<string, object>{ { "Site", site } } ) }
};
var keys = TokenMappings.Keys.Select(a => a);
var pattern = keys.Select(key => $"({{(?:{key})}})").Aggregate((a, b) => a + "|" + b);
this.substrings = System.Text.RegularExpressions.Regex.Split(Source, pattern);
this.tokens = TokenMappings!.Keys.Select(key => $"{{{key}}}").ToArray();
base.OnParametersSet();
}
private string StripCurlyBrackets(string source)
{
return source
.Replace(oldValue: "{", newValue: string.Empty)
.Replace(oldValue: "}", newValue: string.Empty);
}
}
Yes MarkupString allows you to render html.
substrings :
I have json like following
{"data": [
{
"instance": { ...
"inner"" {....
.............}
}
}]
"isvalid":true
"nextVal" : <some num>
}
and POJO like
class A{
private String data;
private boolean isvalid;
private String nextVal;
//with getter setters and proper jackson annotations
}
These can have variable structure inside data, so with object mapper.read I want to take entire data object in string!
have tried direct serialization to my simple object which obviously gives error and also tried JSONNode
mapper.readValue(jsonString, JsonNode.class);
String content = node.get("data").textValue();
This returns blank
anyway I can achieve that to take entire data object value in string with objectmapper?
I tried toString and returned just fine what I wanted - entire data object as String
JsonNode node = (ObjectNode) mapper.readValue(jsonString, JsonNode.class);
node.get("data").toString();
The reason it returns blank is because, data is an array. You need to deseralise it in to JsonArray. Assuming your JSON structure as below,
{
"data": [
{"instance": {
"inner": {
"id": "1"
}
}
}],
"isvalid": true,
"nextVal": 1
}
This will be deserialised using below code (in JSONNode),
List<JsonNode> list = node.findValues("data");
for(JsonNode n: list){
JsonNode in1 = n.findValue("instance");
JsonNode in2 = in1.findValue("inner");
String abc = in2.findValue("id").textValue();
System.out.println(abc);
}
You need to have the POJO structure as shown above. The data will be list of instance object. instance object will have to have inner object.
Update:
Outer node = mapper.readValue(jsonstr, Outer.class);
The classes which needs to be created would be as shown below.
public class Outer {
private List<Data> data;
Boolean valid;
Integer nextval;
public List<Data> getData() {
return data;
}
public void setData(List<Data> data) {
this.data = data;
}
public Boolean isValid() {
return valid;
}
public void setValid(Boolean valid) {
this.valid = valid;
}
public Integer getNextval() {
return nextval;
}
public void setNextval(Integer nextval) {
this.nextval = nextval;
}
}
public class Data {
Instance instance;
public Instance getInstance() {
return instance;
}
public void setInstance(Instance instance) {
this.instance = instance;
}
}
public class Instance {
private Inner inner;
public Inner getInner() {
return inner;
}
public void setInner(Inner inner) {
this.inner = inner;
}
}
public class Inner {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
My problem is that I wish to return camelCased (as opposed to the standard PascalCase) JSON data via ActionResults from ASP.NET MVC controller methods, serialized by JSON.NET.
As an example consider the following C# class:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
By default, when returning an instance of this class from an MVC controller as JSON, it'll be serialized in the following fashion:
{
"FirstName": "Joe",
"LastName": "Public"
}
I would like it to be serialized (by JSON.NET) as:
{
"firstName": "Joe",
"lastName": "Public"
}
How do I do this?
or, simply put:
JsonConvert.SerializeObject(
<YOUR OBJECT>,
new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
For instance:
return new ContentResult
{
ContentType = "application/json",
Content = JsonConvert.SerializeObject(new { content = result, rows = dto }, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }),
ContentEncoding = Encoding.UTF8
};
I found an excellent solution to this problem on Mats Karlsson's blog. The solution is to write a subclass of ActionResult that serializes data via JSON.NET, configuring the latter to follow the camelCase convention:
public class JsonCamelCaseResult : ActionResult
{
public JsonCamelCaseResult(object data, JsonRequestBehavior jsonRequestBehavior)
{
Data = data;
JsonRequestBehavior = jsonRequestBehavior;
}
public Encoding ContentEncoding { get; set; }
public string ContentType { get; set; }
public object Data { get; set; }
public JsonRequestBehavior JsonRequestBehavior { 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("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.");
}
var response = context.HttpContext.Response;
response.ContentType = !String.IsNullOrEmpty(ContentType) ? ContentType : "application/json";
if (ContentEncoding != null)
{
response.ContentEncoding = ContentEncoding;
}
if (Data == null)
return;
var jsonSerializerSettings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
response.Write(JsonConvert.SerializeObject(Data, jsonSerializerSettings));
}
}
Then use this class as follows in your MVC controller method:
public ActionResult GetPerson()
{
return new JsonCamelCaseResult(new Person { FirstName = "Joe", LastName = "Public" }, JsonRequestBehavior.AllowGet)};
}
For WebAPI, check out this link:
http://odetocode.com/blogs/scott/archive/2013/03/25/asp-net-webapi-tip-3-camelcasing-json.aspx
Basically, add this code to your Application_Start:
var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
I think this is the simple answer you are looking for. It's from Shawn Wildermuth's blog:
// Add MVC services to the services container.
services.AddMvc()
.AddJsonOptions(opts =>
{
opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
Add Json NamingStrategy property to your class definition.
[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
An alternative to the custom filter is to create an extension method to serialize any object to JSON.
public static class ObjectExtensions
{
/// <summary>Serializes the object to a JSON string.</summary>
/// <returns>A JSON string representation of the object.</returns>
public static string ToJson(this object value)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter> { new StringEnumConverter() }
};
return JsonConvert.SerializeObject(value, settings);
}
}
Then call it when returning from the controller action.
return Content(person.ToJson(), "application/json");
Simpler is better IMO!
Why don't you do this?
public class CourseController : JsonController
{
public ActionResult ManageCoursesModel()
{
return JsonContent(<somedata>);
}
}
The simple base class controller
public class JsonController : BaseController
{
protected ContentResult JsonContent(Object data)
{
return new ContentResult
{
ContentType = "application/json",
Content = JsonConvert.SerializeObject(data, new JsonSerializerSettings {
ContractResolver = new CamelCasePropertyNamesContractResolver() }),
ContentEncoding = Encoding.UTF8
};
}
}
You must set the settings in the file 'Startup.cs'
You also have to define it in the default values of JsonConvert, this is if you later want to directly use the library to serialize an object.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(options => {
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
}
Below is an action method that returns a json string (cameCase) by serializing an array of objects.
public string GetSerializedCourseVms()
{
var courses = new[]
{
new CourseVm{Number = "CREA101", Name = "Care of Magical Creatures", Instructor ="Rubeus Hagrid"},
new CourseVm{Number = "DARK502", Name = "Defence against dark arts", Instructor ="Severus Snape"},
new CourseVm{Number = "TRAN201", Name = "Transfiguration", Instructor ="Minerva McGonal"}
};
var camelCaseFormatter = new JsonSerializerSettings();
camelCaseFormatter.ContractResolver = new CamelCasePropertyNamesContractResolver();
return JsonConvert.SerializeObject(courses, camelCaseFormatter);
}
Note the JsonSerializerSettings instance passed as the second parameter. That's what makes the camelCase happen.
In ASP.NET Core MVC.
public IActionResult Foo()
{
var data = GetData();
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
return Json(data, settings);
}
I did like this :
public static class JsonExtension
{
public static string ToJson(this object value)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
};
return JsonConvert.SerializeObject(value, settings);
}
}
this a simple extension method in MVC core , it's going to give the ToJson() ability to every object in your project , In my opinion in a MVC project most of object should have the ability to become json ,off course it depends :)
If you are returning ActionResult in .net core web api, or IHttpAction result then you can just wrap up your model in an Ok() method which will match the case on your front end and serialise it for you. No need to use JsonConvert. :)
Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson
this solve my problem
I'm trying to pass data from JQuery to an MVC 4 controller. The controller gets invoked, but no data is passed. In the past I always just used form serialization, but that's not appropriate here.
My Controller:
[HttpPost]
public ActionResult Write(VideoSessionEnvelope envelope)
{
if (ModelState.IsValid)
{
envelope = Log.Write(envelope);
}
var result = Json(envelope);
return result;
}
We use an envelope class as a container for all view models
public class VideoSessionEnvelope : BaseEnvelope
{
public VideoSessionEnvelope()
{
SessionStart = new VideoSessionStartViewModel();
}
public Guid? LogEntryID { get; set; }
public VideoSessionStartViewModel SessionStart { get; set; }
}
}
The view model
public class VideoSessionStartViewModel: IViewModel
{
public string SessionId { get; set; }
public int UserId { get; set; }
public string Message { get; set; }
}
And finally the javascript
var Logging = Logging || {};
Logging.VideoSession = function () {
var Start = function (sessionId, userId, message) {
var envelope = {
SessionStart: {
"SessionId": sessionId,
"UserId": userId,
"Message": message
}
}
var data = JSON.stringify(envelope);
$.ajax({
type: "POST",
url: "/Logging/Write",
data: data,
datatype: "application/json",
success: function (result) {
return result;
},
error: function (request, status, error) {
return error;
}
});
};
return {
Start: Start
};
}();
According to Firebug the data is passed as
JSON
SessionStart Object { SessionId="sessionIdVal", UserId=123, Message="messageValue"}
Message "messageValue"
SessionId "sessionIdVal"
UserId 123
The controller gets called, but the properties in the view model are always null. I've tried several variations on the theme, nothing seems to work.
Try wrapping your data in a literal with the name as envelope so it will be picked up by the Model Binder:
data: { envelope: data },
UPDATE
Remove the call to JSON.stringify(), it is not strictly necessary to serialize the object literal.
I have a request endpoint using in web api:
public HttpResponseMessage Create(IList<SlideContent> l)
{
...
}
i send the parameter as a json and web api serializes it to IList
SlideContent is:
public abstract class SlideItem
{
...
}
and i have specialised classes
public class TitleSlideItem : SlideItem
{
...
}
public class ParagraphSlideItem : SlideItem
{
...
}
just like that i can't call the Create function, because i get a
missingmethodexception: cannot create abstract class
so i can't deserialize the json parameter. if i remove the abstract modifier, then i don't have specialized objects, every object's type will be SlideContent.
I even put annotations in the json, but it doesn't help either.
If i'm not wrong, the i would have to write a custom binder for the abstract class, but how can i do that?
Sincerely,
Zoli
One possibility is to substitute the built-in JSON serializer with a custom formatter using JSON.NET as shown in the following blog post.
public class JsonNetFormatter : MediaTypeFormatter
{
private JsonSerializerSettings _jsonSerializerSettings;
public JsonNetFormatter(JsonSerializerSettings jsonSerializerSettings)
{
_jsonSerializerSettings = jsonSerializerSettings ?? new JsonSerializerSettings();
// Fill out the mediatype and encoding we support
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
Encoding = new UTF8Encoding(false, true);
}
protected override bool CanReadType(Type type)
{
if (type == typeof(IKeyValueModel))
{
return false;
}
return true;
}
protected override bool CanWriteType(Type type)
{
return true;
}
protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext)
{
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
// Create task reading the content
return Task.Factory.StartNew(() =>
{
using (StreamReader streamReader = new StreamReader(stream, Encoding))
{
using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
{
return serializer.Deserialize(jsonTextReader, type);
}
}
});
}
protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext)
{
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
// Create task writing the serialized content
return Task.Factory.StartNew(() =>
{
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(stream, Encoding)) { CloseOutput = false })
{
serializer.Serialize(jsonTextWriter, value);
jsonTextWriter.Flush();
}
});
}
}
then in Application_Start when registering the formatter you could configure the serializer to use type information in the JSON:
var formatters = GlobalConfiguration.Configuration.Formatters;
formatters.Remove(formatters.XmlFormatter);
formatters.Remove(formatters.JsonFormatter);
var serializerSettings = new JsonSerializerSettings();
serializerSettings.TypeNameHandling = TypeNameHandling.Objects;
serializerSettings.Converters.Add(new IsoDateTimeConverter());
formatters.Add(new JsonNetFormatter(serializerSettings));
and then you could POST the following JSON:
[
{
"$type":"AppName.Models.TitleSlideItem, AppName",
"Id":1,
"Title":"some title" // this is a specific property of the TitleSlideItemclass
},
{
"$type":"AppName.Models.ParagraphSlideItem, AppName",
"Id":2,
"Paragraph":"some paragraph" // this is a specific property of the ParagraphSlideItem class
}
]
which will be successfully deserialized inside this action:
public HttpResponseMessage Post(IList<SlideItem> l)
{
...
}
JsonFx creates custom binders. For example take a look at:
https://code.google.com/p/jsonfx/source/browse/trunk/JsonFx/JsonFx.MvcTemplate/Global.asax.cs
Look at the RegisterBinders() function in that file and the source to the binders or just install JsonFx and create a project using their MVC template and see how they do it or just use their library if that would work for you. That's what I would do.