WebApi - Sent Guid taken as a new Guid - c#

I have an ApiController class with a method accepting a complex object.
public class SampleController : ApiController
{
[Route("api/Sample/")]
[HttpPost]
public HttpResponseMessage GetSampleInfo([FromBody]SampleClass sampleClassObject)
{
// Some code
}
}
When debugging, this Controller class method is called, but what was passed as a Guid in the object, shows a new Guid (as 00000000-0000-0000-0000-000000000000). I'm first checking this method with Postman. I tried passing the object using x-www-form-urlencoded and also as application/json.
What I'm passing in Postman:
{
"sampleID": "A9A999AA-AA99-9AA9-A999-9999999999AA",
"otherValue": 1
}
I checked other issues like mine but I have tried the solutions but still I'm getting the passed Guid as a new Guid.
P.S.
The SampleClass looks like as follows:
public class SampleClass
{
public Guid sampleID { get; set; }
public int otherValue { get; set; }
}
Update:
I have used the following JsonConverter.
public class GuidConverterCustom : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Guid) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
switch (reader.TokenType)
{
case JsonToken.Null:
return Guid.Empty;
case JsonToken.String:
string str = reader.Value as string;
if (string.IsNullOrEmpty(str))
{
return Guid.Empty;
}
else
{
return new Guid(str);
}
default:
throw new ArgumentException("Invalid token type");
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (Guid.Empty.Equals(value))
{
writer.WriteValue("");
}
else
{
writer.WriteValue((Guid)value);
}
}
}
And included the following in the Global.asax.cs:
JsonConvert.DefaultSettings = () =>
{
var settings = new JsonSerializerSettings();
settings.Converters.Add(new GuidConverterCustom());
return settings;
};

Your method should look like this, no need for custom JsonConverter.
[Route("sample")]
[HttpPost]
public IHttpActionResult PostGuid(SampleClass id)
{
return Ok();
}
It then works with both x-www-form-urlencoded and application/json. If you use json do not forget the header Content-Type: application/json.

Related

Storing original JSON string in deserialised JSON.NET objects

This is basically a follow-on to the question Newtonsoft Object → Get JSON string .
I have an object that looks like this:
[JsonConverter(typeof(MessageConverter))]
public class Message
{
public Message(string original)
{
this.Original = original;
}
public string Type { get; set; }
public string Original { get; set; }
}
My requirement is to store the original JSON string as part of the object when I initialise it. I have been able to (partially) successfully achieve this using a custom JsonConverter and then basically doing something like this in the JsonConverter:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
return null;
JObject obj = JObject.Load(reader);
return new Message(obj.ToString(Formatting.None))
{
Type = obj["type"].ToString()
};
}
However the problem I run into is when I try to inherit from Message with something like
public class CustomMessage : Message
{
public string Prop1 { get; set; }
}
For obvious reasons my code fails because it tries to return a new Message() not a new CustomMessage().
So short of implementing a big if statement with all my sub-types and manually binding using something like JObject["prop"].ToObject<T>() how can I successfully populate the Original property while still binding all my sub-type values using the default deserialisation?
NOTE: The reason this is being done is because the original message might contain data that isn't actually being bound so I can't just add a property which serialises the object as it stands.
The following solution works
One thing you can do is to decorate each child class by generic JsonConverter attribute.
[JsonConverter(typeof(MessageConverter<Message>))]
public class Message
{
public Message(string original)
{
this.Original = original;
}
public string Type { get; set; }
public string Original { get; set; }
}
[JsonConverter(typeof(MessageConverter<CustomMessage>))]
public class CustomMessage : Message
{
public CustomMessage(string original) : base(original)
{
}
public string Prop1 { get; set; }
}
public class MessageConverter<T> : JsonConverter where T : Message
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
return null;
JObject obj = JObject.Load(reader);
var customObject = JsonConvert.DeserializeObject<T>(obj.ToString(), new JsonSerializerSettings
{
ContractResolver = new CustomContractResolver()
});
customObject.Original = obj.ToString();
return customObject;
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
//This will remove our declared Converter
public class CustomContractResolver : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter (Type objectType)
{
return null;
}
}
And then you can use the same serializer for all of the child classes
CustomMessage x = JsonConvert.DeserializeObject<CustomMessage>("{\"type\":\"Test\",\"Prop1\":\"Prop1\"}");
Message y = JsonConvert.DeserializeObject<Message>("{\"type\":\"Test\"}");
Here is the output screen shot
I'm leaving the question open in the hope that someone comes up with a better answer but I've temporarily used the following solution to resolve my problem.
public static class MessageExtensions
{
public static T Deserialize<T>(this string message) where T : Message
{
T instance = Activator.CreateInstance<T>();
instance.Original = message;
JsonConvert.PopulateObject(message, instance);
return instance;
}
}

WEB API to return Nested Array JSON Result C#

Hi Request your help in Below Scenario.
I have a Requirement where in I want to return Response JSON from DotNet WEB API As below.
{ "Status":"OK", "Series":[["GREEN",40.5],["RED",12.8],["YELLOW",12.8]] }
Below are my Code Sample and Output.
public class Response
{
public string Status {get;set;}
public List<SampleData> Series {get;set;}
}
public class SampleData
{
public string ColorCode{get;set;}
public double ColorVal{get;set;}
}
API Get Method
public Response Get()
{
Response Objresponse = new Response();
Objresponse.Status ="OK";
List<SampleData> _sampleList = new List<SampleData>();
_sampleList.Add(new SampleData{ ColorCode="GREEN", ColorVal=40.5});
_sampleList.Add(new SampleData{ ColorCode="RED",ColorVal=12.8});
_sampleList.Add(new SampleData{ ColorCode="YELLOW",ColorVal=12.8});
Objresponse.Series = _sampleList;
return Objresponse;
}
JSON Output which is returned
{
"Status":"OK",
"Series":[
{"ColorCode":"GREEN","ColorVal":40.5},
{"ColorCode":"RED","ColorVal":12.8},
{"ColorCode":"YELLOW","ColorVal":12.8}
]
}
Request your help to return the output as per my requirement.
Thank you.......
Probably the easiest way would be to create a custom JsonConverter and transform your response inside to your required format:
public class ResponseConverter : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(Response);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => throw new NotImplementedException();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Response originalResponse = (Response)value;
writer.WriteStartObject();
writer.WritePropertyName(nameof(originalResponse.Status));
writer.WriteValue(originalResponse.Status);
writer.WritePropertyName(nameof(originalResponse.Series));
writer.WriteStartArray();
foreach (var val in originalResponse.Series)
{
writer.WriteStartArray();
writer.WriteValue(val.ColorCode);
writer.WriteValue(val.ColorVal);
writer.WriteEndArray();
}
writer.WriteEndArray();
writer.WriteEndObject();
}
}
Then, you can decorate your Response class with a special attribute to denote that it should be serialized with this converter:
[JsonConverter(typeof(ResponseConverter))]
public class Response
{
public string Status { get; set; }
public List<SampleData> Series { get; set; }
}

Custom Data Type for "HttpGet" Route in Asp.Net Web Api Project

I try to add a variable of "custom data type" in my HttpGet Route.
I have this code:
[HttpGet("{idObject}")]
public ResponseSchema Get(ObjectId idObject)
{
if (idObject == null) {
throw new BodyParseException();
}
var user = _usersLogic.GetById(idObject);
if (user == null) {
_response.Success = false;
_response.ErrorCode = "UserDoesNotExist";
}
else {
_response.Objects.Add(user);
}
return _response;
}
ObjectId is a Datatype defined in using MongoDB.Bson.
For the Json Serialization and Deserialization we already have the code to automatically convert on both sides. But can this be similarly done in the Url itself.
We are right now using this Mvc version:
"Microsoft.AspNet.Mvc": "6.0.0-beta8"
So the URL looks like this:
GET Users/55b795827572761a08d735ac
The code to parse it from "string" to "ObjectId" is:
ObjectId.TryParse(idString, out idObject);
The question is where to put that TryParse code. Because I need to tell ASP.NET how it should parse the idObject from String to ObjectId. Since the URL basically is a string.
For Post or Put JSON Payload I already found a solution. I know that this is something different. But Probably it is helpful to understand the scenario, or find a solution to this scenario:
public class EntityBaseDocument
{
[JsonConverter(typeof(ObjectIdConverter))]
public ObjectId Id { get; set; }
}
// Since we have this value converter. We can use ObjectId everywhere
public class ObjectIdConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value.ToString());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
return new ObjectId(token.ToObject<string>());
}
public override bool CanConvert(Type objectType)
{
return typeof(ObjectId).IsAssignableFrom(objectType);
}
}
This will bind it from Uri objects:
public ResponseSchema Get([FromUri]ObjectId idObject)
So: ?param1=something&param2=sometingelse
This will bind it from the body (e.g. a JSon object)
public ResponseSchema Get([FromBody]ObjectId idObject)
Or you can roll your own:
public ResponseSchema Get([ModelBinder(typeof(MyObjectBinder))]ObjectId idObject)
The example on asp.net of a model binder is:
public class GeoPointModelBinder : IModelBinder
{
// List of known locations.
private static ConcurrentDictionary<string, GeoPoint> _locations
= new ConcurrentDictionary<string, GeoPoint>(StringComparer.OrdinalIgnoreCase);
static GeoPointModelBinder()
{
_locations["redmond"] = new GeoPoint() { Latitude = 47.67856, Longitude = -122.131 };
_locations["paris"] = new GeoPoint() { Latitude = 48.856930, Longitude = 2.3412 };
_locations["tokyo"] = new GeoPoint() { Latitude = 35.683208, Longitude = 139.80894 };
}
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(GeoPoint))
{
return false;
}
ValueProviderResult val = bindingContext.ValueProvider.GetValue(
bindingContext.ModelName);
if (val == null)
{
return false;
}
string key = val.RawValue as string;
if (key == null)
{
bindingContext.ModelState.AddModelError(
bindingContext.ModelName, "Wrong value type");
return false;
}
GeoPoint result;
if (_locations.TryGetValue(key, out result) || GeoPoint.TryParse(key, out result))
{
bindingContext.Model = result;
return true;
}
bindingContext.ModelState.AddModelError(
bindingContext.ModelName, "Cannot convert value to Location");
return false;
}
}
I believe NikoliaDante's answer works if you have a route such as /api/users?id={{idHere}}. However, if you are looking to have more RESTful routes, the solution below will do the trick for you. I just tested this out in a Web API 2 application and it works well. This will handle the use case where you may have a route such as /api/users/{{userId}}/something/{{somethingId}}.
//Http Parameter Binding Magic
public class ObjectIdParameterBinding : HttpParameterBinding
{
public ObjectIdParameterBinding(HttpParameterDescriptor p) : base(p){ }
public override Task ExecuteBindingAsync(System.Web.Http.Metadata.ModelMetadataProvider metadataProvider, HttpActionContext actionContext, System.Threading.CancellationToken cancellationToken)
{
var value = actionContext.ControllerContext.RouteData.Values[Descriptor.ParameterName].ToString();
SetValue(actionContext, ObjectId.Parse(value));
var tsc = new TaskCompletionSource<object>();
tsc.SetResult(null);
return tsc.Task;
}
}
//Binding Attribute
public class ObjectIdRouteBinderAttribute : ParameterBindingAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return new ObjectIdParameterBinding(parameter);
}
}
//Controller Example
[Route("api/users/{id}")]
public async Task<User> Get([ObjectIdRouteBinder] ObjectId id)
{
//Yay!
}
ASP.NET Web API provides several approaches for do that. Take a look for
Parameter Binding in Web API documentation.
Summary:
FromUriAttribute - for simple DTO classes
TypeConverter - to help Web API treat your class as simple type
HttpParameterBinding - allow to create behaviour attribute
ValueProvider - for more complex case
IActionValueBinder - to write own parameter-binding process at all

Custom ActionFilterAttribute does not get called when the json payload is invalid

Trying to return a correct error message instead of the WebAPI default one {"Message":"The request is invalid.","ModelState" when Json deserialzation fails.
I implemented my custom ActionFilterAttribute:
internal class ValidateModelAttribute : ActionFilterAttribute {
public override void OnActionExecuting(HttpActionContext actionContext) {
if (!actionContext.ModelState.IsValid) {
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
}
I decorated my Controller method with this attribute:
[ValidateModelAttribute]
public async Task<HttpResponseMessage> Put(string username, string serviceId, [FromBody] Dictionary<string, string> jsonData)
{
// code
}
If I set a breakpoint in the OnActionExecuting it only breaks when the jsonData is parsed succesfully as a json. If the json is invalid it never gets into the filter, and the same error message is returned. So looks like this is done somewhere before but all the posts I found say that this should be the place to handle this.
Any idea what's wrong?
The attribute will never get called since deserialization is failing before the method is ever called, meaning that the attributes decorating the method are not called. You need a custom converter (the technique for handling cultures was borrowed from this answer).
public class Testee {}
public class Tester
{
[JsonConverter(typeof(CustomMesssageConverter<Testee>), "Custom Error Message")]
public Testee Testee { get; set; }
}
public class CustomMesssageConverter<T> : JsonConverter where T : new()
{
private string _customErrorMessage;
public CustomMesssageConverter(string customErrorMessage)
{
_customErrorMessage = customErrorMessage;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
if (reader.TokenType == JsonToken.Null)
return null;
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on type
var target = new T();
//Create a new reader for this jObject, and set all properties to match the original reader.
JsonReader jObjectReader = jObject.CreateReader();
jObjectReader.Culture = reader.Culture;
jObjectReader.DateParseHandling = reader.DateParseHandling;
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
// Populate the object properties
serializer.Populate(jObjectReader, target);
return target;
}
catch(Exception ex)
{
// log ex here
throw new Exception(_customErrorMessage);
}
}
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
}

Date cannot be serialized or deserialized

I want to serialize and deserialize Nullable DateTime to/from JSON but I do not want to annotate it with JsonConverterAttribute. However, I would like to keep it at once place in JsonSerializerSettings not bloating DTOs with those attributes keeping DTOs clean as usual.
Here is DTO:
public class Post
{
public DateTime? Created { get; set; }
}
Here is Custom JsonConverter:
internal class EpochDateTimeConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DateTime).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var t = (long)Convert.ToDouble(reader.Value.ToString());
return t.FromUnixTime();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
long ticks;
DateTime valueAsDate = (DateTime)value;
if (valueAsDate != DateTime.MinValue)
{
if (value is DateTime)
{
var epoc = new DateTime(1970, 1, 1);
var delta = (valueAsDate) - epoc;
if (delta.TotalSeconds < 0)
{
throw new ArgumentOutOfRangeException("Unix epoc starts January 1st, 1970");
}
ticks = (long)delta.TotalSeconds;
}
else
{
throw new Exception("Expected date object value.");
}
writer.WriteValue(ticks);
}
}
}
Here is the minimal repro:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace NameSpaceSample
{
public class Post
{
public DateTime? Created { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
var settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
Converters = new List<JsonConverter>
{
new EpochDateTimeConverter()
}
};
string postAsJson = JsonConvert.SerializeObject(new Post { Created = DateTime.UtcNow }, settings);
Console.WriteLine(postAsJson);// {"Created":"2015-09-17T17:15:06.6160689Z"}
var json = "{\"Created\":1442510191}";
Post post = JsonConvert.DeserializeObject<Post>(json, settings);//Exception here
Console.ReadKey();
}
}
}
The exception thrown at that line is:
JsonReaderException:
Error reading date. Unexpected token: Integer. Path 'Created', line 1, position 21.
NOTE:
I know this can be resolved by just annotating it with JsonConverterAttribute as below but I don't want to do that for aforementioned reason.
public class Post
{
[JsonConverter(typeof(EpochDateTimeConverter))]
public DateTime? Created { get; set; }
}
Figured it out on my own. I just had to change CanConvert function to following:
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
}
It is not very easy to find out. Putting my answer here to help others if they ever face this.
I give you my way from any object to another object so that you don't worry transfer anything ,thanks
public static T ConvertTo<T>(this object value)
{
T returnValue = default(T);
if (value is T)
{
returnValue = (T)value;
}
else
{
try
{
returnValue = (T)Convert.ChangeType(value, typeof(T));
}
catch (InvalidCastException)
{
returnValue = default(T);
}
}
return returnValue;
}
The CanConvert(Type objectType) method of the JsonConverter determines if that converter will be used for the current property that is being serialized/deserialized.
As the type of your property is DateTime? and that is not assignable from DateTime it returns false and the converter is then not being used.
You just need to change the method to the following:
public override bool CanConvert(Type objectType)
{
return typeof(DateTime?).IsAssignableFrom(objectType);
}

Categories

Resources