How to use Json.NET as default deserializer in an ASP.NET MVC 3+ application? - asp.net-mvc-3

There're a lot of resources on how to substitute the Json.NET library as the default serializer in ASP.NET MVC apps, but, for the life of me, I can't find a single resource on how to set it as the default deserializer.
To illustrate that a bit, here's some template code:
// how to use Json.NET when deserializing
// incoming arguments?
V
public ActionResult SomeAction ( Foo foo ) {
// this piece of code has lots of resources
// on how to override the default Javascript serializer
return Json(new Bar());
}
How do I tell my application to use Json.NET when deserializing incoming parameters in controller actions, say, from a jQuery AJAX call?
$.ajax({
type : 'POST',
data : { foo : 'bar' }
});
I've tried adapting MediaTypeFormatters into my code by adjusting this resource from Rick Strahl, but that didn't work either. Note that I'm not in a WebAPI environment --- but I expect that one solution that works on a normal Controller should work (albeit with minimal adjustments) in an ApiController.

You'll need to implement a custom ValueProviderFactory and remove the standard JsonValueProviderFactory.
There's some example code here: http://json.codeplex.com/discussions/347099 however reading through the comments, i'm not 100% sure that it will properly handle dictionaries.

Related

Is there a Best Practice for multiple Http Post - RPC style, custom MediaTypeFormatter or Other

I'm not looking to start a holy war but looking for maybe a document on best
practices for creating APIs which contain multiple HTTP verbs.
I inherited a 4.7.2 Web API project and trying to straighten out some things and
hopefully sometime in the future get to move it to .NET Core.
In the mean time I have a resource that will be gaining new functionality.
To keep this simple, let's just say it is a "document".
Currently there is a [HttpPost] that is used to create the document metadata.
The ask is to extend it to provide "Clone", "Split", "Merge" all of which are
creation types but all have a dependency on an existing document.
I can go the RPC style
api/document/{action}/{id}
api/document/{id}/{action}
which flies in the face of HTTP REST implementations.
The second implementation is a little better but that "action" is not really a "resource".
The other option I have is to have a single HttpPost that looks like the code at the bottom
and pass a custom content-type. If I go this route, I must implement a MediaTypeFormatter
or I get a exception generated. I currently have this implemented and working, it's just
going to be a maintenance nightmare when/if I start adding/changing to use this methodology.
As I said, I'm on 4.7.2 so ConsumesAttribute is not available to me.
The question I have is.... Is there a best practice????
Thoughts?
[HttpPost]
public async Task<IHttpActionResult> Post([FromBody] string metadata)
{
var ct = Request.Headers["Content-Type"]
switch (ct)
{
// Call operation that will handle that Content Type which
// will deserialize document into a specific object type
}
}
Action Content Type
Create application/json
Clone application/vnd.mycompany.create-from-existing.v1+json
Split application/vnd.mycompany.split-from-existing.v1+json
Merge application/vnd.mycompany.merge-existing.v1+json
public class MyContentTypeToStringFormatter : MediaTypeFormatter { }

Swashbuckle.AspNetCore [FromForm]IFormCollection

I have the following action in my .NET Core 2 controller. It is an API that should store all data posted as application/x-www-form-urlencoded
[HttpPost("data/add/{formid}"]
public void Add(int formid, [FromForm]IFormCollection formData) {
//do something
}
So the Swagger UI allows to try the action using UI:
Swagger UI
But the Swagger UI produces POST with body: formData=field1%3Dvalue1%26field2%3Dvalue2
I expect it to be: field1=value1&field2=value2
So the question is, is it a limitation of OpenAPI, or a bug of the SwaggerUI? Or maybe there is a way to get what I expect?
IFormCollection is a dynamic dictionary, so Swagger doesn't know how to handle it, since it effectively has no rules. That in an of itself should be enough to signal to you that this isn't something you should be using in a REST-based API. The whole point is to make the API self-documenting, which means it should accept the actual stuff it needs as strongly-typed params, not a generic data dump.
In other words, instead, do something like:
public void Add(int formid, [FromForm]DataClass data)
Where DataClass is an actual class with properties that match the names of the fields you're posting. Using a strongly-typed param does not preclude posting as application/x-www-form-urlencoded. There's really no good reason to ever use IFormCollection.

Model Binder of Json.Net not being used when i post an object

To clarify...
I configure my WebApiConfig like so:
config.Formatters.JsonFormatter.SerializerSettings.Binder = new TypeNameSerializationBinder("namespace.{0}, assembly");
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
This should allow me to bind derived classes to base class.
And binder does work when WebApi serializes objects to JSON, and sends them to client, but when I post them back to server, the binder isn't used (BindToType method never gets called), and my objects get bound to base class.
When i serialize/deserialize objects manually with this settings it all works fine.
Any ideas?
I had the same problem when trying to deserialize complex objects with a custom JsonConverters. I needed this because I'm using DbGeometry for storing users locations.
I broke my head on this a couple of days, I really thought I was doing something wrong, because every time I posted an geometry to the Web API, the complex type parameter was set to null. This while JsonConverter was perfectly able to convert the json to an filled object.
My workaround for this is written below. I don't like that I can't just use the parameter as I'm supposed to do. But it works, at last.
[HttpPost]
public MyComplexType SaveMyComplexType()
{
var json = Request.Content.ReadAsStringAsync().Result;
var myComplexType = JsonConvert.DeserializeObject<MyComplexType>(json);
//todo: validation and save to db
return myComplexType;
}
After some research, I found that this is a bug in ASP.NET Web Api. When the url encoded parameters are parsed, it just creates a new JsonSerializer (without passing global settings).
I filed it here
http://aspnetwebstack.codeplex.com/workitem/609

Problems deserializing Dictionarys in MVC 3 for AJAX request (an approach that works out of the box with classic ASP.NET Webforms)

I've been successfully WebForms for AJAX calls with relatively complex set of parameters (called using jQuery.ajax). We're attempting to try using the same approach in MVC 3 but seem to be falling at the first hurdle with MVC failing to deserialize Dictionary arrays successfully.
The approach that works without issue in ASP.NET WebForms "classic" is below:
[WebMethod]
public static JQGrid.JQGridData GetListForJQGrid(int? iPageSize, int? iPage, int? iMaxRecords, string sSortField, string sSortOrder,
Dictionary<string, string> dSearchOptions, Dictionary<string, object>[] aOriginalColumnDefinition, string[] aExtraDataColumns)
And below is the MVC 3 equivalent: (nb exactly the same name/parameters - different return type but I don't think that is relevant)
[HttpPost]
public JSONResult GetListForJQGrid(int? iPageSize, int? iPage, int? iMaxRecords, string sSortField, string sSortOrder,
Dictionary<string, string> dSearchOptions, Dictionary<string, object>[] aOriginalColumnDefinition, string[] aExtraDataColumns)
With the WebMethod all the data deserializes perfectly. However, when the MVC method is called all the simple parameters deserialize fine but for some unknown reason the array of Dictionary's arrives as an array of nulls.
So, off the back of that a number of questions:
Has anyone else experienced problems with MVC 3 deserialization of arrays of dictionaries?
Does MVC 3 by default not use System.Web.Script.Serialization.JavaScriptSerializer which is I think what ASP.NET WebMethods use under the bonnet?
Can I force MVC 3 to use System.Web.Script.Serialization.JavaScriptSerializer instead of what it is using?
Or am I missing something / should my approach be slightly different? Please note that at least for now we'll need to share the client side code between classic ASP.NET WebMethods and MVC 3 and so we want that to remain as is if possible.
Finally, I can see there is a possible workaround that could be used looking at this question: POST json dictionary . Is this workaround the only game in town or have things improved since this question was posed?
jQuery AJAX call:
$.ajax(_oJQGProperties.sURL, //URL of WebService/PageMethod used
{
data: JSON.stringify(oPostData),
type: "POST",
contentType: "application/json",
complete: DataCallback
});
Example JSON.stringify(oPostData):
{
"dSearchOptions":{},
"aOriginalColumnDefinition":
[
{"name":"ID","sortable":false,"hidedlg":true,"align":"right","title":false,"width":40},
{"name":"URL","sortable":false,"hidedlg":true,"align":"left","title":false,"width":250,"link":"javascript:DoSummat(this,'{0}');","textfield":"Name"},
{"name":"Description","sortable":false,"hidedlg":true,"align":"left","title":false,"width":620}
],
"aExtraDataColumns":["Name"],
"_search":false,
"iPageSize":-1,
"iPage":1,
"sSortField":"",
"sSortOrder":"",
"iMaxRecords":0
}
I don't have any experience with binding to a dictionary array, but one possible solution is to use a custom model binder. Scott Hanselman has a blog post on this subject that you might find useful: Splitting DateTime - Unit Testing ASP.NET MVC Custom Model.
Long time getting to update this but I thought I'd share where we got to. The problem turned out to be a bug - details of which can be found here:
Bug:
http://connect.microsoft.com/VisualStudio/feedback/details/636647/make-jsonvalueproviderfactory-work-with-dictionary-types-in-asp-net-mvc
Workaround:
POST json dictionary
We used the stated workaround which has been fine. I'm not too clear as to when the fix will be shipped and where exactly the bug lay. (Is it .NET dependant / MVC dependant etc) If anyone else knows I'd love to find out :-)
Update
I haven't heard still if this is shipped (I assume it goes out with MVC 4?) but in the interim this may be an alternative solution:
http://www.dalsoft.co.uk/blog/index.php/2012/01/10/asp-net-mvc-3-improved-jsonvalueproviderfactory-using-json-net/
Update 2
This has now been shipped as a fix with MVC 4. The issue remains unresolved in MVC 3 and so I've now written it up as a blog post here:
http://icanmakethiswork.blogspot.com/2012/10/mvc-3-meet-dictionary.html
I ran into this issue too. After finding this SO post, I thought about upgrading to MVC4, but it's too risky to do all at once in my environment so scratch that.
This link posted in Johnny Reilly's answer looked promising, but it required flattening my dictionary to a string. Because my MVC model is bidirectional (it's used for reads and writes), and I really wanted that dictionary structure I decided to pass on that too. It would have been a real pain to keep two properties for one value. I would have needed to add more tests, watch out for edge cases, etc.
Johnny's JsonValueProviderFactory link seemed promising too, but a bit arcane. I'm also not entirely comfortable monkeying around with a part of MVC like that. I had only a few hours to figure this problem out so I passed on this too.
Then I found this link somewhere, and thought "Yes! this is more like what I want!". In other words attack the model binding problem by using a custom binder. Replace the buggy one with something else, and use MVC's built-in capability to do so. Unfortunately, this did not work as my use case was List of T, and T was my model. This totally did not work with the sample. So I hacked away at it and ultimately failed.
Then, I got a lightbulb moment - JSON.NET does not have this problem. I use it all the time for doing all sorts of things, from cloning objects, to logging, to REST service endpoints. Why not model binding? So I ultimately ended up with this and my problem was solved. I think it should work with just about anything - I trust JSON.NET =)
/// <summary>
/// Custom binder that maps JSON data in the request body to a model class using JSON.NET.
/// </summary>
/// <typeparam name="T">Model type being bound</typeparam>
/// <remarks>
/// This binder is very useful when your MVC3 model contains dictionaries, something that it can't map (this is a known bug, fixed with MVC 4)
/// </remarks>
public class CustomJsonModelBinder<T> : DefaultModelBinder
where T : class
{
/// <summary>
/// Binds the model by using the specified controller context and binding context.
/// </summary>
/// <returns>
/// The bound object.
/// </returns>
/// <param name="controllerContext">The context within which the controller operates. The context information includes the controller, HTTP content, request context, and route data.</param><param name="bindingContext">The context within which the model is bound. The context includes information such as the model object, model name, model type, property filter, and value provider.</param><exception cref="T:System.ArgumentNullException">The <paramref name="bindingContext "/>parameter is null.</exception>
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
request.InputStream.Position = 0;
var input = new StreamReader(request.InputStream).ReadToEnd();
T modelObject = JsonConvert.DeserializeObject<T>(input);
return modelObject;
}
}
To apply the binder, I added an attribute to my model parameter. This causes MVC3 to use my binder instead of the default. Something like this:
public ActionResult SomeAction(
[ModelBinder(typeof(CustomJsonModelBinder<List<MyModel>>))] // This custom binder works around a known dictionary binding bug in MVC3
List<MyModel> myModelList, int someId)
{
One caveat - I was using POST with content type "application/json". If you're doing something like form or multipart data instead it will probably crash horribly.

Razor and Creating Helpers for Users: Html.*

I have an ASP.NET MVC3 (with Razor) application where I allow users to specify components for the view -- like the layout, or partials that I use to render content. (For example, I have a model that has a Title and Description, and I allow users to specify a CSHTML partial in a particular directory that I use to render the data.)
The problem that I'm running into is isolation and ease of use for my users. For example, they can currently write code in their partials like:
#Html.ActionLink("edit", "Edit", "Content", new { Id = Model.Id }, null)
I would instead like them to write something like:
#SomeHelper.EditLinkFor(Model.Id)
Right now, my controllers are all part of the public API. Users need to know controller and action names to specify certain links (eg. edit link). I would like to provide a simplified API.
But how do I do it? If I just create the SomeHelper class, trying to call #Html.ActionLink isn't making much progress. I tried writing:
return System.Web.Mvc.Html.LinkExtensions.ActionLink(null, content.Title, "Details", "Content", new { Id = content.Id }, null);
But I get a null pointer exception. Creating an HtmlHelper instance for the first parameter is not easy.
What are my options? Am I stuck with providing them full access to all my controllers as part of my API? Is there no way to provide a simplified API for their consumption? (I'm not trying to block them from writing Razor code; only encapsulate and simplify calls, and eliminate an underlying dependency on my controllers and models.)
There are a few possible options:
a) You can extend the HtmlHelper with your own class
b) Reusing #helpers accross multiple views
c) Change the base type of your views
The html helpers provided are pretty basic - but you certainly can create a helper. Just be careful - the existing API is provided and fits in exactly with the rest of the MVC programming model (Ajax and html helpers and the route syntax)
The problem is in how you call the code. Please post all of your code so we can check it out.
The question is exactly what do you want to do? You want to create a helper for
EditLinkFor - but something is failing, can we see all the code?

Resources