How to create a query from a model using asp.net mvc framework

I have a model with some nested properties, lists ... and I want to get querystring parameters from this model.

Is there any class / helper in asp.net mvc framework for this?

I know that with model binding we can bind the model to querystring, but I want to do the opposite.

Thanks.

+6
source share
2 answers

I am sure that there is no function "serialize query string" in the structure, mainly because I do not think that there is a standard way of representing nested values ​​and nested collections in a query string.

I thought it was pretty easy to do this using the ModelMetadata infrastructure, but it turns out that there are some complications about getting elements from a collection-based property using ModelMetadata. I cracked the extension method that works around this and built the ToQueryString extension, which you can call from any ModelMetadata object that you have.

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

Usage example:

 var vd = new ViewDataDictionary<Model>(model); // in a Controller, ViewData.ModelMetadata var queryString = vd.ModelMetadata.ToQueryString(); 

I have not tested it very carefully, so there might be some null ref errors in it, but it spits out the correct query string for complex objects that I tried.

+4
source
In code

@Steve a small error occurred when additional investments and transfers were made.

Sample Model

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

Security Code

 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(); 

The queryString value should be:

 "?foo.bar[0].prop=value1&foo.bar[1].prop=value2" 

But the @Steve code produces the following output:

 "?foobar[0].prop=value1&foobar[1].prop=value2" 

Updated Code

Here is a slightly modified version of @Steve's solution:

 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 } 
0
source

All Articles