1

I've made a function which takes a MethodBase object and a dictionary of named parameters and matches the named parameters to the method and converts the data so that the method can be called with the correct parameters.

In order to convert the data I've been using

parameters[paramIndex] = Convert.ChangeType(item.Value, paramType);

However this will not work for MVC bound types as simply invoking a MethodBase object doesn't perform the data conversion via MVC bindings.

I can check whether the type to be converted has a binder by doing

if(ModelBinders.Binders.TryGetValue(paramType, out var binder)){...}

However I'm not sure then how to use the binder to convert the data. I've tried using this answer but there's no explanation of what the ModelBindingContext parameters actually are and how to use them for these purposes.

Context

Thought it might be wise to provide some context: I'm trying to do this as we perform server-side rendering of SVG and HTML DOMs (using Puppeteer/batik) but some of the DOMs have embedded images with links in them.

So before performing the render:

  • Find image URLs from DOM
  • Parsing the parameters and route out
  • Find the method to call in assembly via it's MVC route attribute
  • Call the method with the converted parameters to render the image
  • Convert the result to base64
  • Change the embedded image in DOM to use the base64 representation rather than a URL

Thanks in advance for your help.

The entire method definition is:

    private static object[] MapParameters(MethodBase method, IDictionary<string, object> namedParameters)
    {
        var parms = method.GetParameters();
        var paramNames = parms.Select(p => p.Name).ToArray();
        var parameters = new object[paramNames.Length];
        for (var i = 0; i < parameters.Length; ++i)
            parameters[i] = Type.Missing;

        foreach (var item in namedParameters)
        {
            var param = parms.First(parm => parm.Name == item.Key);
            var paramType = Nullable.GetUnderlyingType(param.ParameterType) ?? param.ParameterType;
            var paramName = item.Key;
            var paramIndex = Array.IndexOf(paramNames, paramName);

            if (ModelBinders.Binders.TryGetValue(paramType, out var binder))
            {
                // Use the binder to convert data
                continue;
            }

            parameters[paramIndex] = Convert.ChangeType(item.Value, paramType);
        }

        return parameters;
    }
Dillanm
  • 876
  • 12
  • 28

1 Answers1

0

To solve this you can just throw a dummy ControllerContext and ModelBindingContext at the IModelBinder.BindModel implementation as so:

private static object ConvertDataWithModelBinder(IModelBinder binder, object value)
{
    const string modelName = "ValueToBeConverted";
    var modelContext = new ModelBindingContext { ModelName = modelName };
    var routeData = new RouteData { Values = { { modelName, value } } };
    var controllerContext = new ControllerContext { RouteData = routeData };
    return binder.BindModel(controllerContext, modelContext);
}

This assumes that the binder implementation looks at the ControllerContext.RouteData.Values object (I checked our model binders and they mostly do), however some of them also use the BindingContext.ValueProvider.GetValue method in order to get the value from the controller context.

You may there also need to add a dummy implementation of ValueProvider containing a single ValueProviderResult with the above ModelName and value.

Dillanm
  • 876
  • 12
  • 28