Is there a way to get a dynamic object from query parameters in an ASP.NET Core WebAPI controller action?
When I try the following I get queries as an empty object
public object Action([FromQuery] dynamic queries)
{
...
}
Here is a workaround of customizing a model binder to bind the query string to Dictionary type:
DynamicModelBinder
public class DynamicModelBinder:IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
var result = new Dictionary<string, dynamic> { };
var query = bindingContext.HttpContext.Request.Query;
if (query == null)
{
bindingContext.ModelState.AddModelError("QueryString", "The data is null");
return Task.CompletedTask;
}
foreach (var k in query.Keys)
{
StringValues v = string.Empty;
var flag = query.TryGetValue(k, out v);
if (flag)
{
if (v.Count > 1)
{
result.Add(k, v);
}
else {
result.Add(k, v[0]);
}
}
}
bindingContext.Result = ModelBindingResult.Success(result);
return Task.CompletedTask;
}
}
Controller
public object Action([ModelBinder(BinderType = typeof(DynamicModelBinder))]dynamic queries)
{
return queries;
}
Result
Related
I want dinamycally load catalogs to DevExpress grid and repositories. Some columns reference to other tables. I want to realize without "if-else, switch" methods, solve with generic for example.
My project consists of entity framework, unitofwork etc.
RepositoryItemLookUpEdit repositoryItemLookUpEdit = new RepositoryItemLookUpEdit()
{
ValueMember = repositoryItem.ValueMember,
DisplayMember = repositoryItem.DisplayMember
;
// var objectType = GetTypeByName(repositoryItem.FinalTable);
if (repositoryItem.FinalTable=="DeviceTypes")
{
objectList = GetObjectList<DeviceTypes>();
}
else if (repositoryItem.FinalTable == "DeviceKinds")
{
objectList = GetObjectList<DeviceKinds>();
}
repositoryItemLookUpEdit.DataSource = objectList;
bandedGridColumn.ColumnEdit = repositoryItemLookUpEdit;
---
private IEnumerable<T> GetObjectList <T>() where T : Entity
{
IEnumerable<T> objectList=Enumerable.Empty<T>();
using (var unitOfWork = new UnitOfWork(new ApplicationDbContext(optionsBuilder.Options)))
{
objectList = unitOfWork.GetRepository<T>().GetAll();
}
return objectList;
}
---
private T GetTypeByName<T>(string typeName) where T:Entity
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Reverse())
{
var type = assembly.GetType(typeName);
if (type != null)
{
return type as T;
}
}
return null;
}
My solution is here:
public List<object> GetTableNyName(string tableName)
{
List<object> objectList = new List<object>();
var entityType = GetTypeOfTable(tableName);
if (entityType!=null)
{
var query = (IQueryable)_context.GetType().GetMethod("Set", 1,
Type.EmptyTypes).MakeGenericMethod(entityType.ClrType).Invoke(_context, null);
objectList = query.OfType<object>().ToList();
}
return objectList;
}
--
private IEntityType GetTypeOfTable(string tableName)
{
var entityType = _context.Model.GetEntityTypes().Where(x => x.ClrType.Name == tableName).FirstOrDefault();
return entityType;
}
I have a product list query that I wrote under normal conditions. I have created a mapper for this query and I can use it as follows.
public IActionResult Index()
{
// use normal selecting
var productList = _context.Products.Select(s => new ProductDto
{
Id = s.Id,
CreateTime = s.CreateTime,
Title = s.Title,
Description = s.Description
}).ToList();
// use my mapper
var productListMap = _context.Products
.ToMap<Product, ProductDto>().ToList();
return View();
}
On simple data types it works just fine as long as the type and name are the same. But it cannot convert complex types (classes and models).
I thought it would be a nice idea to specify it as a parameter at the time of writing, so that it gets the job done. So I want a layout like below.
public IActionResult Index()
{
// what i'm doing now
var productList = _context.Products.Select(s => new ProductDto
{
Id = s.Id,
CreateTime = s.CreateTime,
Title = s.Title,
Description = s.Description,
Features = s.Features.ToMap<Feature, FeatureDto>().ToList(),
Images = s.Images.ToMap<Image, ImageDto>().ToList()
}).ToList();
// i want to do
var productListMap = _context.Products
.ToMap<Product, ProductDto>(p => p.Features, p.Images).ToList();
return View();
}
What do I need to change/add in the mapper class below to make it easy to use this way?
public static class Mapper
{
public static IEnumerable<TDestination> ToMap<TSource, TDestination>(this IEnumerable<TSource> sourceList)
{
return sourceList.Select(source => source.ToMap<TSource, TDestination>());
}
public static TDestination ToMap<TSource, TDestination>(this TSource source)
{
if (source == null) return default;
var entityProperties = source.GetType().GetProperties();
var dtoInstance = Activator.CreateInstance<TDestination>();
for (int i = 0; i < entityProperties.Length; i++)
{
var currentPropertyName = entityProperties[i].Name;
var value = GetPropertyValue(source, currentPropertyName);
if (dtoInstance.GetType().GetProperty(currentPropertyName) == null)
continue;
try { dtoInstance.GetType().GetProperty(currentPropertyName).SetValue(dtoInstance, value); }
catch (Exception ex) { /* Nothing */ }
}
return dtoInstance;
}
public static object GetPropertyValue(object source, string propName)
{
if (source == null) throw new ArgumentException("Value cannot be null.", nameof(source));
if (propName == null) throw new ArgumentException("Value cannot be null.", nameof(propName));
var prop = source.GetType().GetProperty(propName);
return prop != null ? prop.GetValue(source, null) : null;
}
}
I have this json
{"id":"48e86841-f62c-42c9-ae20-b54ba8c35d6d"}
How do I get the 48e86841-f62c-42c9-ae20-b54ba8c35d6d out of it? All examples I can find show to do something like
var o = System.Text.Json.JsonSerializer.Deserialize<some-type>(json);
o.id // <- here's the ID!
But I don't have a type that fits this definition and I don't want to create one. I've tried deserializing to dynamic but I was unable to get that working.
var result = System.Text.Json.JsonSerializer.Deserialize<dynamic>(json);
result.id // <-- An exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in System.Linq.Expressions.dll but was not handled in user code: ''System.Text.Json.JsonElement' does not contain a definition for 'id''
Can anyone give any suggestions?
edit:
I just figured out I can do this:
Guid id = System.Text.Json.JsonDocument.Parse(json).RootElement.GetProperty("id").GetGuid();
This does work - but is there a better way?
you can deserialize to a Dictionary:
var dict = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(json)
Or just deserialize to Object which will yield a JsonElement that you can call GetProperty on.
Support for JsonObject has been added in .NET 6 using System.Text.Json.Nodes.
Example:
const string Json = "{\"MyNumber\":42, \"MyArray\":[10,11]}";
// dynamic
{
dynamic obj = JsonNode.Parse(Json);
int number = (int)obj["MyNumber"];
Debug.Assert(number == 42);
obj["MyString"] = "Hello";
Debug.Assert((string)obj["MyString"] == "Hello");
}
// JsonObject
{
JsonObject obj = JsonNode.Parse(Json).AsObject();
int number = (int)obj["MyNumber"];
Debug.Assert(number == 42);
obj["MyString"] = "Hello";
Debug.Assert((string)obj["MyString"] == "Hello");
}
Sources:
https://github.com/dotnet/runtime/issues/53195
https://github.com/dotnet/runtime/issues/45188
I've recently migrated a project from ASP.NET Core 2.2 to 3, and I'm having this inconvenience. In our team we value lean dependencies, so we are trying to avoid including Newtonsoft.JSON back and try using System.Text.Json. We also decided not to use a ton of POCO objects just for JSON serialization, because our backend models are more complex than needed for Web APIs. Also, because of nontrivial behaviour encapsulation, the backend models cannot be easily used to serialize/deserialize JSON strings.
I understand that System.Text.Json is supposed to be faster than Newtonsoft.JSON, but I believe this has a lot to do with ser/deser from/to specific POCO classes. Anyway, speed was not on our list of pros/cons for this decision, so YMMV.
Long story short, for the time being I wrote a small dynamic object wrapper that unpacks the JsonElements from System.Text.Json and tries to convert/cast as best as possible. The typical usage is to read the request body as a dynamic object. Again, I'm pretty sure this approach kills any speed gains, but that was not a concern for our use case.
This is the class:
public class ReflectionDynamicObject : DynamicObject {
public JsonElement RealObject { get; set; }
public override bool TryGetMember (GetMemberBinder binder, out object result) {
// Get the property value
var srcData = RealObject.GetProperty (binder.Name);
result = null;
switch (srcData.ValueKind) {
case JsonValueKind.Null:
result = null;
break;
case JsonValueKind.Number:
result = srcData.GetDouble ();
break;
case JsonValueKind.False:
result = false;
break;
case JsonValueKind.True:
result = true;
break;
case JsonValueKind.Undefined:
result = null;
break;
case JsonValueKind.String:
result = srcData.GetString ();
break;
case JsonValueKind.Object:
result = new ReflectionDynamicObject {
RealObject = srcData
};
break;
case JsonValueKind.Array:
result = srcData.EnumerateArray ()
.Select (o => new ReflectionDynamicObject { RealObject = o })
.ToArray ();
break;
}
// Always return true; other exceptions may have already been thrown if needed
return true;
}
}
and this is an example usage, to parse the request body - one part is in a base class for all my WebAPI controllers, that exposes the body as a dynamic object:
[ApiController]
public class WebControllerBase : Controller {
// Other stuff - omitted
protected async Task<dynamic> JsonBody () {
var result = await JsonDocument.ParseAsync (Request.Body);
return new ReflectionDynamicObject {
RealObject = result.RootElement
};
}
}
and can be used in the actual controller like this:
//[...]
[HttpPost ("")]
public async Task<ActionResult> Post () {
var body = await JsonBody ();
var name = (string) body.Name;
//[...]
}
//[...]
If needed, you can integrate parsing for GUIDs or other specific data types as needed - while we all wait for some official / framework-sanctioned solution.
Actual way to parse string in System.Text.Json (.NET Core 3+)
var jsonStr = "{\"id\":\"48e86841-f62c-42c9-ae20-b54ba8c35d6d\"}";
using var doc = JsonDocument.Parse(jsonStr);
var root = doc.RootElement;
var id = root.GetProperty("id").GetGuid();
You can use the following extension method to query data like "xpath"
public static string? JsonQueryXPath(this string value, string xpath, JsonSerializerOptions? options = null) => value.Deserialize<JsonElement>(options).GetJsonElement(xpath).GetJsonElementValue();
public static JsonElement GetJsonElement(this JsonElement jsonElement, string xpath)
{
if (jsonElement.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
return default;
string[] segments = xpath.Split(new[] {'.'}, StringSplitOptions.RemoveEmptyEntries);
foreach (var segment in segments)
{
if (int.TryParse(segment, out var index) && jsonElement.ValueKind == JsonValueKind.Array)
{
jsonElement = jsonElement.EnumerateArray().ElementAtOrDefault(index);
if (jsonElement.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
return default;
continue;
}
jsonElement = jsonElement.TryGetProperty(segment, out var value) ? value : default;
if (jsonElement.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined)
return default;
}
return jsonElement;
}
public static string? GetJsonElementValue(this JsonElement jsonElement) => jsonElement.ValueKind != JsonValueKind.Null &&
jsonElement.ValueKind != JsonValueKind.Undefined
? jsonElement.ToString()
: default;
Simple to use as follows
string raw = #"{
""data"": {
""products"": {
""edges"": [
{
""node"": {
""id"": ""gid://shopify/Product/4534543543316"",
""featuredImage"": {
""originalSrc"": ""https://cdn.shopify.com/s/files/1/0286/pic.jpg"",
""id"": ""gid://shopify/ProductImage/146345345339732""
}
}
},
{
""node"": {
""id"": ""gid://shopify/Product/123456789"",
""featuredImage"": {
""originalSrc"": ""https://cdn.shopify.com/s/files/1/0286/pic.jpg"",
""id"": [
""gid://shopify/ProductImage/123456789"",
""gid://shopify/ProductImage/666666666""
]
},
""1"": {
""name"": ""Tuanh""
}
}
}
]
}
}
}";
System.Console.WriteLine(raw2.QueryJsonXPath("data.products.edges.0.node.featuredImage.id"));
I wrote an extension method for this purpose. You can safely use as following:
var jsonElement = JsonSerializer.Deserialize<JsonElement>(json);
var guid = jsonElement.TryGetValue<Guid>("id");
This is the extension class.
public static class JsonElementExtensions
{
private static readonly JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true };
public static T? TryGetValue<T>(this JsonElement element, string propertyName)
{
if (element.ValueKind != JsonValueKind.Object)
{
return default;
}
element.TryGetProperty(propertyName, out JsonElement property);
if (property.ValueKind == JsonValueKind.Undefined ||
property.ValueKind == JsonValueKind.Null)
{
return default;
}
try
{
return property.Deserialize<T>(options);
}
catch (JsonException)
{
return default;
}
}
}
Reason
The reason behind using this extension instead of JsonNode class is because if you need a Controller method accepts just an object without exposing it's model class Asp.Net Core model binding uses JsonElement struct to map the json string. At this point (as far as I know) there is no simple way to convert the JsonElement to JsonNode and when your object can be anything the JsonElement methods will throw exceptions for undefined fields while JsonNode don't.
[HttpPost]
public IActionResult Post(object setupObject)
{
var setup = (JsonElement)setupObject;
var id = setup.TryGetValue<Guid>("id");
var user = setup.TryGetValue<User?>("user");
var account = setup.TryGetValue<Account?>("account");
var payments = setup.TryGetValue<IEnumerable<Payments>?>("payments");
// ...
return Ok();
}
update to .NET Core 3.1 to support
public static dynamic FromJson(this string json, JsonSerializerOptions options = null)
{
if (string.IsNullOrEmpty(json))
return null;
try
{
return JsonSerializer.Deserialize<ExpandoObject>(json, options);
}
catch
{
return null;
}
}
You can also deserialize your json to an object of your target class, and then read its properties as per normal:
var obj = DeSerializeFromStrToObj<ClassToSerialize>(jsonStr);
Console.WriteLine($"Property: {obj.Property}");
where DeSerializeFromStrToObj is a custom class that makes use of reflection to instantiate an object of a targeted class:
public static T DeSerializeFromStrToObj<T>(string json)
{
try
{
var o = (T)Activator.CreateInstance(typeof(T));
try
{
var jsonDict = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
var props = o.GetType().GetProperties();
if (props == null || props.Length == 0)
{
Debug.WriteLine($"Error: properties from target class '{typeof(T)}' could not be read using reflection");
return default;
}
if (jsonDict.Count != props.Length)
{
Debug.WriteLine($"Error: number of json lines ({jsonDict.Count}) should be the same as number of properties ({props.Length})of our class '{typeof(T)}'");
return default;
}
foreach (var prop in props)
{
if (prop == null)
{
Debug.WriteLine($"Error: there was a prop='null' in our target class '{typeof(T)}'");
return default;
}
if (!jsonDict.ContainsKey(prop.Name))
{
Debug.WriteLine($"Error: jsonStr does not refer to target class '{typeof(T)}'");
return default;
}
var value = jsonDict[prop.Name];
Type t = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
object safeValue = value ?? Convert.ChangeType(value, t);
prop.SetValue(o, safeValue, null); // initialize property
}
return o;
}
catch (Exception e2)
{
Debug.WriteLine(e2.Message);
return o;
}
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
return default;
}
}
You can test your jsons for example here
Here you find a complete working example with different ways of serialization and deserialization that might be of interest for you and/or future readers:
using System;
using System.Collections.Generic;
using System.Text.Json;
using static Json_Tests.JsonHelpers;
namespace Json_Tests
{
public class Class1
{
public void Test()
{
var obj1 = new ClassToSerialize();
var jsonStr = obj1.ToString();
// if you have the class structure for the jsonStr (for example, if you have created the jsonStr yourself from your code):
var obj2 = DeSerializeFromStrToObj<ClassToSerialize>(jsonStr);
Console.WriteLine($"{nameof(obj2.Name)}: {obj2.Name}");
// if you do not have the class structure for the jsonStr (forexample, jsonStr comes from a 3rd party service like the web):
var obj3 = JsonSerializer.Deserialize<object>(jsonStr) as JsonElement?;
var propName = nameof(obj1.Name);
var propVal1 = obj3?.GetProperty("Name");// error prone
Console.WriteLine($"{propName}: {propVal1}");
JsonElement propVal2 = default;
obj3?.TryGetProperty("Name", out propVal2);// error prone
Console.WriteLine($"{propName}: {propVal2}");
var obj4 = DeSerializeFromStrToDict(jsonStr);
foreach (var pair in obj4)
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
}
[Serializable]
public class ClassToSerialize
{
// important: properties must have at least getters
public string Name { get; } = "Paul";
public string Surname{ get; set; } = "Efford";
public override string ToString() => JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true });
}
public static class JsonHelpers
{
/// <summary>
/// to use if you do not have the class structure for the jsonStr (forexample, jsonStr comes from a 3rd party service like the web)
/// </summary>
public static Dictionary<string, string> DeSerializeFromStrToDict(string json)
{
try
{
return JsonSerializer.Deserialize<Dictionary<string, string>>(json);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return new Dictionary<string, string>(); // return empty
}
}
/// <summary>
/// to use if you have the class structure for the jsonStr (for example, if you have created the jsonStr yourself from your code)
/// </summary>
public static T DeSerializeFromStrToObj<T>(string json) // see this: https://json2csharp.com/#
{
try
{
var o = (T)Activator.CreateInstance(typeof(T));
try
{
var jsonDict = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
var props = o.GetType().GetProperties();
if (props == null || props.Length == 0)
{
Console.WriteLine($"Error: properties from target class '{typeof(T)}' could not be read using reflection");
return default;
}
if (jsonDict.Count != props.Length)
{
Console.WriteLine($"Error: number of json lines ({jsonDict.Count}) should be the same as number of properties ({props.Length})of our class '{typeof(T)}'");
return default;
}
foreach (var prop in props)
{
if (prop == null)
{
Console.WriteLine($"Error: there was a prop='null' in our target class '{typeof(T)}'");
return default;
}
if (!jsonDict.ContainsKey(prop.Name))
{
Console.WriteLine($"Error: jsonStr does not refer to target class '{typeof(T)}'");
return default;
}
var value = jsonDict[prop.Name];
Type t = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
object safeValue = value ?? Convert.ChangeType(value, t);
prop.SetValue(o, safeValue, null); // initialize property
}
return o;
}
catch (Exception e2)
{
Console.WriteLine(e2.Message);
return o;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return default;
}
}
}
}
I'm looking for a way to "unwrap" JSON that's been posted to a MVC Core service. Let's say, I have the following method:
[HttpPost]
public dynamic SayHello(string FirstName, string SecondName)
{
return $"Hello {FirstName} {SecondName} !";
}
And I post in the following JSON:
{
"FirstName":"Joe",
"SecondName": "Bloggs"
}
I'd expect to get a response of Hello Joe Bloggs !, but I cannot seem to find an easy way to unwrap the JSON object into the properties of the method.
I know the correct solution is to have a HelloModel with those two properties in, slap on a [FromBody] attribute, but for reasons this isn't possible.
Here's another (slightly convoluted) option. You can create your own action filter to intercept the request and populate the parameters with values based on deciding the JSON yourself. For example:
public class JsonToParametersAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var stream = filterContext.HttpContext.Request.Body;
using (var sr = new StreamReader(stream))
using (var jsonTextReader = new JsonTextReader(sr))
{
var serializer = new JsonSerializer();
var body = serializer.Deserialize<JObject>(jsonTextReader);
if (body == null) return;
foreach (var parameter in filterContext.ActionDescriptor.Parameters)
{
var jsonProperty = body.Properties().SingleOrDefault(p => p.Name == parameter.Name);
if (jsonProperty != null)
{
var param = filterContext.ActionDescriptor.Parameters.OfType<ControllerParameterDescriptor>().FirstOrDefault(e => e.Name == parameter.Name);
if (param == null)
{
continue;
}
if (!filterContext.ActionArguments.ContainsKey(parameter.Name))
{
object value;
try
{
value = jsonProperty.Value.ToObject(param.ParameterInfo.ParameterType);
}
catch (Exception)
{
value = GetDefault(param.ParameterInfo.ParameterType);
}
filterContext.ActionArguments.Add(parameter.Name, value);
}
}
}
}
}
private static object GetDefault(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
}
Now decorate your action method with this new attribute:
[HttpPost]
[JsonToParameters]
public dynamic SayHello(string FirstName, string SecondName)
{
return $"Hello {FirstName} {SecondName} !";
}
Superfluous comment: I cannot believe I couldn't find a clear answer for this anywhere yet!
Using ASP.NET MVC model binding requires use of dot notation (variableName.propertyName) when using a query string. However, jQuery will use bracket notation when using a GET request, such as variableName[propertyName]=value&. ASP.NET MVC cannot understand this notation.
If I issued a POST request ASP.NET is able to properly bind the model because it uses dot notation in the posted body.
Is there any way to force ASP.NET to bind to a model that is a complex object when bracketed notation is used within a query string?
I'm not sure if this is the ideal solution, but I solved this using some reflection magic by implementing a generic implementation of IModelBinder. The stipulations on this implementation is that it assumes the elements from JavaScript in the query string are in camelCase and the class in C# is in PascalCase per standard styles. Additionally, it only functions on public [set-able] properties. Here's my implementation below:
public class BracketedQueryStringModelBinder<T> : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanWrite);
Dictionary<string, object> values = new Dictionary<string, object>();
foreach (var p in properties)
{
if (!IsNullable(p.PropertyType))
{
object val = TryGetValueType(p.PropertyType, bindingContext, p.Name);
if (val != null)
{
values.Add(p.Name, val);
}
}
else
{
object val = GetRefernceType(p.PropertyType, bindingContext, p.Name);
values.Add(p.Name, val);
}
}
if (values.Any())
{
object boundModel = Activator.CreateInstance<T>();
foreach (var p in properties.Where(i => values.ContainsKey(i.Name)))
{
p.SetValue(boundModel, values[p.Name]);
}
return boundModel;
}
return null;
}
private static bool IsNullable(Type t)
{
if (t == null)
throw new ArgumentNullException("t");
if (!t.IsValueType)
return true;
return Nullable.GetUnderlyingType(t) != null;
}
private static object TryGetValueType(Type type, ModelBindingContext ctx, string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException("key");
key = ConvertToPascalCase(key);
ValueProviderResult result = ctx.ValueProvider.GetValue(string.Concat(ctx.ModelName, "[", key, "]"));
if (result == null && ctx.FallbackToEmptyPrefix)
result = ctx.ValueProvider.GetValue(key);
if (result == null)
return null;
try
{
object returnVal = result.ConvertTo(type);
ctx.ModelState.SetModelValue(key, result);
return returnVal;
}
catch (Exception ex)
{
ctx.ModelState.AddModelError(ctx.ModelName, ex);
return null;
}
}
private static object GetRefernceType(Type type, ModelBindingContext ctx, string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException("key");
key = ConvertToPascalCase(key);
ValueProviderResult result = ctx.ValueProvider.GetValue(string.Concat(ctx.ModelName, "[", key, "]"));
if (result == null && ctx.FallbackToEmptyPrefix)
result = ctx.ValueProvider.GetValue(key);
if (result == null)
return null;
try
{
object returnVal = result.ConvertTo(type);
ctx.ModelState.SetModelValue(key, result);
return returnVal;
}
catch (Exception ex)
{
ctx.ModelState.AddModelError(ctx.ModelName, ex);
return null;
}
}
private static string ConvertToPascalCase(string str)
{
char firstChar = str[0];
if (char.IsUpper(firstChar))
return char.ToLower(firstChar) + str.Substring(1);
return str;
}
}
Then in your controller you can use it like this:
[HttpGet]
public ActionResult myAction([ModelBinder(typeof(BracketedQueryStringModelBinder<MyClass>))] MyClass mc = null)
{
...
}
The main downfall to this method is that if you do get a query string in dot notation this binding will fail since it doesn't revert back to the standard model binder.