HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Writing a thin navigation controller

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
navigationthinwritingcontroller

Problem

I'm trying to achieve a thin controller, and I started out with a thin controller but over time as the project has progressed the controller has got more complex. Could you please review the following code and let me know if the controller is still considered a thin controller or whether any of the code should be moved out into another class?

```
public class NavigationController : WebReportControllerBase
{
private readonly INavigationService _service;

public NavigationController(INavigationService service)
{
_service = service;
}

internal INavigationService Service
{
get
{
return _service;
}
}

public ActionResult Browse(NavigationPanelInputModel inputModel)
{
var item = inputModel.Item ?? "Category";

var navigationPanelViewModel = new NavigationPanelViewModel();
navigationPanelViewModel.Title = "Browse";
navigationPanelViewModel.NavigationLevel = Service.GetChildLevel(item, "Browse");

if (item != "Category")
{
navigationPanelViewModel.NavigationLevel.AllowFiltering = true;
}

var viewModel = new NavigationBarOverlayViewModel
{
ViewModel = navigationPanelViewModel,
View = "NavigationPanelContainer",
CssClass = "browse"
};

if (Request.IsAjaxRequest()) {
if (inputModel.Item == null)
{
return PartialView("NavigationBarOverlay", viewModel);
}
else
{
return PartialView("~/Areas/WebReport/Views/Shared/DisplayTemplates/NavigationPanel.cshtml", navigationPanelViewModel.NavigationLevel);
}
}
else
{
return View("PageOverlay", viewModel);
}
}

public ActionResult Reports(NavigationPanelInputModel inputModel)
{
var item = inputModel.Item ?? "InitialList";

var navigationPanelViewModel =

Solution

The responsability of a controller is to create ViewModels and return them. This usually imply some boilerplate code, creating new instances, setting parameters, etc.. Then, you need to return the good view, which also leads to some boilerplate code.

Your controllers are thin. Very thin I'd say. I think you're searching too far to refactor perfectly fine code. I know, that's a boring answer, so I'll explain why I think it's the best I can do.

What are our options? Extract a method to return the good kind of view (PartialView or View) according to the type of the request?

But this method would need 2, sometimes 4 parameters to work correctly. At least 2 parameters to define the view to return (The string name of the view) and 2 other parameters to verify if the Item is null and what view to return in that case. That sucks, right? An "helper" method with 4 parameters where 2 are optionals? Nah, let's forget that.

Extract the creation of the ViewModels perhaps? Ok, so I need a Factory Method, but what are the parameters? The Title, the Item, that other string beside the item in your service call, sometimes you'd need to pass the parameters to create NavigationBarOverlayViewModel too, which is 3 more parameters. That leads to a Factory Method that has 3 to 6 parameters, sometimes optionals, always looking alike.

What's common between those refactoring experiments? No reusability, low cohesion, high coupling. So, it's bad.

Plus, those two "helper" methods would serve what purpose? To create ViewModels and to return the good view? If we re-read the first sentence of my answer, that's exactly what the controller is supposed to do! Why would you take their jobs (South Park pun intended)?

So, all in all, that code is really good code. Keep it up.

As a little side note that might decrease code's length, here's a tip:

if (Request.IsAjaxRequest())
{
    return PartialView("NavigationBarOverlay", viewModel);
}
else
{
    return View("PageOverlay", viewModel);
}


Could be replaced with :

return Request.IsAjaxRequest() ? PartialView("NavigationBarOverlay", viewModel) : View("PageOverlay", viewModel);


Is it better? That, I don't know, it's a matter of opinion in that case.

PS: You might want to add [HttpGet] attribute above your controller's actions. You don't want people to reach these actions with a Post, Delete or Put, it'd be weird.

Thanks to @Heslacher, there's a way you can cut on some duplicated code by delegating the creation of NavigationPanelViewModel somewhere else. I would recommend creating a second constructor to this ViewModel that would look like this :

public NavigationPanelViewModel(string title, ??? level, bool allowFiltering)
{
    Title = title;
    NavigationLevel = level;
    AllowFiltering = allowFiltering;
}


(Consider verifying if parameters are null)

and use it like this :

public ActionResult Browse(NavigationPanelInputModel inputModel)
{
    var item = inputModel.Item ?? "Category";
    var childLevel = Service.GetChildLevel(item, "Browse");
    var navigationPanelViewModel = new NavigationPanelViewModel("Browse", childLevel, item != Category);

    var viewModel = new NavigationBarOverlayViewModel
    {
        ViewModel = navigationPanelViewModel,
        View = "NavigationPanelContainer",
        CssClass = "browse"
    };

    if (Request.IsAjaxRequest()) {
        if (inputModel.Item == null)
        {
            return PartialView("NavigationBarOverlay", viewModel);
        }
        else
        {
            return PartialView("~/Areas/WebReport/Views/Shared/DisplayTemplates/NavigationPanel.cshtml", navigationPanelViewModel.NavigationLevel);
        }
    }
    else
    {
        return View("PageOverlay", viewModel);
    }
}

public ActionResult Reports(NavigationPanelInputModel inputModel)
{
    var item = inputModel.Item ?? "InitialList";
    var childLevel = Service.GetChildLevel(item, "Reports");
    var navigationPanelViewModel = new NavigationPanelViewModel("Reports",childLevel, false);

    var viewModel = new NavigationBarOverlayViewModel
    {
        ViewModel = navigationPanelViewModel,
        View = "NavigationPanelContainer",
        CssClass = "reports"
    };

    return View("PageOverlay", viewModel);
}


Note also that the recommended use of var is when you can figure out what is the type just by looking at the code, so :

var childLevel = Service.GetChildLevel(item, "Reports");


isn't explicit. What is returned? We don't know, which is why I'd recommend not using var!

Code Snippets

if (Request.IsAjaxRequest())
{
    return PartialView("NavigationBarOverlay", viewModel);
}
else
{
    return View("PageOverlay", viewModel);
}
return Request.IsAjaxRequest() ? PartialView("NavigationBarOverlay", viewModel) : View("PageOverlay", viewModel);
public NavigationPanelViewModel(string title, ??? level, bool allowFiltering)
{
    Title = title;
    NavigationLevel = level;
    AllowFiltering = allowFiltering;
}
public ActionResult Browse(NavigationPanelInputModel inputModel)
{
    var item = inputModel.Item ?? "Category";
    var childLevel = Service.GetChildLevel<NavigationPanel>(item, "Browse");
    var navigationPanelViewModel = new NavigationPanelViewModel("Browse", childLevel, item != Category);

    var viewModel = new NavigationBarOverlayViewModel
    {
        ViewModel = navigationPanelViewModel,
        View = "NavigationPanelContainer",
        CssClass = "browse"
    };

    if (Request.IsAjaxRequest()) {
        if (inputModel.Item == null)
        {
            return PartialView("NavigationBarOverlay", viewModel);
        }
        else
        {
            return PartialView("~/Areas/WebReport/Views/Shared/DisplayTemplates/NavigationPanel.cshtml", navigationPanelViewModel.NavigationLevel);
        }
    }
    else
    {
        return View("PageOverlay", viewModel);
    }
}

public ActionResult Reports(NavigationPanelInputModel inputModel)
{
    var item = inputModel.Item ?? "InitialList";
    var childLevel = Service.GetChildLevel<NavigationPanel>(item, "Reports");
    var navigationPanelViewModel = new NavigationPanelViewModel("Reports",childLevel, false);

    var viewModel = new NavigationBarOverlayViewModel
    {
        ViewModel = navigationPanelViewModel,
        View = "NavigationPanelContainer",
        CssClass = "reports"
    };

    return View("PageOverlay", viewModel);
}
var childLevel = Service.GetChildLevel<NavigationPanel>(item, "Reports");

Context

StackExchange Code Review Q#106144, answer score: 7

Revisions (0)

No revisions yet.