views:

59

answers:

3

Hi!

I have this "problem" that i have code like this in many of my controller actions:

var users = new List<SelectListItem>();
foreach(var user in userRepository.GetAll())
{
 var item = new SelectListItem { Text = user.FriendlyName, Value = user.UserId.ToString() };

 if (User.Identity.Name == user.UserName)
  item.Selected = true;

 users.Add(item);
}

ViewData["Users"] = users;

How would you refactor this to a more clean solution? I want to get DRY!

A: 

You could place that logic in an (abstract) BaseController and then derive all your controllers from it and have them call a method in the BaseController to get that data as needed.

If you need to include it in ALL your controller actions you could also place it in an overridden OnActionExecuting() in the BaseController.

Nathan Taylor
+2  A: 

I would create this to be an extension method applied to List<user> or whatever your userRepository.GetAll() returns so then in your code you can replace all of these usages with just

ViewData["Users"] = userRepository.GetAll().ToSelectList();

Edit code sample: There's 2 ways you could do this

public static List<SelectListItem> ToSelectList(this List<Agent> users)
{
    List<SelectListItem> items = new List<int>();
    foreach (var user in users)
    {
        var item = new SelectListItem { Text = user.FriendlyName, 
                                Value = user.UserId.ToString() };

        if (User.Identity.Name == user.UserName)
            item.Selected = true;

        items.Add(item);
    }

    return items;
}

And usage would be like

userRepository.GetAll().ToSelectList();

Or if you have problems with identity being in the extension method have

public static List<SelectListItem> ToSelectList(this List<Agent> users, 
                                                        string selectedUserName)
{
    List<SelectListItem> items = new List<int>();
    foreach (var user in users)
    {
        var item = new SelectListItem { Text = user.FriendlyName, 
                                Value = user.UserId.ToString() };

        if (user.UserName == selectedUserName)
            item.Selected = true;

        items.Add(item);
    }

    return items;
}

And usage would be like

userRepository.GetAll().ToSelectList(User.Identity.Name);
Chris Marisic
Yes, i have this approach on other similar situation. The problem? is that i need to set the current user as the selected item. What would be the best way to solve this?
alexn
User.Identity.Name is static already so you can use that inside your extension method just the same.
Chris Marisic
I would probably add some null checking to that code since Identity might not exist at all times, say during unit tests, and if it gets called in a test case it'd be better off skipping the Selected = true block than throwing an exception.
Chris Marisic
Would that logic go in the ToSelectList then?
alexn
IMO this is clearly helper class code, any "helper class" is almost always a code smell unless it can be turned into an extension method and be globally available for a specific type which this fits in for and I think would make the most sense since really all you're doing is ToADifferentTypeOfList()
Chris Marisic
Added code sample for you.
Chris Marisic
Thanks for the example. One thing i don't like is that i have to reference System.Web.Mvc from my datalayer to access SelectListItem, do I have to live with that?
alexn
I think that might be the cost of using the SelectListItem class, i've encountered this also frequently using System.Web.UI.ListItem for similar purposes as you have. The tradeoff of referencing a web assembly is a fair price to pay instead of having to rewrite a List control for the selected item functionality to be supported by a custom object. You could atleast try replicated the SelectItem class with a custom class with a Selected property and bind it to your control and see if it picks up the selected value based off reflection looking for a .Selected property instead of hardcoded types
Chris Marisic
It might also have a DatasSelectedField like dropdown lists have DataTextField / DataValueField when you databind. I haven't done anything with MVC in a long time so I'm not sure off the top of my head how the control stuff works anymore.
Chris Marisic
+1  A: 

Put that code, or part of the code into a separate class - let's call it UserService. Let UserService implement IUserService:

public interface IUserService
{
    IEnumerable<SelectListItem> GetUsers();
}

Inject IUserService into your controller via Constructor Injection:

public MyController(IUserService userService)
{
    this.userService = userService;
}

Use the userService field in your Controller Actions to get the users.

public ViewResult DoSomething()
{
    var users = this.userService.GetUsers();
    // the rest of the implementation
}
Mark Seemann
While I agree this is good design, I'm not really sure it needs a whole service created for it when it's really just a type conversion operation.
Chris Marisic
I like this solution too. I already have a IUserService so this would fit nicely.
alexn