
Five ways to show a drop-down list in Asp.Net MVC, with advantages and disadvantages
Most introductions to Asp.Net MVC talk about how to beautifully and simply organize the binding of a model to simple input fields, such as text or checkbox. If you, a fearless coder, mastered this stage, and want to figure out how to show drop-down lists, checkbox or radio button lists, this post is for you.
We have a database of films about which we know the name and genre. I would like to show the editor of information about the movie, whose genres are selected from the list. The problem is that our View must have data related both to the film itself and to the list of genres. Moreover, the data about the film refers to what we show, and the list of genres refers to how we edit our information.
We can transfer data about the film through the controller, and our view will extract the list of genres by itself. It is clear that this is a violation of all conceivable principles, but theoretically there is such a possibility.
We have such classes of models:
Controller method, as in beginner's guides:
Here, Data is just a static class that gives us the data. It was invented solely for ease of discussion, and I would not recommend using something like this in real life:
We will now give our terrible View, or rather, that part of it that concerns the list of genres. Essentially, our code should pull out all genres and convert them into elements of type SelectListItem.
What is terrible here? The fact is that the main advantage of Asp.Net MVC, in my opinion, is that we have a clear separation of duties (SoC). In particular, it is the controller that is responsible for transmitting data in a view. Of course, this is not a dogma, but simply a good rule. Violating it, you run the risk of heaping up a bunch of unnecessary code in your submissions, and sorting out in a year what’s what will be very difficult.
Plus : simple controller.
Minus : the code specific to the controller gets into the view; flagrant violation of the principles of the MVC model.
When to use : if you need to quickly sketch a demo.
As before, we transfer the model in a standard way through the controller. We transmit all additional data through ViewData. Here we have the controller method:
It is clear that in the general case, we can pile anything in ViewData. Further we use all this business in View:
Plus : the data for “what” and “how” are clearly separated: the former are stored in the model, the latter in ViewData.
Cons : the data is separated, but the controller method is “overloaded”: it deals with two (and in the future - many) things at once; in addition, for some reason I have an instinctive attitude towards ViewData as a "hacker" means of solving problems. Although proponents of dynamic languages may enjoy ViewData with pleasure.
When to use : in small forms with one or two lists.
We use a model that contains all the necessary data. Just like in a book.
Now the controller’s task is to make this model from the available data:
Plus : the canonical implementation of the MVC pattern (this is good not because it’s good, but because it will be easier for other developers to get into the topic).
Cons : as in the previous example, the controller method is overloaded: it is concerned with “what” and “how”; in addition, the same “what” and “how” are connected in the same ViewModel class.
When to use : in small and medium forms with one to three lists and other non-standard input elements.
There is another “back door” through which you can deliver data to the View - this is the RenderAction method. Many grin squeamishly at the mention of this method, because, according to the classics, View should not know about the controller. For me personally, this (gods forgive me) is a good analogue of UserControls from WebForms. Namely, the ability to create an element that is practically (apart from the call parameters of this method) independent of the rest of the page.
So, we use MovieModel as the model, and the controller method is the same as TheUgly. But we will now have a new controller, and a method for rendering a dropdown in it, as well as partial with this dropdown. We will make this partial maximally flexible so that we can use it in other cases, call Dropdown.ascx, and place it in the Views \ Shared folder:
As for the method that renders this view, there are a couple of tricks:
First, we lose here the automatic selection of the desired value, so we need to manually set the Selected property to SelectListItem. Secondly, if we transfer some non-trivial metadata to our View, then we must first install the model, and then the metadata. Otherwise, metadata is automatically generated based on the model. For the same reason, we should not write return View (model). Well, actually metadata is needed in order to determine the name of the property and the default text (NullDisplayText). By the way, you can do without the latter.
Finally, the controller method is called from the main View:
Pros : separation of responsibility at the controller level: MovieController is responsible for the data on the film, GenreController for genres. At the View level, we also have a complete victory: the main view has been greatly simplified, and the details of the implementation of the choice of the genre have gone to auxiliary. Here, by the way, there is a certain analogy with the simplification of a long method and the removal of part of the code in a helper method.
Cons : more code, more complicated structure.
When to use : when the main View becomes large enough, and dropdowns appear in several fields, or when the choice of a genre must be used on several pages.
When the entire non-trivial part of organizing the input (or selection) of the desired value is separated from the main View, there is a desire to somehow simplify this main View. And the obvious solution here is to use Html.EditorForModel (). Now, the metadata of the model class is responsible for choosing a method for displaying a particular field. The only problem is that with built-in tools we can only force the engine to call RenderPartial () in the right place, but not RenderAction (). Therefore, you will have to create a Partial View, which does not carry any load except to call the corresponding RenderAction. (True, if we need to customize the field editor, then we will change this particular view and leave DropDown.ascx neutral.)
So, in the \ Views \ Movie \ EditorTemplates folder, create a Partial View called GenreEditor.ascx. It will have a model of the same type as the GenreId property that we are editing, i.e., int. The view itself will contain only a call to RenderAction:
To use our view, you need to add the necessary attribute to the GenreId property in the model:
Pros : the same as in the previous example, but at the same time we significantly simplified our main View.
Cons : we had to make an extra View, which (so far) does not carry any meaningful load (but, perhaps, it will allow us to customize field editing, for example, add a hint). Another, more important, minus is that it’s more difficult to customize the general structure of the form (for example, show one field on the left and the rest on the right). If global customization is required (for example, everywhere to use tables instead of divs), you can make your own template for Object.
When to use : when there are many fields, they are shown in a more or less standard way, and changes are often made to the list of fields.
Now, if we have dropdowns with categories, ratings, etc., we can still use Dropdown.aspx (as for other dropdowns), but we will have to write similar controller methods and partials similar to our GenreEditor. Is there any way to optimize this? First, you can make a basic Generic Controller, and send our method there. Secondly, you can somehow transfer the name of the associated class (“Genre”) to our partial (for example, through the DataType attribute), and construct the RenderAction call accordingly. Now instead of GenreEditor we will have a universal editor for choosing from the drop-down list. As a result, we get that adding new directories will not increase the amount of necessary code in any way - you just have to appropriately put the attributes of the model. In the examples this is not,
The only really different way from the ones given here that came to my mind is to make dropdown filling through AJAX. The plus here is obvious: data is transmitted independently of html and can be used in other places in a different way. I heard that such a thing is very easily implemented in Spark, but my hands have not yet reached a try.
There is always one problem with examples: they must be simple enough to make it clear what they are talking about, but then it is not clear why such a difficult decision. If you are still concerned about this issue, re-read the “when to use” items.
Of course, before using one of these solutions, it is better to estimate the signal / noise ratio: small projects to support is easier when there is less code, classes, files, etc., while in larger easier to deal with b about with the largest number of elements, each of which is clearly focused on solving its small task. Fortunately, all this is pretty well refactored: we can start with one of the first solutions, and, as our form becomes more complex, move on to more advanced options.
What else? Oh yes, the source can be taken here. Enjoy it!
Formulation of the problem
We have a database of films about which we know the name and genre. I would like to show the editor of information about the movie, whose genres are selected from the list. The problem is that our View must have data related both to the film itself and to the list of genres. Moreover, the data about the film refers to what we show, and the list of genres refers to how we edit our information.
The first way. The ugly.
We can transfer data about the film through the controller, and our view will extract the list of genres by itself. It is clear that this is a violation of all conceivable principles, but theoretically there is such a possibility.
We have such classes of models:
public class MovieModel {
public string Title { get; set; }
public int GenreId { get; set; }
}
public class GenreModel {
public int Id { get; set; }
public string Name { get; set; }
}
Controller method, as in beginner's guides:
public ActionResult TheUgly(){
var model = Data.GetMovie();
return View(model);
}
Here, Data is just a static class that gives us the data. It was invented solely for ease of discussion, and I would not recommend using something like this in real life:
public static class Data {
public static MovieModel GetMovie() {
return new MovieModel {Title = "Santa Barbara", GenreId = 1};
}
}
We will now give our terrible View, or rather, that part of it that concerns the list of genres. Essentially, our code should pull out all genres and convert them into elements of type SelectListItem.
<%
var selectList = from genre in Data.GetGenres()
select new SelectListItem {Text = genre.Name, Value = genre.Id.ToString()};
%>
<%:Html.DropDownListFor(model => model.GenreId, selectList, "choose") %>
What is terrible here? The fact is that the main advantage of Asp.Net MVC, in my opinion, is that we have a clear separation of duties (SoC). In particular, it is the controller that is responsible for transmitting data in a view. Of course, this is not a dogma, but simply a good rule. Violating it, you run the risk of heaping up a bunch of unnecessary code in your submissions, and sorting out in a year what’s what will be very difficult.
Plus : simple controller.
Minus : the code specific to the controller gets into the view; flagrant violation of the principles of the MVC model.
When to use : if you need to quickly sketch a demo.
The second way. The bad.
As before, we transfer the model in a standard way through the controller. We transmit all additional data through ViewData. Here we have the controller method:
public ActionResult TheBad() {
var model = Data.GetMovie();
ViewData["AllGenres"] = from genre in Data.GetGenres()
select new SelectListItem {Text = genre.Name, Value = genre.Id.ToString()};
return View(model);
}
It is clear that in the general case, we can pile anything in ViewData. Further we use all this business in View:
<%:Html.DropDownListFor(model => model.GenreId,
(IEnumerable) ViewData["AllGenres"],
"choose")%>
Plus : the data for “what” and “how” are clearly separated: the former are stored in the model, the latter in ViewData.
Cons : the data is separated, but the controller method is “overloaded”: it deals with two (and in the future - many) things at once; in addition, for some reason I have an instinctive attitude towards ViewData as a "hacker" means of solving problems. Although proponents of dynamic languages may enjoy ViewData with pleasure.
When to use : in small forms with one or two lists.
The third way. The good.
We use a model that contains all the necessary data. Just like in a book.
public class ViewModel {
public MovieModel Movie { get; set; }
public IEnumerable Genres { get; set; }
}
Now the controller’s task is to make this model from the available data:
public ActionResult TheGood() {
var model = new ViewModel();
model.Movie = Data.GetMovie();
model.Genres = from genre in Data.GetGenres()
select new SelectListItem {Text = genre.Name, Value = genre.Id.ToString()};
return View(model);
}
Plus : the canonical implementation of the MVC pattern (this is good not because it’s good, but because it will be easier for other developers to get into the topic).
Cons : as in the previous example, the controller method is overloaded: it is concerned with “what” and “how”; in addition, the same “what” and “how” are connected in the same ViewModel class.
When to use : in small and medium forms with one to three lists and other non-standard input elements.
The fourth way. The Tricky.
There is another “back door” through which you can deliver data to the View - this is the RenderAction method. Many grin squeamishly at the mention of this method, because, according to the classics, View should not know about the controller. For me personally, this (gods forgive me) is a good analogue of UserControls from WebForms. Namely, the ability to create an element that is practically (apart from the call parameters of this method) independent of the rest of the page.
So, we use MovieModel as the model, and the controller method is the same as TheUgly. But we will now have a new controller, and a method for rendering a dropdown in it, as well as partial with this dropdown. We will make this partial maximally flexible so that we can use it in other cases, call Dropdown.ascx, and place it in the Views \ Shared folder:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl>" %>
<% =Html.DropDownList(ViewData.ModelMetadata.PropertyName, Model, ViewData.ModelMetadata.NullDisplayText)%>
As for the method that renders this view, there are a couple of tricks:
public class GenreController : Controller{
public ActionResult GetGenresDropdown(int selectedId) {
ViewData.Model = from genre in Data.GetGenres()
select new SelectListItem
{ Text = genre.Name,
Value = genre.Id.ToString(),
Selected = (genre.Id == selectedId) };
ViewData.ModelMetadata =
new ModelMetadata(
ModelMetadataProviders.Current,
null,
null,
typeof (int),
"GenreId")
{NullDisplayText = "choose"};
return View("Dropdown");
}
}
First, we lose here the automatic selection of the desired value, so we need to manually set the Selected property to SelectListItem. Secondly, if we transfer some non-trivial metadata to our View, then we must first install the model, and then the metadata. Otherwise, metadata is automatically generated based on the model. For the same reason, we should not write return View (model). Well, actually metadata is needed in order to determine the name of the property and the default text (NullDisplayText). By the way, you can do without the latter.
Finally, the controller method is called from the main View:
<% Html.RenderAction("GetGenresDropdown", "Genre", new {selectedId = Model.GenreId}); %>
Pros : separation of responsibility at the controller level: MovieController is responsible for the data on the film, GenreController for genres. At the View level, we also have a complete victory: the main view has been greatly simplified, and the details of the implementation of the choice of the genre have gone to auxiliary. Here, by the way, there is a certain analogy with the simplification of a long method and the removal of part of the code in a helper method.
Cons : more code, more complicated structure.
When to use : when the main View becomes large enough, and dropdowns appear in several fields, or when the choice of a genre must be used on several pages.
The fifth way. The smart.
When the entire non-trivial part of organizing the input (or selection) of the desired value is separated from the main View, there is a desire to somehow simplify this main View. And the obvious solution here is to use Html.EditorForModel (). Now, the metadata of the model class is responsible for choosing a method for displaying a particular field. The only problem is that with built-in tools we can only force the engine to call RenderPartial () in the right place, but not RenderAction (). Therefore, you will have to create a Partial View, which does not carry any load except to call the corresponding RenderAction. (True, if we need to customize the field editor, then we will change this particular view and leave DropDown.ascx neutral.)
So, in the \ Views \ Movie \ EditorTemplates folder, create a Partial View called GenreEditor.ascx. It will have a model of the same type as the GenreId property that we are editing, i.e., int. The view itself will contain only a call to RenderAction:
<% Html.RenderAction("GetGenresDropdown", "Genre", new {selectedId = Model}); %>
To use our view, you need to add the necessary attribute to the GenreId property in the model:
[UIHint("GenreEditor")]
Pros : the same as in the previous example, but at the same time we significantly simplified our main View.
Cons : we had to make an extra View, which (so far) does not carry any meaningful load (but, perhaps, it will allow us to customize field editing, for example, add a hint). Another, more important, minus is that it’s more difficult to customize the general structure of the form (for example, show one field on the left and the rest on the right). If global customization is required (for example, everywhere to use tables instead of divs), you can make your own template for Object.
When to use : when there are many fields, they are shown in a more or less standard way, and changes are often made to the list of fields.
Now, if we have dropdowns with categories, ratings, etc., we can still use Dropdown.aspx (as for other dropdowns), but we will have to write similar controller methods and partials similar to our GenreEditor. Is there any way to optimize this? First, you can make a basic Generic Controller, and send our method there. Secondly, you can somehow transfer the name of the associated class (“Genre”) to our partial (for example, through the DataType attribute), and construct the RenderAction call accordingly. Now instead of GenreEditor we will have a universal editor for choosing from the drop-down list. As a result, we get that adding new directories will not increase the amount of necessary code in any way - you just have to appropriately put the attributes of the model. In the examples this is not,
How else can you?
The only really different way from the ones given here that came to my mind is to make dropdown filling through AJAX. The plus here is obvious: data is transmitted independently of html and can be used in other places in a different way. I heard that such a thing is very easily implemented in Spark, but my hands have not yet reached a try.
And why, in general, take a steam bath with this?
There is always one problem with examples: they must be simple enough to make it clear what they are talking about, but then it is not clear why such a difficult decision. If you are still concerned about this issue, re-read the “when to use” items.
Of course, before using one of these solutions, it is better to estimate the signal / noise ratio: small projects to support is easier when there is less code, classes, files, etc., while in larger easier to deal with b about with the largest number of elements, each of which is clearly focused on solving its small task. Fortunately, all this is pretty well refactored: we can start with one of the first solutions, and, as our form becomes more complex, move on to more advanced options.
What else? Oh yes, the source can be taken here. Enjoy it!