MongoDB .NET - Get IMongoCollection by Name - c#

I'm refactoring some legacy code away from the MongoDB.Driver.Legacy API.
I've got the following method, which gets a MongoCollection by string collection name.
protected virtual MongoCollection GetMongoCollection(Type type)
{
return Store.GetCollection(GetCollectionName(type));
}
Store in this example is a MongoDatabase from the Legacy API. The GetCollectionName() method looks up various things in BsonClassMap to determine the string name of the collection:
private string GetCollectionName(Type type)
{
return !IsRegisteredWithClassMap(type) ? type.Name : GetRegisteredClassMapType(type).Name;
}
private Type GetRegisteredClassMapType(Type objectType)
{
if (objectType.BaseType == null)
{
return null;
}
var isroot = BsonClassMap.LookupClassMap(objectType.BaseType).IsRootClass;
return isroot ? objectType.BaseType : GetRegisteredClassMapType(objectType.BaseType);
}
private bool IsRegisteredWithClassMap(Type objectType)
{
var isRegistered = GetRegisteredClassMapType(objectType);
return isRegistered != null;
}
How would I implement the GetMongoCollection() method using the new API. The IMongoDatabase from the new API doesn't have a GetCollection() method which accepts a string. Only a Generic version GetCollection<T>

If you set a discrimitor to the class (custom collection name), you can use this:
[BsonDiscriminator("CollectionName")]
public class MyModel
{
...
}
var client = new MongoClient("connection string");
var database = client.GetDatabase("database name");
var collection = database.GetCollection<MyModel>(BsonClassMap.LookupClassMap(typeof(MyModel)).Discriminator)

try this:
protected virtual MongoCollection GetMongoCollection(Type type)
{
var method = typeof(IMongoDatabase).GetMethod("GetCollection");
var result = method.MakeGenericMethod(type).Invoke(yourMongoDbInstance);
return (MongoCollection) result;
}
if you can afford more effort just change the previous method signature in this way:
protected virtual MongoCollection GetMongoCollection<T>() {
return yourMongoDbInstance.GetCollection<T>();
}

Related

Newtonsoft Json doesn't update renamed fields in C# file [duplicate]

I have a class that has a default constructor and also an overloaded constructor that takes in a set of parameters. These parameters match to fields on the object and are assigned on construction. At this point i need the default constructor for other purposes so i would like to keep it if i can.
My Problem: If I remove the default constructor and pass in the JSON string, the object deserializes correctly and passes in the constructor parameters without any issues. I end up getting back the object populated the way I would expect. However, as soon as I add the default constructor into the object, when i call JsonConvert.DeserializeObject<Result>(jsontext) the properties are no longer populated.
At this point I have tried adding new JsonSerializerSettings(){CheckAdditionalContent = true} to the deserialization call. That did not do anything.
Another note: the constructor parameters do match the names of the fields exactly except that the parameters are start with a lowercase letter. I wouldn't think this would matter since, like i mentioned, the deserialization works fine with no default constructor.
Here is a sample of my constructors:
public Result() { }
public Result(int? code, string format, Dictionary<string, string> details = null)
{
Code = code ?? ERROR_CODE;
Format = format;
if (details == null)
Details = new Dictionary<string, string>();
else
Details = details;
}
Json.Net prefers to use the default (parameterless) constructor on an object if there is one. If there are multiple constructors and you want Json.Net to use a non-default one, then you can add the [JsonConstructor] attribute to the constructor that you want Json.Net to call.
[JsonConstructor]
public Result(int? code, string format, Dictionary<string, string> details = null)
{
...
}
It is important that the constructor parameter names match the corresponding property names of the JSON object (ignoring case) for this to work correctly. You do not necessarily have to have a constructor parameter for every property of the object, however. For those JSON object properties that are not covered by the constructor parameters, Json.Net will try to use the public property accessors (or properties/fields marked with [JsonProperty]) to populate the object after constructing it.
If you do not want to add attributes to your class or don't otherwise control the source code for the class you are trying to deserialize, then another alternative is to create a custom JsonConverter to instantiate and populate your object. For example:
class ResultConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Result));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load the JSON for the Result into a JObject
JObject jo = JObject.Load(reader);
// Read the properties which will be used as constructor parameters
int? code = (int?)jo["Code"];
string format = (string)jo["Format"];
// Construct the Result object using the non-default constructor
Result result = new Result(code, format);
// (If anything else needs to be populated on the result object, do that here)
// Return the result
return result;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, add the converter to your serializer settings, and use the settings when you deserialize:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Result result = JsonConvert.DeserializeObject<Result>(jsontext, settings);
A bit late and not exactly suited here, but I'm gonna add my solution here, because my question had been closed as a duplicate of this one, and because this solution is completely different.
I needed a general way to instruct Json.NET to prefer the most specific constructor for a user defined struct type, so I can omit the JsonConstructor attributes which would add a dependency to the project where each such struct is defined.
I've reverse engineered a bit and implemented a custom contract resolver where I've overridden the CreateObjectContract method to add my custom creation logic.
public class CustomContractResolver : DefaultContractResolver {
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var c = base.CreateObjectContract(objectType);
if (!IsCustomStruct(objectType)) return c;
IList<ConstructorInfo> list = objectType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).OrderBy(e => e.GetParameters().Length).ToList();
var mostSpecific = list.LastOrDefault();
if (mostSpecific != null)
{
c.OverrideCreator = CreateParameterizedConstructor(mostSpecific);
c.CreatorParameters.AddRange(CreateConstructorParameters(mostSpecific, c.Properties));
}
return c;
}
protected virtual bool IsCustomStruct(Type objectType)
{
return objectType.IsValueType && !objectType.IsPrimitive && !objectType.IsEnum && !objectType.Namespace.IsNullOrEmpty() && !objectType.Namespace.StartsWith("System.");
}
private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
method.ThrowIfNull("method");
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
}
I'm using it like this.
public struct Test {
public readonly int A;
public readonly string B;
public Test(int a, string b) {
A = a;
B = b;
}
}
var json = JsonConvert.SerializeObject(new Test(1, "Test"), new JsonSerializerSettings {
ContractResolver = new CustomContractResolver()
});
var t = JsonConvert.DeserializeObject<Test>(json);
t.A.ShouldEqual(1);
t.B.ShouldEqual("Test");
Based on some of the answers here, I have written a CustomConstructorResolver for use in a current project, and I thought it might help somebody else.
It supports the following resolution mechanisms, all configurable:
Select a single private constructor so you can define one private constructor without having to mark it with an attribute.
Select the most specific private constructor so you can have multiple overloads, still without having to use attributes.
Select the constructor marked with an attribute of a specific name - like the default resolver, but without a dependency on the Json.Net package because you need to reference Newtonsoft.Json.JsonConstructorAttribute.
public class CustomConstructorResolver : DefaultContractResolver
{
public string ConstructorAttributeName { get; set; } = "JsonConstructorAttribute";
public bool IgnoreAttributeConstructor { get; set; } = false;
public bool IgnoreSinglePrivateConstructor { get; set; } = false;
public bool IgnoreMostSpecificConstructor { get; set; } = false;
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
// Use default contract for non-object types.
if (objectType.IsPrimitive || objectType.IsEnum) return contract;
// Look for constructor with attribute first, then single private, then most specific.
var overrideConstructor =
(this.IgnoreAttributeConstructor ? null : GetAttributeConstructor(objectType))
?? (this.IgnoreSinglePrivateConstructor ? null : GetSinglePrivateConstructor(objectType))
?? (this.IgnoreMostSpecificConstructor ? null : GetMostSpecificConstructor(objectType));
// Set override constructor if found, otherwise use default contract.
if (overrideConstructor != null)
{
SetOverrideCreator(contract, overrideConstructor);
}
return contract;
}
private void SetOverrideCreator(JsonObjectContract contract, ConstructorInfo attributeConstructor)
{
contract.OverrideCreator = CreateParameterizedConstructor(attributeConstructor);
contract.CreatorParameters.Clear();
foreach (var constructorParameter in base.CreateConstructorParameters(attributeConstructor, contract.Properties))
{
contract.CreatorParameters.Add(constructorParameter);
}
}
private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
protected virtual ConstructorInfo GetAttributeConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(c => c.GetCustomAttributes().Any(a => a.GetType().Name == this.ConstructorAttributeName)).ToList();
if (constructors.Count == 1) return constructors[0];
if (constructors.Count > 1)
throw new JsonException($"Multiple constructors with a {this.ConstructorAttributeName}.");
return null;
}
protected virtual ConstructorInfo GetSinglePrivateConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
return constructors.Length == 1 ? constructors[0] : null;
}
protected virtual ConstructorInfo GetMostSpecificConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.OrderBy(e => e.GetParameters().Length);
var mostSpecific = constructors.LastOrDefault();
return mostSpecific;
}
}
Here is the complete version with XML documentation as a gist:
https://gist.github.com/bjorn-jarisch/80f77f4b6bdce3b434b0f7a1d06baa95
Feedback appreciated.
The default behaviour of Newtonsoft.Json is going to find the public constructors. If your default constructor is only used in containing class or the same assembly, you can reduce the access level to protected or internal so that Newtonsoft.Json will pick your desired public constructor.
Admittedly, this solution is rather very limited to specific cases.
internal Result() { }
public Result(int? code, string format, Dictionary<string, string> details = null)
{
Code = code ?? ERROR_CODE;
Format = format;
if (details == null)
Details = new Dictionary<string, string>();
else
Details = details;
}
Based on the answer by Zoltan, I created a variation that lets you use a specific constructor based on its signature.
Usage
return new JsonSerializerSettings
{
ContractResolver = new DynamicObjectResolver(t =>
{
if (t == typeof(QueueProperties))
return new Type[] { typeof(string) };
return null;
})
};
An here is the implementation
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Reflection;
namespace ConsoleApp76.Json
{
class DynamicObjectResolver : DefaultContractResolver
{
private readonly Func<Type, Type[]> GetConstructorSignature;
private readonly ConcurrentDictionary<Type, ConstructorInfo> TypeToConstructorLookup =
new ConcurrentDictionary<Type, ConstructorInfo>();
public DynamicObjectResolver(Func<Type, Type[]> getConstructorSignature)
{
if (getConstructorSignature is null)
throw new ArgumentNullException(nameof(getConstructorSignature));
GetConstructorSignature = getConstructorSignature;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var result = base.CreateObjectContract(objectType);
ConstructorInfo constructor = TypeToConstructorLookup.GetOrAdd(objectType, t => FindConstructorInfo(t));
if (constructor is null)
return result;
result.OverrideCreator = CreateParameterizedConstructor(constructor);
foreach (var param in CreateConstructorParameters(constructor, result.Properties))
result.CreatorParameters.Add(param);
return result;
}
private ConstructorInfo FindConstructorInfo(Type objectType)
{
Type[] constructorSignature = GetConstructorSignature(objectType);
if (constructorSignature is null)
return null;
return objectType.GetConstructor(
bindingAttr:
System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Instance,
binder: null,
types: new Type[] { typeof(string) },
modifiers: null);
}
private static ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
if (method is null)
throw new ArgumentNullException(nameof(method));
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
}
}
Solution:
public Response Get(string jsonData) {
var json = JsonConvert.DeserializeObject<modelname>(jsonData);
var data = StoredProcedure.procedureName(json.Parameter, json.Parameter, json.Parameter, json.Parameter);
return data;
}
Model:
public class modelname {
public long parameter{ get; set; }
public int parameter{ get; set; }
public int parameter{ get; set; }
public string parameter{ get; set; }
}

Json serdes override / customize base class construction [duplicate]

I have a class that has a default constructor and also an overloaded constructor that takes in a set of parameters. These parameters match to fields on the object and are assigned on construction. At this point i need the default constructor for other purposes so i would like to keep it if i can.
My Problem: If I remove the default constructor and pass in the JSON string, the object deserializes correctly and passes in the constructor parameters without any issues. I end up getting back the object populated the way I would expect. However, as soon as I add the default constructor into the object, when i call JsonConvert.DeserializeObject<Result>(jsontext) the properties are no longer populated.
At this point I have tried adding new JsonSerializerSettings(){CheckAdditionalContent = true} to the deserialization call. That did not do anything.
Another note: the constructor parameters do match the names of the fields exactly except that the parameters are start with a lowercase letter. I wouldn't think this would matter since, like i mentioned, the deserialization works fine with no default constructor.
Here is a sample of my constructors:
public Result() { }
public Result(int? code, string format, Dictionary<string, string> details = null)
{
Code = code ?? ERROR_CODE;
Format = format;
if (details == null)
Details = new Dictionary<string, string>();
else
Details = details;
}
Json.Net prefers to use the default (parameterless) constructor on an object if there is one. If there are multiple constructors and you want Json.Net to use a non-default one, then you can add the [JsonConstructor] attribute to the constructor that you want Json.Net to call.
[JsonConstructor]
public Result(int? code, string format, Dictionary<string, string> details = null)
{
...
}
It is important that the constructor parameter names match the corresponding property names of the JSON object (ignoring case) for this to work correctly. You do not necessarily have to have a constructor parameter for every property of the object, however. For those JSON object properties that are not covered by the constructor parameters, Json.Net will try to use the public property accessors (or properties/fields marked with [JsonProperty]) to populate the object after constructing it.
If you do not want to add attributes to your class or don't otherwise control the source code for the class you are trying to deserialize, then another alternative is to create a custom JsonConverter to instantiate and populate your object. For example:
class ResultConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Result));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load the JSON for the Result into a JObject
JObject jo = JObject.Load(reader);
// Read the properties which will be used as constructor parameters
int? code = (int?)jo["Code"];
string format = (string)jo["Format"];
// Construct the Result object using the non-default constructor
Result result = new Result(code, format);
// (If anything else needs to be populated on the result object, do that here)
// Return the result
return result;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, add the converter to your serializer settings, and use the settings when you deserialize:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Result result = JsonConvert.DeserializeObject<Result>(jsontext, settings);
A bit late and not exactly suited here, but I'm gonna add my solution here, because my question had been closed as a duplicate of this one, and because this solution is completely different.
I needed a general way to instruct Json.NET to prefer the most specific constructor for a user defined struct type, so I can omit the JsonConstructor attributes which would add a dependency to the project where each such struct is defined.
I've reverse engineered a bit and implemented a custom contract resolver where I've overridden the CreateObjectContract method to add my custom creation logic.
public class CustomContractResolver : DefaultContractResolver {
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var c = base.CreateObjectContract(objectType);
if (!IsCustomStruct(objectType)) return c;
IList<ConstructorInfo> list = objectType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).OrderBy(e => e.GetParameters().Length).ToList();
var mostSpecific = list.LastOrDefault();
if (mostSpecific != null)
{
c.OverrideCreator = CreateParameterizedConstructor(mostSpecific);
c.CreatorParameters.AddRange(CreateConstructorParameters(mostSpecific, c.Properties));
}
return c;
}
protected virtual bool IsCustomStruct(Type objectType)
{
return objectType.IsValueType && !objectType.IsPrimitive && !objectType.IsEnum && !objectType.Namespace.IsNullOrEmpty() && !objectType.Namespace.StartsWith("System.");
}
private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
method.ThrowIfNull("method");
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
}
I'm using it like this.
public struct Test {
public readonly int A;
public readonly string B;
public Test(int a, string b) {
A = a;
B = b;
}
}
var json = JsonConvert.SerializeObject(new Test(1, "Test"), new JsonSerializerSettings {
ContractResolver = new CustomContractResolver()
});
var t = JsonConvert.DeserializeObject<Test>(json);
t.A.ShouldEqual(1);
t.B.ShouldEqual("Test");
Based on some of the answers here, I have written a CustomConstructorResolver for use in a current project, and I thought it might help somebody else.
It supports the following resolution mechanisms, all configurable:
Select a single private constructor so you can define one private constructor without having to mark it with an attribute.
Select the most specific private constructor so you can have multiple overloads, still without having to use attributes.
Select the constructor marked with an attribute of a specific name - like the default resolver, but without a dependency on the Json.Net package because you need to reference Newtonsoft.Json.JsonConstructorAttribute.
public class CustomConstructorResolver : DefaultContractResolver
{
public string ConstructorAttributeName { get; set; } = "JsonConstructorAttribute";
public bool IgnoreAttributeConstructor { get; set; } = false;
public bool IgnoreSinglePrivateConstructor { get; set; } = false;
public bool IgnoreMostSpecificConstructor { get; set; } = false;
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);
// Use default contract for non-object types.
if (objectType.IsPrimitive || objectType.IsEnum) return contract;
// Look for constructor with attribute first, then single private, then most specific.
var overrideConstructor =
(this.IgnoreAttributeConstructor ? null : GetAttributeConstructor(objectType))
?? (this.IgnoreSinglePrivateConstructor ? null : GetSinglePrivateConstructor(objectType))
?? (this.IgnoreMostSpecificConstructor ? null : GetMostSpecificConstructor(objectType));
// Set override constructor if found, otherwise use default contract.
if (overrideConstructor != null)
{
SetOverrideCreator(contract, overrideConstructor);
}
return contract;
}
private void SetOverrideCreator(JsonObjectContract contract, ConstructorInfo attributeConstructor)
{
contract.OverrideCreator = CreateParameterizedConstructor(attributeConstructor);
contract.CreatorParameters.Clear();
foreach (var constructorParameter in base.CreateConstructorParameters(attributeConstructor, contract.Properties))
{
contract.CreatorParameters.Add(constructorParameter);
}
}
private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
protected virtual ConstructorInfo GetAttributeConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(c => c.GetCustomAttributes().Any(a => a.GetType().Name == this.ConstructorAttributeName)).ToList();
if (constructors.Count == 1) return constructors[0];
if (constructors.Count > 1)
throw new JsonException($"Multiple constructors with a {this.ConstructorAttributeName}.");
return null;
}
protected virtual ConstructorInfo GetSinglePrivateConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
return constructors.Length == 1 ? constructors[0] : null;
}
protected virtual ConstructorInfo GetMostSpecificConstructor(Type objectType)
{
var constructors = objectType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.OrderBy(e => e.GetParameters().Length);
var mostSpecific = constructors.LastOrDefault();
return mostSpecific;
}
}
Here is the complete version with XML documentation as a gist:
https://gist.github.com/bjorn-jarisch/80f77f4b6bdce3b434b0f7a1d06baa95
Feedback appreciated.
The default behaviour of Newtonsoft.Json is going to find the public constructors. If your default constructor is only used in containing class or the same assembly, you can reduce the access level to protected or internal so that Newtonsoft.Json will pick your desired public constructor.
Admittedly, this solution is rather very limited to specific cases.
internal Result() { }
public Result(int? code, string format, Dictionary<string, string> details = null)
{
Code = code ?? ERROR_CODE;
Format = format;
if (details == null)
Details = new Dictionary<string, string>();
else
Details = details;
}
Based on the answer by Zoltan, I created a variation that lets you use a specific constructor based on its signature.
Usage
return new JsonSerializerSettings
{
ContractResolver = new DynamicObjectResolver(t =>
{
if (t == typeof(QueueProperties))
return new Type[] { typeof(string) };
return null;
})
};
An here is the implementation
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Reflection;
namespace ConsoleApp76.Json
{
class DynamicObjectResolver : DefaultContractResolver
{
private readonly Func<Type, Type[]> GetConstructorSignature;
private readonly ConcurrentDictionary<Type, ConstructorInfo> TypeToConstructorLookup =
new ConcurrentDictionary<Type, ConstructorInfo>();
public DynamicObjectResolver(Func<Type, Type[]> getConstructorSignature)
{
if (getConstructorSignature is null)
throw new ArgumentNullException(nameof(getConstructorSignature));
GetConstructorSignature = getConstructorSignature;
}
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var result = base.CreateObjectContract(objectType);
ConstructorInfo constructor = TypeToConstructorLookup.GetOrAdd(objectType, t => FindConstructorInfo(t));
if (constructor is null)
return result;
result.OverrideCreator = CreateParameterizedConstructor(constructor);
foreach (var param in CreateConstructorParameters(constructor, result.Properties))
result.CreatorParameters.Add(param);
return result;
}
private ConstructorInfo FindConstructorInfo(Type objectType)
{
Type[] constructorSignature = GetConstructorSignature(objectType);
if (constructorSignature is null)
return null;
return objectType.GetConstructor(
bindingAttr:
System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Instance,
binder: null,
types: new Type[] { typeof(string) },
modifiers: null);
}
private static ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
{
if (method is null)
throw new ArgumentNullException(nameof(method));
var c = method as ConstructorInfo;
if (c != null)
return a => c.Invoke(a);
return a => method.Invoke(null, a);
}
}
}
Solution:
public Response Get(string jsonData) {
var json = JsonConvert.DeserializeObject<modelname>(jsonData);
var data = StoredProcedure.procedureName(json.Parameter, json.Parameter, json.Parameter, json.Parameter);
return data;
}
Model:
public class modelname {
public long parameter{ get; set; }
public int parameter{ get; set; }
public int parameter{ get; set; }
public string parameter{ get; set; }
}

Call WebApi action with a list of parameters [duplicate]

I have an ASP.NET Web API (version 4) REST service where I need to pass an array of integers.
Here is my action method:
public IEnumerable<Category> GetCategories(int[] categoryIds){
// code to retrieve categories from database
}
And this is the URL that I have tried:
/Categories?categoryids=1,2,3,4
You just need to add [FromUri] before parameter, looks like:
GetCategories([FromUri] int[] categoryIds)
And send request:
/Categories?categoryids=1&categoryids=2&categoryids=3
As Filip W points out, you might have to resort to a custom model binder like this (modified to bind to actual type of param):
public IEnumerable<Category> GetCategories([ModelBinder(typeof(CommaDelimitedArrayModelBinder))]long[] categoryIds)
{
// do your thing
}
public class CommaDelimitedArrayModelBinder : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
var key = bindingContext.ModelName;
var val = bindingContext.ValueProvider.GetValue(key);
if (val != null)
{
var s = val.AttemptedValue;
if (s != null)
{
var elementType = bindingContext.ModelType.GetElementType();
var converter = TypeDescriptor.GetConverter(elementType);
var values = Array.ConvertAll(s.Split(new[] { ","},StringSplitOptions.RemoveEmptyEntries),
x => { return converter.ConvertFromString(x != null ? x.Trim() : x); });
var typedValues = Array.CreateInstance(elementType, values.Length);
values.CopyTo(typedValues, 0);
bindingContext.Model = typedValues;
}
else
{
// change this line to null if you prefer nulls to empty arrays
bindingContext.Model = Array.CreateInstance(bindingContext.ModelType.GetElementType(), 0);
}
return true;
}
return false;
}
}
And then you can say:
/Categories?categoryids=1,2,3,4 and ASP.NET Web API will correctly bind your categoryIds array.
I recently came across this requirement myself, and I decided to implement an ActionFilter to handle this.
public class ArrayInputAttribute : ActionFilterAttribute
{
private readonly string _parameterName;
public ArrayInputAttribute(string parameterName)
{
_parameterName = parameterName;
Separator = ',';
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.ContainsKey(_parameterName))
{
string parameters = string.Empty;
if (actionContext.ControllerContext.RouteData.Values.ContainsKey(_parameterName))
parameters = (string) actionContext.ControllerContext.RouteData.Values[_parameterName];
else if (actionContext.ControllerContext.Request.RequestUri.ParseQueryString()[_parameterName] != null)
parameters = actionContext.ControllerContext.Request.RequestUri.ParseQueryString()[_parameterName];
actionContext.ActionArguments[_parameterName] = parameters.Split(Separator).Select(int.Parse).ToArray();
}
}
public char Separator { get; set; }
}
I am applying it like so (note that I used 'id', not 'ids', as that is how it is specified in my route):
[ArrayInput("id", Separator = ';')]
public IEnumerable<Measure> Get(int[] id)
{
return id.Select(i => GetData(i));
}
And the public url would be:
/api/Data/1;2;3;4
You may have to refactor this to meet your specific needs.
In case someone would need - to achieve same or similar thing(like delete) via POST instead of FromUri, use FromBody and on client side(JS/jQuery) format param as $.param({ '': categoryids }, true)
c#:
public IHttpActionResult Remove([FromBody] int[] categoryIds)
jQuery:
$.ajax({
type: 'POST',
data: $.param({ '': categoryids }, true),
url: url,
//...
});
The thing with $.param({ '': categoryids }, true) is that it .net will expect post body to contain urlencoded value like =1&=2&=3 without parameter name, and without brackets.
Easy way to send array params to web api
API
public IEnumerable<Category> GetCategories([FromUri]int[] categoryIds){
// code to retrieve categories from database
}
Jquery : send JSON object as request params
$.get('api/categories/GetCategories',{categoryIds:[1,2,3,4]}).done(function(response){
console.log(response);
//success response
});
It will generate your request URL like
../api/categories/GetCategories?categoryIds=1&categoryIds=2&categoryIds=3&categoryIds=4
You may try this code for you to take comma separated values / an array of values to get back a JSON from webAPI
public class CategoryController : ApiController
{
public List<Category> Get(String categoryIDs)
{
List<Category> categoryRepo = new List<Category>();
String[] idRepo = categoryIDs.Split(',');
foreach (var id in idRepo)
{
categoryRepo.Add(new Category()
{
CategoryID = id,
CategoryName = String.Format("Category_{0}", id)
});
}
return categoryRepo;
}
}
public class Category
{
public String CategoryID { get; set; }
public String CategoryName { get; set; }
}
Output :
[
{"CategoryID":"4","CategoryName":"Category_4"},
{"CategoryID":"5","CategoryName":"Category_5"},
{"CategoryID":"3","CategoryName":"Category_3"}
]
ASP.NET Core 2.0 Solution (Swagger Ready)
Input
DELETE /api/items/1,2
DELETE /api/items/1
Code
Write the provider (how MVC knows what binder to use)
public class CustomBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(int[]) || context.Metadata.ModelType == typeof(List<int>))
{
return new BinderTypeModelBinder(typeof(CommaDelimitedArrayParameterBinder));
}
return null;
}
}
Write the actual binder (access all sorts of info about the request, action, models, types, whatever)
public class CommaDelimitedArrayParameterBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var value = bindingContext.ActionContext.RouteData.Values[bindingContext.FieldName] as string;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
var ints = value?.Split(',').Select(int.Parse).ToArray();
bindingContext.Result = ModelBindingResult.Success(ints);
if(bindingContext.ModelType == typeof(List<int>))
{
bindingContext.Result = ModelBindingResult.Success(ints.ToList());
}
return Task.CompletedTask;
}
}
Register it with MVC
services.AddMvc(options =>
{
// add custom binder to beginning of collection
options.ModelBinderProviders.Insert(0, new CustomBinderProvider());
});
Sample usage with a well documented controller for Swagger
/// <summary>
/// Deletes a list of items.
/// </summary>
/// <param name="itemIds">The list of unique identifiers for the items.</param>
/// <returns>The deleted item.</returns>
/// <response code="201">The item was successfully deleted.</response>
/// <response code="400">The item is invalid.</response>
[HttpDelete("{itemIds}", Name = ItemControllerRoute.DeleteItems)]
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(void), StatusCodes.Status404NotFound)]
public async Task Delete(List<int> itemIds)
=> await _itemAppService.RemoveRangeAsync(itemIds);
EDIT: Microsoft recommends using a TypeConverter for these kids of operations over this approach. So follow the below posters advice and document your custom type with a SchemaFilter.
Instead of using a custom ModelBinder, you can also use a custom type with a TypeConverter.
[TypeConverter(typeof(StrListConverter))]
public class StrList : List<string>
{
public StrList(IEnumerable<string> collection) : base(collection) {}
}
public class StrListConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
return null;
if (value is string s)
{
if (string.IsNullOrEmpty(s))
return null;
return new StrList(s.Split(','));
}
return base.ConvertFrom(context, culture, value);
}
}
The advantage is that it makes the Web API method's parameters very simple. You dont't even need to specify [FromUri].
public IEnumerable<Category> GetCategories(StrList categoryIds) {
// code to retrieve categories from database
}
This example is for a List of strings, but you could do categoryIds.Select(int.Parse) or simply write an IntList instead.
I originally used the solution that #Mrchief for years (it works great). But when when I added Swagger to my project for API documentation my end point was NOT showing up.
It took me a while, but this is what I came up with. It works with Swagger, and your API method signatures look cleaner:
In the end you can do:
// GET: /api/values/1,2,3,4
[Route("api/values/{ids}")]
public IHttpActionResult GetIds(int[] ids)
{
return Ok(ids);
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Allow WebApi to Use a Custom Parameter Binding
config.ParameterBindingRules.Add(descriptor => descriptor.ParameterType == typeof(int[]) && descriptor.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get)
? new CommaDelimitedArrayParameterBinder(descriptor)
: null);
// Allow ApiExplorer to understand this type (Swagger uses ApiExplorer under the hood)
TypeDescriptor.AddAttributes(typeof(int[]), new TypeConverterAttribute(typeof(StringToIntArrayConverter)));
// Any existing Code ..
}
}
Create a new class: CommaDelimitedArrayParameterBinder.cs
public class CommaDelimitedArrayParameterBinder : HttpParameterBinding, IValueProviderParameterBinding
{
public CommaDelimitedArrayParameterBinder(HttpParameterDescriptor desc)
: base(desc)
{
}
/// <summary>
/// Handles Binding (Converts a comma delimited string into an array of integers)
/// </summary>
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
HttpActionContext actionContext,
CancellationToken cancellationToken)
{
var queryString = actionContext.ControllerContext.RouteData.Values[Descriptor.ParameterName] as string;
var ints = queryString?.Split(',').Select(int.Parse).ToArray();
SetValue(actionContext, ints);
return Task.CompletedTask;
}
public IEnumerable<ValueProviderFactory> ValueProviderFactories { get; } = new[] { new QueryStringValueProviderFactory() };
}
Create a new class: StringToIntArrayConverter.cs
public class StringToIntArrayConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
}
Notes:
https://stackoverflow.com/a/47123965/862011 pointed me in the right direction
Swagger was only failing to pick my comma delimited end points when using the [Route] attribute
public class ArrayInputAttribute : ActionFilterAttribute
{
private readonly string[] _ParameterNames;
/// <summary>
///
/// </summary>
public string Separator { get; set; }
/// <summary>
/// cons
/// </summary>
/// <param name="parameterName"></param>
public ArrayInputAttribute(params string[] parameterName)
{
_ParameterNames = parameterName;
Separator = ",";
}
/// <summary>
///
/// </summary>
public void ProcessArrayInput(HttpActionContext actionContext, string parameterName)
{
if (actionContext.ActionArguments.ContainsKey(parameterName))
{
var parameterDescriptor = actionContext.ActionDescriptor.GetParameters().FirstOrDefault(p => p.ParameterName == parameterName);
if (parameterDescriptor != null && parameterDescriptor.ParameterType.IsArray)
{
var type = parameterDescriptor.ParameterType.GetElementType();
var parameters = String.Empty;
if (actionContext.ControllerContext.RouteData.Values.ContainsKey(parameterName))
{
parameters = (string)actionContext.ControllerContext.RouteData.Values[parameterName];
}
else
{
var queryString = actionContext.ControllerContext.Request.RequestUri.ParseQueryString();
if (queryString[parameterName] != null)
{
parameters = queryString[parameterName];
}
}
var values = parameters.Split(new[] { Separator }, StringSplitOptions.RemoveEmptyEntries)
.Select(TypeDescriptor.GetConverter(type).ConvertFromString).ToArray();
var typedValues = Array.CreateInstance(type, values.Length);
values.CopyTo(typedValues, 0);
actionContext.ActionArguments[parameterName] = typedValues;
}
}
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
_ParameterNames.ForEach(parameterName => ProcessArrayInput(actionContext, parameterName));
}
}
Usage:
[HttpDelete]
[ArrayInput("tagIDs")]
[Route("api/v1/files/{fileID}/tags/{tagIDs}")]
public HttpResponseMessage RemoveFileTags(Guid fileID, Guid[] tagIDs)
{
_FileRepository.RemoveFileTags(fileID, tagIDs);
return Request.CreateResponse(HttpStatusCode.OK);
}
Request uri
http://localhost/api/v1/files/2a9937c7-8201-59b7-bc8d-11a9178895d0/tags/BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63,BBA5CD5D-F07D-47A9-8DEE-D19F5FA65F63
If you want to list/ array of integers easiest way to do this is accept the comma(,) separated list of string and convert it to list of integers.Do not forgot to mention [FromUri] attriubte.your url look like:
...?ID=71&accountID=1,2,3,289,56
public HttpResponseMessage test([FromUri]int ID, [FromUri]string accountID)
{
List<int> accountIdList = new List<int>();
string[] arrAccountId = accountId.Split(new char[] { ',' });
for (var i = 0; i < arrAccountId.Length; i++)
{
try
{
accountIdList.Add(Int32.Parse(arrAccountId[i]));
}
catch (Exception)
{
}
}
}
I have created a custom model binder which converts any comma separated values (only primitive, decimal, float, string) to their corresponding arrays.
public class CommaSeparatedToArrayBinder<T> : IModelBinder
{
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
{
Type type = typeof(T);
if (type.IsPrimitive || type == typeof(Decimal) || type == typeof(String) || type == typeof(float))
{
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; }
string[] values = key.Split(',');
IEnumerable<T> result = this.ConvertToDesiredList(values).ToArray();
bindingContext.Model = result;
return true;
}
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Only primitive, decimal, string and float data types are allowed...");
return false;
}
private IEnumerable<T> ConvertToDesiredArray(string[] values)
{
foreach (string value in values)
{
var val = (T)Convert.ChangeType(value, typeof(T));
yield return val;
}
}
}
And how to use in Controller:
public IHttpActionResult Get([ModelBinder(BinderType = typeof(CommaSeparatedToArrayBinder<int>))] int[] ids)
{
return Ok(ids);
}
Make the method type [HttpPost], create a model that has one int[] parameter, and post with json:
/* Model */
public class CategoryRequestModel
{
public int[] Categories { get; set; }
}
/* WebApi */
[HttpPost]
public HttpResponseMessage GetCategories(CategoryRequestModel model)
{
HttpResponseMessage resp = null;
try
{
var categories = //your code to get categories
resp = Request.CreateResponse(HttpStatusCode.OK, categories);
}
catch(Exception ex)
{
resp = Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
return resp;
}
/* jQuery */
var ajaxSettings = {
type: 'POST',
url: '/Categories',
data: JSON.serialize({Categories: [1,2,3,4]}),
contentType: 'application/json',
success: function(data, textStatus, jqXHR)
{
//get categories from data
}
};
$.ajax(ajaxSettings);
Or you could just pass a string of delimited items and put it into an array or list on the receiving end.
I addressed this issue this way.
I used a post message to the api to send the list of integers as data.
Then I returned the data as an ienumerable.
The sending code is as follows:
public override IEnumerable<Contact> Fill(IEnumerable<int> ids)
{
IEnumerable<Contact> result = null;
if (ids!=null&&ids.Count()>0)
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:49520/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
String _endPoint = "api/" + typeof(Contact).Name + "/ListArray";
HttpResponseMessage response = client.PostAsJsonAsync<IEnumerable<int>>(_endPoint, ids).Result;
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
result = JsonConvert.DeserializeObject<IEnumerable<Contact>>(response.Content.ReadAsStringAsync().Result);
}
}
}
catch (Exception)
{
}
}
return result;
}
The receiving code is as follows:
// POST api/<controller>
[HttpPost]
[ActionName("ListArray")]
public IEnumerable<Contact> Post([FromBody]IEnumerable<int> ids)
{
IEnumerable<Contact> result = null;
if (ids != null && ids.Count() > 0)
{
return contactRepository.Fill(ids);
}
return result;
}
It works just fine for one record or many records. The fill is an overloaded method using DapperExtensions:
public override IEnumerable<Contact> Fill(IEnumerable<int> ids)
{
IEnumerable<Contact> result = null;
if (ids != null && ids.Count() > 0)
{
using (IDbConnection dbConnection = ConnectionProvider.OpenConnection())
{
dbConnection.Open();
var predicate = Predicates.Field<Contact>(f => f.id, Operator.Eq, ids);
result = dbConnection.GetList<Contact>(predicate);
dbConnection.Close();
}
}
return result;
}
This allows you to fetch data from a composite table (the id list), and then return the records you are really interested in from the target table.
You could do the same with a view, but this gives you a little more control and flexibility.
In addition, the details of what you are seeking from the database are not shown in the query string. You also do not have to convert from a csv file.
You have to keep in mind when using any tool like the web api 2.x interface is that the get, put, post, delete, head, etc., functions have a general use, but are not restricted to that use.
So, while post is generally used in a create context in the web api interface, it is not restricted to that use. It is a regular html call that can be used for any purpose permitted by html practice.
In addition, the details of what is going on are hidden from those "prying eyes" we hear so much about these days.
The flexibility in naming conventions in the web api 2.x interface and use of regular web calling means you send a call to the web api that misleads snoopers into thinking you are really doing something else. You can use "POST" to really retrieve data, for example.
My solution was to create an attribute to validate strings, it does a bunch of extra common features, including regex validation that you can use to check for numbers only and then later I convert to integers as needed...
This is how you use:
public class MustBeListAndContainAttribute : ValidationAttribute
{
private Regex regex = null;
public bool RemoveDuplicates { get; }
public string Separator { get; }
public int MinimumItems { get; }
public int MaximumItems { get; }
public MustBeListAndContainAttribute(string regexEachItem,
int minimumItems = 1,
int maximumItems = 0,
string separator = ",",
bool removeDuplicates = false) : base()
{
this.MinimumItems = minimumItems;
this.MaximumItems = maximumItems;
this.Separator = separator;
this.RemoveDuplicates = removeDuplicates;
if (!string.IsNullOrEmpty(regexEachItem))
regex = new Regex(regexEachItem, RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var listOfdValues = (value as List<string>)?[0];
if (string.IsNullOrWhiteSpace(listOfdValues))
{
if (MinimumItems > 0)
return new ValidationResult(this.ErrorMessage);
else
return null;
};
var list = new List<string>();
list.AddRange(listOfdValues.Split(new[] { Separator }, System.StringSplitOptions.RemoveEmptyEntries));
if (RemoveDuplicates) list = list.Distinct().ToList();
var prop = validationContext.ObjectType.GetProperty(validationContext.MemberName);
prop.SetValue(validationContext.ObjectInstance, list);
value = list;
if (regex != null)
if (list.Any(c => string.IsNullOrWhiteSpace(c) || !regex.IsMatch(c)))
return new ValidationResult(this.ErrorMessage);
return null;
}
}
I just added the Query key (Refit lib) in the property for the request.
[Query(CollectionFormat.Multi)]
public class ExampleRequest
{
[FromQuery(Name = "name")]
public string Name { get; set; }
[AliasAs("category")]
[Query(CollectionFormat.Multi)]
public List<string> Categories { get; set; }
}
All other solutions need too much work. I was trying to use IEnumerable<long> or long[] in a HttpGet method parameter, but I see no point of doing all the work just to make the signature of the handler method parameter long[]. I ended up just making it string, and then separated it within the handler. Took me one line.
public async Task<IActionResult> SomeHandler(string idsString)
{
var ids = idsString.Split(',').Select(x => long.Parse(x));
Now you can just pass the numbers like
.../SomeHandler?idsString=123,456,789,012

Serializing a IKVMC generated object to JSON

I have a java library that contains all the domain models for our backend REST API. The backend API is implemented in Java and translate Java objects to JSON using Jackson.
Recently we need to implement a new feature and have a Windows .NET application talk to our API.
However, since the domain model (contract) is all in Java, we had to translate all the Java classes to C# classes so that we can use Json.NET to serialize/deserialize JSON, but this quickly became time-consuming. In addition, whenver there is contract change in Java we likely have to do this for C# classes also.
I searched online and found out IKVMC can translate a jar to a DLL, so I tried it out, however, it's causing some Json.NET serialization problems.
For example
I have a Java object that looks like this:
public class ApiToken {
private String apiKey;
private String apiSecret;
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getApiSecret() {
return apiSecret;
}
public void setApiSecret(String apiSecret) {
this.apiSecret = apiSecret;
}
#Override
public int hashCode() {
return Objects.hashCode(apiKey, apiSecret);
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ApiToken other = (ApiToken) obj;
return Objects.equal(this.apiKey, other.apiKey) && Objects.equal(this.apiSecret, other.apiSecret);
}
#Override
public String toString() {
return Objects.toStringHelper(this).add("apiKey", apiKey).add("apiSecret", apiSecret).toString();
}
}
AFTER translation by ikvmc, it looks like this:
public class ApiToken : Object
{
[LineNumberTable(11)]
[MethodImpl(MethodImplOptions.NoInlining)]
public ApiToken();
public virtual string getApiKey();
public virtual void setApiKey(string apiKey);
public virtual string getApiSecret();
public virtual void setApiSecret(string apiSecret);
[LineNumberTable(35)]
public override int hashCode();
[LineNumberTable(new byte[] {159, 182, 100, 98, 99, 98, 110, 98, 103})]
[MethodImpl(MethodImplOptions.NoInlining)]
public override bool equals(object obj);
[LineNumberTable(52)]
public override string toString();
}
when I create this object in C#, JSON.net does NOT seralize this object correctly. Instead, it just produces an empty JSON {}
I suspect this is because there are no fields/properties exposed in the object that is generated by ikvmc.
Does anyone know if there's a workaround for this?
Thanks a lot and much appreciated
Update:
This is how I'm serializing the object
ApiToken apiToken = new ApiToken();
apiToken.setApiKey("test");
apiToken.setApiSecret("secret");
string json = JsonConvert.SerializeObject(apiToken);
The json output is {}.
I suspect this is because there are no fields/properties exposed in the object that is generated by ikvmc
Yes.
Does anyone know if there's a workaround for this?
You can do it by writing a custom ContractResolver and ValueProvider as below
var obj = new ApiToken();
obj.setApiKey("X-X-X");
obj.setI(666);
var settings = new Newtonsoft.Json.JsonSerializerSettings() {
ContractResolver = new MyContractResolver()
};
var json = JsonConvert.SerializeObject(obj, settings);
//json : {"ApiKey":"X-X-X","I":666}
var newobj = JsonConvert.DeserializeObject<ApiToken>(json, settings);
//Test class
public class ApiToken
{
private String apiKey;
public String getApiKey()
{
return apiKey;
}
public void setApiKey(String apiKey)
{
this.apiKey = apiKey;
}
private int i;
public int getI()
{
return i;
}
public void setI(int i)
{
this.i = i;
}
public string dummy()
{
return "abcde";
}
}
public class MyContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
{
protected override IList<Newtonsoft.Json.Serialization.JsonProperty> CreateProperties(Type type, Newtonsoft.Json.MemberSerialization memberSerialization)
{
//Find methods. setXXX getXXX
var properties = type.GetMethods()
.Where(m => m.Name.Length > 3)
.GroupBy(m => m.Name.Substring(3))
.Where(g => g.Count() == 2 && g.Any(x=>x.Name=="set" + g.Key) && g.Any(x=>x.Name=="get" + g.Key))
.ToList();
//Create a JsonProperty for each set/getXXX pair
var ret = properties.Select(prop=>
{
var jProp = new Newtonsoft.Json.Serialization.JsonProperty();
jProp.PropertyName = prop.Key;
jProp.PropertyType = prop.First(m => m.Name.StartsWith("get")).ReturnType;
jProp.ValueProvider = new MyValueProvider(prop.ToList());
jProp.Readable = jProp.Writable = true;
return jProp;
})
.ToList();
return ret;
}
}
public class MyValueProvider : Newtonsoft.Json.Serialization.IValueProvider
{
List<MethodInfo> _MethodInfos = null;
public MyValueProvider(List<MethodInfo> methodInfos)
{
_MethodInfos = methodInfos;
}
public object GetValue(object target)
{
return _MethodInfos.First(m => m.Name.StartsWith("get")).Invoke(target, null);
}
public void SetValue(object target, object value)
{
_MethodInfos.First(m => m.Name.StartsWith("set")).Invoke(target, new object[] { value });
}
}

Duck Typing DynamicObject derivate

I wrote a class that allows a derivate to specify which of its properties can be lazy loaded. The code is:
public abstract class SelfHydratingEntity<T> : DynamicObject where T : class {
private readonly Dictionary<string, LoadableBackingField> fields;
public SelfHydratingEntity(T original) {
this.Original = original;
this.fields = this.GetBackingFields().ToDictionary(f => f.Name);
}
public T Original { get; private set; }
protected virtual IEnumerable<LoadableBackingField> GetBackingFields() {
yield break;
}
public override bool TryGetMember(GetMemberBinder binder, out object result) {
LoadableBackingField field;
if (this.fields.TryGetValue(binder.Name, out field)) {
result = field.GetValue();
return true;
} else {
var getter = PropertyAccessor.GetGetter(this.Original.GetType(), binder.Name);
result = getter(this.Original);
return true;
}
}
public override bool TrySetMember(SetMemberBinder binder, object value) {
LoadableBackingField field;
if (this.fields.TryGetValue(binder.Name, out field)) {
field.SetValue(value);
return true;
} else {
var setter = PropertyAccessor.GetSetter(this.Original.GetType(), binder.Name);
setter(this.Original, value);
return true;
}
}
}
And a derivate class:
public class SelfHydratingPerson : SelfHydratingEntity<IPerson> {
private readonly IDataRepository dataRepository;
public SelfHydratingDerivate(IDataRepository dataRepository, IPerson person)
: base(person) {
this.dataRepository = dataRepository
}
protected override IEnumerable<LoadableBackingField> GetBackingFields() {
yield return new LoadableBackingField("Address", () => this.dataRepository.Addresses.Get(this.Original.AddressID));
}
}
This works perfectly fine for getting and settings property values, but I get a either a RuntimeBinderException when I implicitly cast or an InvalidCastException with an explicitly cast SelfHydratingEntity back to T.
I know that you can override the DynamicObject.TryConvert method, but I'm wondering what exactly to put in this method. I've read a lot about duck typing today, and have tried out several libraries, but none of them work for this particular scenario. All of the libraries I've tried today generate a wrapper class using Reflection.Emit that makes calls to "get_" and "set_" methods and naturally use reflection to find these methods on the wrapped instance. SelfHydratingEntity of course doesn't have the "get_" and "set_" methods defined.
So, I'm wondering if this kind of thing is even possible. Is there any way to cast an instance of SelfHydratingEntity to T? I'm looking for something like this:
var original = GetOriginalPerson();
dynamic person = new SelfHydratingPerson(new DataRepository(), original);
string name = person.Name; // Gets property value on original
var address = person.Address; // Gets property value using LoadableBackingField registration
var iPerson = (IPerson)person;
- or -
var iPerson = DuckType.As<IPerson>(person);
Have you seen this Duck Typing project. It looks pretty good. I have just found a great example from Mauricio. It uses the Windsor Castle dynamic proxy to intercept method calls
Using the code from Mauricio the following code works like a dream
class Program
{
static void Main(string[] args)
{
dynamic person = new { Name = "Peter" };
var p = DuckType.As<IPerson>(person);
Console.WriteLine(p.Name);
}
}
public interface IPerson
{
string Name { get; set; }
}
public static class DuckType
{
private static readonly ProxyGenerator generator = new ProxyGenerator();
public static T As<T>(object o)
{
return generator.CreateInterfaceProxyWithoutTarget<T>(new DuckTypingInterceptor(o));
}
}
public class DuckTypingInterceptor : IInterceptor
{
private readonly object target;
public DuckTypingInterceptor(object target)
{
this.target = target;
}
public void Intercept(IInvocation invocation)
{
var methods = target.GetType().GetMethods()
.Where(m => m.Name == invocation.Method.Name)
.Where(m => m.GetParameters().Length == invocation.Arguments.Length)
.ToList();
if (methods.Count > 1)
throw new ApplicationException(string.Format("Ambiguous method match for '{0}'", invocation.Method.Name));
if (methods.Count == 0)
throw new ApplicationException(string.Format("No method '{0}' found", invocation.Method.Name));
var method = methods[0];
if (invocation.GenericArguments != null && invocation.GenericArguments.Length > 0)
method = method.MakeGenericMethod(invocation.GenericArguments);
invocation.ReturnValue = method.Invoke(target, invocation.Arguments);
}
}
impromptu-interface
https://github.com/ekonbenefits/impromptu-interface
Can static cast interfaces onto objects derived from DynamicObject.

Categories