Search code examples
vb.netrazorasp.net-mvc-5html-helper

In an MVC5 controller how do I create an IDictionary or routeValueDictionary for an HtmlHelper's htmlAttributes or routeValues?


MVC5

The ultimate objective is to create the view's links in the controller because they change based on program logic.

The following link compiles and runs:

Dim myLink = HtmlHelper.GenerateLink(Request.RequestContext, RouteTable.Routes, "edit", "Default", "Edit", "Role", Nothing, Nothing)

The line above generates: <a href=""/Role/Edit"">edit</a>. This is then displayed in the view using html.raw and results in the link: http://localhost:53908/Role/Edit

The link displayed above navigates correctly, but won't work with the action method because I need to pass an ID parameter, so I tried to change the last parameter in the helper as follows:

Dim myLink = HtmlHelper.GenerateLink(Request.RequestContext, RouteTable.Routes, "edit", "Default", "Edit", "Role", Nothing, New With {.ID = "somedata"})

But the line above returns a syntax warning and the code can't run:

Runtime errors might occur when converting '<anonymous type: ID As String>' to 'IDictionary(Of String, Object)'

So then I tried to create an IDictionary as below to pass into the helper:

Dim temp As IDictionary(Of String, String)
temp.Add("ID", "somedata")
Dim myLink = HtmlHelper.GenerateLink(Request.RequestContext, RouteTable.Routes, "edit", "Default", "Edit", "Role", Nothing, temp)

The code above satisfies the HtmlHelper, but the temp.Add line doesn't work because:

temp is used before it has been assigned a value.

A null reference exception occurs at runtime. I'm not particularly conversant with IDictionary and couldn't figure out what to do next.

So I have a series of questions:

  1. Is there a way to create the IDictionary asked for by the helper 'on the fly' with the call to the helper?
  2. Should I be using the HtmlHelper.GenerateLink method differently?
  3. How do I create an IDictionary?

Added after the original post:

The initial question concerned creating an in-place IDictionary to make use of HtmlHelpers in the Controller so that creating links could be accomplished in code rather than in the view. The overall intent is to allow Controller code to perform the necessary logic and calculate links rather than doing that in the view.

The initial question posed dealt with htmlAttributes by accident because in this particular case the real need was to create routeValues. But the answer below actually serves to inform how to do both.

The key to success was using the proper keywords. In the case of creating either a New RouteValueDictionary or a New IDictionary, the New Dictionary(Of String, Object) must be used, meaning the keyword Object is required, and the keyword From is necessary to setup the in-place record definitions. In MVC5 the syntax New Dictionary(Of String, String) is seen as an anonymous type and can't be converted to an IDictionary, in-place or otherwise.

The final code used in the Controller to setup an equivalent ActionLink in the view, based on the answer below, was:

Dim editLink = HtmlHelper.GenerateLink(Request.RequestContext,
    RouteTable.Routes,
    "Edit",      'the link text
    "Default",   'a route name, can found in RouteConfig.vb
    "Edit",      'the target ActionResult method
    "Role",      'the target Controller
    New RouteValueDictionary(New Dictionary(Of String, Object) From {"ID", role.Id}, {"selectedDomain", selectedDomain}}),
    Nothing)

In the case above, a second route value was added to the routeValueDictionary as needed by the ActionResult method.

If htmlAttributes are also required they can be added in place of the final Nothing show above, as specified in the answer below.


Solution

  • The parameter is type IDictionary(Of String, Object). That's just an interface, so you need to create an object of a type that implements that interface. The obvious choice is Dictionary(Of String, Object), e.g.

    Dim temp As IDictionary(Of String, Object) = New Dictionary(Of String, Object)
    temp.Add("ID", "somedata")
    Dim myLink = HtmlHelper.GenerateLink(Request.RequestContext, RouteTable.Routes, "edit", "Default", "Edit", "Role", Nothing, temp)
    

    I can't recall when anonymous types started being supported in such scenarios but it appears that it was later than MVC 5. If you want to be able to use anonymous types then you would have to write your own method that accepts an anonymous type, uses Reflection to extract the data from it, builds a dictionary and then calls the existing method. You might do that like this:

    Public Module HtmlHelperExtender
    
        Public Function GenerateLink(requestContext As RequestContext,
                                     routeCollection As RouteCollection,
                                     linkText As String,
                                     routeName As String,
                                     actionName As String,
                                     controllerName As String,
                                     routeValues As RouteValueDictionary,
                                     htmlAttributes As Object) As String
            'Create a dictionary from the properties of the anonymously typed object.
            Dim attributesType = htmlAttributes.GetType()
            Dim properties = attributesType.GetProperties()
            Dim htmlAttributesDictionary = properties.ToDictionary(Function(p) p.Name, Function(p) p.GetValue(htmlAttributes))
    
            Return HtmlHelper.GenerateLink(requestContext,
                                           routeCollection,
                                           linkText,
                                           routeName,
                                           actionName,
                                           controllerName,
                                           routeValues,
                                           htmlAttributesDictionary)
        End Function
    
    End Module
    

    You can then call that method much like you were originally trying to:

    Dim myLink = HtmlHelperExtender.GenerateLink(Request.RequestContext,
                                                 RouteTable.Routes,
                                                 "edit",
                                                 "Default",
                                                 "Edit",
                                                 "Role",
                                                 Nothing,
                                                 New With {.ID = "somedata"})
    

    Another option that just occurred to me is to stick with the dictionary but create and populate it in-place, which is possible using the From keyword:

    Dim myLink = HtmlHelper.GenerateLink(Request.RequestContext,
                                         RouteTable.Routes,
                                         "edit",
                                         "Default",
                                         "Edit",
                                         "Role",
                                         Nothing,
                                         New Dictionary(Of String, Object) From {{"ID", "somedata"}})