I have net6.0 project with minimal api and I would like to use NetwtonsoftJson instead of built in System.Text.Json library for serialization and deserialization.
At the moment I have this configurations for JsonOptions and that works as expected
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.SerializerOptions.WriteIndented = true;
options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
});
If I try to change to something equivalent that uses Newtonsoft.Json.JsonSerializerSettings like below I am not getting same behavior. Instead it looks like it uses default System.Text.Json configuration.
builder.Services.Configure<JsonSerializerSettings>(options =>
{
options.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
options.Converters.Add(
new StringEnumConverter
{
NamingStrategy = new Newtonsoft.Json.Serialization.CamelCaseNamingStrategy()
});
});
In net5.0 I know I could use this
services.AddControllers().AddNewtonsoftJson((options) => //options); // OR
services.AddMvc().AddNewtonsoftJson((options) => //options);
However, if I use it like above in my net6.0 project then I am not using anymore MinimalApi ?
From my understanding Minimal APIs rely on some conventions regarding type binding. From what I can see they search for method with next signature - ValueTask<TModel?> BindAsync(HttpContext context, ParameterInfo parameter) on the type otherwise will try to use httpContext.Request.ReadFromJsonAsync which internally uses System.Text.Json and that can't be changed, so services.Add...().AddNewtonsoftJson((options) => //options); approach will not work.
To use Newtonsoft.Json you can try next (other than directly handling request via app.MapPost("/pst", (HttpContext c) => c.Request...)):
If you have control over all your classes which needs to be deserialized using it you can inherit them all from some generic base class which will have the method with needed signature (also you can use interface with implemented static method):
public class BaseModel<TModel>
{
public static async ValueTask<TModel?> BindAsync(HttpContext context, ParameterInfo parameter)
{
if (!context.Request.HasJsonContentType())
{
throw new BadHttpRequestException(
"Request content type was not a recognized JSON content type.",
StatusCodes.Status415UnsupportedMediaType);
}
using var sr = new StreamReader(context.Request.Body);
var str = await sr.ReadToEndAsync();
return JsonConvert.DeserializeObject<TModel>(str);
}
}
And usage:
class PostParams : BaseModel<PostParams>
{
[JsonProperty("prop")]
public int MyProperty { get; set; }
}
// accepts json body {"prop": 2}
app.MapPost("/pst", (PostParams po) => po.MyProperty);
Note that BaseModel<TModel> implemenation in this example is quite naive and possibly can be improved (check out HttpRequestJsonExtensions.ReadFromJsonAsync at least).
If you don't have control over the models or don't want to inherit them from some base you can look into creating wrappers:
public class Wrapper<TModel>
{
public Wrapper(TModel? value)
{
Value = value;
}
public TModel? Value { get; }
public static async ValueTask<Wrapper<TModel>?> BindAsync(HttpContext context, ParameterInfo parameter)
{
if (!context.Request.HasJsonContentType())
{
throw new BadHttpRequestException(
"Request content type was not a recognized JSON content type.",
StatusCodes.Status415UnsupportedMediaType);
}
using var sr = new StreamReader(context.Request.Body);
var str = await sr.ReadToEndAsync();
return new Wrapper<TModel>(JsonConvert.DeserializeObject<TModel>(str));
}
}
And usage changes to:
class PostParams
{
[JsonProperty("prop")]
public int MyProperty { get; set; }
}
// accepts json body {"prop": 2}
app.MapPost("/pst", (Wrapper<PostParams> po) => po.Value.MyProperty);
Some extra useful links:
MVC model binders - by David Fowler. Though I was not able to make it work for services.AddControllers().AddNewtonsoftJson((options) => //options);
ParameterBinder - similar approach by Damian Edwards
My goal is to call api (via post), accept payload as base type and later cast it to concrete type. If I do that from main solution (where my api stands), everything works well. But I can't understand why same code doesn't work from other solutions.
So I have my request (declared in different solutions)
namespace Nb
{
public class NbRequestBase
{
public string BaseProp { get; set; }
}
public class NbRequestConcrete : NbRequestBase
{
public string ConcreteProp { get; set; }
}
}
And this is my endpoint:
[HttpPost]
[Route("payments/nb")]
public IHttpActionResult Prepare(NbRequestBase request)
{
if(request is NbRequestConcrete)
{
}
try
{
// <<< INSERT CODE HERE >>>
NbRequestConcrete nbRequestConcrete = (NbRequestConcrete)request;
return Ok();
}
catch (Exception ex)
{
_logger.Error(ex);
return InternalServerError();
}
}
and this is my calling code:
NbRequestConcrete requestTwo = new NbRequestConcrete()
{
BaseProp = "BaseProp",
ConcreteProp = "ConcreteProp"
};
using (var client = new HttpClient())
{
var _clientId = "_clientId";
var _clientSecret = "_clientSecret";
client.BaseAddress = new Uri("http://localhost:50228");
#region Formatter
JsonMediaTypeFormatter formatter = new JsonMediaTypeFormatter();
formatter.SerializerSettings.TypeNameHandling = TypeNameHandling.All;
List<MediaTypeFormatter> formatters = new List<MediaTypeFormatter>();
formatters.Add(formatter);
#endregion
var responseMessage = client.PostAsync($"payments/nb?clientId={_clientId}&clientSecret={_clientSecret}", requestTwo, formatter).Result;
responseMessage.EnsureSuccessStatusCode();
}
If I put my calling code into other project/solution (for example just new console app), API endpoint is hit, but payload is null.
payload when called form console app
If I put exacly same calling code into project where my api is (for example in same API endpoint method, at try/catch block start and call it again), API endpoint is hit, payload is NOT null and casting works. Why is it? And how to fix it?
payload when called from same solution try/catch start
And BTW. How to make this call via postman?
Regards
This line tells the model binder to set the values of any matching properties in request to the value that was passed to the API:
public IHttpActionResult Prepare(NbRequestBase request)
The model binder does not attach all the other properties to the request, because it has no idea what they would be.
Problem was Assemblies name where NbRequestConcrete in console app lived in one assembly and on API lived in other. So request was different.
{
"$type": "Nb.NbRequestConcrete, Tester",
"ConcreteProp": "ConcreteProp",
"BaseProp": "BaseProp"
}
VS
{
"$type": "Nb.NbRequestConcrete, MYApi",
"ConcreteProp": "ConcreteProp",
"BaseProp": "BaseProp"
}
I'm using Swashbuckle.Examples in Web API for better documentation. It is working fine for Swashbuckle Sample response but when I'm using Sample Example
When I run the project it is showing an Error.
My controller
[SwaggerResponse(HttpStatusCode.OK, Type = typeof(IEnumerable<ReasonReponsesuccessMessage_list>))]
[SwaggerResponseExample(HttpStatusCode.OK, typeof(IEnumerable<ReasonReponseSuccessExample_list>))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(IEnumerable<ReponseEmptyMessage>))]
[SwaggerOperation("List reasons")]
[ActionName("Reasons")]
[Route("api/{Id}")]
[HttpGet]
public HttpResponseMessage GetReasons(string Id)
{
}
Response Example Class
public class ReasonReponseSuccessExample_list : IExamplesProvider
{
object IExamplesProvider.GetExamples()
{
ReasonReponsesuccessMessage_list ReasonReponsesuccessMessage_list = new ReasonReponsesuccessMessage_list();
ReasonReponsesuccessMessage_list.Message = "Success";
ReasonReponsesuccessMessage_list.Data = new List<tbl_reason>
{
new tbl_reason{ id="SAA133",primary_name="Wrong Invoice",alt_name="Wrong Invoice"},
new tbl_reason{ id="B97123",primary_name="Payment Problem",alt_name=""}
};
ReasonReponsesuccessMessage_list.Extras = "";
ReasonReponsesuccessMessage_list.Success = true;
return ReasonReponsesuccessMessage_list;
}
}
ERROR:
Expected examplesProviderType to implement
Swashbuckle.Examples.IExamplesProvider.
System.Collections.Generic.IEnumerable`1[IgniteAPI.Payload.ReasonReponseSuccessExample_list]
does not.
I'm Getting this error in global.asmx
GlobalConfiguration.Configure(WebApiConfig.Register);
As you can see in the error, you need to specify type that implements
IExamplesProvider
Use
[SwaggerResponseExample(HttpStatusCode.OK, typeof(ReasonReponseSuccessExample_list))]
instead of
[SwaggerResponseExample(HttpStatusCode.OK, typeof(IEnumerable<ReasonReponseSuccessExample_list>))]
I am using
.NET Framework 4.6.1
Microsoft.AspNet.WebApi 5.2.4
ASP.NET uses Newtonsoft.Json 11.0.2
In Global.asax I specify that I want to use the SnakeCaseNamingStrategy for my JSON serialization:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
...
var formatters = GlobalConfiguration.Configuration.Formatters;
// Remove the xmlformatter
formatters.Remove(formatters.XmlFormatter);
// Ignore reference loops
formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Ignore;
// Use snake_case
formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver()
{
NamingStrategy = new SnakeCaseNamingStrategy()
};
}
}
This works great when returning OK and most other success indicating status codes (200-300):
[HttpGet, Route("api/Test")]
public IHttpActionResult Test()
{
return Ok(new { Message = "Hello World!" });
}
Returns:
{
"message": "Hello World!"
}
But, when returning any error code or exception, ASP.NET seems to ignore any formatter settings:
[HttpGet, Route("api/Test")]
public IHttpActionResult Test()
{
return BadRequest("Hello World!");
}
Returns:
{
"Message": "Hello World!" // 'Message' is not snake_case
}
And
[HttpGet, Route("api/Test")]
public IHttpActionResult Test()
{
var runtimeErroredArray = new int [2];
runtimeErroredArray[2] = 5; // runtime error
return Ok();
}
Returns:
{
"Message": "An error has occurred.",
"ExceptionMessage": "Index was outside the bounds of the array.", // Should be 'exception_message'
"ExceptionType": "System.IndexOutOfRangeException", // "System.IndexOutOfRangeException" can stay the same obviously, but 'ExceptionType' should be 'exception_type'
"StackTrace": "---"
}
I don't understand why this happens, but I would mainly like to know how to solve it.
The question:
Is there a way I can force exception messages and error code messages follow the GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver I set in Global.asax (using SnakeCaseNamingStrategy)?
EDIT:
I have tried setting JsonConvert.DefaultSettings in Global.asax, but that seemed to be ignored, even when returning an Ok response now.
New code in Global.asax:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
...
// Overwrite the default settings
JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
{
// Ignore reference loops
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
// Use snake_case
ContractResolver = new DefaultContractResolver()
{
NamingStrategy = new SnakeCaseNamingStrategy()
}
};
}
}
return Ok(new { Message = "Hello World!" }); (like in the first example) in action Test now returns:
{
"Message": "Hello World!"
}
JsonConvert.DefaultSettings Seems to be ignored completely by ASP.NET.
After quite some research I pinpointed some differences in the ASP.NET Mvc source code (see https://github.com/aspnet and https://github.com/ASP-NET-MVC) regarding returning success codes and failure codes.
These differences all had one crucial similarity which pointed me to the solution of this problem.
Explanation
In ASP.NET Mvc there are 2 extension methods for creating HttpResponseMessage's
CreateErrorResponse
CreateResponse
After looking at both methods I didn't think I saw anything that could cause returning error codes or throwing exceptions serialize incorrectly. But after looking at BadRequestErrorMessageResult, I found out CreateErrorResponse and BadRequestErrorMessageResult use the HttpError class to pass their information to the serializer.
Due to HttpError extending System.Collections.Generic.Dictionary<string,object> the serializer did not serialize the HttpError with the settings I specified, because Dictioneries are treated differently during serialization.
TL;DR
Whenever ASP.NET Mvc returns an error; it will use the HttpError class, which is a Dictionary<string, object>. Dictionaries have separate settings for serialization.
The Answer
You have to specify you want to parse Dictionary keys in your serialization settings:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// ...
var formatters = GlobalConfiguration.Configuration.Formatters;
// Remove the xmlformatter
formatters.Remove(formatters.XmlFormatter);
// Ignore reference loops
formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Ignore;
// Use snake_case
formatters.JsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver()
{
NamingStrategy = new SnakeCaseNamingStrategy()
{
ProcessDictionaryKeys = true // <--- Use SnakeCaseNamingStrategy for Dictionaries (So HttpError's)
}
};
}
}
Can some please help me to know how to pass multiple objects from a C# console app to Web API controller as shown below?
using (var httpClient = new System.Net.Http.HttpClient())
{
httpClient.BaseAddress = new Uri(ConfigurationManager.AppSettings["Url"]);
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = httpClient.PutAsync("api/process/StartProcessiong", objectA, objectB);
}
My Web API method is like this:
public void StartProcessiong([FromBody]Content content, [FromBody]Config config)
{
}
In the current version of Web API, the usage of multiple complex objects (like your Content and Config complex objects) within the Web API method signature is not allowed. I'm betting good money that config (your second parameter) is always coming back as NULL. This is because only one complex object can be parsed from the body for one request. For performance reasons, the Web API request body is only allowed to be accessed and parsed once. So after the scan and parsing occurs of the request body for the "content" parameter, all subsequent body parses will end in "NULL". So basically:
Only one item can be attributed with [FromBody].
Any number of items can be attributed with [FromUri].
Below is a useful extract from Mike Stall's excellent blog article (oldie but goldie!). You'll want to pay attention to item 4:
Here are the basic rules to determine whether a parameter is read with model binding or a formatter:
If the parameter has no attribute on it, then the decision is made purely on the parameter's .NET type. "Simple types" use model binding. Complex types use the formatters. A "simple type" includes: primitives, TimeSpan, DateTime, Guid, Decimal, String, or something with a TypeConverter that converts from strings.
You can use a [FromBody] attribute to specify that a parameter should be from the body.
You can use a [ModelBinder] attribute on the parameter or the parameter's type to specify that a parameter should be model bound. This attribute also lets you configure the model binder. [FromUri] is a derived instance of [ModelBinder] that specifically configures a model binder to only look in the URI.
The body can only be read once. So if you have 2 complex types in the signature, at least one of them must have a [ModelBinder] attribute on it.
It was a key design goal for these rules to be static and predictable.
A key difference between MVC and Web API is that MVC buffers the content (e.g. request body). This means that MVC's parameter binding can repeatedly search through the body to look for pieces of the parameters. Whereas in Web API, the request body (an HttpContent) may be a read-only, infinite, non-buffered, non-rewindable stream.
You can read the rest of this incredibly useful article on your own so, to cut a long story short, what you're trying to do is not currently possible in that way (meaning, you have to get creative). What follows is not a solution, but a workaround and only one possibility; there are other ways.
Solution/Workaround
(Disclaimer: I've not used it myself, I'm just aware of the theory!)
One possible "solution" is to use the JObject object. This objects provides a concrete type specifically designed for working with JSON.
You simply need to adjust the signature to accept just one complex object from the body, the JObject, let's call it stuff. Then, you manually need to parse properties of the JSON object and use generics to hydrate the concrete types.
For example, below is a quick'n'dirty example to give you an idea:
public void StartProcessiong([FromBody]JObject stuff)
{
// Extract your concrete objects from the json object.
var content = stuff["content"].ToObject<Content>();
var config = stuff["config"].ToObject<Config>();
. . . // Now do your thing!
}
I did say there are other ways, for example you can simply wrap your two objects in a super-object of your own creation and pass that to your action method. Or you can simply eliminate the need for two complex parameters in the request body by supplying one of them in the URI. Or ... well, you get the point.
Let me just reiterate I've not tried any of this myself, although it should all work in theory.
As #djikay mentioned, you cannot pass multiple FromBody parameters.
One workaround I have is to define a CompositeObject,
public class CompositeObject
{
public Content Content { get; set; }
public Config Config { get; set; }
}
and have your WebAPI takes this CompositeObject as the parameter instead.
public void StartProcessiong([FromBody] CompositeObject composite)
{ ... }
You could try posting multipart content from the client like this:
using (var httpClient = new HttpClient())
{
var uri = new Uri("http://example.com/api/controller"));
using (var formData = new MultipartFormDataContent())
{
//add content to form data
formData.Add(new StringContent(JsonConvert.SerializeObject(content)), "Content");
//add config to form data
formData.Add(new StringContent(JsonConvert.SerializeObject(config)), "Config");
var response = httpClient.PostAsync(uri, formData);
response.Wait();
if (!response.Result.IsSuccessStatusCode)
{
//error handling code goes here
}
}
}
On the server side you could read the the content like this:
public async Task<HttpResponseMessage> Post()
{
//make sure the post we have contains multi-part data
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
//read data
var provider = new MultipartMemoryStreamProvider();
await Request.Content.ReadAsMultipartAsync(provider);
//declare backup file summary and file data vars
var content = new Content();
var config = new Config();
//iterate over contents to get Content and Config
foreach (var requestContents in provider.Contents)
{
if (requestContents.Headers.ContentDisposition.Name == "Content")
{
content = JsonConvert.DeserializeObject<Content>(requestContents.ReadAsStringAsync().Result);
}
else if (requestContents.Headers.ContentDisposition.Name == "Config")
{
config = JsonConvert.DeserializeObject<Config>(requestContents.ReadAsStringAsync().Result);
}
}
//do something here with the content and config and set success flag
var success = true;
//indicate to caller if this was successful
HttpResponseMessage result = Request.CreateResponse(success ? HttpStatusCode.OK : HttpStatusCode.InternalServerError, success);
return result;
}
}
I know this is an old question, but I had the same issue and here is what I came up with and hopefully will be useful to someone. This will allow passing JSON formatted parameters individually in request URL (GET), as one single JSON object after ? (GET) or within single JSON body object (POST). My goal was RPC-style functionality.
Created a custom attribute and parameter binding, inheriting from HttpParameterBinding:
public class JSONParamBindingAttribute : Attribute
{
}
public class JSONParamBinding : HttpParameterBinding
{
private static JsonSerializer _serializer = JsonSerializer.Create(new JsonSerializerSettings()
{
DateTimeZoneHandling = DateTimeZoneHandling.Utc
});
public JSONParamBinding(HttpParameterDescriptor descriptor)
: base(descriptor)
{
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
HttpActionContext actionContext,
CancellationToken cancellationToken)
{
JObject jobj = GetJSONParameters(actionContext.Request);
object value = null;
JToken jTokenVal = null;
if (!jobj.TryGetValue(Descriptor.ParameterName, out jTokenVal))
{
if (Descriptor.IsOptional)
value = Descriptor.DefaultValue;
else
throw new MissingFieldException("Missing parameter : " + Descriptor.ParameterName);
}
else
{
try
{
value = jTokenVal.ToObject(Descriptor.ParameterType, _serializer);
}
catch (Newtonsoft.Json.JsonException e)
{
throw new HttpParseException(String.Join("", "Unable to parse parameter: ", Descriptor.ParameterName, ". Type: ", Descriptor.ParameterType.ToString()));
}
}
// Set the binding result here
SetValue(actionContext, value);
// now, we can return a completed task with no result
TaskCompletionSource<AsyncVoid> tcs = new TaskCompletionSource<AsyncVoid>();
tcs.SetResult(default(AsyncVoid));
return tcs.Task;
}
public static HttpParameterBinding HookupParameterBinding(HttpParameterDescriptor descriptor)
{
if (descriptor.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<JSONParamBindingAttribute>().Count == 0
&& descriptor.ActionDescriptor.GetCustomAttributes<JSONParamBindingAttribute>().Count == 0)
return null;
var supportedMethods = descriptor.ActionDescriptor.SupportedHttpMethods;
if (supportedMethods.Contains(HttpMethod.Post) || supportedMethods.Contains(HttpMethod.Get))
{
return new JSONParamBinding(descriptor);
}
return null;
}
private JObject GetJSONParameters(HttpRequestMessage request)
{
JObject jobj = null;
object result = null;
if (!request.Properties.TryGetValue("ParamsJSObject", out result))
{
if (request.Method == HttpMethod.Post)
{
jobj = JObject.Parse(request.Content.ReadAsStringAsync().Result);
}
else if (request.RequestUri.Query.StartsWith("?%7B"))
{
jobj = JObject.Parse(HttpUtility.UrlDecode(request.RequestUri.Query).TrimStart('?'));
}
else
{
jobj = new JObject();
foreach (var kvp in request.GetQueryNameValuePairs())
{
jobj.Add(kvp.Key, JToken.Parse(kvp.Value));
}
}
request.Properties.Add("ParamsJSObject", jobj);
}
else
{
jobj = (JObject)result;
}
return jobj;
}
private struct AsyncVoid
{
}
}
Inject binding rule inside WebApiConfig.cs's Register method:
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.ParameterBindingRules.Insert(0, JSONParamBinding.HookupParameterBinding);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
This allows for controller actions with default parameter values and mixed complexity, as such:
[JSONParamBinding]
[HttpPost, HttpGet]
public Widget DoWidgetStuff(Widget widget, int stockCount, string comment="no comment")
{
... do stuff, return Widget object
}
example post body:
{
"widget": {
"a": 1,
"b": "string",
"c": { "other": "things" }
},
"stockCount": 42,
"comment": "sample code"
}
or GET single param (needs URL encoding)
controllerPath/DoWidgetStuff?{"widget":{..},"comment":"test","stockCount":42}
or GET multiple param (needs URL encoding)
controllerPath/DoWidgetStuff?widget={..}&comment="test"&stockCount=42
Create one complex object to combine Content and Config in it as others mentioned, use dynamic and just do a .ToObject(); as:
[HttpPost]
public void StartProcessiong([FromBody] dynamic obj)
{
var complexObj= obj.ToObject<ComplexObj>();
var content = complexObj.Content;
var config = complexObj.Config;
}
Best way to pass multiple complex object to webapi services is by using tuple other than dynamic, json string, custom class.
HttpClient.PostAsJsonAsync("http://Server/WebService/Controller/ServiceMethod?number=" + number + "&name" + name, Tuple.Create(args1, args2, args3, args4));
[HttpPost]
[Route("ServiceMethod")]
[ResponseType(typeof(void))]
public IHttpActionResult ServiceMethod(int number, string name, Tuple<Class1, Class2, Class3, Class4> args)
{
Class1 c1 = (Class1)args.Item1;
Class2 c2 = (Class2)args.Item2;
Class3 c3 = (Class3)args.Item3;
Class4 c4 = (Class4)args.Item4;
/* do your actions */
return Ok();
}
No need to serialize and deserialize passing object while using tuple.
If you want to send more than seven complex object create internal tuple object for last tuple argument.
Here's another pattern that may be useful to you. It's for a Get but the same principle and code applies for a Post/Put but in reverse. It essentially works on the principle of converting objects down to this ObjectWrapper class which persists the Type's name to the other side:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace WebAPI
{
public class ObjectWrapper
{
#region Public Properties
public string RecordJson { get; set; }
public string TypeFullName { get; set; }
#endregion
#region Constructors
public ObjectWrapper() : this(null, null)
{
}
public ObjectWrapper(object objectForWrapping) : this(objectForWrapping, null)
{
}
public ObjectWrapper(object objectForWrapping, string typeFullName)
{
if (typeFullName == null && objectForWrapping != null)
{
TypeFullName = objectForWrapping.GetType().FullName;
}
else
{
TypeFullName = typeFullName;
}
RecordJson = JsonConvert.SerializeObject(objectForWrapping);
}
#endregion
#region Public Methods
public object ToObject()
{
var type = Type.GetType(TypeFullName);
return JsonConvert.DeserializeObject(RecordJson, type);
}
#endregion
#region Public Static Methods
public static List<ObjectWrapper> WrapObjects(List<object> records)
{
var retVal = new List<ObjectWrapper>();
records.ForEach
(item =>
{
retVal.Add
(
new ObjectWrapper(item)
);
}
);
return retVal;
}
public static List<object> UnwrapObjects(IEnumerable<ObjectWrapper> objectWrappers)
{
var retVal = new List<object>();
foreach(var item in objectWrappers)
{
retVal.Add
(
item.ToObject()
);
}
return retVal;
}
#endregion
}
}
In the REST code:
[HttpGet]
public IEnumerable<ObjectWrapper> Get()
{
var records = new List<object>();
records.Add(new TestRecord1());
records.Add(new TestRecord2());
var wrappedObjects = ObjectWrapper.WrapObjects(records);
return wrappedObjects;
}
This is the code on the client side (UWP) using a REST client library. The client library just uses the Newtonsoft Json serialization library - nothing fancy.
private static async Task<List<object>> Getobjects()
{
var result = await REST.Get<List<ObjectWrapper>>("http://localhost:50623/api/values");
var wrappedObjects = (IEnumerable<ObjectWrapper>) result.Data;
var unwrappedObjects = ObjectWrapper.UnwrapObjects(wrappedObjects);
return unwrappedObjects;
}
Basically you can send complex object without doing any extra fancy thing. Or without making changes to Web-Api. I mean why would we have to make changes to Web-Api, while the fault is in our code that's calling the Web-Api.
All you have to do use NewtonSoft's Json library as following.
string jsonObjectA = JsonConvert.SerializeObject(objectA);
string jsonObjectB = JsonConvert.SerializeObject(objectB);
string jSoNToPost = string.Format("\"content\": {0},\"config\":\"{1}\"",jsonObjectA , jsonObjectB );
//wrap it around in object container notation
jSoNToPost = string.Concat("{", jSoNToPost , "}");
//convert it to JSON acceptible content
HttpContent content = new StringContent(jSoNToPost , Encoding.UTF8, "application/json");
var response = httpClient.PutAsync("api/process/StartProcessiong", content);
Here I found a workaround to pass multiple generic objects (as json) from jquery to a WEB API using JObject, and then cast back to your required specific object type in api controller. This objects provides a concrete type specifically designed for working with JSON.
var combinedObj = {};
combinedObj["obj1"] = [your json object 1];
combinedObj["obj2"] = [your json object 2];
$http({
method: 'POST',
url: 'api/PostGenericObjects/',
data: JSON.stringify(combinedObj)
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
alert("Saved Successfully !!!");
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
alert("Error : " + response.data.ExceptionMessage);
});
and then you can get this object in your controller
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public [OBJECT] PostGenericObjects(object obj)
{
string[] str = GeneralMethods.UnWrapObjects(obj);
var item1 = JsonConvert.DeserializeObject<ObjectType1>(str[0]);
var item2 = JsonConvert.DeserializeObject<ObjectType2>(str[1]);
return *something*;
}
I have made a generic function to unwrap the complex object, so there is no limitation of number of objects while sending and unwrapping. We can even send more than two objects
public class GeneralMethods
{
public static string[] UnWrapObjects(object obj)
{
JObject o = JObject.Parse(obj.ToString());
string[] str = new string[o.Count];
for (int i = 0; i < o.Count; i++)
{
string var = "obj" + (i + 1).ToString();
str[i] = o[var].ToString();
}
return str;
}
}
I have posted the solution to my blog with a little more description with simpler code to integrate easily.
Pass multiple complex objects to Web API
I hope it would help someone. I would be interested to hear from the experts here regarding the pros and cons of using this methodology.
Late answer, but you can take advantage of the fact that you can deserialize multiple objects from one JSON string, as long as the objects don't share any common property names,
public async Task<HttpResponseMessage> Post(HttpRequestMessage request)
{
var jsonString = await request.Content.ReadAsStringAsync();
var content = JsonConvert.DeserializeObject<Content >(jsonString);
var config = JsonConvert.DeserializeObject<Config>(jsonString);
}
Create a Composite object
public class CollectiveObject<X, Y>
{
public X FirstObj;
public Y SecondObj;
}
initialize a composite object with any two objects which you willing to send.
CollectiveObject<myobject1, myobject2> collectiveobj =
new CollectiveObject<myobject1, myobject2>();
collectiveobj.FirstObj = myobj1;
collectiveobj.SecondObj = myobj2;
Do serialization
var req = JSONHelper.JsonSerializer`<CollectiveObject<myobject1, `myobject2>>(collectiveobj);`
`
your API must be like
[Route("Add")]
public List<APIAvailibilityDetails> Add([FromBody]CollectiveObject<myobject1, myobject2> collectiveobj)
{ //to do}