I have been trying to get the example from this link to work.
I'm trying to detect extra fields in a json request and return an error if they are present.
Here's what I have:
ApiController:
public class MyClassController : ApiController
{
public IHttpActionResult Add(MyClass myClass)
{
if (myClass.ContainsExtra)
{
return BadRequest(ModelState);
}
...
}
...
}
DynamicObject:
public class MyClass : DynamicObject
{
private Dictionary<string, object> fields =
new Dictionary<string, object>(
StringComparer.OrdinalIgnoreCase);
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public bool ContainsExtra { get; private set; }
[JsonIgnore]
public Dictionary<string, object> Extra
{
get { return fields; }
}
public override bool TryGetMember(GetMemberBinder binder,
out object value)
{
return fields.TryGetValue(binder.Name, out value);
}
public override bool TrySetMember(SetMemberBinder binder,
object value)
{
this.ContainsExtra = true;
fields[binder.Name] = value;
return true;
}
}
If I send this Json from Fiddler
{“FirstName”:”Test”, “LastName”:”Test”, “City”:”New York”}
The TrySetMember method should fire and it should set the bool ContainsExtra to true so it can be evaluated in the Add method on MyClassController. When it does contain an extra field, it should return an error to the client.
Unfortunately I just can't seem to get the TrySetMember to fire.
What am I Missing ?
I got the functionality that i wanted by simply setting the MissingMemberHandling setting of the JSONMediaTypeFormatter to MissingMemberHandling.Error, in my case I:
WebConfigFile:
// Set up The Json Media Type Formatter
var JsonMTF = new JsonMediaTypeFormatter
{
SerializerSettings = { MissingMemberHandling = MissingMemberHandling.Error }
};
// Clear all formatters
config.Formatters.Clear();
// Add the JSON formatter
config.Formatters.Add(JsonMTF);
Then write an ActionFilterAttribute:
public class ValidateModelFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext filterContext)
{
if (!filterContext.ModelState.IsValid)
{
filterContext.Response = filterContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, filterContext.ModelState);
}
}
}
Finally add the filter in the WebConfigFile:
// Add Validate Model Filter Attribute
config.Filters.Add(new ValidateModelFilterAttribute());
Related
I need to deserialize jsons to a type that contains a property of interface type - IExceptionModel. I prescribe maps for interfaces to classes like this:
public static class JsonSerialization
{
public static T FromJson<T>(this string obj) => JsonConvert.DeserializeObject<T>(obj, Settings);
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
ContractResolver = new ContractResolver()
};
private class ContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
var result = base.CreateContract(objectType);
if (objectType == typeof(IExceptionModel))
{
result.CreatedType = typeof(ExceptionModel);
result.DefaultCreator = () => result.CreatedType.GetConstructor(new Type[0]).Invoke(null);
}
return result;
}
}
}
Here are my interface and class types:
public interface IExceptionModel : IModelBase
{
public string Message { get; set; }
public int Index { get; set; }
}
public class ExceptionModel : IExceptionModel
{
public string Message { get ; set ; }
public byte Index { get; set; }
}
Here is the class to deserialize to:
public class Status
{
public IExceptionModel Error { get; set; }
}
When I take a proper input string like:
{
"Error" : {
"Message": "Error",
"Index": 404
}
}
and feed it to FromJson<Status> method, described above, I get Error property set to null, although I believe I have resolved the contract for the interface.
What else do I need to do in order to make it work?
Update.
When preparing this example, I messed some details. The IExceptionModel Error property doesn't have setter on the interface. It does on implementation. So now, when I have added setter to the interface, the property ends up with the needed value. If I wipe it, it has null after deserialization.
So the question turns into, how do I tell Newtonsoft Serializer to use the constructor of the implementation, use ITS getters and setters to fill what properties it can and only then treat it as the interface instance?
I found a workaround to assign an internal setter to the interface property and then instruct:
jsonContract.DefaultCreatorNonPublic = true;
But it makes the interface look crooked, to say the least.
I made some corrections and this worked for me:
result.CreatedType = typeof(Status); --> result.CreatedType = typeof(ExceptionModel);
public byte Index { get; set; } --> public int Index { get; set; }
I uploaded this online example: https://dotnetfiddle.net/ETSJee
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public interface IModelBase {}
public interface IExceptionModel : IModelBase
{
public string Message { get; set; }
public int Index { get; set; }
}
public class ExceptionModel : IExceptionModel
{
public string Message { get ; set ; }
public int Index { get; set; }
}
public class Status
{
public IExceptionModel Error { get; set; }
}
public static class JsonSerialization
{
public static T FromJson<T>(this string obj)
{
return JsonConvert.DeserializeObject<T>(obj, Settings);
}
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
ContractResolver = new ContractResolver()
};
private class ContractResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
var result = base.CreateContract(objectType);
if (objectType == typeof(IExceptionModel))
{
result.CreatedType = typeof(ExceptionModel);
result.DefaultCreator = () => result.CreatedType.GetConstructor(new Type[0]).Invoke(null);
}
return result;
}
}
}
class Program
{
static void Main(string[] args)
{
var txt = #"
{
'Error' : {
'Message': 'Error',
'Index': 404
}
}
";
var obj = JsonSerialization.FromJson<Status>(txt);
Console.WriteLine(obj.Error.Index);
Console.WriteLine(obj.Error.Message);
}
}
this works for me without any contract resolvers
var status = JsonConvert.DeserializeObject<Status>(txt);
public class Status
{
public IExceptionModel Error { get; set; }
[JsonConstructor]
public Status (ExceptionModel error) {
Error=error;
}
public Status() {}
}
if you need to use it in many classes you can use this code instead
public class Status
{
[JsonProperty("Error")]
private ExceptionModel _error
{
set { Error = value; }
get { return (ExceptionModel)Error; }
}
[JsonIgnore]
public IExceptionModel Error { get; set; }
}
test
var status = JsonConvert.DeserializeObject<MyClass>(txt);
Console.WriteLine(status.Error.Index); //404
Console.WriteLine(status.Error.Message); //Error
public class MyClass:Status
{
public string Name {get; set;}
}
I'm having issues with my custom serializer sometimes not working when passing information between Orchestration Functions and I don't know if this is because of how the object is nested / constructed or if this has something to do with durable functions and how I'm implementing the serializer. Mostly it seems to fails on a Activity call inside an Ochestration that's been called by a Durable Client.
Here is the details:
So I have a custom base class for what is essentially a string Enum (It is a compilation of ideas I found here on Stack Overflow)
public abstract class StringEnum<T>
where T : StringEnum<T>
{
public readonly string Value;
protected StringEnum(string value)
{
Value = value;
}
public override string ToString()
{
return Value;
}
public override bool Equals(object obj)
{
try
{
return (string)obj == Value;
}
catch
{
return false;
}
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public static IEnumerable<T> All
=> typeof(T).GetProperties()
.Where(p => p.PropertyType == typeof(T))
.Select(x => (T)x.GetValue(null, null));
public static implicit operator string(StringEnum<T> enumObject)
{
return enumObject?.Value;
}
public static implicit operator StringEnum<T>(string stringValue)
{
if (All.Any(x => x.Value == stringValue))
{
Type t = typeof(T);
ConstructorInfo ci = t.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(string) }, null);
return (T)ci.Invoke(new object[] { stringValue });
}
return null;
}
public static bool operator ==(StringEnum<T> a, StringEnum<T> b)
{
return a.Value == b.Value;
}
public static bool operator !=(StringEnum<T> a, StringEnum<T> b)
{
return a.Value != b.Value;
}
}
I have two implementations of this:
public class ReportType : StringEnum<ReportType>, IReportType
{
private ReportType(string value): base(value) { }
public new string Value { get { return base.Value; } }
public static ReportType A_Orders => new ReportType("A_GET_ORDERS");
// ... more types
}
public class ReportStatus : StringEnum<ReportStatus>
{
private ReportStatus(string value): base(value) { }
public new string Value { get { return base.Value; } }
public static ReportStatus New => new ReportStatus("New");
public static ReportStatus Done => new ReportStatus("Done");
// ... more types
}
I wrote a custom JsonConverter to handle the JSON transitions for this class
public class StringEnumJsonConverter<T> : JsonConverter<T>
where T : StringEnum<T>
{
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer)
{
string s = (string)reader.Value;
return (T)s;
}
}
I then implemented it in the function startup
[assembly: FunctionsStartup(typeof(Functions.Startup))]
namespace Functions
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<IMessageSerializerSettingsFactory, StringEnumMessageSerializerSettingsFactory>();
}
internal class StringEnumMessageSerializerSettingsFactory : IMessageSerializerSettingsFactory
{
public JsonSerializerSettings CreateJsonSerializerSettings()
{
return new JsonSerializerSettings()
{
Converters = new List<JsonConverter>
{
new StringEnumJsonConverter<ReportType>(),
new StringEnumJsonConverter<ReportStatus>(),
},
ContractResolver = new StringEnumResolver()
};
}
}
internal class StringEnumResolver : DefaultContractResolver
{
protected override JsonContract CreateContract(Type objectType)
{
if (objectType == typeof(ReportType))
{
return GetContract(new StringEnumJsonConverter<ReportType>()), objectType);
}
else if (objectType == typeof(ReportStatus))
{
return GetContract(new StringEnumJsonConverter<ReportStatus>(), objectType);
}
return base.CreateContract(objectType);
}
private JsonContract GetContract(JsonConverter converter, Type objectType)
{
var contract = base.CreateObjectContract(objectType);
contract.Converter = converter;
return contract;
}
}
}
}
I have a class that uses the ReportType
public class ReportsRequestOptions
{
public List<ReportType> ReportTypes { get; set; }
public List<int> Ids { get; set; }
public DateTime From { get; set; }
public DateTime To { get; set; }
}
and a class that uses both ReportType and ReportStatus which is used in a list in another class
public class ReportRequest
{
public ReportType ReportName { get; }
public ReportStatus ReportStatus { get; set; }
// other fields that work
}
internal class ClientReportsRequest
{
public int Id {get; set; }
public List<ReportRequest> Requests { get; set; }
public DateTime To {get; set; }
public DateTime From {get; set; }
}
I use ReportsRequestOptions when I move data from my HttpTrigger to my main Orchestration function but when I then pass a ClientReportsRequest into a sub Orchestration the JsonConverter doesn't seem to work, the values are just Null instead of the strings they normally show as. I can put a break point in the converter and see that it is being called but for some reason the values don't appear in my locals so I can't inspect it to find out why this is happening.
Implementation:
[FunctionName(nameof(RunReportsAsync))]
public async Task<IActionResult> RunReportsAsync(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
[DurableClient] IDurableClient client
)
{
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
ReportsRequestOptions requestOptions = JsonConvert.DeserializeObject<ReportsRequestOptions>(requestBody, new StringEnumJsonConverter<ReportType>());
// StringEnum data is correct at this point
if (!requestOptions.ReportTypes.Any())
requestOptions.ReportTypes.AddRange(ReportType.All);
var instanceId = await client.StartNewAsync(nameof(GetReports), requestOptions);
return new OkObjectResult(instanceId);
}
[FunctionName(nameof(GetReports))]
public async Task<RunLog> GetReports(
[OrchestrationTrigger] IDurableOrchestrationContext context
)
{
var requestOptions = context.GetInput<ReportsRequestOptions>();
// string enum data is correct at this point
var clientReportsRequests = GetClientInfo(storeIds)
.Select(x => new ClientReportsRequest()
{
ReportTypes = requestOptions.ReportTypes,
Id = x.Id,
From = requestOptions.From,
To = requestOptions.To
});
// ParallelForEach Async code shouldn't be the issue here.
// it's based on this article: https://dev.to/cgillum/scheduling-tons-of-orchestrator-functions-concurrently-in-c-1ih7
var results = (await clientReportsRequests.ParallelForEachAsync(MaxParallelStoreThreadCount, clientReportsRequest =>
{
return context.CallSubOrchestratorAsync<(int, List<ReportRequest>)>(nameof(GetReportsForClient), clientReportsRequest);
})).ToDictionary(x => x.Item1, x => x.Item2);
return new RunLog(requestOptions, results);
}
[FunctionName(nameof(GetReportsForClient))]
public async Task<(int, List<ReportRequest>)> GetReportsForClient(
[OrchestrationTrigger] IDurableOrchestrationContext context
)
{
var requestOptions = context.GetInput<ClientReportsRequest>();
var completedRequests = new List<ReportRequest>();
foreach (var request in requestOptions.Requests)
{
completedRequests.add(GetReport(request));
// GetReport code has been truncated for brevity but the issue is that neither field in the request
// has it's StringEnum data at this point
}
return (requestOptions.Id, completedRequests);
}
I've been beating my head against this for a couple of days and can't find an answer, anyone got any ideas? Is there a better way I should be serializing this?
Ugh, this was a non-issue. I was missing a public get on Requests field in the ClientReportsRequest sorry to have wasted anyone's time.
I'm trying to read a mondodb document into my domain class (Company) but get an error on one of the properties.
The error message reads:
"Expected a nested document representing the serialized form of a
OrgNumber value, but found a value of type String instead"
The objects looks like this:
public class OrgNumber
{
public string Value { get; private set; }
...
private OrgNumber() { }
public OrgNumber(string value) {
Value = value;
}
}
public class Company
{
public string Name { get; private set; }
public OrgNumber OrgNumber { get; private set; }
...
private Company() { }
public Company(string name, OrgNumber orgNumber)
{
Name = name;
OrgNumber = orgNumber;
}
}
The mongodb document looks like this:
{
"name": "Company name",
"orgNumber": "1234-5678",
}
I'm reading the document and mapping it directly into my domain model:
var collection = _mongoDb.GetCollection<Company>("Companies");
var result = await collection.Find(c => c.CompanyId == companyId).SingleOrDefaultAsync();
How do I correctly get the string representation of OrgNumber to the correct type OrgNumber?
You can create your own serializer inheriting from SerializerBase<T>
public class OrgNumberSerializer : SerializerBase<OrgNumber>
{
public override OrgNumber Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var serializer = BsonSerializer.LookupSerializer(typeof(string));
var data = serializer.Deserialize(context, args);
return new OrgNumber(data.ToString());
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, OrgNumber value)
{
var serializer = BsonSerializer.LookupSerializer(typeof(string));
serializer.Serialize(context, value.Value);
}
}
Then it needs to be registered globally using below line:
BsonSerializer.RegisterSerializer(typeof(OrgNumber), new OrgNumberSerializer());
You can find more about the details here
I have the same issue as this guy posting single item in json array to .net object.
To summarize, I am sending JSON data to an action and getting null when there is only one item passed. But it works when there is more than one item.
I know this has to do with how the JSON string is constructed (list of object vs. single object) but I have no choice; I am using a limited jquery json library (that's another story)
JSON string example with a list of fields
{
"formtemplate":{
"fields":{
"field":[
{
"_class":"form-control text-input",
"_label":"Text Field",
"_name":"text-1467980984393",
"_type":"text",
"_subtype":"text"
},
{
"_class":"form-control text-input",
"_label":"Text Field",
"_name":"text-1467980999418",
"_type":"text",
"_subtype":"text"
}
]
}
}
}
JSON string example with only one field item
{
"formtemplate":{
"fields":{
"field":{
"_class":"form-control text-input",
"_label":"Text Field",
"_name":"text-1467980984393",
"_type":"text",
"_subtype":"text"
}
}
}
}
Model
public class Fields
{
public List<Field> field { get; set; }
}
public class Formtemplate
{
public Fields fields { get; set; }
}
public class CustomFields
{
public Formtemplate formtemplate { get; set; }
}
public class Field
{
public string _label { get; set; }
public string _name { get; set; }
public string _type { get; set; }
public List<Option> option { get; set; }
}
Action
[HttpPost]
public JsonResult saveCustomfields(CustomFields customfields)
{
}
I followed the advice on the link provided but it didn't seem to work, here is what I did
public class Fields
{
private List<Field> field;
public object Numbers
{
get { return field; }
set
{
if (value.GetType() == typeof(Field))
{
field = new List<Field> { (Field)value };
}
else if (value.GetType() == typeof(List<Field>))
{
field = (List<Field>)value;
}
}
}
}
In the action I get both of "Numbers" and "field" as null when there is one item or more. The solution is logical but it didn't work for me.
Update
I researched a bit more and implemented a custom converter from this link How to handle both a single item and an array for the same property using JSON.net. But when I debug; it seems like SingleOrArrayConverter is not even called
public class Fields
{
[JsonProperty("field")]
[JsonConverter(typeof(SingleOrArrayConverter<Field>))]
public List<Field> field { get; set; }
}
public class Formtemplate
{
[JsonProperty("fields")]
public Fields fields { get; set; }
}
public class CustomFields
{
[JsonProperty("formtemplate")]
public Formtemplate formtemplate { get; set; }
}
public class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
But still no success, does it have to do with my action?
I have a request to create a Web API that is able to accept a POST request and take different actions depending on the type of data (DataAvailableNotification vs ExpiredNotification) received in the parameters.
I've created an ApiController and exposed two methods:
[HttpPost]
public void DataAvailable(DataAvailableNotification dataAvailable,
[FromUri] string book, [FromUri] string riskType)
{
}
[HttpPost]
public void DataAvailable(ExpiredNotification dataAvailable,
[FromUri] string book, [FromUri] string riskType)
{
}
public class DataAvailableNotification
{
[JsonProperty(PropertyName = "$type")]
public string RdfType { get { return "App.RRSRC.Feeds.DataAvailable"; } }
public string SnapshotRevisionId { get; set; }
public string[] URLs { get; set; }
public string ConsumerId { get; set; }
public Guid ChannelId { get; set; }
}
public class ExpiredNotification
{
[JsonProperty(PropertyName = "$type")]
public string RdfType { get { return "Service.Feeds.Expired"; } }
public string ConsumerId { get; set; }
public Guid ChannelId { get; set; }
}
However, they don't get called at all.
If I comment out one of them the notification reaches the controller but I cannot handle the notification type correctly (given that both notifications will map to the same method).
Is there any way configuring Web API to look into the type of the POSTed value and call the best matching controller method?
PS: I cannot have 2 different of URLs to handle the different notifications. So please don't suggest this.
Use one action and filter based on the type.
I got around the same issue by using reflection and doing something like the following.
[HttpPost]
public void DataAvailable([FromBody]IDictionary<string, string> dataAvailable,
[FromUri] string book, [FromUri] string riskType) {
if(dataAvailable != null && dataAvailable.ContainsKey("$type") {
var type = dataAvaliable["$type"];
if(type == "App.RRSRC.Feeds.DataAvailable"){
DataAvailableNotification obj = createInstanceOf<DataAvailableNotification>(dataAvailable);
DataAvailable(obj,book,riskType);
} else if (type == "Service.Feeds.Expired") {
ExpiredNotification obj = createInstanceOf<ExpiredNotification>(dataAvailable);
DataAvailable(obj,book,riskType);
}
}
}
private void DataAvailable(DataAvailableNotification dataAvailable, string book, string riskType) {
}
private void DataAvailable(ExpiredNotification dataAvailable, string book, string riskType) {
}
private T createInstanceOf<T>(IDictionary<string, string> data) where T : class, new() {
var result = new T();
var type = typeof(T);
//map properties
foreach (var kvp in data) {
var propertyName = kvp.Key;
var rawValue = kvp.Value;
var property = type.GetProperty(propertyName);
if (property != null && property.CanWrite) {
property.SetValue(result, rawValue );
}
}
return result;
}
The solution I settled with is similar to what #Nikosi and #jpgrassi suggested.
In the controller I've created a single notification point:
[HttpPost]
public void Notify(BaseNotification notification,
[FromUri] string book, [FromUri] string riskType)
{
DataAvailableNotification dataAvailableNotification;
ExpiredNotification expiredNotification;
if ((dataAvailableNotification = notification as DataAvailableNotification) != null)
{
HandleDataAvailableNotification(dataAvailableNotification);
}
else if ((expiredNotification = notification as ExpiredNotification) != null)
{
HandleExpiredNotification(expiredNotification);
}
}
private void HandleDataAvailableNotification(DataAvailableNotification dataAvailableNotification)
{
}
private void HandleExpiredNotification(ExpiredNotification expiredNotification)
{
}
BaseNotification is the base class for all notifications:
public abstract class BaseNotification
{
[JsonProperty(PropertyName = "$type")]
public abstract string RdfType { get; }
public string ConsumerId { get; set; }
public Guid ChannelId { get; set; }
}
Created a JsonConverter:
public class RdfNotificationJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotSupportedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var resultJson = JObject.Load(reader);
var rdfType = resultJson["$type"].ToObject<string>();
BaseNotification result;
switch (rdfType)
{
case "App.RRSRC.Feeds.DataAvailable":
{
result = new DataAvailableNotification
{
SnapshotRevisionId = resultJson["SnapshotRevisionId"].ToObject<string>(),
URLs = resultJson["URLs"].ToObject<string[]>()
};
break;
}
case "Service.Feeds.Expired":
{
result = new ExpiredNotification();
break;
}
default:
{
throw new NotSupportedException();
}
}
result.ChannelId = resultJson["ChannelId"].ToObject<Guid>();
result.ConsumerId = resultJson["ConsumerId"].ToObject<string>();
return result;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(BaseNotification);
}
}
And registered the new converter in the configuration:
public static void Configure(HttpSelfHostConfiguration config)
{
Throw.IfNull(config, "config");
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new RdfNotificationJsonConverter());
}
I like this solution better because I have the actual type in the controller and the converter handles the ugly deserialization part (also more testable).
PS: I'll move the literal strings somewhere else so I don't specify them twice in the solution.