我有一个模型,有一些嵌套的属性,列表......我想从那个模型中获取一个查询字符串参数。
asp.net mvc 框架中是否有任何类/助手可以执行此操作?
我知道使用模型绑定器我们可以从查询字符串绑定模型,但我想做相反的事情。
谢谢。
我有一个模型,有一些嵌套的属性,列表......我想从那个模型中获取一个查询字符串参数。
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);
}
}
示例用法:
var vd = new ViewDataDictionary<Model>(model); // in a Controller, ViewData.ModelMetadata
var queryString = vd.ModelMetadata.ToQueryString();
我没有对它进行过彻底的测试,所以它可能会潜伏一些空引用错误,但它会为我尝试过的复杂对象吐出正确的查询字符串。
@Steve 的代码在出现额外嵌套和枚举时有一些小错误。
public class BarClass {
public String prop { get; set; }
}
public class FooClass {
public List<BarClass> bar { get; set; }
}
public class Model {
public FooClass foo { get; set; }
}
var model = new Model {
foo = new FooClass {
bar = new List<BarClass> {
new BarClass { prop = "value1" },
new BarClass { prop = "value2" }
}
}
};
var queryString = new ViewDataDictionary<Model>(model).ModelMetadata.ToQueryString();
的值queryString
应该是:
"?foo.bar[0].prop=value1&foo.bar[1].prop=value2"
但是@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
}