¿Por qué DropDownListFor pierde la selección múltiple después de Enviar pero el ListBoxFor no?

He leído muchos artículos sobre el uso de MultiSelectList y aún no entiendo qué está pasando mal con mi DropDownListFor. Tengo un ListBoxFor con la misma Vista, ViewModel y datos que funcionan bien. Quiero usar DropDownListFor debido a su parámetro optionLabel que ListBoxFor no tiene.

Cuando la Vista se carga por primera vez, tanto DropDownListFor como ListBoxFor muestran los múltiples elementos seleccionados.

Vista inicial

Cuando se hace clic en el botón Enviar, la colección de elementos seleccionados se publica de nuevo en la acción del Controlador y la vista se actualiza con el ListBoxPara mostrar los dos elementos seleccionados, pero DropDownListFor solo muestra un elemento seleccionado.

Vista renovada La acción del controlador está construyendo MultiSelectList de la siguiente manera:

vm.TasksFilterGroup.Assignees = new MultiSelectList(employees, "Id", "FullName", new string[] { "51b6f06a-e04d-4f98-88ef-cd0cfa8a2757", "51b6f06a-e04d-4f98-88ef-cd0cfa8a2769" }); 

El código de vista se ve así:

 
@Html.ListBoxFor(m => m.TasksFilterGroup.SelectedAssignees, Model.TasksFilterGroup.Assignees, new { @class = "form-control", multiple = "multiple" })
@Html.DropDownListFor(m => m.TasksFilterGroup.SelectedAssignees, Model.TasksFilterGroup.Assignees, new { @class = "form-control", multiple = "multiple" })

¿Por qué DropDownListFor pierde la selección múltiple después de Enviar pero el ListBoxFor no?

Como los nombres de los métodos implican, DropDownListFor() es para crear un (para seleccionar 1 opción) y ListBoxFor() es para crear un (para seleccionar múltiples opciones). Si bien ambos métodos comparten mucho código común, sí producen resultados diferentes.

Agregar el atributo multiple="multiple" cambia la visualización, pero no cambia la funcionalidad del código ejecutado por estos métodos.

Si inspecciona el código fuente , notará que todas las sobrecargas de DropDownListFor() finalmente llaman al método private static MvcHtmlString DropDownListHelper() , y de forma similar, ListBoxFor() finalmente llama al método private static MvcHtmlString ListBoxHelper() .

Ambos métodos llaman al método private static MvcHtmlString SelectInternal() , pero la diferencia es que DropDownListHelper() pasa allowMultiple = false mientras que ListBoxHelper() pasa allowMultiple = true .

Dentro del método SelectInternal() , la línea clave del código es

 object defaultValue = (allowMultiple) ? htmlHelper.GetModelStateValue(fullName, typeof(string[])) : htmlHelper.GetModelStateValue(fullName, typeof(string)); 

El valor de defaultValue se usa cuando se construye html para los elementos y se usa para establecer los atributos selected .

En el caso de ListBoxFor() , el valor de defaultValue será la matriz definida por su propiedad SelectedAssignees . En el caso de DropDownListFor() , devuelve null porque el valor de su propiedad no se puede convertir en una string (es una matriz).

Como defaultValue es null , ninguno de los elementos tiene el atributo selected y usted pierde el enlace del modelo.

Como nota al margen, si tuviera que establecer los valores de SelectedAssignees en el método GET antes de pasar el modelo a la vista, verá que ninguno de ellos está seleccionado al usar DropDownListFor() por los mismos motivos descritos anteriormente.

Tenga en cuenta también que el código para generar SelectList debería simplemente ser

 vm.TasksFilterGroup.Assignees = new SelectList(employees, "Id", "FullName" }); 

No tiene sentido establecer el 3er parámetro cuando se utilizan los métodos DropDownListFor() o ListBoxFor() porque es el valor de la propiedad a la que se vincula ( SelectedAssignees ) que determina qué opciones se seleccionan (el tercer parámetro es ignorado por los métodos). Si desea que se seleccionen las opciones que coincidan con esos valores Guid , en el método GET, use

 vm.TasksFilterGroup.SelectedAssignees= new string[]{ "51b6f06a-e04d-4f98-88ef-cd0cfa8a2757", "51b6f06a-e04d-4f98-88ef-cd0cfa8a2769" };