📜 ⬆️ ⬇️

Five ways to show a drop-down list in Asp.Net MVC, with advantages and disadvantages

Most introductions to Asp.Net MVC tell you how beautifully and simply to organize the binding of the model to simple input fields, such as text or checkboxes. If you, the fearless coder, have mastered this stage, and you want to figure out how to show drop-down lists, lists of checkboxes or radiobuttons, This post is for you.

Formulation of the problem

We have a base of films about which we know the name and genre. I want to show the editor of information about the movie, whose genres are selected from the list. The problem is that our View should have data related both to the actual film and to the list of genres. Moreover, the data about the film relate to what we show, and the list of genres - to how we edit our information.

Method one. The Ugly.

We can transmit data about the movie through the controller, and the list of genres will be extracted by our twist 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; } } 

Method controller, as in the manuals for beginners:
  public ActionResult TheUgly(){ var model = Data.GetMovie(); return View(model); } 

Here Data is just a static class that gives us data. It was designed exclusively for ease of discussion, and I would not advise using something like this in real life:
  public static class Data { public static MovieModel GetMovie() { return new MovieModel {Title = "Santa Barbara", GenreId = 1}; } } 

We now give our terrible View, more precisely, that part of it that concerns the list of genres. In fact, 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 concerns (SoC). In particular, the controller is responsible for transferring data to the view. Of course, this is not a dogma, but simply a good rule. Violating it, you risk bloat a bunch of unnecessary code in your submissions, and a year later it will be very difficult to figure out what's what.

Plus : a simple controller.
Minus : the submission falls into the code peculiar to the controller; gross violation of the principles of the MVC model.
When to use : if you need to quickly jot down a demo.

The second way. The Bad.

As before, the model is transmitted in a standard way through the controller. All additional data is transmitted via ViewData. The controller method is here:
  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 up anything in ViewData. Then we use this whole thing in View:
 <%:Html.DropDownListFor(model => model.GenreId, (IEnumerable<SelectListItem>) ViewData["AllGenres"], "choose")%> 

Plus : the data for "what" and "how" are clearly separated: the first are stored in the model, the second - in ViewData.
Disadvantages : the data is divided, but the controller method is “overloaded”: it deals with two (and in the long run, many) things at once; In addition, for some reason I have an instinctive attitude towards ViewData as a “hacker” means of solving problems. Though, supporters of dynamic languages, probably, with pleasure use ViewData.
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 the book.
  public class ViewModel { public MovieModel Movie { get; set; } public IEnumerable<SelectListItem> 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 is good, but because it will be easier for other developers to get into the subject).
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 method. The Tricky.

There is another “back door” through which data can be delivered to the View - this is the RenderAction method. Many people squeamishly at the mention of this method, because, according to the classics, View should not know about the controller. For me personally, this (may the gods forgive me) is a good analogue of Web Forms UserControls. Namely, the ability to create a certain element, practically (except for the parameters for calling this method) is independent of the rest of the page.

So, as a model, we use MovieModel, and the controller method is the same as TheUgly. But we will now have a new controller, and a method for drawing the dropdown in it, as well as a partial with this dropdown. We will make this partial maximally flexible, in order to use it in other cases, we will call it Dropdown.ascx and put it in the Views \ Shared folder:

 <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<SelectListItem>>" %> <% =Html.DropDownList(ViewData.ModelMetadata.PropertyName, Model, ViewData.ModelMetadata.NullDisplayText)%> 

As for the method that renders this view, there are a couple of tricks here:
  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 the automatic selection of the desired value here, so we need to manually set the Selected property of the SelectListItem. Secondly, if we pass some non-trivial metadata to our View, we must first install the model, and then the metadata. Otherwise, the metadata will automatically be created based on the model. For the same reason, we should not write the return View (model). Well, the actual metadata is needed in order to determine the name of the property and the default text (NullDisplayText). Without the latter, by the way, you can do.

Finally, the controller method is called from the main View:
 <% Html.RenderAction("GetGenresDropdown", "Genre", new {selectedId = Model.GenreId}); %> 

Pros : the division of responsibility at the controller level: MovieController is responsible for the data about the film, GenreController - for genres. At the View level, we also have a complete victory: the main view was simplified significantly, and the details of the implementation of the selection of the genre went to the support. Here, by the way, there is a certain analogy with the simplification of the long method and the removal of part of the code in the auxiliary method.
Cons : more code, more complicated structure.
When to use : when the main View becomes large enough, and the dropdowns appear in several fields, or when the choice of a genre needs to 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 drastically somehow simplify this main View. And the obvious solution here is to use Html.EditorForModel (). Now, for choosing the method of displaying a particular field, the metadata of the model class are responsible. The only problem is with the 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 for calling the corresponding RenderAction. (However, if we need to customize the field editor, we will change this view, and leave DropDown.ascx neutral.)

So, in the \ Views \ Movie \ EditorTemplates folder, create a Partial View called GenreEditor.ascx. The model will have the same type as the GenreId property that we are editing, i.e., int. The view itself will contain only the RenderAction call:
 <% 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:

Pros : the same as in the previous example, but at the same time, we have significantly simplified our main View.
Disadvantages : we had to make an extra View, which (so far) does not bear any meaningful load (but, perhaps, will allow us to customize the editing of the field, for example, add a hint). Another, more important, minus is more difficult to customize the overall structure of the form (for example, one field is shown on the left side, and the others are on the right side). If global customization is required (for example, to use tables instead of divs everywhere), you can create your own template for Object.
When to use : when there are many fields, they are shown in a more or less standard way, and often changes are 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 methods of controllers and partials similar to our GenreEditor. Is it possible to somehow optimize it? 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 - we just need to add attributes to the model accordingly. In examples it is not, but the reader is lekgo it implements itself.

How else can you?

The only way that really came to my mind that I’ve mentioned here is to fill up dropdown via AJAX. The advantage here is obvious: the data is transmitted independently of html and can be used elsewhere in another way. I heard that such a thing is very easy to implement in Spark, but my hands have not yet come to try.

And why, in general, soared with this?

There is always one problem with examples: they should be simple enough so that it is clear what this is 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."

Of course, before using one of these solutions, it is better to estimate the signal-to-noise ratio: small projects are easier to maintain when there is less code, classes, files, etc., while large ones are easier to deal with. a great number of elements, each of which is clearly focused on solving its own small task. Fortunately, all this refactortes quite well: we can start with one of the first solutions, and, as our form becomes more complex, we can move on to more advanced options.

What else? Oh yeah, the source can be found here . Enjoy!

Source: https://habr.com/ru/post/106370/

All Articles