具有SelectList(s)最佳实践的ASP.NET MVC ViewModel

时间:2011-10-25 17:41:21

标签: asp.net-mvc selectlist

我注意到在NerdDinner应用程序中,如果ModelState对于晚餐无效,它只会返回模型的视图:

        if (ModelState.IsValid) {
            ...
            return RedirectToAction("Details", new { id=dinner.DinnerID });
        }

        return View(dinner);

但是,在我的应用程序中,模型(在这种情况下的视图模型)包含多个SelectLists。此时未实例化这些列表,因为此视图模型刚刚从表单提交中填充。在将这些SelectLists发送给用户之前,重新填充此SelectLists的建议方法是什么?

这就是我希望控制器做的事情:

public ActionResult Save(MyModel model)
{
    if (ModelState.IsValid)
    {
        businessClass.Save(model);
        return RedirectToAction("Index", "Home");
    }

    // This won't work because model has uninstantiated SelectLists
    return View("MyView", model);
}

如果ModelState无效,我不想将模型发送到我的业务逻辑,但将SelectList填充代码放在我的控制器中似乎没有意义。我应该在业务逻辑中创建一个公共方法,仅仅是为了在我的视图模型上做这种事情吗?

5 个答案:

答案 0 :(得分:14)

就个人而言,我喜欢保持简单: -

[HttpGet]
public Edit(int id) {
     EditForm form = new EditForm();
     // Populate from the db or whatever...
     PopulateEditPageSelectLists(form);
     return View(form);
}

[HttpPost]
public Edit(EditForm form) {
     if (ModelState.IsValid) {
         // Do stuff and redirect...
     }
     PopulateEditPageSelectLists(form);
     return View(form);
}

public void PopulateEditPageSelectLists(form) {
     // Get lookup data from the db or whatever.
}

如果填充选择列表的逻辑是疯狂的,那么移动到一个单独的类或任何它可能是值得的,但作为第一步,这是最好的起点。

答案 1 :(得分:5)

你不想说你想要多少可重用性。但就个人而言,我喜欢“清晰”(不要侵入控制器)和尽可能重用的东西,而在MVC意味着 - 过滤器。

看看这个:

public class SupplyLanguagesAttribute : System.Web.Mvc.ActionFilterAttribute
{
    public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
    {
        filterContext.Controller.ViewData["languagesList"] =
            someService.LoadLanguagesAsDictionary();

        base.OnActionExecuting(filterContext);
    }
}

然后您只需将它用于您可能需要语言的每种操作方法:

[SupplyLanguages]
public ActionResult DoSomething()
{
...
}

然后在视图中,您可以直接从ViewData使用DropDownList的数据,或者您甚至可以“包装”它(并避免在视图中使用“魔术字符串”),使用自定义可重用的DropDown:

public static MvcHtmlString LanguageDropDown(this HtmlHelper html, string name, object selectValue, bool defaultOption = false)
    {
        var languages = html.ViewData["languagesList"] as IDictionary<string,string>;

        if (languages == null || languages.Count() == 0)
            throw new ArgumentNullException("LanguageDropDown cannot operate without list of languages loaded in ViewData. Use SupplyLanguages filter.");

        var list = new SelectList(languages, "Key", "Value", selectValue);

        return SelectExtensions.DropDownList(html, name, list);
    }

答案 2 :(得分:1)

如果ModelState无效,我的控制器会填充模型上的SelectLists。

关注分离后,您的业务类根本不了解视图模型。如果您的视图需要员工列表,您的控制器将从您的业务层获取员工列表,并创建您的视图所需的SelectList。

实施例

public ActionResult Save(MyModel model) 
{ 
    if (ModelState.IsValid) 
    { 
        businessClass.Save(model); 
        return RedirectToAction("Index", "Home"); 
    } 
    model.PossibleEmployees 
             = _employeeRepository.All().Select(e => 
                                                new SelectListItem{Text=e.Name, 
                                                                   Value=e.Id});
    return View("MyView", model); 
} 

更新

如果您的选择列表填充代码确定要显示的WHICH选项,我认为您可能应将其移至业务层中的服务。如果可重用性是一个大问题,那么鲁肯的答案似乎最有可能被重用。

答案 3 :(得分:0)

即使模型无效,我也会用它来填充列表。另一个可能的解决方案是让一个动作返回json信息并通过ajax构建select。因此,我也使用了静态属性/缓存集合。我想这总是取决于具体情况。

PS:您可以在每个操作中使用本地模型,因此我可以在模型构造函数中保留初始化。 (通常我也会使用[NonAction]实用程序覆盖基础模型。

例如,我在您的应用程序中广泛使用了一个Employee列表。

我在基本控制器中添加了一些实用工具方法来构建SelectListItems等。由于每个模型都继承自基础,我在应用程序中几乎无处不在。当然,收藏品是通过专门的商业目标填写的。

答案 4 :(得分:0)

我所做的是在类中返回一个SelectList的静态函数。该方法接受Enum值,该值定义要返回的SelectList。在View中,DropDownList或DropDownListFor函数调用此函数来获取SelectList。

静态函数如下所示:

class HelperMethods
{
  enum LookupType {Users, Companies, States};

  public static SelectList CommonSelectList(LookupType type, int? filterValue = null)
    //filterValue can be used if the results need to be filtered in some way
    var db = new WhateverEntities();

    switch (type)
    {  
       case LookupType.Users:
         var list = db.Users.OrderBy(u => u.LastName).ToList()
         return new SelectList(list, "ID", "FullName")
         break;

       case LookupType.Companies
         var list = db.Companies.OrderBy(u => u.Name).ToList()
         return new SelectList(list, "ID", "Name")
         break;

       //and so on...
    }
  }
}

该视图包含:

@Html.DropDownListFor(m => m.UserID, HelperMethods.CommonSelectList(LookupType.Users))

这样,模型和控制器不需要代码来配置SelectList以发送到View。它使重用已经配置的SelectList变得非常容易。此外,如果View需要循环遍历对象列表,则可以使用此相同的函数来获取该列表。这是我发现这种方式最简单,最方便的方式。