I have a call to a WebAPI with the following code:
var client = new HttpClient
{
BaseAddress = new Uri("http://localhost:8490/")
};
var jObject = new JObject();
jObject.Add("paramA", paramA);
jObject.Add("paramB", paramB);
JArray jArr = JArray.FromObject(paramsGenericArr);
jObject.Add("paramC", jArr);
var content = new StringContent(jObject.ToString(), Encoding.UTF8, "application/json");
var result = await client.PostAsync("api/path/tofunc", content).ConfigureAwait(false);
result.EnsureSuccessStatusCode();
The ParamsGeneric class is an abstract type with 2 derived classes:
[DataContract]
public class ParamsTypeA : ParamsGeneric
{
[DataMember]
public long itemC {get; set;}
public ParamsTypeA() :
base()
{}
}
[DataContract]
public class ParamsTypeB : ParamsGeneric
{
[DataMember]
public long itemD {get; set;}
public ParamsTypeB() :
base()
{}
}
[DataContract]
[KnownType(typeof(ParamsTypeA))]
[KnownType(typeof(ParamsTypeB))]
public abstract class ParamsGeneric
{
[DataMember]
public long itemA { get; set; }
[DataMember]
public long itemB {get; set;}
public ParamsGeneric()
{}
}
I suspect that I have a problem with the deserialization in the WebAPI:
public class ClientData
{
public string paramA { get; set; }
public string paramB { get; set; }
public ParamsGeneric[] paramC { get; set; }
}
[HttpPost]
[Route("api/path/tofunc")]
public async Task<bool> DoStuffAsync(ClientData clientData)
{
....
}
I have a problem with the paramsGenericArr/paramC (which is of type ParamsGeneric[], and holds items of type ParamsTypeA & ParamsTypeB)
The WebAPI receives a blank array (ParamsGeneric[0]), along with the other parameters.
Help will be appriciated.
UPDATE
Even if I try to pass a single ParamsGeneric object instead of an array, I receive null instead of the object.
SOLUTION
var serializer = new JsonSerializer();
serializer.TypeNameHandling = TypeNameHandling.Auto;
JArray jArr = JArray.FromObject(paramsGenericArr, serializer);
Did the trick.
While inheritance in messages / json is definitely possible, IMHO; it's just too much of a hassle :)
Anyway, you can actually let Newtonsoft.Json handle the inheritance for you, by setting TypeNameHandling
// Allow inheritance in json payload
JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.All;
.. or just
serializerSettings.TypeNameHandling = TypeNameHandling.Auto;
.. depending on your needs.
This is the easy fix, which will work fine if it's an internal API or if you can guarantee you will always be in control of the clients. If you have external clients, I would go the 'override default model binder'-approach such as what is posted here "Deserialising Json to derived types in Asp.Net Web API" - or very strongly consider avoiding inheritance in the model of the API.
Try to pass data to your API as below
Dictionary<string, string> param = new Dictionary<string, string>();
param.Add("paramA", paramA);
param.Add("paramB", paramB);
HttpClient client = new HttpClient();
HttpFormUrlEncodedContent contents = new HttpFormUrlEncodedContent(param);
var result = await client.PostAsync(new Uri("http://localhost:8490/api/path/tofunc")
, contents);
var reply = await result.Content.ReadAsStringAsync();
if (reply.IsSuccessStatusCode)
{
}
Hope this will help you.
[HttpPost]
[Route("api/path/tofunc")]
public async Task<bool> DoStuffAsync([FromBody]ClientData clientData)
{
....
}
Please keep [FromBody] in the web api method, so that model binder will map your body data to parameter i.e clientData.
Related
I know this has been asked here and at various other places but I have not seen a simple answer.
Or at least, I have not been able to find any.
In short, I have an .Net Core Web Api endpoint that accepts XML.
Using (in Startup):
services.AddControllers().AddXmlSerializerFormatters();
I want to modelbind it to a class. Example:
[Route("api/[controller]")]
[ApiController]
public class PersonController : ControllerBase
{
[HttpPost]
[Consumes("application/xml")]
[ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Post))]
public async Task<ActionResult> PostPerson([FromBody] Person person)
{
return Ok();
}
}
// Class/Model
[XmlRoot(ElementName = "Person")]
public class Person
{
[XmlElement(ElementName = "Name")]
public string Name { get; set; }
[XmlElement(ElementName = "Id")]
public int Id { get; set; }
}
Passing in:
<Person><Name>John</Name><Id>123</Id></Person>
works fine. However, as soon as namespaces comes into play it either fails to bind the model:
<Person xmlns="http://example.org"><Name>John</Name><Id>123</Id></Person>
<Person xmlns="http://example.org"><Name>John</Name><Id xmlns="http://example.org">123</Id></Person>
Or the model can be bound but the properties are not:
<Person><Name xmlns="http://example.org">John</Name><Id>123</Id></Person>
<Person><Name xmlns="http://example.org">John</Name><Id xmlns="http://example.org">123</Id></Person>
etc.
I understand namespaces. I do realize that I can set the namespaces in the XML attribute for the root and elements.
However, I (we) have a dozens of callers and they all set their namespaces how they want. And I want to avoid to have
dozens of different versions of the (in the example) Person classes (one for each caller). I would also mean that if a caller
changes their namespace(s) I would have to update that callers particular version and redeploy the code.
So, how can I modelbind incoming XML to an instance of Person without taking the namespaces into account?
I've done some tests overriding/creating an input formatter use XmlTextReader and set namespaces=false:
XmlTextReader rdr = new XmlTextReader(s);
rdr.Namespaces = false;
But Microsoft recommdes to not use XmlTextReader since .Net framework 2.0 so would rather stick to .Net Core (5 in this case).
You can use custom InputFormatter,here is a demo:
XmlSerializerInputFormatterNamespace:
public class XmlSerializerInputFormatterNamespace : InputFormatter, IInputFormatter, IApiRequestFormatMetadataProvider
{
public XmlSerializerInputFormatterNamespace()
{
SupportedMediaTypes.Add("application/xml");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var xmlDoc = await XDocument.LoadAsync(context.HttpContext.Request.Body, LoadOptions.None, CancellationToken.None);
Dictionary<string, string> d = new Dictionary<string, string>();
foreach (var elem in xmlDoc.Descendants())
{
d[elem.Name.LocalName] = elem.Value;
}
return InputFormatterResult.Success(new Person { Id = Int32.Parse(d["Id"]), Name = d["Name"] });
}
}
Person:
public class Person
{
public string Name { get; set; }
public int Id { get; set; }
}
startup:
services.AddMvc(options =>
{
options.RespectBrowserAcceptHeader = true; // false by default
options.InputFormatters.Insert(0, new XmlSerializerInputFormatterNamespace());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddXmlSerializerFormatters()
.AddXmlDataContractSerializerFormatters();
result:
So, in order to be able to modelbind XML to a class without taking namespaces into consideration I created new InputFormatter.
And I use XmlTextReader in order to ignore namespaces. Microsoft recommends to use XmlReader rather than XmlTextReader.
But since XmlTextReader is there still (in .Net 6.0 Preview 3) I'll use it for now.
Simply create an inputformatter that inherits from XmlSerializerInputFormatter like so:
public class XmlNoNameSpaceInputFormatter : XmlSerializerInputFormatter
{
private const string ContentType = "application/xml";
public XmlNoNameSpaceInputFormatter(MvcOptions options) : base(options)
{
SupportedMediaTypes.Add(ContentType);
}
public override bool CanRead(InputFormatterContext context)
{
var contentType = context.HttpContext.Request.ContentType;
return contentType.StartsWith(ContentType);
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
var type = GetSerializableType(context.ModelType);
var request = context.HttpContext.Request;
using (var reader = new StreamReader(request.Body))
{
var content = await reader.ReadToEndAsync();
Stream s = new MemoryStream(Encoding.UTF8.GetBytes(content));
XmlTextReader rdr = new XmlTextReader(s);
rdr.Namespaces = false;
var serializer = new XmlSerializer(type);
var result = serializer.Deserialize(rdr);
return await InputFormatterResult.SuccessAsync(result);
}
}
}
Then add it to the inputformatters like so:
services.AddControllers(o =>
{
o.InputFormatters.Add(new XmlNoNameSpaceInputFormatter(o));
})
.AddXmlSerializerFormatters();
Now we can modelbind Person or any other class no matter if there is namespaces or not in the incoming XML.
Thanks to #yiyi-you
I am trying to use Newtonsoft Json in my project. What I want to do is like below.
public class Subscribe<T> : BaseSubscribe where T : Message, new() // A class
message.body = JsonConvert.DeserializeObject <T.body>(receiveMessage); // In a class member functiton
I got an error. T is a generic variable so I can not initialize. Is there any ways to use json converter on generic variable?
thanks.
try this:
var message = JsonConvert.DeserializeObject<T>(receiveMessage);
var messageBody = message.body;
But please, share more of your code and the error.
If you want to be able to deserialise generic types using JsonConvert then you will need to help the deserialiser by supplying the concrete type name in your json. This is done using the TypeNameHandling flag on the JsonSerializerSettings object. Example below.
[TestFixture]
public class GenericJsonConvertFixture
{
public abstract class Employee
{
public string Name { get; set; }
}
public class Manager : Employee
{
}
public class Subscribe<T> where T : Employee
{
public T Employee { get; set; }
}
[Test]
public async Task TestDeserialisingGenericTypes()
{
var sub = new Subscribe<Employee>()
{
Employee = new Manager() { Name = "Elon" }
};
var json = JsonConvert.SerializeObject(sub, new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects
});
var newSub = JsonConvert.DeserializeObject(json, new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.Objects
});
Assert.That(newSub is Subscribe<Employee>);
Assert.That(((Subscribe<Employee>)newSub).Employee is Manager);
}
}
I have LoginModel:
public class LoginModel : IData
{
public string Email { get; set; }
public string Password { get; set; }
}
and I have the Web api method
public IHttpActionResult Login([FromBody] LoginModel model)
{
return this.Ok(model);
}
And it's return 200 and body:
{
Email: "dfdf",
Password: "dsfsdf"
}
But I want to get with lower first letter in property like
{
email: "dfdf",
password: "dsfsdf"
}
And I have Json contract resolver for correcting
public class FirstLowerContractResolver : DefaultContractResolver
{
protected override string ResolvePropertyName(string propertyName)
{
if (string.IsNullOrWhiteSpace(propertyName))
return string.Empty;
return $"{char.ToLower(propertyName[0])}{propertyName.Substring(1)}";
}
}
How I can apply this?
If your are using Newtonsoft.Json, you can add JsonProperties to your view model :
public class LoginModel : IData
{
[JsonProperty(PropertyName = "email")]
public string Email {get;set;}
[JsonProperty(PropertyName = "password")]
public string Password {get;set;}
}
To force all json data returned from api to camel case it's easier to use Newtonsoft Json with the default camel case contract resolver.
Create a class like this one:
using Newtonsoft.Json.Serialization;
internal class JsonContentNegotiator : IContentNegotiator
{
private readonly JsonMediaTypeFormatter _jsonFormatter;
public JsonContentNegotiator(JsonMediaTypeFormatter formatter)
{
_jsonFormatter = formatter;
_jsonFormatter.SerializerSettings.ContractResolver =
new CamelCasePropertyNamesContractResolver();
}
public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
return new ContentNegotiationResult(_jsonFormatter, new MediaTypeHeaderValue("application/json"));
}
}
and set this during api configuration (at startup):
var jsonFormatter = new JsonMediaTypeFormatter();
httpConfiguration.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator(jsonFormatter));
You can add the two following statement in the configuration of the web API or to the startup file
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented;
But it is very important to use the return Ok() method instead of return Json() otherwise; this will not work.
if you have to use Json method (and have no other choice)
then see this answer https://stackoverflow.com/a/28960505/4390133
If you need it only in some certain place and not throughout whole application, then you can do following:
var objectToSerialize = new {Property1 = "value1", SubOjbect = new { SubObjectId = 1 }};
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data, new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver() });
It should result in {"property1":"value1","subOjbect":{"subObjectId":1}} (note that nested properties also starts from lowercase)
We have got a Odata response as below:
"{\r\n \"#odata.context\":\"http://localhost/ApplicationService/model/$metadata#Edm.String\",\"value\":\"{\\\"Messages\\\":[\\\"message 1\\\",\\\"message 2\\\",\\\"message 3\\\",\\\"message 4\\\"],\\\"IsValidEntity\\\":false}\"\r\n}"
Now say we have a class:
public class myValidationResult
{
public myValidationResult()
{
Messages = new List<string>();
}
public List<string> Messages { get; set; }
public bool IsValidEntity { get; set; }
}
This class used in MyOdataController class as below:
public class MyODataController : ODataController
{
[Authorize(Roles = "Admin")]
public async Task<IHttpActionResult> Post(T entity)
{
myValidationResult vResult = new myValidationResult();
vResult.Messages.Add("message 1");
vResult.Messages.Add("message 2");
vResult.Messages.Add("message 3");
vResult.Messages.Add("message 4");
vResult.IsValidEntity = false;
var strResult = JsonConvert.SerializeObject(vResult);
var resp = Content(HttpStatusCode.BadRequest, strResult );
return resp;
}
}
For the client Consuming this, we created below Class:
public class OData<T>
{
[JsonProperty("odata.context")]
public string Metadata { get; set; }
public T value { get; set; }
}
In the method where we call the Odata method & store response in 'msg':
var resp = msg.Result.Content.ReadAsStringAsync().Result;
resp is:
"{\r\n \"#odata.context\":\"http://localhost/ApplicationService/model/$metadata#Edm.String\",\"value\":\"{\\\"Messages\\\":[\\\"message 1\\\",\\\"message 2\\\",\\\"message 3\\\",\\\"message 4\\\"],\\\"IsValidEntity\\\":false}\"\r\n}"
var odatares = JsonConvert.DeserializeObject<OData<myValidationResult>>(resp);
But the above line giving error:
Can not convert value\":\"{\\\"Messages\\\":[\\\"message 1\\\",\\\"message 2\\\",\\\"message 3\\\",\\\"message 4\\\"],\\\"IsValidEntity\\\":false} to <.....namespace......>myValidationResult
Please suggest accordingly.
The OData response contains a string, not an instance of myValidationResult. Also, the response looks like it's missing some backslashes. (Are you sure the response shown is exactly what you received from the service?)
You can either fix the serialization of myValidationResult on the service:
// Don't serialize vResult yourself. OData will do it for you.
var resp = Content(HttpStatusCode.BadRequest, vResult );
Or deserialize in two steps as follows.
var data = "{\r\n \"#odata.context\":\"http://localhost/ApplicationService/model/$metadata#Edm.String\",\"value\":\"{\\\"Messages\\\":[\\\"message 1\\\",\\\"message 2\\\",\\\"message 3\\\",\\\"message 4\\\"],\\\"IsValidEntity\\\":false}\"\r\n}";
var outer = Newtonsoft.Json.JsonConvert.DeserializeObject<OData<string>>(data);
var inner = Newtonsoft.Json.JsonConvert.DeserializeObject<myValidationResult>(outer.value);
One more thing: The JsonProperty on OData<T> should be named #odata.context.
In my case the OData response did not contain a string but an object array which contains the data string as its first element. So in this case reading the data should look like this:
var outer = Newtonsoft.Json.JsonConvert.DeserializeObject<OData<object[]>>(data);
var inner = Newtonsoft.Json.JsonConvert.DeserializeObject<myValidationResult>(outer.value[0].ToString());
problably I'm not experienced enought and my question is kind of dumb:
For learning purposes I'm trying to connect to a REST-Service, which delivers JSON-Data.
From what I've learned, the purpose of JSON is to deliver the same data to any possible client without having a State of itself.
My code is looking like this:
public static void DoSomething()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("SomeUrl"));
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// List data response.
HttpResponseMessage response = client.GetAsync("").Result;
if (response.IsSuccessStatusCode)
{
Task<Stream> readTask = response.Content.ReadAsStreamAsync();
readTask.ContinueWith(task =>
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(RootObject));
using (Stream result = task.Result)
{
result.Position = 0;
RootObject obj = (RootObject)ser.ReadObject(result);
}
});
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
}
public class Sum
{
public int id { get; set; }
public string name { get; set; }
public int profileIconId { get; set; }
public int summonerLevel { get; set; }
public long revisionDate { get; set; }
}
public class RootObject
{
public Sum khalgor { get; set; }
}
But here's my Problem: I've created this classes "Sum" and "RootObject" by using the Website http://json2csharp.com/, the JSON-String is looking like this:
{"khalgor":{"id":23801741,"name":"Khalgor","profileIconId":7,"summonerLevel":30,"revisionDate":1396876104000}}
The Problem: The Name "Khalgor" seems to be used as a Root-Object, but it's a Name. So if I'd like to user for another Name, I'd have to user another RootObject.
It does not make that much sense to create such a Structure, so my question: What's the best practice here? Do I map this RootObject/Property to another object manually? Do I use some Reflection to dynamically create an Property or rename it?
As usual, thanks a lot for all Responses
Matthias
Edit:
I tinkered arround a bit and that's my first idea of a solution:
public static class LOLObjectFactory
{
public static ILOLObject Create(string jsonString)
{
JavaScriptSerializer jss = new JavaScriptSerializer();
Dictionary<String, object> entry = (jss.Deserialize<dynamic>(jsonString) as Dictionary<string, object>).First().Value as Dictionary<String, object>;
Type selectedType = null;
List<string> fieldNames = entry.Select(f => f.Key).OrderBy(f => f).ToList();
Type[] types = typeof(ILOLObject).Assembly.GetTypes();
foreach(var type in types)
{
List<string> typeProperties = type.GetProperties().Select(f => f.Name).OrderBy(f => f).ToList();
if (fieldNames.SequenceEqual(typeProperties) && typeof(ILOLObject).IsAssignableFrom(type))
{
selectedType = type;
break;
}
}
ILOLObject result = System.Activator.CreateInstance(selectedType) as ILOLObject;
foreach(var prop in result.GetType().GetProperties())
{
prop.SetValue(result, entry.First(f => f.Key == prop.Name).Value);
}
return result;
}
}
So all the objects I have have the ILOLObject implemented. I'm sure it's not working for everything, but I guess that would be a good approach?
Edit2: Just by looking at it I see I'll have a lot of work to do, but I think the idea behind it is quite clear.
I think your best bet for json "fragments" is to deserialize into a dynamic object:
dynamic stuff = JsonConvert.DeserializeObject(inputData);
Then you can deserialize parts that make sense into proper .NET objects.
SomeObject o = JsonConvert.DeserializeObject<SomeObject>(stuff["someProperty"].ToString());
If you want to ignore the root altogether (e.g. it changes its name everytime) use Json.NET to parse it into an object and ignore the topmost element. Example:
JObject obj = JObject.Parse(json);
if (obj != null)
{
var root = obj.First;
if (root != null)
{
var sumJson = root.First;
if (sumJson != null)
{
var sum = sumJson.ToObject<Sum>();
}
}
}