I must create webhook endpoint that will consume JSON messages.
Messages is send as x-www-form-urlencoded in form:
key = json
value = {"user_Id": "728409840", "call_id": "1114330","answered_time": "2015-04-16 15:37:47"}
as shown in PostMan:
request looks like this:
json=%7B%22user_Id%22%3A+%22728409840%22%2C+%22call_id%22%3A+%221114330%22%2C%22answered_time%22%3A+%222015-04-16+15%3A37%3A47%22%7D
To get values from request as my class (model) I must create temporary object containing single string property:
public class Tmp
{
public string json { get; set; }
}
and method inside my controller that consumes that request:
[AllowAnonymous]
[Route("save_data")]
[HttpPost]
public IHttpActionResult SaveData(Tmp tmp)
{
JObject json2 = JObject.Parse(tmp.json);
var details = json2.ToObject<CallDetails>();
Debug.WriteLine(details);
//data processing
return Content(HttpStatusCode.OK, "OK", new TextMediaTypeFormatter(), "text/plain");
}
As You can see Tmp class is useless.
Is there a way to get request data as this class:
public class CallDetails
{
public string UserId { get; set; }
public string CallId { get; set; }
public string AnsweredTime { get; set; }
}
I'm aware of IModelBinder class, but before I start I'd like to know if there is an easier way.
I can't change web-request format, by format I mean that is will always be POST containing single key - JSON yhat has json string as value.
You can use JsonProperty attribute for mapping json object properties to c# object properties:
public class CallDetails
{
[JsonProperty("user_id")]
public string UserId { get; set; }
[JsonProperty("call_id")]
public string CallId { get; set; }
[JsonProperty("answered_time")]
public string AnsweredTime { get; set; }
}
Then it can be used without temp class:
[AllowAnonymous]
[Route("save_data")]
[HttpPost]
public IHttpActionResult SaveData(CallDetails callDetails)
Update. Because the data is sent as x-www-form-urlencoded - I think the way you handled it is most straightforward and not so bad. If you want to check another options here're some of them:
Option 1 - custom model binder. Something like this:
public class CustomModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var body = actionContext.Request.Content.ReadAsStringAsync().Result;
body = body.Replace("json=", "");
var json = HttpUtility.UrlDecode(body);
bindingContext.Model = JsonConvert.DeserializeObject<CallDetails>(json);
return true;
}
}
And usage: SaveData([ModelBinder(typeof(CustomModelBinder))]CallDetails callDetails). Downside - you'll lose validation and maybe other stuff defined in web api pipeline.
Option 2 - DelegatingHandler
public class NormalizeHandler : DelegatingHandler
{
public NormalizeHandler(HttpConfiguration httpConfiguration)
{
InnerHandler = new HttpControllerDispatcher(httpConfiguration);
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var source = await request.Content.ReadAsStringAsync();
source = source.Replace("json=", "");
source = HttpUtility.UrlDecode(source);
request.Content = new StringContent(source, Encoding.UTF8, "application/json");
return await base.SendAsync(request, cancellationToken);
}
}
Usage:
[AllowAnonymous]
[HttpPost]
public IHttpActionResult SaveData(CallDetails callDetails)
Downside - you'll need to define custom route for it:
config.Routes.MapHttpRoute(
name: "save_data",
routeTemplate: "save_data",
defaults: new { controller = "YourController", action = "SaveData" },
constraints: null,
handler: new NormalizeHandler(config)
);
You don´t forget to decode the url encoded before use JObject.Parse ?, it´s maybe works. And the properties of the object don´t match the json atributes
Json.NET by NewtonSoft can help you deserialize an object. If your json property names don't match your actual class names you can write a custom converter to help.
EDIT
You could try this if you are on MVC6. Change your parameter from type Tmp to type CallDetails and mark it with attribute [FromBody], like this:
public IHttpActionResult SaveData([FromBody]CallDetails details)
For example look at the section "Different model binding" in this post. However, I'm still thinking that you will need to deserialize manually because the property names of class CallDetails don't exactly match the incoming JSON properties.
Related
I've inherited a codebase from another developer, who has since left the company. I'm working with a very generic class that handles web requests / responses. The original design behind the class is that any action verb (post, put, get, etc) will all be handled by the SendContent method.
Here's an example of the Post method, which I've abridged here for clarity:
public Task<Result<TResult?>> Post<TResult, TPayload>(string endpoint, RequestHeaders headers, TPayload payload,
CancellationToken cancellationToken = default) where TResult : class
{
var postJson = JsonSerializer.Serialize(payload);
return SendContent<TResult?>(endpoint, HttpMethod.Post, headers,
new StringContent(postJson, Encoding.UTF8, "application/json"),cancellationToken);
}
Here's an example of the SendContent method, again, abridged for clarity:
protected async Task<Result<TResult?>> SendContent<TResult>(string endpoint, HttpMethod httpMethod,
RequestHeaders headers, HttpContent httpContent, CancellationToken cancellationToken) where TResult : class
{
// httpRequestMessage created here.
try
{
using var httpResponse =
await _httpClient.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false);
var jsonString = await httpResponse.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<TResult>(jsonString)
It's fine, it works.
The issue that I've recently found is what happens if the JSON returned doesn't match the schema of TResult in any way.
For example, say TResult is:
public class TestReturnObject
{
public int Id { get; set; }
public string Name { get; set; }
}
An instance of which would be serialized to:
{"Id":2,"Name":"Test"}
But say something were to be changed on the API side, either in error or on purpose, and what I get returned is actually this:
{"UniqueId":"b37ffcdb-36b0-4930-ae59-9ebaa2f4e996","IsUnknown":true}
In that instance:
var result = JsonSerializer.Deserialize<TResult>(jsonString)
The "result" object will be a new instance of TResult. All the properties will be null, because there's no match on any of the names / values.
I've looked at the System.Text.Json source starting here: https://github.com/dotnet/runtime/blob/f03470b9ef57df11db0040168e0a9776fd11bc6a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs#L47
It seems to me that the reason for this is that the code will instantiate the object and then map the properties. Obviously, at runtime, it has no idea the string isn't TestReturnObject.
Right now, I've got the calling code null checking various properties that are required. Is there a smarter way of handling it, or does the code just hit the limits of generics in C#?
You may implement IJsonOnDeserialized in your model then catch any extra properties with JsonExtensionDataAttribute
You may even throw an exception after such exists (or just add boolean property which would say that JSON contained them)
public class TestReturnObject : IJsonOnDeserialized
{
public int Id { get; set; }
public string Name { get; set; }
[JsonExtensionData]
public IDictionary<string, object>? ExtraProperties { get; set; }
void IJsonOnDeserialized.OnDeserialized()
{
if (ExtraProperties?.Count > 0)
{
throw new JsonException($"Contains extra properties: {string.Join(", ", ExtraProperties.Keys)}.");
}
}
}
here is working example
I have a method that receives some data from a 3rd party. The data is a JSON object (not a string, I tried receiving as a string and data was not accessable - the data property was null)
[HttpPost]
[Route("com/sendemail")]
public async Task<IActionResult> SendEmail(dynamic data)
{
mailData = JsonConvert.DeserializeObject<EmailTemplate>(data);
}
I am trying to get it into a .net object which is needed to be passed into another function I dont control. It has to be an EmailTemplate object, which is defined as:
public class EmailTemplate
{
public string From { get; set; }
public string To { get; set; }
public string Subject { get; set; }
public string EmailHtml { get; set; }
}
mailData is of type EmailTemplate. The Deserialize object call fails because that method requires a string, which this isnt. Ive tried other methods, such as
mailData = (EmailTemplate)data;
and
mailData.To = data.To
but neither work. Any pointers gratefully received.
PS. Heres what the data looks like in visual studio
Your controller couldn't accept a string, because (I assume) the request's content-type is 'application/json' and the framework couldn't convert it to a string. You should change your controller's data parameter type to EmailTemplate:
[HttpPost]
[Route("com/sendemail")]
public async Task<IActionResult> SendEmail([FromBody] EmailTemplate data)
{
//...
}
When your class matches the Json-object that is sent, this will work:
public async Task<IActionResult> SendEmail([FromBody]EmailTemplate data)
When you use your dynamic approach, you need to access the dynamic objects members and create your .NET object with them.
public async Task<IActionResult> SendEmail([FromBody]dynamic data)
{
mailData = new EmailTemplate {
From = data.From,
To = data.To,
Subject = data.Subject,
EmailHtml = data.EmailHtml
};
}
I am trying to define a Controller Action in ASP.NET Core 2.2.
The tricky part is that I prefer this to be a GET endpoint, and the data that it must recieve is a collection of custom objects. Here is my sample code:
[Route("api/example")]
[ApiController]
public class ExampleController : ControllerBase
{
[HttpGet("getData")]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
public async Task<IActionResult> GetMarketData([FromQuery] MyObject[] queryData)
{
return this.Ok(0);
}
}
public class MyObject
{
public int A { get; set; }
public int B { get; set; }
}
I am expecting this to bind to something like
http://localhost/api/example/getData/?queryData=[A=1,B=1],queryData=[A=2,B=2]
However I can't seem to get it to work.
(Sending a request to the URL, does not parse the objects, and I receive an empty array in my controller)
I'm not sure if this is the best way to approach this, and maybe I need to change the place where I bind the data from?
The only thing I care about is being able to recieve an array (or some kind of a collection) of MyObject that I can process and return a response. I would also prefer for this to be a GET request, as, after all, I am trying to query this API and get data from it.
I know I can get it to work with using a [FromBody] attribute, but as far as I know GET requests should not use the body.
Any help is gladly appreciated.
Your GET request must be constructed as follows:
GET: /api/example/getData?queryData[0].A=1&queryData[0].B=2&queryData[1].A=3
Very similar to model binding when using <form>s :)
Your QueryString should look like:
/TestMe?queryData[0].A=1&queryData[0].B=1&queryData[1].A=2&queryData[1].B=2
If your code looks like:
public class MyObject
{
public int A { get; set; }
public int B { get; set; }
}
[Route("/TestMe")]
public IActionResult TestMe([FromQuery] MyObject[] queryData)
{
return Json(queryData);
}
Note that [FromQuery] isn't even required.
it's not going to work since there's not a default binder for collection types, you'd have to use a custom binder. I made my own implementation of a generic array model binder, here it goes:
// generic array model binder
public class ArrayModelBinder<TType> : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext.ModelMetadata.IsEnumerableType) {
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ToString();
if (!string.IsNullOrEmpty(value)) {
var elementType = typeof(TType);
var typeConverter = TypeDescriptor.GetConverter(elementType);
var splittedValues = value.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
var values = splittedValues.Select(t => typeConverter.ConvertFromString(t.Trim())).ToArray();
var typedValues = Array.CreateInstance(elementType, values.Length);
values.CopyTo(typedValues, 0);
bindingContext.Model = typedValues;
return SuccessBinding(bindingContext, typedValues);
}
return SuccessBinding(bindingContext, null);
}
return FailedBinding(bindingContext);
}
private static Task SuccessBinding(ModelBindingContext bindingContext, Array typedValues) {
bindingContext.Result = ModelBindingResult.Success(typedValues);
return Task.CompletedTask;
}
private static Task FailedBinding(ModelBindingContext bindingContext) {
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
To use it on your Action you'll just have to use this piece of code:
public async Task<IActionResult> GetMarketData([ModelBinder(BinderType = typeof(ArrayModelBinder<object>))] MyObject[] queryData)
{
return this.Ok(0);
}
I have the source of this implementation and other things in a repo of my own Library feel free to check it out CcLibrary.AspNetCore
This is in OWIN & .Net 4.5.2
Using debug I'm proving this controller's method is being called by the web request.
My thing is the request body contains a JSON stringified object:
"{ 'id':'12', 'text1':'hello', 'test2':'world' }"
Which is applicably encoded as it is transferred on the line.
I've tried so many things I'm so confused now.
How do I get the decoded string so I can JSON.Parse() that or better yet get .Net to just given me an object?
In one version (long ago now) I had a defined type for this object. If I need that great, not a high challenge. But if I only have the JSON object from the string that's fine too.
public class cController : ApiController {
[HttpPut]
public string put(string id) {
var bdy = this.Request.Content;
//Console.WriteLine("PUT containers {0}", body);
return string.Empty;
}
}
In case it helps, the bdy.ContentReadStream is null. I don't know if this is good, bad, or important. Maybe Request.Content isn't the way to go but seems to me like if I'm going to read the body as a stream then it shouldn't be null.
I also tried working through System.Web.HttpContext. If that is somehow the answer I have no problem going back to that. But I couldn't find the secret sauce.
Pass the desired model as a parameter to the action and the frame work should be able to parse it provided it is valid JSON
public class cController : ApiController {
[HttpPut]
public IHttpActionResult Put(string id,[FromBody] Model body) {
if(ModelState.IsValue) {
return Ok(body.text1);
}
return BadRequest();
}
}
Where Model is defined as
public class Model {
public string id { get; set; }
public string text1 { get; set; }
public string test2 { get; set; }
}
I've seen some tutorials out there that claim to work, but they are outdated or simply do not work.
How can I use JSON.Net to serialize and deserialize the data received to and sent from my API controllers?
We are using VS2012.
Update
I have a model like this
public class SearchModel
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public Dictionary<string, object> Terms { get; set; }
}
And an Api controller like this
public class ModelSearchApiController : ApiController
{
public List<Model> Get([FromUri] SearchModel search)
{
return new List<Model>();
}
}
However, search provides the correct value set in the Ajax request, the property Terms is always an empty dictionary.
I know we can provide a value like [ { Key:"foo", Value:123 } ] but why can't I just pass a normal JSON object (ie { foo:123 }) ??? Why can it serialize a Dictionary into a nice standard JSON object, but cannot take that exact same object and recreate a Dictionary. This is beyound me.
Edit
In other words, if the browser sends these arguments :
pageIndex: 0
pageSize: 100
terms[foo]: Bar
terms[buz]: 1234
What would be the required object signature? Because the object mentionned above does not work and the dictionary is just empty.
JSON.NET is the default serializer for ASP.NET Web API - it can convert between JSON and CLR objects, and does so for all JSON input. However, you're not trying to convert a JSON input to your SearchModel - you're trying to convert from the URI-based format which is similar to application/x-www-form-urlencoded, into the CLR type SearchModel, and that is not supported by JSON.NET (it's not JSON!). In general, the serializers are used to convert (on incoming requests) from the request body to the action parameter.
Let's look at this (complete) example below (assuming the default route, to "api/{controller}"). It's very similar to your question, but I also added a Post method in addition to the GET method.
public class ModelSearchApiController : ApiController
{
public List<Model> Get([FromUri] SearchModel search)
{
return new List<Model>
{
new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
};
}
public List<Model> Post(SearchModel search)
{
return new List<Model>
{
new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
};
}
}
public class Model
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public Dictionary<string, object> Terms { get; set; }
}
public class SearchModel
{
public int PageIndex { get; set; }
public int PageSize { get; set; }
public Dictionary<string, object> Terms { get; set; }
}
If you send this request to the server:
POST http://localhost:64699/api/ModelSearchApi HTTP/1.1
User-Agent: Fiddler
Host: localhost:64699
Content-Type: application/json
Content-Length: 65
{"PageIndex":1,"PageSize":10,"Terms":{"foo":"bar","foo2":"bar2"}}
It will be bound, as you expect, to the SearchModel parameter - the Terms property will be a dictionary with two entries (foo=bar, foo2=bar2).
Now, for the GET parameter. ASP.NET Web API has a concept of model binders and value provider, which would be the component which would convert between the query string into the action parameters. The default binder / provider do not support the "arbitrary" name/value pair syntax *for dictionary inside complex types. You can, as you pointed out, use the key/value pair syntax, and that will be understood, as shown below.
GET http://localhost:64699/api/ModelSearchApi?PageIndex=1&PageSize=10&Terms[0][key]=foo&Terms[0][value]=bar HTTP/1.1
User-Agent: Fiddler
Host: localhost:64699
Now, for your problem you have two options. You can change your API to use a custom model binder or value provider which knows how to understand the "simple" name/value syntax, as shown below:
public class ModelSearchApiController : ApiController
{
public List<Model> Get([ModelBinder(typeof(MySearchModelBinder))] SearchModel search)
{
return new List<Model>
{
new Model { PageIndex = search.PageIndex, PageSize = search.PageSize, Terms = search.Terms }
};
}
}
public class MySearchModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
SearchModel value = new SearchModel();
value.Terms = new Dictionary<string,object>();
foreach (var queryParams in actionContext.Request.GetQueryNameValuePairs())
{
if (queryParams.Key == "PageIndex")
{
value.PageIndex = int.Parse(queryParams.Value);
}
else if (queryParams.Key == "PageSize")
{
value.PageSize = int.Parse(queryParams.Value);
}
else if (queryParams.Key.StartsWith("Terms."))
{
value.Terms.Add(queryParams.Key.Substring("Terms.".Length), queryParams.Value);
}
}
bindingContext.Model = value;
return true;
}
}
Another option is to pre-process your input data on the client prior to sending to the server, using a function similar to the one below.
function objToKVPArray(obj) {
var result = [];
var k;
for (k in obj) {
if (obj.hasOwnProperty(k)) {
result.push({ key: k, value: obj[k] });
}
}
return result;
}
You can take reference the link below. Hope this help.
And here is sample using Json.net with web API.