RegionInfo serializing in ElasticSearch - elasticsearch

It seems that the RegionInfo object has kinda been forgotten in terms of serialization. CultureInfo works great and is serialized to and from a string. When attempting to throw in a RegionInfo object, I get a mess of all the properties of RegionInfo that can't be deserialized because there is no constructor that takes all those properties in reverse. I would love to just serialize and deserialize the RegionInfos as strings, like CultureInfo, but can't quite figure that out.
My attempt:
I created a regioninfo converter
public class RegionInfoConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, ((RegionInfo)value).Name);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var token = JToken.Load(reader);
return new RegionInfo(token.ToObject<string>());
}
public override bool CanConvert(Type objectType)
{
return typeof(RegionInfo) == objectType;
}
}
I stuffed that into the ConnectionSettings:
var connectionSettings = new ConnectionSettings(pool,
(builtin, settings) => new JsonNetSerializer(
builtin,
settings,
contractJsonConverters: new JsonConverter[] { new RegionInfoConverter() })
);
but I get the error: object mapping for [region] tried to parse field [region] as object, but found a concrete value
That sounds like one of my serializer pieces is wrong, but I don't feel like I quite understand enough to figure out which part it is. Thanks.

I think the issue here may be that Elasticsearch has initially inferred an object datatype mapping for RegionInfo from a document to be indexed, and is now being passed a string value for RegionInfo. You may need to delete the index and create again, mapping the RegionInfo property as a keyword datatype.
Here's a working example
private static void Main()
{
var defaultIndex = "my_index";
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var settings = new ConnectionSettings(pool, (b, s) =>
new JsonNetSerializer(b, s, contractJsonConverters: new JsonConverter[] { new RegionInfoConverter() })
)
.DefaultIndex(defaultIndex);
var client = new ElasticClient(settings);
if (client.IndexExists(defaultIndex).Exists)
client.DeleteIndex(defaultIndex);
var createIndexResponse = client.CreateIndex(defaultIndex, c => c
.Settings(s => s
.NumberOfShards(1)
.NumberOfReplicas(0)
)
.Mappings(m => m
.Map<MyEntity>(mm => mm
.AutoMap()
.Properties(p => p
.Keyword(k => k
.Name(n => n.RegionInfo)
)
)
)
)
);
var indexResponse = client.Index(new MyEntity
{
RegionInfo = RegionInfo.CurrentRegion
}, i => i.Refresh(Refresh.WaitFor));
}
public class MyEntity
{
public RegionInfo RegionInfo { get; set; }
}
public class RegionInfoConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
return;
}
writer.WriteValue(((RegionInfo)value).Name);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.String)
throw new JsonSerializationException($"Cannot deserialize {nameof(RegionInfo)} from {reader.TokenType}");
return new RegionInfo((string)reader.Value);
}
public override bool CanConvert(Type objectType)
{
return typeof(RegionInfo) == objectType;
}
}
The index request sends the following JSON
{
"regionInfo": "AU"
}

Related

ASP.NET Core 2.1 reflection doesn't work when deploying in AWS lambda

I have a library with the below code for dependency injection. This loads all implementation classes ending with Handler and register them.
public static class HandlerRegistrationExtension
{
private static IDictionary<Type, Type> _decoratorsAttributes;
public static void AddHandlers(this IServiceCollection services, IDictionary<Type, Type> decoratorsAttributes)
{
_decoratorsAttributes = decoratorsAttributes ?? new Dictionary<Type, Type>();
List<Type> allAssembliesTypes = Assembly
.GetEntryAssembly()
.GetReferencedAssemblies()
.Select(Assembly.Load)
.SelectMany(a => a.GetTypes())
.ToList();
List<Type> handlerTypes = allAssembliesTypes
.Where(x => x.GetInterfaces().Any(y => IsHandlerInterface(y)))
.Where(x => x.Name.EndsWith("Handler", StringComparison.Ordinal))
.ToList();
foreach (Type type in handlerTypes)
{
AddHandler(services, type);
}
}
private static void AddHandler(IServiceCollection services, Type type)
{
object[] attributes = type.GetCustomAttributes(false);
List<Type> pipeline = attributes
.Select(x => ToDecorator(x))
.Concat(new[] { type })
.Reverse()
.ToList();
Type interfaceType = type.GetInterfaces().Single(y => IsHandlerInterface(y));
Func<IServiceProvider, object> factory = BuildPipeline(pipeline, interfaceType);
services.AddTransient(interfaceType, factory);
}
private static Func<IServiceProvider, object> BuildPipeline(List<Type> pipeline, Type interfaceType)
{
List<ConstructorInfo> ctors = pipeline
.Select(x =>
{
Type type = x.IsGenericType ? x.MakeGenericType(interfaceType.GenericTypeArguments) : x;
return type.GetConstructors().Single();
})
.ToList();
Func<IServiceProvider, object> func = provider =>
{
object current = null;
foreach (ConstructorInfo ctor in ctors)
{
List<ParameterInfo> parameterInfos = ctor.GetParameters().ToList();
object[] parameters = GetParameters(parameterInfos, current, provider);
current = ctor.Invoke(parameters);
}
return current;
};
return func;
}
private static object[] GetParameters(List<ParameterInfo> parameterInfos, object current, IServiceProvider provider)
{
var result = new object[parameterInfos.Count];
for (int i = 0; i < parameterInfos.Count; i++)
{
result[i] = GetParameter(parameterInfos[i], current, provider);
}
return result;
}
private static object GetParameter(ParameterInfo parameterInfo, object current, IServiceProvider provider)
{
Type parameterType = parameterInfo.ParameterType;
if (IsHandlerInterface(parameterType))
return current;
object service = provider.GetService(parameterType);
if (service != null)
return service;
throw new ArgumentException($"Type {parameterType} not found");
}
private static Type ToDecorator(object attribute)
{
Type type = attribute.GetType();
if (_decoratorsAttributes.ContainsKey(type))
{
return _decoratorsAttributes[type];
}
throw new ArgumentException(attribute.ToString());
}
private static bool IsHandlerInterface(Type type)
{
if (!type.IsGenericType)
return false;
Type typeDefinition = type.GetGenericTypeDefinition();
return typeDefinition == typeof(ICommandHandler<,>) || typeDefinition == typeof(IQueryHandler<,>);
}
}
When I deploy the application in AWS Lambda function it seems like the code that is requesting an implementation of a handler could not be found.
private readonly IServiceProvider _provider;
public MessagesDispatcher(IServiceProvider provider)
{
_provider = provider;
}
public async Task<T> DispatchAsync<T>(ICommand<T> command, CancellationToken cancellationToken)
{
Type type = typeof(ICommandHandler<,>);
Type[] typeArgs = { command.GetType(), typeof(T) };
Type handlerType = type.MakeGenericType(typeArgs);
dynamic handler = _provider.GetService(handlerType);
T result = await handler.HandleAsync((dynamic)command, cancellationToken);
return result;
}
I wonder what could change deploying the application in a lambda related to the assemblies loaded with reflection since the code works fine when using LocalEntryPoint.cs.
After a bit of searching and try/fail process I found that the below method getting the Assembly with determining a type coming for the binary works in AWS Lambda environment. Below is the changed method AddHandlers.
public static void AddHandlers(
this IServiceCollection services,
IDictionary<Type, Type> decoratorsAttributes,
params Assembly[] assemblies) // Added a parameter to pass multiple assemblies
{
_decoratorsAttributes = decoratorsAttributes ?? new Dictionary<Type, Type>();
List<Type> allAssembliesTypes = assemblies // Here we get the types from the assembly
.SelectMany(a => a.GetTypes())
.ToList();
List<Type> handlerTypes = allAssembliesTypes
.Where(x => x.GetInterfaces().Any(y => IsHandlerInterface(y)))
.Where(x => x.Name.EndsWith("Handler", StringComparison.Ordinal))
.ToList();
foreach (Type type in handlerTypes)
{
AddHandler(services, type);
}
}
In Startup.cs I call AddHandlers like below.
services.AddHandlers(new Dictionary<Type, Type>
{
{ typeof(CircuitBreakerCommandDecoratorAttribute), typeof(CircuitBreakerCommandDecorator<,>) },
{ typeof(CircuitBreakerQueryDecoratorAttribute), typeof(CircuitBreakerQueryDecorator<,>) }
},
typeof(RegisterUserCommandHandler).GetTypeInfo().Assembly); .. This way the assembly containing the types I am scanning is loaded correctly

Access custom attributes of .NET class inside custom json converter

In my project, I have written a custom json converter to trim the white-spaces present in the string property.
Here is an example of the typical class we will use,
public class Candidate
{
public string CandidateName { get; set; }
}
Here is my custom json converter
public class StringSanitizingConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue , JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
string sanitizedString = (reader.Value as string).Trim();
if (StringSanitizeOptions.HasFlag(StringSanitizeOptions.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();
return sanitizedString;
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var text = (string)value;
if (text == null)
writer.WriteNull();
else
writer.WriteValue(text.Trim());
}
}
With my custom converter I am now able to format the string by trimming any white-spaces present sent to the action methods using my 'Candidate' as one of its parameter.
public void Post(ComplexType complexTypeParameter){
}
Everything worked well so far. I later wanted to enhance this json converter to format the string properties based on the attributes set to the string property in the Candidate class. for example, assume I have written my candidate class like this,
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
}
And if I wanted to format the string properties of a class based on the custom attribute configuration inside the json converter , I am not able to access this custom attribute and its configuration inside the ReadJson method of the custom converter.
Here is what I have tried so far but with no luck,
Not present in the CustomAttributes property of the objectType
parameter sent to the ReadJson() method.
Was trying to see if I could extract the parent class of the property inside the ReadJson() method, so that I could apply reflection on the class to extract the custom attributes given to any of its property,but I could not extract that too.
The stack of containing object(s) is not made available to JsonConverter.ReadJson(), thus you cannot do what you want inside ReadJson().
Instead, what you can do is to create a custom contract resolver that applies an appropriately configured instance of StringSanitizingConverter based on the properties of the object for which a contract is being generated.
First, let's say your data model, attribute, and JsonConverter look like the following (where I had to modify a few things to make your code compile and include some additional test cases):
public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }
[StringSanitizingOptions(Option.DoNotTrim)]
public string StringLiteral { get; set; }
public string DefaultString { get; set; }
public List<string> DefaultStrings { get; set; }
}
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class StringSanitizingOptionsAttribute : System.Attribute
{
public Option StringSanitizeOptions { get; set; }
public StringSanitizingOptionsAttribute(Option stringSanitizeOptions)
{
this.StringSanitizeOptions = stringSanitizeOptions;
}
}
[Flags]
public enum Option
{
Default = 0,
ToLowerCase = (1<<0),
DoNotTrim = (1<<1),
}
public static class StringSanitizeOptionsExtensions
{
public static bool HasFlag(this Option options, Option flag)
{
return (options & flag) == flag;
}
}
public class StringSanitizingConverter : JsonConverter
{
readonly Option options;
public StringSanitizingConverter() : this(Option.Default) { }
public StringSanitizingConverter(Option options)
{
this.options = options;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
var sanitizedString = (reader.Value as string);
if (!options.HasFlag(Option.DoNotTrim))
sanitizedString = sanitizedString.Trim();
if (options.HasFlag(Option.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();
return sanitizedString;
}
return reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is never called with null
var text = (string)value;
if (!options.HasFlag(Option.DoNotTrim))
text = text.Trim();
writer.WriteValue(text);
}
}
Next, grab ConfigurableContractResolver from How to add metadata to describe which properties are dates in JSON.Net, and define the extension method JsonContractExtensions.AddStringConverters():
public static class JsonContractExtensions
{
public static JsonContract AddStringConverters(this JsonContract contract)
{
if (contract is JsonPrimitiveContract)
{
if (contract.UnderlyingType == typeof(string))
contract.Converter = new StringSanitizingConverter();
}
else if (contract is JsonObjectContract)
{
var objectContract = (JsonObjectContract)contract;
foreach (var property in objectContract.Properties)
{
if (property.PropertyType == typeof(string))
{
var attr = property.AttributeProvider.GetAttributes(typeof(StringSanitizingOptionsAttribute), true)
.Cast<StringSanitizingOptionsAttribute>()
.SingleOrDefault();
if (attr != null)
{
property.Converter = property.MemberConverter = new StringSanitizingConverter(attr.StringSanitizeOptions);
}
}
}
}
return contract;
}
}
public class ConfigurableContractResolver : DefaultContractResolver
{
// This contract resolver taken from the answer to
// https://stackoverflow.com/questions/46047308/how-to-add-metadata-to-describe-which-properties-are-dates-in-json-net
// https://stackoverflow.com/a/46083201/3744182
readonly object contractCreatedPadlock = new object();
event EventHandler<ContractCreatedEventArgs> contractCreated;
int contractCount = 0;
void OnContractCreated(JsonContract contract, Type objectType)
{
EventHandler<ContractCreatedEventArgs> created;
lock (contractCreatedPadlock)
{
contractCount++;
created = contractCreated;
}
if (created != null)
{
created(this, new ContractCreatedEventArgs(contract, objectType));
}
}
public event EventHandler<ContractCreatedEventArgs> ContractCreated
{
add
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
}
contractCreated += value;
}
}
remove
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated.");
}
contractCreated -= value;
}
}
}
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
OnContractCreated(contract, objectType);
return contract;
}
}
public class ContractCreatedEventArgs : EventArgs
{
public JsonContract Contract { get; private set; }
public Type ObjectType { get; private set; }
public ContractCreatedEventArgs(JsonContract contract, Type objectType)
{
this.Contract = contract;
this.ObjectType = objectType;
}
}
public static class ConfigurableContractResolverExtensions
{
public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler)
{
if (resolver == null || handler == null)
throw new ArgumentNullException();
resolver.ContractCreated += handler;
return resolver;
}
}
Then, finally you can deserialize and serialize Candidate as follows:
var settings = new JsonSerializerSettings
{
ContractResolver = new ConfigurableContractResolver
{
}.Configure((s, e) => { e.Contract.AddStringConverters(); }),
};
var candidate = JsonConvert.DeserializeObject<Candidate>(json, settings);
var json2 = JsonConvert.SerializeObject(candidate, Formatting.Indented, settings);
Notes:
I don't know why the stack of containing object(s) is not available in ReadJson(). Possibilities include:
Simplicity.
A JSON object is "an unordered set of name/value pairs", so trying to access the containing .Net object while reading a property value isn't guaranteed to work, since the information required might not have been read in yet (and the parent might not even have been constructed).
Because a default instance of StringSanitizingConverter is applied to the contract generated for string itself, it is not necessary to add the converter to JsonSerializer.SettingsConverters. This in turn may lead to a small performance enhancement as CanConvert will no longer get called.
JsonProperty.MemberConverter was recently marked obsolete in Json.NET 11.0.1 but must be set to the same value as JsonProperty.Converter in previous versions of Json.NET. If you are using 11.0.1 or a more recent version you should be able to remove the setting.
You may want to cache the contract resolver for best performance.
To modify JsonSerializerSettings in asp.net-web-api, see JsonSerializerSettings and Asp.Net Core, Web API: Configure JSON serializer settings on action or controller level, How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? or ASP.NET Core API JSON serializersettings per request, depending on your requirements and the version of the framework in use.
Sample working .Net fiddle here.

How to serialize an Object to Map by Moshi

I want to serialize an Object to Map by Moshi.Here is my codes by Gson
public static Map<String, String> toMap(Object obj, Gson gson) {
if (gson == null) {
gson = new Gson();
}
String json = gson.toJson(obj);
Map<String, String> map = gson.fromJson(json, new TypeToken<Map<String, String>>() {
}.getType());
return map;
}
And how to write by Moshi ?
Here's one way. Check out the toJsonValue doc here.
Moshi moshi = new Moshi.Builder().build();
JsonAdapter<Object> adapter = moshi.adapter(Object.class);
Object jsonStructure = adapter.toJsonValue(obj);
Map<String, Object> jsonObject = (Map<String, Object>) jsonStructure;
If you know the type of obj, it'd be better to look up the adapter of that type, rather than of Object. (The Object JsonAdadpter has to look up the runtime type on every toJson call.
#NanoJava8 solution crashes but can be made to work with a minor change using Map instead of HashMap
Type type = Types.newParameterizedType(Map.class, String.class, String.class);
JsonAdapter<Map<String,String>> adapter = moshi.adapter(type);
Map<String,String> map = adapter.fromJson(json);
As stated by Jesse in the answer Moshi support fields as Map but not HashMap.
In Kotlin:
val type = Types.newParameterizedType(
MutableMap::class.java,
String::class.java,
String::class.java
)
val adapter: JsonAdapter<Map<String, String>> = moshi.adapter(type)
val map: Map<String, String> = adapter.fromJson(responseJson)
Type type = Types.newParameterizedType(HashMap.class, String.class, String.class);
JsonAdapter<Map<String,String>> adapter = moshi.adapter(type);
Map<String,String> map = adapter.fromJson(json);
class HashMapJsonAdapter<K, V>(
private val keyAdapter: JsonAdapter<K>,
private val valueAdapter: JsonAdapter<V>
) : JsonAdapter<HashMap<K, V>>() {
#Throws(IOException::class)
override fun toJson(writer: JsonWriter, map: HashMap<K, V>?) {
writer.beginObject()
for ((key, value) in map ?: emptyMap<K, V>()) {
if (key == null) {
throw JsonDataException("Map key is null at ${writer.path}")
}
keyAdapter.toJson(writer, key)
valueAdapter.toJson(writer, value)
}
writer.endObject()
}
#Throws(IOException::class)
override fun fromJson(reader: JsonReader): HashMap<K, V>? {
val result = linkedMapOf<K, V>()
reader.beginObject()
while (reader.hasNext()) {
val name = keyAdapter.fromJson(reader)
val value = valueAdapter.fromJson(reader)
val replaced = result.put(name!!, value!!)
if (replaced != null) {
throw JsonDataException("Map key '$name' has multiple values at path ${reader.path} : $replaced and value")
}
}
reader.endObject()
return result
}
override fun toString(): String = "JsonAdapter($keyAdapter=$valueAdapter)"
companion object
}

Serialize Dictionary<long, VALUE> to BSON documents

I want to serialize Dictionary<long, VALUE> to following JSON in MongoDB.
{
"213" : {},
"63624" : {},
...
}
I don't want other DictionaryRepresentation except DictionaryRepresentation.Document.Document
I am using MongoDB C# Driver (v2.0.1.27), and it is not smart to convert the long type key into string, which causes an exception.
You can do this with the existing serializers but it requires a small amount of configuration.
Assume the following class:
public class C
{
public int Id { get; set; }
public Dictionary<long, long> D { get; set; }
}
You can configure a custom serializer for the D property (the Dictionary) that uses a key serializer that serializes longs to strings. The code would look like this:
BsonClassMap.RegisterClassMap<C>(cm =>
{
cm.AutoMap();
var customDictionarySerializer = new DictionaryInterfaceImplementerSerializer<Dictionary<long, long>>(
dictionaryRepresentation: DictionaryRepresentation.Document,
keySerializer: new Int64Serializer(BsonType.String),
valueSerializer: BsonSerializer.SerializerRegistry.GetSerializer<long>());
cm.GetMemberMap(c => c.D).SetSerializer(customDictionarySerializer);
});
The key idea here is that even though the keys and values are both longs, we are using different serializers for the keys and the values.
If we then run a quick test:
var document = new C { Id = 1, D = new Dictionary<long, long> { { 2, 3 } } };
var json = document.ToJson();
Console.WriteLine(json);
We see that the Dictionary keys are now being serialized as strings:
{ "_id" : 1, "D" : { "2" : NumberLong(3) } }
Also I worked out another solution, hope it helps other people
public class LongDictionarySerializer<K> : DictionarySerializerBase<Dictionary<long, K>>
{
public LongDictionarySerializer() : base(DictionaryRepresentation.Document)
{
}
protected override Dictionary<long, K> CreateInstance()
{
return new Dictionary<long, K>();
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Dictionary<long, K> value)
{
if (value != null)
{
Dictionary<string, K> dic = value.ToDictionary(d => d.Key.ToString(), d => d.Value);
BsonSerializer.Serialize<Dictionary<string, K>>(context.Writer, dic);
}
else
BsonSerializer.Serialize<object>(context.Writer, null);
}
public override Dictionary<long, K> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
Dictionary<string, K> dic = BsonSerializer.Deserialize<Dictionary<string, K>>(context.Reader);
if (dic == null)
return null;
Dictionary<long, K> ret = new Dictionary<long, K>();
foreach( var pair in dic )
{
long key;
if (!long.TryParse(pair.Key, out key))
continue;
ret[key] = pair.Value;
}
return ret;
}
}
Then on the field
[BsonElement(Fields.Markets)]
[BsonSerializer(typeof(LongDictionarySerializer<XXX>))]
public Dictionary<long, XXX> Markets { get; set; }

How to add errors to Modelstate in Custom mediaFormatter

I am using a custom Media formatter to read post data for multipartform in webapi. Handling the serialization errors and validation errors in a custom Action Filter Attribute. In the formatter I am mapping the input to a Type Object ImageMedia. I want to add any serliazation errors to the ModelState so that I can handle the those in the CustomFilterAttribute, which recieve an ActionContext. Here is the code:
public class ImageMediaFormatter : MediaTypeFormatter
{
public ImageMediaFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpg"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/gif"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
}
public override bool CanReadType(Type type)
{
return type == typeof(ImageMedia);
}
public override bool CanWriteType(Type type)
{
return false;
}
public override Task<object> ReadFromStreamAsync(
Type type, Stream stream, HttpContent request,
IFormatterLogger formatterContext)
{
return Task.Factory.StartNew<object>(() =>
{
var streamProvider = new MultipartMemoryStreamProvider();
var keys = new Dictionary<string, string>();
var result = request.ReadAsMultipartAsync(streamProvider).Result;
//get and remove random slashes from posted vaues
var categoryid =
streamProvider.Contents.First(x => x.Headers.ContentDisposition.Name.Contains("userGeneratedCategoryId")).ReadAsStringAsync().Result.Replace("\"", string.Empty);
keys.Add(Constants.UserGeneratedCategoryId, categoryid);
var imageBuffer =
streamProvider.Contents.First(x => x.Headers.ContentDisposition.Name.Contains(Constants.ImageFile)).ReadAsByteArrayAsync().Result;
return new ImageMedia(keys, imageBuffer);
});
}
}
public class ImageMedia
{
public ImageMedia(Dictionary<string, string> keys, byte[] imageBuffer)
: this()
{
var keyvaluePair = new KeyValuePair<string, string>();
foreach (var property in GetType().GetProperties())
{
try
{
keyvaluePair = keys.FirstOrDefault(pair => pair.Key.ToLower() == property.Name.ToLower());
if (!string.IsNullOrEmpty(keyvaluePair.Key))
{
property.SetValue(this, keyValuePair.Value, null);
}
}
catch (Exception ex)
{
// Add these serialization errors to the Model State so I can handle these in a Custom Validation Action Attribute
Errors.Add(keyvaluePair.Key, new List<string> { ex.Message });
IsValid = false;
}
}
Buffer = imageBuffer;
}
private ImageMedia()
{
Errors = new Dictionary<string, List<string>>();
}
public int UserGeneratedCategoryId { get; set; }
public byte[] Buffer { get;set;}
}
You could added errors to the supplied IFormatterLogger context that is passed in to ReadFromStreamAsync method:
public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent request,
IFormatterLogger formatterContext)
Example:
formatterLogger.LogError(errorPath: "OrderedDate", errorMessage: "Not a valid date");

Resources