MVC 3 Razor视图中的级联下拉菜单

47

我对如何在Razor视图中实现级联下拉列表以获取地址信息感兴趣。我的站点实体具有SuburbId属性。 Suburb具有CityId,而City具有ProvinceId。 我想在Site视图上显示所有Suburb,City和Province的下拉列表,例如,郊区下拉列表最初将显示“先选择城市”,而城市下拉列表则显示“先选择省份”。 选择省份后,省份内的城市将被填充等。

我应该如何实现这个功能?我从哪里开始?


我的博客《ASP.Net MVC中的级联DropDownList》(http://blogs.msdn.com/b/rickandy/archive/2012/01/09/cascasding-dropdownlist-in-asp-net-mvc.aspx)恰好介绍了这个。另外,还可以参考我的教程《使用DropDownList Box和jQuery》(http://www.asp.net/mvc/tutorials/javascript/working-with-the-dropdownlist-box-and-jquery/using-the-dropdownlist-helper-with-aspnet-mvc)。 - RickAndMSFT
5个回答

87

让我们通过一个例子来说明。像往常一样,从一个模型开始:

public class MyViewModel
{
    public string SelectedProvinceId { get; set; }
    public string SelectedCityId { get; set; }
    public string SelectedSuburbId { get; set; }
    public IEnumerable<Province> Provinces { get; set; }
}

public class Province
{
    public string Id { get; set; }
    public string Name { get; set; }
}

接下来是控制器:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        var model = new MyViewModel
        {
            // TODO: Fetch those from your repository
            Provinces = Enumerable.Range(1, 10).Select(x => new Province
            {
                Id = (x + 1).ToString(),
                Name = "Province " + x
            })
        };
        return View(model);
    }

    public ActionResult Suburbs(int cityId)
    {
        // TODO: Fetch the suburbs from your repository based on the cityId
        var suburbs = Enumerable.Range(1, 5).Select(x => new
        {
            Id = x,
            Name = "suburb " + x
        });
        return Json(suburbs, JsonRequestBehavior.AllowGet);
    }

    public ActionResult Cities(int provinceId)
    {
        // TODO: Fetch the cities from your repository based on the provinceId
        var cities = Enumerable.Range(1, 5).Select(x => new
        {
            Id = x,
            Name = "city " + x
        });
        return Json(cities, JsonRequestBehavior.AllowGet);
    }
}

最后是一个视图:

@model SomeNs.Models.MyViewModel

@{
    ViewBag.Title = "Home Page";
}

<script type="text/javascript" src="/scripts/jquery-1.4.4.js"></script>
<script type="text/javascript">
    $(function () {
        $('#SelectedProvinceId').change(function () {
            var selectedProvinceId = $(this).val();
            $.getJSON('@Url.Action("Cities")', { provinceId: selectedProvinceId }, function (cities) {
                var citiesSelect = $('#SelectedCityId');
                citiesSelect.empty();
                $.each(cities, function (index, city) {
                    citiesSelect.append(
                        $('<option/>')
                            .attr('value', city.Id)
                            .text(city.Name)
                    );
                });
            });
        });

        $('#SelectedCityId').change(function () {
            var selectedCityId = $(this).val();
            $.getJSON('@Url.Action("Suburbs")', { cityId: selectedCityId }, function (suburbs) {
                var suburbsSelect = $('#SelectedSuburbId');
                suburbsSelect.empty();
                $.each(suburbs, function (index, suburb) {
                    suburbsSelect.append(
                        $('<option/>')
                            .attr('value', suburb.Id)
                            .text(suburb.Name)
                    );
                });
            });
        });
    });
</script>

<div>
    Province: 
    @Html.DropDownListFor(x => x.SelectedProvinceId, new SelectList(Model.Provinces, "Id", "Name"))
</div>
<div>
    City: 
    @Html.DropDownListFor(x => x.SelectedCityId, Enumerable.Empty<SelectListItem>())
</div>
<div>
    Suburb: 
    @Html.DropDownListFor(x => x.SelectedSuburbId, Enumerable.Empty<SelectListItem>())
</div>

作为改进,可以编写一个jQuery插件来避免重复某些部分,从而缩短JavaScript代码。


更新:

说到插件,你可以使用以下代码:

(function ($) {
    $.fn.cascade = function (options) {
        var defaults = { };
        var opts = $.extend(defaults, options);

        return this.each(function () {
            $(this).change(function () {
                var selectedValue = $(this).val();
                var params = { };
                params[opts.paramName] = selectedValue;
                $.getJSON(opts.url, params, function (items) {
                    opts.childSelect.empty();
                    $.each(items, function (index, item) {
                        opts.childSelect.append(
                            $('<option/>')
                                .attr('value', item.Id)
                                .text(item.Name)
                        );
                    });
                });
            });
        });
    };
})(jQuery);

然后简单地将其连接起来:

$(function () {
    $('#SelectedProvinceId').cascade({
        url: '@Url.Action("Cities")',
        paramName: 'provinceId',
        childSelect: $('#SelectedCityId')
    });

    $('#SelectedCityId').cascade({
        url: '@Url.Action("Suburbs")',
        paramName: 'cityId',
        childSelect: $('#SelectedSuburbId')
    });
});

2
我正打算写一条评论,说:“为什么要费这个力气?使用jquery插件即可。” - 然后我读到了你的最后一句话。 :) +1 - RPM1984
4
我尚未在我的代码中使用它,但看起来很不错。 - ProfK
1
我刚刚写完了我的JavaScript函数,然后向下滚动查看一个函数:/ 为它点赞。 - IAmGroot
6
@Darin Dimitrov,嘿,我只是想过来告诉你我(以及所有开发者,我相信)非常感激你在SO上乐于助人。虽然我是一个已经做了几年开发的人,但是对于SO还是新手,我认为你非常棒,愿意花时间帮助解决重要的(有时候与工作相关的)问题。 - Chazt3n
1
这很棒。不过有一个问题:所有请求都将被缓存。为了解决这个问题,请在插件中更改此行代码:$.getJSON(opts.url, params, function (items) {$.getJSON(opts.url + "?" + new Date().getTime(), params, function (items) {。我通常不会内联所有这些(而是将URL放在变量中)。如果你这样做了,请确保它位于更改处理程序之内:$(this).change(function () { - JoeBrockhaus
显示剩余3条评论

5

感谢Darin提供的解决方案,它对我帮助很大。但是正如“xxviktor”所提到的,我遇到了循环引用错误。为了解决这个问题,我采取了以下方法。

    public string GetCounties(int countryID)
    {
        List<County> objCounties = new List<County>();
        var objResp = _mastRepo.GetCounties(countryID, ref objCounties);
        var objRetC = from c in objCounties
                      select new SelectListItem
                      {
                          Text = c.Name,
                          Value = c.ID.ToString()
                      };
        return new JavaScriptSerializer().Serialize(objRetC);
    }

要实现自动级联,我稍微扩展了jQuery扩展的方法。

        $('#ddlCountry').cascade({
            url: '@Url.Action("GetCounties")',
            paramName: 'countryID',
            childSelect: $('#ddlState'),
            childCascade: true
        });

实际的JS代码使用以下参数(在JSON请求中)。

                // trigger child change
                if (opts.childCascade) {
                    opts.childSelect.change();
                }

希望这能帮助到有类似问题的人。

1

0

<script src="~/Scripts/jquery-1.10.2.min.js"></script>


<script type="text/javascript">
    $(document).ready(function () {
        //Dropdownlist Selectedchange event
        $("#country").change(function () {
            $("#State").empty();
            $.ajax({
                type: 'POST',
                url: '@Url.Action("State")', // we are calling json method

                dataType: 'json',

                data: { id: $("#country").val() },
                // here we are get value of selected country and passing same value
              

                success: function (states) {
                    // states contains the JSON formatted list
                    // of states passed from the controller

                    $.each(states, function (i, state) {
                        $("#State").append('<option value="' + state.Value + '">' +
                             state.Text + '</option>');
                        // here we are adding option for States

                    });
                },
            error: function (ex) {
                alert('Failed to retrieve states.' + ex);
            }
        });
        return false;
    })
    });
</script>
<div>
    @Html.DropDownList("country", ViewBag.country as List<SelectListItem>, "CountryName", new { style = "width: 200px;" })
</div>
<div>

</div>
<div>
    @Html.DropDownList("State", ViewBag.country as List<SelectListItem>)

</div>

从控制器中获取值

 public async Task<ActionResult> Country()
    {

        Country co = new Country();
        List<SelectListItem> li = new List<SelectListItem>();
        li.Add(new SelectListItem { Text = "Select", Value = "0" });
        li.Add(new SelectListItem { Text = "India", Value = "1" });
        li.Add(new SelectListItem { Text = "Nepal", Value = "2" });
        li.Add(new SelectListItem { Text = "USA", Value = "3" });
        li.Add(new SelectListItem { Text = "Kenya", Value = "4" }); ;
        ViewBag.country= li;
        return View();
    }
    public JsonResult  state(string id)
    {
        List<SelectListItem> states = new List<SelectListItem>();
        states.Add(new SelectListItem { Text = "--Select State--", Value = "0" });
        switch (id)
        {
            case "1":


                states.Add(new SelectListItem { Text = "MP", Value = "1" });
                states.Add(new SelectListItem { Text = "UP", Value = "2" });
                break;
            case "3":

                states.Add(new SelectListItem { Text = "USA1", Value = "3" });
                states.Add(new SelectListItem { Text = "USA2", Value = "4" });
                break;
        }

        return Json(new SelectList(states, "Value", "Text", JsonRequestBehavior.AllowGet));
    }

0
为了实现支持MVC内置验证和绑定的级联下拉列表,您需要做一些与其他答案不同的事情。
如果您的模型具有验证功能,则将支持它。以下是带有验证的模型摘录:
[Required]
[DisplayFormat(ConvertEmptyStringToNull = false)]    
public Guid cityId { get; set; }

在您的控制器中,您需要添加一个获取方法,以便您的视图稍后能够获取相关数据:
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetData(Guid id)
{
    var cityList = (from s in db.City where s.stateId == id select new { cityId = s.cityId, name = s.name }); 
    //simply grabbing all of the cities that are in the selected state 

    return Json(cityList.ToList(), JsonRequestBehavior.AllowGet);  
}

现在,回到我之前提到的视图:

在你的视图中,你有两个类似于这样的下拉菜单:

<div class="editor-label">
    @Html.LabelFor(model => model.stateId, "State")
</div>
<div class="editor-field">
    @Html.DropDownList("stateId", String.Empty)
    @Html.ValidationMessageFor(model => model.stateId)
</div>

<div class="editor-label">
    @Html.LabelFor(model => model.cityId, "City")
</div>
<div class="editor-field">
    @*<select id="cityId"></select>*@
    @Html.DropDownList("cityId", String.Empty)
    @Html.ValidationMessageFor(model => model.cityId)
</div>

下拉菜单中的内容由控制器绑定并自动填充。注意:根据我的经验,取消此绑定并依赖于JavaScript 来填充下拉菜单会导致您失去验证。此外,我们这里的绑定方式与验证相协调,所以没有理由要更改它。
现在进入我们的jQuery插件:
(function ($) {
$.fn.cascade = function (secondaryDropDown, actionUrl, stringValueToCompare) {
    primaryDropDown = this; //This doesn't necessarily need to be global
    globalOptions = new Array(); //This doesn't necessarily need to be global
    for (var i = 0; i < secondaryDropDown.options.length; i++) {
        globalOptions.push(secondaryDropDown.options[i]);
    }

    $(primaryDropDown).change(function () {
        if ($(primaryDropDown).val() != "") {
            $(secondaryDropDown).prop('disabled', false); //Enable the second dropdown if we have an acceptable value
            $.ajax({
                url: actionUrl,
                type: 'GET',
                cache: false,
                data: { id: $(primaryDropDown).val() },
                success: function (result) {
                    $(secondaryDropDown).empty() //Empty the dropdown so we can re-populate it
                    var dynamicData = new Array();
                    for (count = 0; count < result.length; count++) {
                        dynamicData.push(result[count][stringValueToCompare]);
                    }

                    //allow the empty option so the second dropdown will not look odd when empty
                    dynamicData.push(globalOptions[0].value);
                    for (var i = 0; i < dynamicData.length; i++) {
                        for (var j = 0; j < globalOptions.length; j++) {
                            if (dynamicData[i] == globalOptions[j].value) {
                                $(secondaryDropDown).append(globalOptions[j]);
                                break;
                            }
                        }

                    }
                },
                dataType: 'json',
                error: function () { console.log("Error retrieving cascading dropdown data from " + actionUrl); }
            });
        }
        else {
            $(secondaryDropDown).prop('disabled', true);
        }
        secondaryDropDown.selectedindex = 0; //this prevents a previous selection from sticking
    });
    $(primaryDropDown).change();
};
} (jQuery));

你可以将我创建的上述jQuery代码复制到你的视图中的<script>...</script>标签中,或者如果你愿意的话,放在一个单独的脚本文件中(请注意,我已更新此内容以使其跨浏览器兼容,但我使用的场景已不再需要,但它应该能够正常工作)。
在同一些脚本标签中(而不是在单独的文件中),你可以使用以下javascript调用插件。
$(document).ready(function () {
    var primaryDropDown = document.getElementById('stateId');
    var secondaryDropdown = document.getElementById('cityId');
    var actionUrl = '@Url.Action("GetData")'
    $(primaryDropDown).cascade(secondaryDropdown, actionUrl);
});

记得添加 $(document).ready 部分,页面必须在尝试使下拉菜单级联之前完全加载。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接