いくつかのネストされたプロパティ、リストを持つモデルがあり、そのモデルからクエリ文字列パラメーターを取得したいと考えています。
これを行うためのasp.net mvcフレームワークにクラス/ヘルパーはありますか?
モデルバインダーを使用すると、クエリ文字列からモデルをバインドできることはわかっていますが、逆にしたいと思います。
ありがとう。
いくつかのネストされたプロパティ、リストを持つモデルがあり、そのモデルからクエリ文字列パラメーターを取得したいと考えています。
これを行うためのasp.net mvcフレームワークにクラス/ヘルパーはありますか?
モデルバインダーを使用すると、クエリ文字列からモデルをバインドできることはわかっていますが、逆にしたいと思います。
ありがとう。
フレームワークに「クエリ文字列へのシリアル化」機能がないことはかなり確信しています。これは主に、クエリ文字列でネストされた値とネストされたコレクションを表す標準的な方法がないと思うためです。
これは ModelMetadata インフラストラクチャを使用すると非常に簡単に実行できると思っていましたが、ModelMetadata を使用してコレクション値のプロパティからアイテムを取得する際には、いくつかの複雑な問題があることがわかりました。私は、それを回避する拡張メソッドをハックし、任意の ModelMetadata オブジェクトから呼び出すことができる ToQueryString 拡張機能を作成しました。
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();
十分にテストしていないため、null 参照エラーが潜んでいる可能性がありますが、試した複雑なオブジェクトに対して正しいクエリ文字列を吐き出します。
@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
}