ASP.NET MVC Html.DropDownList SelectedValue

103

Eu tentei isso é RC1 e, em seguida, atualizei para RC2, o que não resolveu o problema.

// in my controller
ViewData["UserId"] = new SelectList(
    users, 
    "UserId", 
    "DisplayName", 
    selectedUserId.Value); // this has a value

resultado: a propriedade SelectedValue é definida no objeto

// in my view
<%=Html.DropDownList("UserId", (SelectList)ViewData["UserId"])%>

resultado: todas as opções esperadas são apresentadas ao cliente, mas o atributo selecionado não é definido. O item em SelectedValue existe na lista, mas o primeiro item da lista é sempre selecionado como padrão.

Como devo fazer isso?

Atualização Graças à resposta de John Feminella, descobri qual é o problema. "UserId" é uma propriedade no modelo para a qual minha visualização é fortemente tipada. Quando Html.DropDownList ("UserId" é alterado para qualquer outro nome, exceto "UserId", o valor selecionado é processado corretamente.

No entanto, isso resulta no valor não sendo vinculado ao modelo.

blu
fonte
Tem certeza de que o valor está na lista?
John Sheehan,
O problema ainda existe na versão 1 da ASP.NET MVC
Mathias F
O que é selectedUserId !?
fulvio
Como você deve vincular o valor na atualização se o nome da sua entrada não for o mesmo da sua propriedade?
Ryudice

Respostas:

68

Foi assim que resolvi esse problema:

Eu tinha o seguinte:

Controlador:

ViewData["DealerTypes"] = Helper.SetSelectedValue(listOfValues, selectedValue) ;

Visão

<%=Html.DropDownList("DealerTypes", ViewData["DealerTypes"] as SelectList)%>

Alterado pelo seguinte:

Visão

<%=Html.DropDownList("DealerTypesDD", ViewData["DealerTypes"] as SelectList)%>

Parece que o DropDown não deve ter o mesmo nome, tem o nome ViewData: É estranho mas funcionou.

Sanchitos
fonte
19
+1 para "o DropDown não deve ter o mesmo nome tem o nome ViewData" Muito obrigado
Daniel Dyson
Obrigado!!! Você acabou de resolver meu problema depois de muitas horas me perguntando por que isso não funcionou :)
Jen
1
Isso funciona para o valor selecionado, mas surge um problema quando você tenta atualizar "DealerTypes" no banco de dados, uma vez que DropDownList está agora vinculado a "DealerTypesDD" que não existe no modelo. Uma solução alternativa é adicionar um campo oculto e htmlAttributes ao DropDownList: <input type = "hidden" name = "DealerTypes" id = "DealerTypes" value = "" /> <% = Html.DropDownList ("DealerTypesDD", ViewData [ "DealerTypes"] como SelectList, new {@onchange = "DealerTypes.value = this.value"})%>
Zim
3
É melhor você adicionar 'DS' ao nome de viewdata, não estragando a vinculação do modelo ...
noocyte
1
Que diabos! Isso não é corrigido no MVC5 ??? Como outro truque mencionado por @Paul Hatcher, copie o id selecionado em ViewData ["DealerTypes"].
jsgoupil 01 de
51

Experimente isto:

public class Person {
    public int Id { get; set; }
    public string Name { get; set; }
}

E depois:

var list = new[] {   
    new Person { Id = 1, Name = "Name1" }, 
    new Person { Id = 2, Name = "Name2" }, 
    new Person { Id = 3, Name = "Name3" } 
};

var selectList = new SelectList(list, "Id", "Name", 2);
ViewData["People"] = selectList;

Html.DropDownList("PeopleClass", (SelectList)ViewData["People"])

Com MVC RC2, eu obtenho:

<select id="PeopleClass" name="PeopleClass">
    <option value="1">Name1</option>
    <option selected="selected" value="2">Name2</option>
    <option value="3">Name3</option>
</select>
John Feminella
fonte
Isso foi útil para rastrear a causa. Eu atualizei minha pergunta para refletir os acréscimos.
blu
é o primeiro no modelo? segundo no controlador? e terceiro em vista claro, mas 2 primeiros .. ???
rr
Boa resposta, mas não é melhor usar um ViewModel?
Jess
@Jess: Escrevi esta resposta em março de 2009, há mais de 5 anos. Os tempos mudaram! Fique à vontade para atualizar. :)
John Feminella
5

Você ainda pode nomear o DropDown como "UserId" e ainda ter a vinculação de modelo funcionando corretamente para você.

O único requisito para que isso funcione é que a chave ViewData que contém a SelectList não tenha o mesmo nome da propriedade Model que você deseja vincular. No seu caso específico, seria:

// in my controller
ViewData["Users"] = new SelectList(
    users, 
    "UserId", 
    "DisplayName", 
    selectedUserId.Value); // this has a value

// in my view
<%=Html.DropDownList("UserId", (SelectList)ViewData["Users"])%>

Isso produzirá um elemento de seleção denominado UserId, que tem o mesmo nome da propriedade UserId em seu modelo e, portanto, o fichário do modelo o definirá com o valor selecionado no elemento select do html gerado pelo auxiliar Html.DropDownList.

Não sei por que esse construtor Html.DropDownList específico não seleciona o valor especificado na SelectList quando você coloca a lista de seleção em ViewData com uma chave igual ao nome da propriedade. Suspeito que tenha algo a ver com a forma como o auxiliar DropDownList é usado em outros cenários, onde a convenção é que você tem uma SelectList no ViewData com o mesmo nome da propriedade em seu modelo. Isso funcionará corretamente:

// in my controller
ViewData["UserId"] = new SelectList(
    users, 
    "UserId", 
    "DisplayName", 
    selectedUserId.Value); // this has a value

// in my view
<%=Html.DropDownList("UserId")%>
Rui
fonte
Muito obrigado amigo, acima de todas as respostas isso funcionou para mim, não sei por que, mas funcionou
Lamin Sanneh
Finalmente! Depois de horas tentando, finalmente encontrei seu "único requisito". Isso foi o suficiente.
SteveCav
2

Se não achamos que este é um bug que a equipe deve corrigir, pelo menos o MSDN deve melhorar o documento. O confuso realmente vem do pobre documento disso. No MSDN , ele explica o nome dos parâmetros como,

Type: System.String
The name of the form field to return.

Isso significa apenas que o html final que ele gera usará esse parâmetro como o nome da entrada selecionada. Mas, na verdade, significa mais do que isso.

Eu acho que o designer assume que o usuário usará um modelo de visualização para exibir a lista suspensa, também usará a postagem de volta para o mesmo modelo de visualização. Mas em muitos casos, não seguimos realmente essa suposição.

Use o exemplo acima,

public class Person {
    public int Id { get; set; }
    public string Name { get; set; }
}

Se seguirmos a suposição, devemos definir um modelo de visualização para esta visualização relacionada à lista suspensa

public class PersonsSelectViewModel{
    public string SelectedPersonId,
    public List<SelectListItem> Persons;
}

Porque quando postar de volta, apenas o valor selecionado será postado de volta, então ele assume que deve postar de volta na propriedade SelectedPersonId do modelo, o que significa que o primeiro nome de parâmetro de Html.DropDownList deve ser 'SelectedPersonId'. Portanto, o designer pensa que, ao exibir a vista do modelo na vista, a propriedade do modelo SelectedPersonId deve conter o valor padrão dessa lista suspensa. Mesmo que sua Lista de <SelectListItem> Pessoas já tenha definido o sinalizador Selecionado para indicar qual é selecionado / padrão, o tml.DropDownList irá realmente ignorar isso e reconstruir seu próprio IEnumerable <SelectListItem> e definir o item padrão / selecionado com base no nome.

Aqui está o código do asp.net mvc

private static MvcHtmlString SelectInternal(this HtmlHelper htmlHelper, ModelMetadata metadata,
            string optionLabel, string name, IEnumerable<SelectListItem> selectList, bool allowMultiple,
            IDictionary<string, object> htmlAttributes)
{
    ...

    bool usedViewData = false;

    // If we got a null selectList, try to use ViewData to get the list of items.
    if (selectList == null)
    {
        selectList = htmlHelper.GetSelectData(name);
        usedViewData = true;
    }

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

    // If we haven't already used ViewData to get the entire list of items then we need to
    // use the ViewData-supplied value before using the parameter-supplied value.
    if (defaultValue == null && !String.IsNullOrEmpty(name))
    {
        if (!usedViewData)
        {
            defaultValue = htmlHelper.ViewData.Eval(name);
        }
        else if (metadata != null)
        {
            defaultValue = metadata.Model;
        }
    }

    if (defaultValue != null)
    {
        selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
    }

    ...

    return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
}

Então, o código realmente foi além, ele não apenas tenta pesquisar o nome no modelo, mas também no viewdata, assim que encontrar um, ele irá reconstruir a selectList e ignorar seu Selected original.

O problema é que, em muitos casos, não o usamos realmente dessa forma. queremos apenas adicionar uma selectList com um / vários item (ns) Selected set true.

Claro que a solução é simples, use um nome que não está no modelo nem nos dados de exibição. Quando não consegue encontrar uma correspondência, ele usará a selectList original e o Selected original terá efeito.

Mas ainda acho que o MVC deve melhorá-lo adicionando mais uma condição

if ((defaultValue != null) && (!selectList.Any(i=>i.Selected)))
{
    selectList = GetSelectListWithDefaultValue(selectList, defaultValue, allowMultiple);
}

Porque, se a selectList original já teve uma Selecionada, por que você ignoraria isso?

Apenas meus pensamentos.

Liuhongbo
fonte
Esta é realmente a melhor explicação que li até agora. Me salvou de muita dor de cabeça. Tudo que eu tive que fazer foi definir o valor da propriedade viewmodel e funcionou. Obrigado.
Moses Machua
2

O código do post anterior do MVC 3 não funciona, mas é um bom começo. Eu irei consertar isso. Eu testei este código e ele funciona em MVC 3 Razor C #. Este código usa o padrão ViewModel para preencher uma propriedade que retorna um List<SelectListItem>.

A classe Model

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

A classe ViewModel

using System.Web.Mvc;

public class ProductListviewModel
{
    public List<SelectListItem> Products { get; set; }
}

O Método do Controlador

public ViewResult List()
{
    var productList = new List<SelectListItem>();

    foreach (Product p in Products)
    {
        productList.Add(new SelectListItem
        {
            Value = p.ProductId.ToString(),
            Text = "Product: " + p.Name + " " + p.Price.ToString(),
            // To set the selected item use the following code 
            // Note: you should not set every item to selected
            Selected = true
        });
    }

    ProductListViewModel productListVM = new ProductListViewModeld();

    productListVM.Products = productList;

    return View(productListVM);
}

A vista

@model MvcApp.ViewModels.ProductListViewModel

@using (Html.BeginForm())
{
    @Html.DropDownList("Products", Model.Products)
}

A saída HTML será algo como

<select id="Products" name="Products">
    <option value="3">Product: Widget 10.00</option>
    <option value="4">Product: Gadget 5.95</option>
</select>

dependendo de como você formata a saída. Eu espero que isso ajude. O código funciona.

wayne.blackmon
fonte
1
Isso não define a selectedopção.
Jess
Para definir o item selecionado usando SelectListItem, defina a propriedade Selected como true.
wayne.blackmon
1

Isso parece ser um bug na classe SelectExtensions, uma vez que verifica apenas ViewData e não o modelo para o item selecionado. Portanto, o truque é copiar o item selecionado do modelo para a coleção ViewData com o nome da propriedade.

Isso foi tirado da resposta que dei nos fóruns MVC, também tenho uma resposta mais completa em uma postagem de blog que usa o atributo DropDownList de Kazi ...

Dado um modelo

public class ArticleType
{
   public Guid Id { get; set; }
   public string Description { get; set; }
}

public class Article
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public ArticleType { get; set; }
}

e um modelo de visão básico de

public class ArticleModel
{
     public Guid Id { get; set; }
     public string Name { get; set; }

     [UIHint("DropDownList")]
     public Guid ArticleType { get; set; }
}

Em seguida, escrevemos um modelo de editor DropDownList da seguinte maneira.

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<script runat="server">  
    IEnumerable<SelectListItem> GetSelectList()
    {
        var metaData = ViewData.ModelMetadata;
        if (metaData == null)
        {
            return null;
        }

        var selected = Model is SelectListItem ? ((SelectListItem) Model).Value : Model.ToString();
        ViewData[metaData.PropertyName] = selected;

        var key = metaData.PropertyName + "List";
        return (IEnumerable<SelectListItem>)ViewData[key];
    }
</script>
<%= Html.DropDownList(null, GetSelectList()) %>

Isso também funcionará se você alterar o ArticleType no modelo de visualização para um SelectListItem, embora seja necessário implementar um conversor de tipo de acordo com o blog de Kazi e registrá-lo para forçar o fichário a tratar isso como um tipo simples.

Em seu controlador, então temos ...

public ArticleController 
{
     ...
     public ActionResult Edit(int id)
     {
          var entity = repository.FindOne<Article>(id);
          var model = builder.Convert<ArticleModel>(entity);

          var types = repository.FindAll<ArticleTypes>();
          ViewData["ArticleTypeList"] = builder.Convert<SelectListItem>(types);

          return VIew(model);
     }
     ...
}
Paul Hatcher
fonte
Obrigado pela resposta, vou ter que verificar isso.
blu
0

O problema é que as caixas de depósito não funcionam da mesma forma que as caixas de listagem, pelo menos da maneira que o design da ASP.NET MVC2 espera: uma caixa de depósito permite apenas zero ou um valor, pois as caixas de listagem podem ter uma seleção de vários valores. Portanto, sendo estrito com HTML, esse valor não deve estar na lista de opções como flag "selecionado", mas na própria entrada.

Veja o seguinte exemplo:

<select id="combo" name="combo" value="id2">
  <option value="id1">This is option 1</option>
  <option value="id2" selected="selected">This is option 2</option>
  <option value="id3">This is option 3</option>
</select>

<select id="listbox" name="listbox" multiple>
  <option value="id1">This is option 1</option>
  <option value="id2" selected="selected">This is option 2</option>
  <option value="id3">This is option 3</option>
</select>

O combo tem a opção selecionada, mas também tem seu atributo de valor definido . Portanto, se você deseja que a ASP.NET MVC2 renderize uma caixa de depósito e também tenha um valor específico selecionado (ou seja, valores padrão, etc.), você deve fornecer a ela um valor na renderização, como este:

// in my view             
<%=Html.DropDownList("UserId", selectListItems /* (SelectList)ViewData["UserId"]*/, new { @Value = selectedUser.Id } /* Your selected value as an additional HTML attribute */)%>
Dalotodo
fonte
0

Na ASP.NET MVC 3 você pode simplesmente adicionar sua lista a ViewData ...

var options = new List<SelectListItem>();

options.Add(new SelectListItem { Value = "1", Text = "1" });
options.Add(new SelectListItem { Value = "2", Text = "2" });
options.Add(new SelectListItem { Value = "3", Text = "3", Selected = true });

ViewData["options"] = options;

... e, em seguida, referenciá-lo pelo nome em sua visualização de navalha ...

@Html.DropDownList("options")

Você não precisa "usar" manualmente a lista na chamada DropDownList. Fazendo desta forma, defina corretamente o valor selecionado para mim também.

Aviso Legal:

  1. Ainda não tentei isso com o mecanismo de visualização de formulários da web, mas também deve funcionar.
  2. Eu não testei isso na v1 e v2, mas pode funcionar.
John Bubriski
fonte
0

Consegui obter o resultado desejado, mas com uma abordagem um pouco diferente. Na lista suspensa, usei o modelo e fiz referência a ele. Não tenho certeza se era isso que você estava procurando.

@Html.DropDownList("Example", new SelectList(Model.FeeStructures, "Id", "NameOfFeeStructure", Model.Matters.FeeStructures))

Model.Matters.FeeStructures acima é meu id, que pode ser o valor do item que deve ser selecionado.

Jukes
fonte