C# 如何使用asp.net mvc框架从模型生成查询字符串

C# 如何使用asp.net mvc框架从模型生成查询字符串,c#,asp.net-mvc-4,C#,Asp.net Mvc 4,我有一个模型,有一些嵌套的属性,列表。。。我想从这个模型中得到一个查询字符串参数 asp.net mvc框架中是否有任何类/帮助程序可以执行此操作 我知道使用模型绑定器,我们可以从查询字符串绑定模型,但我想做相反的事情 谢谢。我相当肯定框架中没有“序列化到查询字符串”功能,主要是因为我认为没有一种标准方法来表示查询字符串中的嵌套值和嵌套集合 我认为使用ModelMetadata基础结构可以很容易地做到这一点,但事实证明,使用ModelMetadata从集合值属性获取项会有一些复杂性。我已经拼凑了

我有一个模型,有一些嵌套的属性,列表。。。我想从这个模型中得到一个查询字符串参数

asp.net mvc框架中是否有任何类/帮助程序可以执行此操作

我知道使用模型绑定器,我们可以从查询字符串绑定模型,但我想做相反的事情


谢谢。

我相当肯定框架中没有“序列化到查询字符串”功能,主要是因为我认为没有一种标准方法来表示查询字符串中的嵌套值和嵌套集合

我认为使用ModelMetadata基础结构可以很容易地做到这一点,但事实证明,使用ModelMetadata从集合值属性获取项会有一些复杂性。我已经拼凑了一个扩展方法来解决这个问题,并构建了一个ToQueryString扩展,您可以从您拥有的任何ModelMetadata对象调用它

public static string ToQueryString(this ModelMetadata modelMetadata)
{
    if(modelMetadata.Model == null)
        return string.Empty;

    var parameters = modelMetadata.Properties.SelectMany (mm => mm.SelectPropertiesAsQueryStringParameters(null));
    var qs = string.Join("&",parameters);
    return "?" + qs;
}

private static IEnumerable<string> SelectPropertiesAsQueryStringParameters(this ModelMetadata modelMetadata, string prefix)
{
    if(modelMetadata.Model == null)
        yield break;

    if(modelMetadata.IsComplexType)
    {
        IEnumerable<string> parameters;
        if(typeof(IEnumerable).IsAssignableFrom(modelMetadata.ModelType))
        {
            parameters = modelMetadata.GetItemMetadata()
                                    .Select ((mm,i) => new {
                                        mm, 
                                        prefix = string.Format("{0}{1}[{2}]", prefix, modelMetadata.PropertyName, i)
                                    })
                                    .SelectMany (prefixed =>
                                        prefixed.mm.SelectPropertiesAsQueryStringParameters(prefixed.prefix)
                                    );          
        } 
        else 
        {
            parameters = modelMetadata.Properties
                        .SelectMany (mm => mm.SelectPropertiesAsQueryStringParameters(string.Format("{0}{1}", prefix, modelMetadata.PropertyName)));
        }

        foreach (var parameter in parameters)
        {
            yield return parameter;
        }
    } 
    else 
    {
        yield return string.Format("{0}{1}{2}={3}",
            prefix, 
            prefix != null && modelMetadata.PropertyName != null ? "." : string.Empty,
            modelMetadata.PropertyName, 
            modelMetadata.Model);
    }
}

// Returns the metadata for each item from a ModelMetadata.Model which is IEnumerable
private static IEnumerable<ModelMetadata> GetItemMetadata(this ModelMetadata modelMetadata)
{
    if(modelMetadata.Model == null)
        yield break;

    var genericType = modelMetadata.ModelType
                        .GetInterfaces()
                        .FirstOrDefault (x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>));

    if(genericType == null)
        yield return modelMetadata;

    var itemType = genericType.GetGenericArguments()[0];

    foreach (object item in ((IEnumerable)modelMetadata.Model))
    {
        yield return ModelMetadataProviders.Current.GetMetadataForType(() => item, itemType);
    }
}
公共静态字符串ToQueryString(此ModelMetadata ModelMetadata)
{
if(modelMetadata.Model==null)
返回字符串。空;
var parameters=modelMetadata.Properties.SelectMany(mm=>mm.SelectPropertiesAsQueryStringParameters(null));
var qs=string.Join(“&”,参数);
返回“?”+qs;
}
私有静态IEnumerable SelectPropertiesQueryStringParameters(此模型元数据模型元数据,字符串前缀)
{
if(modelMetadata.Model==null)
屈服断裂;
if(modelMetadata.IsComplexType)
{
可数参数;
if(typeof(IEnumerable).IsAssignableFrom(modelMetadata.ModelType))
{
parameters=modelMetadata.GetItemMetadata()
.选择((毫米,i)=>新建{
嗯,
prefix=string.Format(“{0}{1}[{2}]”,前缀,modelMetadata.PropertyName,i)
})
.SelectMany(前缀=>
前缀.mm.SelectPropertiesAsQueryStringParameters(前缀.prefixed)
);          
} 
其他的
{
parameters=modelMetadata.Properties
.SelectMany(mm=>mm.SelectPropertiesAsQueryStringParameters(string.Format(“{0}{1}”,前缀,modelMetadata.PropertyName));
}
foreach(参数中的var参数)
{
收益率参数;
}
} 
其他的
{
产生返回字符串.Format(“{0}{1}{2}={3}”,
前缀
前缀!=null&&modelMetadata.PropertyName!=null?“:string.Empty,
modelMetadata.PropertyName,
modelMetadata.Model);
}
}
//返回ModelMetadata.Model(IEnumerable)中每个项的元数据
私有静态IEnumerable GetItemMetadata(此ModelMetadata ModelMetadata)
{
if(modelMetadata.Model==null)
屈服断裂;
var genericType=modelMetadata.ModelType
.GetInterfaces()
.FirstOrDefault(x=>x.IsGenericType&&x.GetGenericTypeDefinition()==typeof(IEnumerable));
if(genericType==null)
收益率模型;
var itemType=genericType.GetGenericArguments()[0];
foreach(对象项位于((IEnumerable)modelMetadata.Model))
{
返回ModelMetadataProviders.Current.GetMetadataForType(()=>item,itemType);
}
}
用法示例:

var vd = new ViewDataDictionary<Model>(model); // in a Controller, ViewData.ModelMetadata
var queryString = vd.ModelMetadata.ToQueryString();
var vd=新的ViewDataDictionary(模型);//在控制器中,ViewData.ModelMetadata
var queryString=vd.ModelMetadata.ToQueryString();

我还没有对它进行彻底的测试,所以可能有一些null ref错误潜伏在其中,但它为我尝试过的复杂对象吐出了正确的查询字符串。

@Steve的代码在出现额外嵌套和枚举时有一些小错误

样本模型 但是@Steve的代码产生以下输出:

"?foobar[0].prop=value1&foobar[1].prop=value2"
更新代码 以下是@Steve解决方案的一个稍加修改的版本:

public static class QueryStringExtensions {
    #region inner types

    private struct PrefixedModelMetadata {

        public readonly String Prefix;
        public readonly ModelMetadata ModelMetadata;

        public PrefixedModelMetadata (String prefix, ModelMetadata modelMetadata) {
            Prefix = prefix;
            ModelMetadata = modelMetadata;
        }
    }

    #endregion
    #region fields

    private static readonly Type IEnumerableType = typeof(IEnumerable),
                                 IEnumerableGenericType = typeof(IEnumerable<>);

    #endregion
    #region methods

    public static String ToQueryString<ModelType> (this ModelType model) {
        return new ViewDataDictionary<ModelType>(model).ModelMetadata.ToQueryString();
    }

    public static String ToQueryString (this ModelMetadata modelMetadata) {
        if (modelMetadata.Model == null) {
            return String.Empty;
        }

        var keyValuePairs = modelMetadata.Properties.SelectMany(mm =>
            mm.SelectPropertiesAsQueryStringParameters(new List<String>())
        );

        return String.Join("&", keyValuePairs.Select(kvp => String.Format("{0}={1}", kvp.Key, kvp.Value)));
    }

    private static IEnumerable<KeyValuePair<String, String>> SelectPropertiesAsQueryStringParameters (this ModelMetadata modelMetadata, List<String> prefixChain) {
        if (modelMetadata.Model == null) {
            yield break;
        }

        if (modelMetadata.IsComplexType) {
            IEnumerable<KeyValuePair<String, String>> keyValuePairs;

            if (IEnumerableType.IsAssignableFrom(modelMetadata.ModelType)) {
                keyValuePairs = modelMetadata.GetItemMetadata().Select((mm, i) =>
                    new PrefixedModelMetadata(
                        modelMetadata: mm,
                        prefix: String.Format("{0}[{1}]", modelMetadata.PropertyName, i)
                    )
                ).SelectMany(prefixed => prefixed.ModelMetadata.SelectPropertiesAsQueryStringParameters(
                    prefixChain.ToList().AddChainable(prefixed.Prefix, addOnlyIf: IsNeitherNullNorWhitespace)
                ));
            }
            else {
                keyValuePairs = modelMetadata.Properties.SelectMany(mm =>
                    mm.SelectPropertiesAsQueryStringParameters(
                        prefixChain.ToList().AddChainable(
                            modelMetadata.PropertyName,
                            addOnlyIf: IsNeitherNullNorWhitespace
                        )
                    )
                );
            }

            foreach (var keyValuePair in keyValuePairs) {
                yield return keyValuePair;
            }
        }
        else {
            yield return new KeyValuePair<String, String>(
                key: AntiXssEncoder.HtmlFormUrlEncode(
                    String.Join(".",
                        prefixChain.AddChainable(
                            modelMetadata.PropertyName,
                            addOnlyIf: IsNeitherNullNorWhitespace
                        )
                    )
                ),
                value: AntiXssEncoder.HtmlFormUrlEncode(modelMetadata.Model.ToString()));
        }
    }

    // Returns the metadata for each item from a ModelMetadata.Model which is IEnumerable
    private static IEnumerable<ModelMetadata> GetItemMetadata (this ModelMetadata modelMetadata) {
        if (modelMetadata.Model == null) {
            yield break;
        }

        var genericType = modelMetadata.ModelType.GetInterfaces().FirstOrDefault(x =>
            x.IsGenericType && x.GetGenericTypeDefinition() == IEnumerableGenericType
        );

        if (genericType == null) {
            yield return modelMetadata;
        }

        var itemType = genericType.GetGenericArguments()[0];

        foreach (Object item in ((IEnumerable) modelMetadata.Model)) {
            yield return ModelMetadataProviders.Current.GetMetadataForType(() => item, itemType);
        }
    }

    private static List<T> AddChainable<T> (this List<T> list, T item, Func<T, Boolean> addOnlyIf = null) {
        if (addOnlyIf == null || addOnlyIf(item)) {
            list.Add(item);
        }

        return list;
    }

    private static Boolean IsNeitherNullNorWhitespace (String value) {
        return !String.IsNullOrWhiteSpace(value);
    }

    #endregion
}
公共静态类QueryStringExtensions{
#区域内部类型
私有结构前缀modelmetadata{
公共只读字符串前缀;
公共只读模型元数据模型元数据;
公共前缀ModelMetadata(字符串前缀,ModelMetadata ModelMetadata){
前缀=前缀;
ModelMetadata=ModelMetadata;
}
}
#端区
#区域字段
私有静态只读类型IEnumerableType=typeof(IEnumerable),
IEnumerableGenericType=typeof(IEnumerable);
#端区
#区域方法
公共静态字符串ToQueryString(此ModelType模型){
返回新的ViewDataDictionary(model).ModelMetadata.ToQueryString();
}
公共静态字符串ToQueryString(此ModelMetadata ModelMetadata){
if(modelMetadata.Model==null){
返回字符串。空;
}
var keyValuePairs=modelMetadata.Properties.SelectMany(mm=>
mm.SelectPropertiesSqueryStringParameters(新列表())
);
返回String.Join(“&”,keyValuePairs.Select(kvp=>String.Format(“{0}={1}”,kvp.Key,kvp.Value));
}
私有静态IEnumerable SelectPropertiesQueryStringParameters(此模型元数据模型元数据,列表前缀链){
if(modelMetadata.Model==null){
屈服断裂;
}
if(modelMetadata.IsComplexType){
IEnumerable键值对;
if(IEnumerableType.IsAssignableFrom(modelMetadata.ModelType)){
keyValuePairs=modelMetadata.GetItemMetadata()。选择((mm,i)=>
新的前缀modelmetadata(
模型元数据:嗯,
前缀:String.Format(“{0}[{1}]”,modelMetadata.PropertyName,i)
)
).SelectMany(前缀=>prefixed.ModelMetadata.SelectPropertiesAsQueryString
"?foo.bar[0].prop=value1&foo.bar[1].prop=value2"
"?foobar[0].prop=value1&foobar[1].prop=value2"
public static class QueryStringExtensions {
    #region inner types

    private struct PrefixedModelMetadata {

        public readonly String Prefix;
        public readonly ModelMetadata ModelMetadata;

        public PrefixedModelMetadata (String prefix, ModelMetadata modelMetadata) {
            Prefix = prefix;
            ModelMetadata = modelMetadata;
        }
    }

    #endregion
    #region fields

    private static readonly Type IEnumerableType = typeof(IEnumerable),
                                 IEnumerableGenericType = typeof(IEnumerable<>);

    #endregion
    #region methods

    public static String ToQueryString<ModelType> (this ModelType model) {
        return new ViewDataDictionary<ModelType>(model).ModelMetadata.ToQueryString();
    }

    public static String ToQueryString (this ModelMetadata modelMetadata) {
        if (modelMetadata.Model == null) {
            return String.Empty;
        }

        var keyValuePairs = modelMetadata.Properties.SelectMany(mm =>
            mm.SelectPropertiesAsQueryStringParameters(new List<String>())
        );

        return String.Join("&", keyValuePairs.Select(kvp => String.Format("{0}={1}", kvp.Key, kvp.Value)));
    }

    private static IEnumerable<KeyValuePair<String, String>> SelectPropertiesAsQueryStringParameters (this ModelMetadata modelMetadata, List<String> prefixChain) {
        if (modelMetadata.Model == null) {
            yield break;
        }

        if (modelMetadata.IsComplexType) {
            IEnumerable<KeyValuePair<String, String>> keyValuePairs;

            if (IEnumerableType.IsAssignableFrom(modelMetadata.ModelType)) {
                keyValuePairs = modelMetadata.GetItemMetadata().Select((mm, i) =>
                    new PrefixedModelMetadata(
                        modelMetadata: mm,
                        prefix: String.Format("{0}[{1}]", modelMetadata.PropertyName, i)
                    )
                ).SelectMany(prefixed => prefixed.ModelMetadata.SelectPropertiesAsQueryStringParameters(
                    prefixChain.ToList().AddChainable(prefixed.Prefix, addOnlyIf: IsNeitherNullNorWhitespace)
                ));
            }
            else {
                keyValuePairs = modelMetadata.Properties.SelectMany(mm =>
                    mm.SelectPropertiesAsQueryStringParameters(
                        prefixChain.ToList().AddChainable(
                            modelMetadata.PropertyName,
                            addOnlyIf: IsNeitherNullNorWhitespace
                        )
                    )
                );
            }

            foreach (var keyValuePair in keyValuePairs) {
                yield return keyValuePair;
            }
        }
        else {
            yield return new KeyValuePair<String, String>(
                key: AntiXssEncoder.HtmlFormUrlEncode(
                    String.Join(".",
                        prefixChain.AddChainable(
                            modelMetadata.PropertyName,
                            addOnlyIf: IsNeitherNullNorWhitespace
                        )
                    )
                ),
                value: AntiXssEncoder.HtmlFormUrlEncode(modelMetadata.Model.ToString()));
        }
    }

    // Returns the metadata for each item from a ModelMetadata.Model which is IEnumerable
    private static IEnumerable<ModelMetadata> GetItemMetadata (this ModelMetadata modelMetadata) {
        if (modelMetadata.Model == null) {
            yield break;
        }

        var genericType = modelMetadata.ModelType.GetInterfaces().FirstOrDefault(x =>
            x.IsGenericType && x.GetGenericTypeDefinition() == IEnumerableGenericType
        );

        if (genericType == null) {
            yield return modelMetadata;
        }

        var itemType = genericType.GetGenericArguments()[0];

        foreach (Object item in ((IEnumerable) modelMetadata.Model)) {
            yield return ModelMetadataProviders.Current.GetMetadataForType(() => item, itemType);
        }
    }

    private static List<T> AddChainable<T> (this List<T> list, T item, Func<T, Boolean> addOnlyIf = null) {
        if (addOnlyIf == null || addOnlyIf(item)) {
            list.Add(item);
        }

        return list;
    }

    private static Boolean IsNeitherNullNorWhitespace (String value) {
        return !String.IsNullOrWhiteSpace(value);
    }

    #endregion
}