简体   繁体   中英

Html.DropDownListFor with null selectList parameter

This is taken from VS Add New Scaffolded Item... when creating a new controller.

In the controller:

// GET: Drivers/Create
public ActionResult Create()
{
    ViewBag.Tenant = new SelectList(db.Tenants, "TenantID", "TenantName");
    return View();
}

The view then renders a drop-down list:

@Html.DropDownListFor(model => model.Tenant, null, htmlAttributes: new { @class = "form-control" })

The relevant model information:

public partial class Driver
{
    public int DriverID { get; set; }
    public int Tenant { get; set; }
    public virtual Tenant Tenant1 { get; set; }
}

public partial class Tenant
{
    public Tenant()
    {
        this.Drivers = new HashSet<Driver>();
    }

    public int TenantID { get; set; }
    public string TenantName { get; set; }
    public virtual ICollection<Driver> Drivers { get; set; }
}

Can someone explain why this works?

I looked at other questions and documentation and couldn't find the answer. I suspect it is something along the lines of "convention over configuration" and it is pulling from the ViewBag using the name of the property. In fact, I changed the ViewBag property to Tenantz and got the following exception:

There is no ViewData item of type 'IEnumerable' that has the key 'Tenant'.

So is setting the property name of the ViewBag the same as the model property you want to update a good practice? It seems ok but I always hear how you should avoid ViewBag and dynamic types.

As you have already discovered there's a convention. The following line in your view:

@Html.DropDownListFor(model => model.Tenant, null, htmlAttributes: new { @class = "form-control" })

is exactly the same as this line:

@Html.DropDownList("Tenant", null, htmlAttributes: new { @class = "form-control" })

Now if you look at how the DropDownList helper is implemented in the source code you will notice that it simply does that:

object obj = htmlHelper.ViewData.Eval(name) as IEnumerable<SelectListItem>;

where name is the first argument passed to the DropDownList helper. And guess what? It discovers the corresponding value that you have set in your controller: ViewBag.Tenant = ... .

This being said using ViewBag is an absolutely, disastrously, terribly bad practice. You've already find out why. It can bite you like a dog without you even knowing what's going on. The best way to protect against those dogs ( ViewBag ) is to search them inside your solution and give them poison. Simply get rid of absolutely any ViewBag calls in your code and use view models. Then you will not get bad surprises and everything will have a reasonable explanation and questions like this wouldn't be necessary on StackOverflow.

Like for example you could write a normal view model:

public class DriverViewModel
{
    public int? SelectedTenantID { get; set; }

    public IEnumerable<SelectListItem> Tenants { get; set; }
}

and a normal controller action that will query your datastore for the required information and project your entity model to the view model:

// GET: Drivers/Create
public ActionResult Create()
{
    var viewModel = new DriverViewModel();
    viewModel.Tenants = new SelectList(db.Tenants, "TenantID", "TenantName");
    return View(viewModel);
}

and finally the corresponding strongly typed view:

@model DriverViewModel
...
@Html.DropDownListFor(
    model => model.SelectedTenantID, 
    Model.Tenants, 
    htmlAttributes: new { @class = "form-control" }
)

In this case you are using a strongly typed view, with a strongly typed view model and a helper. There are no longer any doubts (and dogs that bite). The code is readable and you cannot ask, why this convention over configuration is doing this or that. So as long as there's no trace of ViewBag in your application there will be no such questions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM