Best way to populate SelectList for ViewModel on GET/POST

雨燕双飞 提交于 2019-12-22 10:10:02

问题


I have the following ViewModel:

public class EditViewModel
{
    public int FooType { get; set; }
    public IEnumerable<SelectListItem> FooTypes { get; set; }
}

I originally populated it in my Edit action like so:

public ActionResult Edit(int id)
{
    EditViewModel model = new EditViewModel();
    model.FooTypes = new SelectList(repository.GetFooTypes(), "Id", "Value");

    return View(model);
}

When I created the action to POST the values I had to repeat the same code:

public ActionResult Edit(int id, EditViewModel model)
{
    if( !ModelState.IsValid )
    {
        model.FooTypes = new SelectList(repository.GetFooTypes(), "Id", "Value");

        return View(model);
    }

    return RedirectToAction("Index");
}

I don't like having this code in two separate locations. Is there any common practice for refactoring this into a single spot so I dont need to repeat this code?


回答1:


Given that c# is an object oriented language, there are plenty of options available.

The simplest would be to just wrap it in a method within the controller:

private SelectList GetFooTypesList()
{
    return new SelectList(repository.GetFooTypes(), "Id", "Value);
}

and call it when setting up your model

or if you're using it in multiple classes you could create a helper method in another class that accepts the repository or an IEnumerable as a parameter.

If you want to get really advanced, you could use a ModelFactory to create the FooType model for you, with a prepopulated FooType property so the controller doesn't need to worry about it at all.

There's plenty of options, you just need to pick the one that's best for you.

My personal preference is the simple helper method in the controller.




回答2:


I've done it in the model before (when it was the coding practice for that project team), but it depends on your philosophy on what's "business logic" and what's "data access", and what belongs in the model vs controller. Different, and justifiable, opinions exist.

Model, where you need a nullable type for FooType:

public class EditViewModel
{
    public int? FooType { get; set; }
    public IEnumerable<SelectListItem> GetFooTypes(object selectedFooType = null)
    {
        return new SelectList(repository.GetFooTypes(), "Id", "Value", selectedFooType);
    }
}

"Get" controller, where you need to create the model first to ensure the Model property is available in the view:

public ActionResult Edit(int id)
{
    EditViewModel model = new EditViewModel();

    return View(model);
}

The View (sans Barbara Wawa):

@Html.DropDownListFor(m => m.FooType, Model.GetFooTypes(Model.FooType))

An alternative that takes the "view stuff" out of the model might look like so:

Model:

public class EditViewModel
{
    public int? FooType { get; set; }
    public IEnumerable<int?> FooTypes
    {
        get
        {
            // declare/define repository in your model somewhere    
            return repository.GetFooTypes();
        }
    }
}

View:

@Html.DropDownListFor(m => m.FooType, new SelectList(Model.FooTypes, "Id", "Value", Model.FooType))



回答3:


In the reply by "nekno" (answered Sep 30 at 22:19), there are two alternatives of a ViewModel which either returns a 'IEnumerable<SelectListItem>' or a 'IEnumerable<int?>'. Both of these alternative uses a repository but without actually creating it, so I would like to extend the code example a little bit, and chooses the second alternative i.e. the class with the property typed 'IEnumerable<int?>' :

using Microsoft.Practices.ServiceLocation; // ServiceLocator , http://commonservicelocator.codeplex.com/
using MyOwnRepositoryNameSpace; // IRepository
public class EditViewModel
{
    public int? FooType { get; set; }

    public IEnumerable<int?> FooTypes
    {
        get
        {
            return Repository.GetFooTypes();
        }
    }

    private IRepository Repository
    {
        get
        {
            return ServiceLocator.Current.GetInstance<IRepository>();
        }
    }   
}

The above kind of code with a "Dependecy Lookup" is now using a dependency to a third-part library, in this case the Common Service Locator library.

My question is how can the above code be replaced with "Dependency Injection" ? The ViewModel itself would indeed be very trivial to implement, just like this:

using MyOwnRepositoryNameSpace; // IRepository
public class EditViewModel
{
    private readonly IRepository _repository;

    public EditViewModel(IRepository repository) 
    {
        _repository = repository;
    }

    public int? FooType { get; set; }

    public IEnumerable<int?> FooTypes
    {
        get
        {
            return _repository.GetFooTypes();
        }
    }
}

The problem is how to make the ViewModel become injected with an implementation, when the ASP.NET MVC framework will instantiate the 'EditViewModel' and send it as a parameter into an Action method such as tihs method signature:

public ActionResult Edit(int id, EditViewModel model)  {
// How do we make the framework instantiate the above 'EditViewModel' with an implementation of 'IRepository' when the Action method is invoked ???

The official MVC tutorial does not seem to provide any nice solution as far I can see. In the section "Handling Edits" (methods 'public ActionResult Edit(...)' ) at the below pages they are duplicating the creation of the options in a similar way as in the poster of this stackoverflow question you are now reading.

http://www.asp.net/mvc/tutorials/mvc-music-store/mvc-music-store-part-5

http://mvcmusicstore.codeplex.com/SourceControl/changeset/view/d9f25c5263ed#MvcMusicStore%2fControllers%2fStoreManagerController.cs

If there is a solution about how to make the framework inject view model with your data retrievers (such as a repository) then I believe it may be to use some implementation of either 'IModelBinderProvider' or 'IModelBinder' but I have experimented with these without real success...

So, can anyone provide a link to a complete working example with ASP.NET MVC 3 code that enables injection of a data retriever into the constructor of a view model that the framework instantiates and will send as parameter into an action method ?

Update 2012-01-01: For those intrested in a solution to this specific question about constructor injection of a ViewModel instance, when the framework instantiates it and sends it as parameter to an MVC Action Method parameter, I have created a new question with a more specific subject, and thus hopefully more likely that someone with a solution will find it and post a good answer: Constructor injection of a View Model instance used as an Action method parameter



来源:https://stackoverflow.com/questions/7603519/best-way-to-populate-selectlist-for-viewmodel-on-get-post

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!