tags:

views:

293

answers:

4

I've got a master page which has a function in it called GetSiteMap(), this function is used to custom render a sitemap based on the current location. My problem is that in MVC you don't have the code behind model thus not exposing that kind of functionality.

What's the correct way to do it? Should I have a master page controller of some sort with that function defined in it?

Public Function GetSitemap() As String
    Dim s As New SiteNavigation
    Dim siteMapNodeCollection As SiteMapNodeCollection

    If Not SiteMap.CurrentNode.Equals(SiteMap.RootNode) Then
      If Not SiteMap.CurrentNode.HasChildNodes Then
        ' otherwise it'll go to the pseudo-current directory, which is wrong
        Dim parentNode As SiteMapNode = SiteMap.CurrentNode.ParentNode.ParentNode
        s.AddBackLink(parentNode.Url, parentNode.Title)
      Else
        Dim parentNode As SiteMapNode = SiteMap.CurrentNode.ParentNode
        s.AddBackLink(parentNode.Url, parentNode.Title)
      End If
    End If

    If Not SiteMap.CurrentNode.HasChildNodes Then
      siteMapNodeCollection = SiteMap.CurrentNode.ParentNode.ChildNodes
    Else
      siteMapNodeCollection = SiteMap.CurrentNode.ChildNodes
    End If

    For Each siteMapNode As SiteMapNode In siteMapNodeCollection
      GenerateLinks(siteMapNode, s)
    Next

    Return s.GetSiteNavigation()
  End Function

  Private Sub GenerateLinks(ByRef siteMapNode As SiteMapNode, ByRef siteNavigation As SiteNavigation)
    If siteMapNode.Url.Length = 0 And siteMapNode.Description = "separator" Then
      siteNavigation.AddSeparator()
    ElseIf siteMapNode.Url.Length = 0 And siteMapNode.Description = "heading" Then
      siteNavigation.AddHeading(siteMapNode.Title)
    Else
      siteNavigation.AddLink(siteMapNode.Url, siteMapNode.Description, siteMapNode.Title, siteMapNode.HasChildNodes)
    End If
  End Sub

Sorry, this is what I meant. I wrote this very quickly the other day so it's not perfect, but for now it does the job. I'm using sitemap and giving certain elements no URL and instead a description such as "separator" to indicate that the <li> element is rendered in a different way (a different class is applied to this HTML element).

+1  A: 

It depends what it does; if it is just inspecting the request, then you could write (for example) an extension method on HtmlHelper and write it from the view;

<%=Html.GetSiteMap()%>

Another alternatives would be to push this into a master page. If you need to do a database query etc, you might consider an "action filter" to do half the work (preparing the data), and then (as above) a call on HtmlHelper in the view to display it. Any use?

Marc Gravell
Ah yes, I haven't implemented any helpers yet. The likelihood is that it will fire off a database query for permissions. I'm not sure just yet. It literally just outputs some HTML anyway. I'll quickly have a bash at the HTML helper.
Kezzer
I would refactor it so that it doesn't produce HTML. In MVC this is really best left to the View, not controller actions. HtmlHelpers ought to be operating on data supplied by the controller, not making database queries. You'll be breaking separation of concerns if you build DB access into your helper.
tvanfosson
A: 

MVC does have strongly typed views. From your markup, you can access your model to determine location which basicly replaces codebehind logic:

<%= Html.Encode(ViewData.Model.GetSiteMap()) %>

which will render the appropriate sitemap. Your model could be a 'Master Presentation' class that exposes different methods that are commonly used by pages.

I guess this is similar to Marc's proposed solution.

martijn_himself
+1  A: 

Since the site map is customizable based on the user's location, I'm going to assume that you are talking about generating a user interface element, not a search engine mapping. Generating a sitemap for search engine consumption is probably best done off-line and simply updated periodically.

To generate the user interface I would consider using a ViewUserControl and rendering it as a partial in whatever pages you need. The ViewUserControl will make it much easier to create and maintain the mark up associated with it. It will also be available to any view (and can be included on your MasterPage) that needs it. As @Marc Gravell suggests, the data for it can be generated using an ActionFilter. It would probably be easiest, though, if the control is ubiquitous to create a base controller from which your controllers can derive and override OnActionExecuted in the base controller. You can detect when a ViewResult has been generated, then create and assign to ViewData the data for the sitemap control.

Base Controller:

public override OnActionExecuted( ActionExecutedContext filterContext )
{
     if (filterContext != null && filterContext.Result is ViewResult)
     {
         ViewData["siteMap"] = this.GetSiteMap();
     }
}

MasterPage/View

<% Html.RenderPartial( "SiteMap", ViewData["siteMap"], ViewData ) %>

ViewUserControl

 foreach (var elem in Model)
 {
    .... render your HTML
 }
tvanfosson
Where should the code which has GetSiteMap() in it go though? It uses a custom class also which represents the SiteMap, where should that go?
Kezzer
The code could be in the base controller itself -- that's what I've represented in my example. The site map is really a function of the controller actions in an MVC app, so I think I would keep it with the controllers. I'd put the class representing the SiteMap in Models.
tvanfosson
A: 

My problem is that in MVC you don't have the code behind model thus not exposing that kind of functionality

That's not true. aspx + code behind was the default implementation until few releases ago. Currently the default template make no use of code behind but is builtin in the runtime.

In MVC the Controller should create the Model of your sitemap and the View (maybe an usercontrol) has the only responsibility to render it.

Maybe this can help http://mvcsitemap.codeplex.com/

Andrea Balducci