Create a generic repository DropDown with SelectLi

2020-05-06 12:41发布

问题:

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.

回答1:

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.