从括号表示法中的查询字符串绑定模型

时间:2017-06-15 16:05:50

标签: c# jquery asp.net asp.net-mvc

多余的评论:我无法相信我无法在任何地方找到明确的答案!

使用ASP.NET MVC模型绑定时,在使用查询字符串时需要使用点表示法(variableName.propertyName)。但是,jQuery在使用GET请求时将使用括号表示法,例如variableName[propertyName]=value&。 ASP.NET MVC无法理解这种表示法。

如果我发出POST请求,ASP.NET可以正确绑定模型,因为它在发布的正文中使用点表示法。

在查询字符串中使用括号表示法时,有没有办法强制ASP.NET绑定到作为复杂对象的模型?

1 个答案:

答案 0 :(得分:2)

我不确定这是否是理想的解决方案,但我通过实现IModelBinder的通用实现,使用一些反思魔法解决了这个问题。这个实现的规定是它假设查询字符串中的JavaScript元素在camelCase中,而C#中的类在每个标准样式的PascalCase中。此外,它仅适用于公共[可设置]属性。以下是我的实施方式:

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;
    }
}

然后在您的控制器中,您可以像这样使用它:

[HttpGet]
public ActionResult myAction([ModelBinder(typeof(BracketedQueryStringModelBinder<MyClass>))] MyClass mc = null)
{
    ...
}

此方法的主要缺点是,如果您以点表示法获得查询字符串,则此绑定将失败,因为它不会恢复为标准模型绑定器。