Search code examples
c#asp.net-mvcgenericsrepositorymvc-editor-templates

Create a generic repository DropDown with SelectListItem in MVC


Having returned to this problem after a few months I've added my current best answer below.

In the original question I was still looking for a simple way to achieve a generic DropDown but the title was more closely tied to the specific error I was then facing.

I've amended the title to reflect the answer more closely. Hopefully this might help someone.


Original Question:

Generic Editor Template for DropDownListFor throws Cannot convert type error

I'm trying to create a generic template for a drop down list using ideas lifted from the follwing post:

Move Html DropDownListFor into Editor Template

I've created a DropDownHelper class:

public class DDLOptions<T>
{
    public T Value { get; set; }
    public IEnumerable<SelectListItem> Items { get; set; }
}

I've amended the Controller from this:

    public ActionResult Create()
    {
        var model = new FilmEditViewModel();

        FilmDropDownViewModel films = new FilmDropDownViewModel
            {
                Items = _repo.GetSelect(),                    
            };

        model.filmName = films;           
        return View(model);
    }

...to this:

    public ActionResult Create()
    {
        var model = new FilmEditViewModel();

        DDLOptions<FilmDropDownViewModel> films
            = new DDLOptions<FilmDropDownViewModel>
            {
                Items = _repo.GetSelect()
            };

        model.filmName = films;           
        return View(model);
    }

This is throwing the following error:

Cannot implicitly convert type 
'BootstrapSupport.DDLOptions<FilmStore.ViewModels.FilmDropDownViewModel>' 
to 'FilmStore.ViewModels.FilmDropDownViewModel'

I'm also having difficulty working out how to amend the Editor Template to work with the modified DDLOptions class.


Solution

  • There is a way to create a generic DropDown which I've mangled together from a few pointers on StackOverflow and this article on CodeProject. Comments on whether this follows best practice would be appreciated.

    I use both an AddList and an EditList to allow for a selected item on the EditList and some jQuery based on html class attributes. The generic EditList is created as follows:

    Models

    I have a viewmodel for any DropDown that fits with the generic pattern and then a ViewModel for the entity I'm returning. Annotations are held in a validation file.

    DropDown ViewModel

    public class DropDownViewModel
    {
        public IEnumerable<SelectListItem> Items { get; set; }
    }
    

    Entity ViewModel

    public partial class OrganisationEditViewModel
    {
        public int entityID { get; set; }
        public string entityName { get; set; }
        public DropDownViewModel entityTypeID { get; set; }
        public string notes { get; set; }
    }
    

    Validation

    [MetadataTypeAttribute(typeof(OrganisationEditViewModelMetaData))]
    public partial class OrganisationEditViewModel
    {
    
    }
    
    public class OrganisationEditViewModelMetaData
    {
        [Key]
        [ScaffoldColumn(false)]
        [HiddenInput(DisplayValue = false)]
        public int entityID { get; set; }
    
        [Required]
        [Display(Name = "Organisation")]
        public string entityName { get; set; }
    
        [Required]
        [Display(Name = "Entity Type")]
        [UIHint("_dropDownEdit")]
        public DropDownViewModel entityTypeID { get; set; }
    
        [Display(Name = "Notes")]
        public string notes { get; set; }
    
    }
    

    Editor Template

    The UIHint annotation on the ViewModel points to an Editor Template. I'm using chosen.js on my lookups, hence the html attributes.

    @model WhatWorks.ViewModels.DropDownViewModel
    
    @Html.DropDownList("", Model.Items, new { @class = "chosen chosenLookup" })
    

    Controller

    The controller queries the current entity to get the selected string for the EditList. The DropDown is called from the GetEditList<O> function in the repository. The ViewModel is then mapped via Automapper (GetUpdate(id)).

    public ActionResult Edit(int id = 0)
    {
        var query = _repo.GetByID(id);
        string selected = query.tEntityType.entityTypeID.ToString();
    
        DropDownViewModel entityType = new DropDownViewModel
        {
            Items = _repo.GetEditList<tEntityType>("entityType", "entityTypeID", selected)
        };
    
        OrganisationEditViewModel a = GetUpdate(id);
        a.entityTypeID = entityType;
    
        if (a == null)
        {
            return HttpNotFound();
        }
        return View(a);
    }
    

    Repository

    The generic DropDown repository method gets an IEnumerable set of data from the calling type <O> (tEntityType in this case). The return method converts everything into strings and checks for the selected item.

    //generic Edit dropdown
        public IEnumerable<SelectListItem> GetEditList<O>(string text, string value, string selected) 
                                                            where O : class
        {
            IEnumerable<O> result = context.Set<O>();
            var query = from e in result
                        select new
                        {
                            Value = e.GetType().GetProperty(value).GetValue(e, null),
                            Text = e.GetType().GetProperty(text).GetValue(e, null)
                        };
    
            return query.AsEnumerable()
                .Select(s => new SelectListItem
                {
                    Value = s.Value.ToString(),
                    Text = s.Text.ToString(),
                    Selected = (selected == s.Value.ToString() ? true : false)
                });
        }
    

    View

    Finally, the view renders the DropDown via a standard Html.Editor which picks up the Editor Template from the UIHint annotation on the validation file.