Question

I am creating a helper class for MVC and found a problem when the parameter "routeValues" is passed in different ways. The methods are created to define some attributes by default. The code below is a snippet i am using to explain what my problem is.

I have a method "MyBeginForm()" that does not accept a parameter for "routeValues", the "routeValues" parameter is is passed directly to the "BeginForm" method as null. The other method "MyBeginForm(object routeValues)" accepts a parameter for "routeValues" and I have passed "null" value through the parameter. The problem is that the html generated is different from each other.

//Custom Class for custom attributes
public class MyHtmlHelper<TModel>
{
    private readonly HtmlHelper<TModel> htmlHelper;

    internal MyHtmlHelper(HtmlHelper<TModel> htmlHelper)
    {
        this.htmlHelper = htmlHelper;
    }

    //Here the routeValues parameter of Begin Form is passed directly to the method as null
    public MvcForm MyBeginForm()
    {
        var myAttributes = new Dictionary<string, object>(){
            {"test", "value"},
            {"test2", "value2"},
        };

        return htmlHelper.BeginForm("Index", "Home", null, FormMethod.Post, myAttributes);
    }

    //Here I have passed the null value through the parameter
    public MvcForm MyBeginForm(object routeValues)
    {
        var myAttributes = new Dictionary<string, object>(){
            {"test", "value"},
            {"test2", "value2"},
        };

        return htmlHelper.BeginForm("Index", "Home", routeValues, FormMethod.Post, myAttributes);
    }
}

//This class is used for static call in html
public static class MyHtmlHelperkEx
{
    public static MyHtmlHelper<TModel> MyHtmlHelper<TModel>(this HtmlHelper<TModel> htmlHelper)
    {
        return new MyHtmlHelper<TModel>(htmlHelper);
    }
}

The following snippet are used on the html side

<h1>Without Parameter</h1>
@using (Html.MyHtmlHelper().MyBeginForm()) { }

<h1>With parmeter</h1>
@using (Html.MyHtmlHelper().MyBeginForm(null)) { }

And the following is the html generated. You can see the attributes are generated differently.

<h1>Without Parameter</h1>
<form action="/" method="post" test="value" test2="value2">
    System.Web.Mvc.Html.MvcForm
</form>

<h1>With parmeter</h1>
<form comparer="System.Collections.Generic.GenericEqualityComparer`1[System.String]" count="2" keys="System.Collections.Generic.Dictionary`2+KeyCollection[System.String,System.Object]" values="System.Collections.Generic.Dictionary`2+ValueCollection[System.String,System.Object]" action="/" method="post"></form>

Can someone explain why this is happening and how i can solve it please.

Was it helpful?

Solution

You are mixing the types of routeValues and htmlAttributes parameters.

There are 2 overloads of BeginForm that expect 5 parameters:

  • One where routeValues and htmlAttributes are both declared as anonymous objects. See msdn

  • Another where routeValues is of type RouteValueDictionary and htmlAttributes is of type IDictionary<string, Object>. See msdn

However in your helper, you have mixed the types. The htmlAttributes are declared as a dictionary and the routeValues as an object.

The MyBeginForm overload without arguments works because you pass null as the routeValues arguments, so the compiler uses the type of the other arguments to decide which overload of BeginForm should be used. As the htmlAttributes is a dictionary it will use the second overload I describe above. You could quickly try changing that method of yours to force the null routeValues as of type object, which would force the wrong overload to be picker and you would get the same unexpected html:

return htmlHelper.BeginForm("Index", "Home", (object)null, FormMethod.Post, myAttributes);

In the same way, your second MyBeginForm overload doesn´t work as you expected because you have declared routeValues of type object, so the compiler will always get the first overload I mentioned above (where both routeValues and htmlAttributes are treated as anonymous objects).

Now that you know what´s going, you just need to make sure you are picking the right overload to fix your issue. I would fix the MyBeginForm overload that allows routeValues to be specified, and then update the overload without parameters to call this one (avoiding duplicated code).

The easiest way to fix it would be to define the htmlAttributes inside the method as an anonymous object:

public MvcForm MyBeginForm(object routeValues)
{
    var myAttributes = new{
        test = "value",
        test2 = "value2",
    };
    return htmlHelper.BeginForm("Index", "Home", routeValues, FormMethod.Post, myAttributes);
}

The other option consists in declaring routeValues parameter as an RouteValuesDictionary, so you can keep defining the htmlAttributes as a dictionary.

public MvcForm MyBeginForm(RouteValueDictionary routeValues)
{
    var myAttributes = new Dictionary<string, object>(){
        {"test", "value"},
        {"test2", "value2"},
    };
    return htmlHelper.BeginForm("Index", "Home", routeValues, FormMethod.Post, myAttributes);
}

In both cases, calling your helper passing null as parameter will render the expected html (Although I prefer using routeValues as an anonymous object):

<form action="/" method="post" test="value" test2="value2"></form>

You could then update the first overload of your helper like this:

public MvcForm MyBeginForm()
{            
    return MyBeginForm(null);
}

Now both overloads of MyBeginForm should work as you expected.

Hope it helps!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top