Search code examples
asp.net-mvc-3oauthquery-stringformcollectionformbuilder

Dynamic Form Building and Passing Query Parameters


I am working on a form that is generated dynamically based on some meta-data tables in my database. I create input tags with names like setting_1, setting_53, setting_22 where the number is the primary key of the meta-data. Since the content is dynamic, I am using FormCollection as the sole parameter on POST requests.

Question 1: Is there a FormCollection-like class for GET requests? I want direct access to the query parameters.

Question 2: If I need to pass these query parameters around, is there an easy/safe way to build my URLs?

One of my big concerns is that some of the settings are populated via OAuth, so the user will be redirected to an external page. I will have to pass the query string as "state" which I will need to recover once the user returns. I will need to use this state to pick up where the user left off in the form entry process. All the more reason why I need a very fool-proof mechanism for passing query parameters around.

Has anyone dealt with dynamic pages like these? Are there good patterns and practices for passing these pages around?


Solution

  • Well, you can certainly look at Request.QueryString inside of a controller action.

    But if it were me doing it, I'd write a custom model binder instead.

    Here's a sample model binder. I haven't tested this!

    public class MyModelBinder: DefaultModelBinder
    {
        private static void BindSettingProperty(
            ControllerContext controllerContext, 
            ModelBindingContext bindingContext, 
            PropertyDescriptor propertyDescriptor)
        {
            if (propertyDescriptor.PropertyType != typeof(IDictionary<string, string>))
            {
                throw new InvalidOperationException("This binder is for setting dictionaries only.");
            }
            var originalValue = propertyDescriptor.GetValue(bindingContext.Model) as IDictionary<string, string>;
            var value = originalValue ?? new Dictionary<string, string>();
            var settingKeys = controllerContext.HttpContext.Request.QueryString.AllKeys.Where(k => k.StartsWith("setting_", StringComparison.OrdinalIgnoreCase));
            foreach (var settingKey in settingKeys)
            {
                var key = settingKey.Substring(8);
                value.Add(key, bindingContext.ValueProvider.GetValue(settingKey).AttemptedValue);
            }
            if (value.Any() && (originalValue == null))
            {
                propertyDescriptor.SetValue(bindingContext.Model, value);
            }
        }
    
        protected override void BindProperty(
            ControllerContext controllerContext, 
            ModelBindingContext bindingContext, 
            PropertyDescriptor propertyDescriptor)
        {
            if (propertyDescriptor.Name.StartsWith("setting_", StringComparison.OrdinalIgnoreCase)
            {
                BindSettingProperty(controllerContext, bindingContext, propertyDescriptor);
            }
            else
            {
                base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
            }
        }
    }