How do I configure a custom converter that returns json, instead of a string, in NewtonSoft.Json? - json.net

I have a simple model that contains a NetTopologySuite Polygon:
public class MyModel
{
[Newtonsoft.Json.JsonConverter(typeof(MyPolygonConverter))]
public NetTopologySuite.Geometries.Polygon poly { get; set; }
}
And I've build a custom converter, using NetTopologySuite.IO.GeoJson:
public class MyPolygonConverter : JsonConverter<Polygon>
{
public override void WriteJson(JsonWriter writer, Polygon value, JsonSerializer serializer)
{
var geoJsonSerializer = NetTopologySuite.IO.GeoJsonSerializer.Create();
using var stringWriter = new StringWriter();
using var jsonWriter = new JsonTextWriter(stringWriter);
geoJsonSerializer.Serialize(jsonWriter, value);
var geoJson = stringWriter.ToString();
writer.WriteValue(geoJson);
}
...
}
And then I create a model object and serialize it:
var model = new MyModel
{
poly = new Polygon(new LinearRing(new[]
{
new Coordinate(-100, 45),
new Coordinate(-98, 45),
new Coordinate(-99, 46),
new Coordinate(-100, 45),
}))
};
var geoJson = JsonConvert.SerializeObject(model, Formatting.None,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
});
And what I get contains the Polygon as an escaped GeoJson string:
{
"poly" : "{\"type\":\"Polygon\",\"coordinates\":[[[-100.0,45.0],[-98.0,45.0],[-99.0,46.0],[-100.0,45.0]]]}"
}
What I want is the Polygon as json, in the json object:
{
"poly" : {
"type" : "Polygon",
"coordinates" : [
[
[-100.0,45.0],
[-98.0,45.0],
[-99.0,46.0],
[-100.0,45.0]
]
]
}
}
What I'm thinking is that I shouldn't be using a JsonConverter at all, but something else.
But what?

You can use JsonWriter.WriteRawValue(string) to write a raw JSON value directly to the JSON output:
writer.WriteRawValue(geoJson);
That being said, GeoJsonSerializer is a subclass of Newtonsoft's own JsonSerializer so you can just pass the incoming JsonWriter in to its Serialize() method:
public override void WriteJson(JsonWriter writer, Polygon value, JsonSerializer serializer)
{
var geoJsonSerializer = NetTopologySuite.IO.GeoJsonSerializer.Create();
geoJsonSerializer.Serialize(writer, value);
}
Demo fiddle #1 here.
The only difference between GeoJsonSerializer and the base class is that NullValueHandling.Ignore is enabled by default and several converters are added, so you could also just use this serializer to serialize your entire model, thereby eliminating the need to add [Newtonsoft.Json.JsonConverter(typeof(MyPolygonConverter))] to your model:
var serializer = NetTopologySuite.IO.GeoJsonSerializer.CreateDefault(
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented, // Or none, if you prefer
});
var sb = new StringBuilder();
using (var sw = new StringWriter(sb))
using (var jsonWriter = new JsonTextWriter(sw))
serializer.Serialize(jsonWriter, model);
var geoJson = sb.ToString();
Demo fiddle #2 here.

Related

IActionSelectorDecisionTreeProvider namespace could not be found in .Net 5

I want to create an extension method of #Html.Action But Showing Error for IActionSelectorDecisionTreeProvider could not load namespace
var actionSelector = GetServiceOrFail<IActionSelectorDecisionTreeProvider>(currentHttpContext);
Error Screen Short
I have installed all dependencies of dot.net 5 like
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Routing;
But No Luck.
Working Code In Dot Net 5
public static class HtmlHelperViewExtensions
{
public static IHtmlContent RenderAction(this IHtmlHelper helper, string action, object parameters = null)
{
var controller = (string)helper.ViewContext.RouteData.Values["controller"];
return RenderAction(helper, action, controller, parameters);
}
public static IHtmlContent RenderAction(this IHtmlHelper helper, string action, string controller, object parameters = null)
{
var area = (string)helper.ViewContext.RouteData.Values["area"];
return RenderAction(helper, action, controller, area, parameters);
}
public static IHtmlContent RenderAction(this IHtmlHelper helper, string action, string controller, string area, object parameters = null)
{
if (action == null)
throw new ArgumentNullException("action");
if (controller == null)
throw new ArgumentNullException("controller");
//if (area == null)
// throw new ArgumentNullException("area");
var task = RenderActionAsync(helper, action, controller, area, parameters);
return task.Result;
}
private static async Task<IHtmlContent> RenderActionAsync(this IHtmlHelper helper, string action, string controller, string area, object parameters = null)
{
// fetching required services for invocation
var currentHttpContext = helper.ViewContext?.HttpContext;
var httpContextFactory = GetServiceOrFail<IHttpContextFactory>(currentHttpContext);
var actionInvokerFactory = GetServiceOrFail<IActionInvokerFactory>(currentHttpContext);
var actionSelector = GetServiceOrFail<IActionDescriptorCollectionProvider>(currentHttpContext);
// creating new action invocation context
var routeData = new RouteData();
var routeParams = new RouteValueDictionary(parameters ?? new { });
var routeValues = new RouteValueDictionary(new { area = area, controller = controller, action = action });
var newHttpContext = httpContextFactory.Create(currentHttpContext.Features);
newHttpContext.Response.Body = new MemoryStream();
foreach (var router in helper.ViewContext.RouteData.Routers)
routeData.PushState(router, null, null);
routeData.PushState(null, routeValues, null);
routeData.PushState(null, routeParams, null);
var actionDescriptor = actionSelector.ActionDescriptors.Items.Where(i => i.RouteValues["controller"] == controller && i.RouteValues["action"] == action).First();
var actionContext = new ActionContext(newHttpContext, routeData, actionDescriptor);
// invoke action and retreive the response body
var invoker = actionInvokerFactory.CreateInvoker(actionContext);
string content = null;
await invoker.InvokeAsync().ContinueWith(task => {
if (task.IsFaulted)
{
content = task.Exception.Message;
}
else if (task.IsCompleted)
{
newHttpContext.Response.Body.Position = 0;
using (var reader = new StreamReader(newHttpContext.Response.Body))
content = reader.ReadToEnd();
}
});
return new HtmlString(content);
}
private static TService GetServiceOrFail<TService>(HttpContext httpContext)
{
if (httpContext == null)
throw new ArgumentNullException(nameof(httpContext));
var service = httpContext.RequestServices.GetService(typeof(TService));
if (service == null)
throw new InvalidOperationException($"Could not locate service: {nameof(TService)}");
return (TService)service;
}
}

How to use #Html.Action instead of ViewComponent in ASP.NET Core

Is there any way to implement #Html.Action method in ASP.NET Core (like it was in ASP.NET MVC)? I know about the ViewComponent feature of ASP.NET Core. But there is a scenario when we have to use Action method.
This is the way to implement this as a HtmlHelper extension.
You can use it as follows:
The last parameter is an anonymous type.
#Html.Action("Action");
#Html.Action("Action", new { string a = "a", int i = 5 }
#Html.Action("Action", "Controller");
#Html.Action("Action", "Controller", new (string a = "a", int i = 5 }
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Mvc.Rendering
{
public static class HtmlHelperViewExtensions
{
public static IHtmlContent Action(this IHtmlHelper helper, string action, object parameters = null)
{
var controller = (string)helper.ViewContext.RouteData.Values["controller"];
return Action(helper, action, controller, parameters);
}
public static IHtmlContent Action(this IHtmlHelper helper, string action, string controller, object parameters = null)
{
var area = (string)helper.ViewContext.RouteData.Values["area"];
return Action(helper, action, controller, area, parameters);
}
public static IHtmlContent Action(this IHtmlHelper helper, string action, string controller, string area, object parameters = null)
{
if (action == null)
throw new ArgumentNullException(nameof(controller));
if (controller == null)
throw new ArgumentNullException(nameof(action));
var task = ActionAsync(helper, action, controller, area, parameters);
return task.Result;
}
private static async Task<IHtmlContent> ActionAsync(this IHtmlHelper helper, string action, string controller, string area, object parameters = null)
{
// fetching required services for invocation
var currentHttpContext = helper.ViewContext.HttpContext;
var httpContextFactory = GetServiceOrFail<IHttpContextFactory>(currentHttpContext);
var actionInvokerFactory = GetServiceOrFail<IActionInvokerFactory>(currentHttpContext);
var actionSelector = GetServiceOrFail<IActionDescriptorCollectionProvider>(currentHttpContext);
// creating new action invocation context
var routeData = new RouteData();
var routeParams = new RouteValueDictionary(parameters ?? new { });
var routeValues = new RouteValueDictionary(new { area, controller, action });
var newHttpContext = httpContextFactory.Create(currentHttpContext.Features);
newHttpContext.Response.Body = new MemoryStream();
foreach (var router in helper.ViewContext.RouteData.Routers)
routeData.PushState(router, null, null);
routeData.PushState(null, routeValues, null);
routeData.PushState(null, routeParams, null);
var actionDescriptor = actionSelector.ActionDescriptors.Items.First(i => i.RouteValues["Controller"] == controller && i.RouteValues["Action"] == action);
var actionContext = new ActionContext(newHttpContext, routeData, actionDescriptor);
// invoke action and retreive the response body
var invoker = actionInvokerFactory.CreateInvoker(actionContext);
string content = null;
await invoker.InvokeAsync().ContinueWith(task =>
{
if (task.IsFaulted)
{
content = task.Exception.Message;
}
else if (task.IsCompleted)
{
newHttpContext.Response.Body.Position = 0;
using (var reader = new StreamReader(newHttpContext.Response.Body))
content = reader.ReadToEnd();
}
});
return new HtmlString(content);
}
private static TService GetServiceOrFail<TService>(HttpContext httpContext)
{
if (httpContext == null)
throw new ArgumentNullException(nameof(httpContext));
var service = httpContext.RequestServices.GetService(typeof(TService));
if (service == null)
throw new InvalidOperationException($"Could not locate service: {nameof(TService)}");
return (TService)service;
}
}
}
While it does work and renders the content correctly, when you try to access the IHttpContextAccessor.HttpContext property afterwards it is empty for some reason. I.e. you are opening another controller method which has a reference to IHttpContextAccessor via dependency injection and try to access the HttpContext property after you have renderend an Html.Action-element inside a partial view for example. If I remove the Html.Action-Element, HttpContext is filled correctly. I assume it somehow destroys the context.

Is there a utility to serialise an object as HTTP content type "application/x-www-form-urlencoded"?

I've never had to do this before, because it's always only been an actual form that I've posted as that content type, but recently I had to post three variables like that, and I resorted to a sordid concatenation with & and =:
var content = new StringContent("grant_type=password&username=" + username + "&password=" + password.ToClearString(), Encoding.UTF8,
"application/x-www-form-urlencoded");
I'm sure there must be a utility method that would do that, and do it better, with any necessary encoding. What would that be?
If this is a POCO and just using the Newtonsoft library, you can use this as well:
public static class FormUrlEncodedContentExtension
{
public static FormUrlEncodedContent ToFormUrlEncodedContent(this object obj)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
var keyValues = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
var content = new FormUrlEncodedContent(keyValues);
return content;
}
}
And a sample usage would be:
var myObject = new MyObject {Grant_Type = "TypeA", Username = "Hello", Password = "World"};
var request = new HttpRequestMessage(HttpMethod.Post, "/path/to/post/to")
{
Content = myObject.ToFormUrlEncodedContent()
};
var client = new HttpClient {BaseAddress = new Uri("http://www.mywebsite.com")};
var response = await client.SendAsync(request);
Use reflection to get the property names and values and then use them to create a System.Net.Http.FormUrlEncodedContent
public static class FormUrlEncodedContentExtension {
public static FormUrlEncodedContent ToFormUrlEncodedContent(this object obj) {
var nameValueCollection = obj.GetType()
.GetProperties()
.ToDictionary(p => p.Name, p => (p.GetValue(obj) ?? "").ToString());
var content = new FormUrlEncodedContent(nameValueCollection);
return content;
}
}
From there it is a simple matter of calling the extension method on an object to convert it to a FormUrlEncodedContent
var model = new MyModel {
grant_type = "...",
username = "...",
password = "..."
};
var content = model.ToFormUrlEncodedContent();
You should be able to use string interpolation for that. Something like:
var content = new StringContent($"grant_type=password&username={username}&password={password}", Encoding.UTF8, "application/x-www-form-urlencoded");
Or wrap this inside a helper/factory method:
public static class StringContentFactory
{
public static StringContent Build(string username, string password)
{
return new StringContent($"grant_type=password&username={username}&password={password}", Encoding.UTF8, "application/x-www-form-urlencoded");
}
}

Will JsonContent get serialised multiple times using Json.NET in Web Api 2

If I have this controller:
public class SomeController : ApiController
{
public SomeInfoDto Get()
{
return new SomeInfoDto();
}
}
When I call /api/Some with a get request I will get JSON in the Content Body because JSON.NET has been kind enough to serialise it for me.
However, if I wanted to send a 500 HTTP code and some JSON I thought I could do this:
public HttpResponseMessage Get()
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = // not sure what to put here
}
}
So I googled to find out what to put for the Content and found this resource
public class JsonContent : HttpContent
{
private readonly JToken _value;
public JsonContent(JToken value)
{
_value = value;
Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
protected override Task SerializeToStreamAsync(Stream stream,
TransportContext context)
{
var jw = new JsonTextWriter(new StreamWriter(stream))
{
Formatting = Formatting.Indented
};
_value.WriteTo(jw);
jw.Flush();
return Task.FromResult<object>(null);
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
}
Which I can easily modify to fit my purposes.
However, my question is, if I get SerializeToStreamAsync to use JsonConvert.SerializeObject(_value) further down the Web Api pipeline will it be serialised again?
I have set the GlobalConfiguration.Configuration like this:
var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
settings.Formatting = Formatting.Indented;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
My Implementation for the SerializeToStreamAsync is this:
protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
using (var streamWriter = new StreamWriter(stream))
{
streamWriter.WriteAsync(JsonConvert.SerializeObject(this.value)).Wait();
streamWriter.FlushAsync().Wait();
}
return Task.FromResult<object>(null);
}
EDIT: Providing a concrete sample with formatter.
If you want to send JSON content with Http Status Code 500, you can use the extension method HttpRequestMessage.CreateResponse. There is no need for any advanced serialization and formatting. More info on that here.
https://msdn.microsoft.com/en-us/library/system.net.http.httprequestmessageextensions.createresponse%28v=vs.118%29.aspx
Request.CreateResponse(HttpStatusCode.InternalServerError, new string[] { "value1", "value2" }, new JsonMediaTypeFormatter())
(Or)
Request.CreateResponse(HttpStatusCode.InternalServerError, new string[] { "value1", "value2" }, 'application/json')

JavaScriptSerializer with custom Type

I have a function with a List return type. I'm using this in a JSON-enabled WebService like:
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public List<Product> GetProducts(string dummy) /* without a parameter, it will not go through */
{
return new x.GetProducts();
}
this returns:
{"d":[{"__type":"Product","Id":"2316","Name":"Big Something ","Price":"3000","Quantity":"5"}]}
I need to use this code in a simple aspx file too, so I created a JavaScriptSerializer:
JavaScriptSerializer js = new JavaScriptSerializer();
StringBuilder sb = new StringBuilder();
List<Product> products = base.GetProducts();
js.RegisterConverters(new JavaScriptConverter[] { new ProductConverter() });
js.Serialize(products, sb);
string _jsonShopbasket = sb.ToString();
but it returns without a type:
[{"Id":"2316","Name":"Big One ","Price":"3000","Quantity":"5"}]
Does anyone have any clue how to get the second Serialization work like the first?
Thanks!
When you create the JavaScriptSerializer, pass it an instance of SimpleTypeResolver.
new JavaScriptSerializer(new SimpleTypeResolver())
No need to create your own JavaScriptConverter.
Ok, I have the solution, I've manually added the __type to the collection in the JavaScriptConverter class.
public class ProductConverter : JavaScriptConverter
{ public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
Product p = obj as Product;
if (p == null)
{
throw new InvalidOperationException("object must be of the Product type");
}
IDictionary<string, object> json = new Dictionary<string, object>();
json.Add("__type", "Product");
json.Add("Id", p.Id);
json.Add("Name", p.Name);
json.Add("Price", p.Price);
return json;
}
}
Is there any "offical" way to do this?:)
Building on Joshua's answer, you need to implement a SimpleTypeResolver
Here is the "official" way that worked for me.
1) Create this class
using System;
using System.Web;
using System.Web.Compilation;
using System.Web.Script.Serialization;
namespace XYZ.Util
{
/// <summary>
/// as __type is missing ,we need to add this
/// </summary>
public class ManualResolver : SimpleTypeResolver
{
public ManualResolver() { }
public override Type ResolveType(string id)
{
return System.Web.Compilation.BuildManager.GetType(id, false);
}
}
}
2) Use it to serialize
var s = new System.Web.Script.Serialization.JavaScriptSerializer(new XYZ.Util.ManualResolver());
string resultJs = s.Serialize(result);
lblJs.Text = string.Format("<script>var resultObj = {0};</script>", resultJs);
3) Use it to deserialize
System.Web.Script.Serialization.JavaScriptSerializer(new XYZ.Util.ManualResolver());
var result = json.Deserialize<ShoppingCartItem[]>(jsonItemArray);
Full post here: http://www.agilechai.com/content/serialize-and-deserialize-to-json-from-asp-net/

Resources