在 mvc 中加载 2 个下拉列表的更好方法

Posted

技术标签:

【中文标题】在 mvc 中加载 2 个下拉列表的更好方法【英文标题】:better way to load 2 dropdown in mvc 【发布时间】:2015-04-22 00:57:35 【问题描述】:

这就是我在页面加载状态和城市下拉列表中加载的方式:

我的控制器方法

这是页面加载时调用的第一个方法。

public ActionResult Index()

    var states = GetStates();
    var cities =  Enumerable.Empty<SelectListItem>();
    ViewBag.States = states;
    ViewBag.Cities = cities;


private IEnumerable<SelectListItem> GetStates()

    using (var db = new DataEntities())
    
        return db.States.Select(d => new SelectListItem  Text = d.StateName, Value =d.Id.ToString() );
    


[HttpGet]
public ActionResult GetCities(int id)

    using (var db = new DataEntities())
    
        var data = db.Cities.Where(d=>d.StateId==id).Select(d => new  Text = d.CityName, Value = d.Id ).ToList();
        return Json(data, JsonRequestBehavior.AllowGet);
    

我的观点

IEnumerable<SelectListItem> States = ViewBag.States;
IEnumerable<SelectListItem> Cities = ViewBag.Cities;

@html.DropDownList("State", States, "Select State", new  onchange="loadCities(this)")
@Html.DropDownListFor(m => m.CityId, Cities, "Select City", new  id="ddlCity")
function loadCities(obj) 
            $.ajax(
                url: "/Home/GetCities",
                data:  id: $(obj).val() ,
                contentType:"application/json",
                success:function(responce)                   
                    var html = '<option value="0">Select City</option>';
                    $(responce).each(function () 
                        html += '<option value="'+this.Value+'">'+this.Text+'</option>'
                    );
                    $("#ddlCity").html(html);
                
            );
        

还有比这更好的加载州和城市下拉菜单的方法吗?

public class HomeController : Controller
    
        public ActionResult Index(int id=0)
        
            Person model = null;
            var states = GetStates().ToList();
            var cities =  Enumerable.Empty<SelectListItem>();
            if (id > 0)
            
                using (var db = new  DataEntities())
                
                    model = db.People.Include("City").FirstOrDefault(d => d.Id == id);
                    if (model == null)
                        model = new Person();
                    else
                    
                       states.First(d => d.Value == model.City.StateId.ToString()).Selected = true;
                       cities = db.Cities.Where(d => d.StateId == model.City.StateId).ToList().Select(d => new SelectListItem  Text = d.CityName,Value=d.Id.ToString(),Selected=d.Id==model.CityId );
                    

                
            
            else
            
                model = new Person();
            
            ViewBag.States = states;
            ViewBag.Cities = cities;
            ViewBag.Persons = GetPersons();
            return View(model);
        

        [HttpGet]
        public ActionResult GetCities(int id)
        
            using (var db = new DataEntities())
            
                var data = db.Cities.Where(d=>d.StateId==id).Select(d => new  Text = d.CityName, Value = d.Id ).ToList();
                return Json(data, JsonRequestBehavior.AllowGet);
            
        

        public ActionResult SavePersonDetail([Bind(Exclude = "Id")] Person model)
        

            // var employeeDal= new Emploee();
            //employee.firstname=model.

            if (ModelState.IsValid)
            
                var Id = model.Id;
                int.TryParse(Request["Id"], out Id);
            using (var db = new DataEntities())
            
                if (Id > 0)
                
                    var person = db.People.FirstOrDefault(d => d.Id == Id);
                    if (person != null)
                    
                        model.Id = Id;
                        db.People.ApplyCurrentValues(model);
                    
                
                else
                
                    db.People.AddObject(model);                    
                
                db.SaveChanges();
                               
            
            if (!Request.IsAjaxRequest())
            
                ViewBag.States = GetStates();
                ViewBag.Persons = GetPersons();
                ViewBag.Cities = Enumerable.Empty<SelectListItem>();
                return View("Index");
            
            else
            
                return PartialView("_personDetail",GetPersons());
            
        

        public ActionResult Delete(int id)
        
            using (var db = new DataEntities())
            
                var model = db.People.FirstOrDefault(d => d.Id == id);
                if (model != null)
                
                    db.People.DeleteObject(model);
                    db.SaveChanges();
                                
            
            if (Request.IsAjaxRequest())
            
                return Content(id.ToString());
            
            else
            
                ViewBag.States = GetStates();
                ViewBag.Persons = GetPersons();
                ViewBag.Cities = Enumerable.Empty<SelectListItem>();
                return View("Index");
            
        

        private IEnumerable<SelectListItem> GetStates()
        
            using (var db = new DataEntities())
            
               return db.States.ToList().Select(d => new SelectListItem  Text = d.StateName, Value =d.Id.ToString() );
            
        

        private IEnumerable<Person> GetPersons()
        
            using (var db = new DataEntities())
            
                return db.People.Include("City").Include("City.State").ToList();
            
        

        public ActionResult HomeAjax()
        
            ViewBag.States = GetStates();
            ViewBag.Cities = Enumerable.Empty<SelectListItem>();
            using (var db = new DataEntities())
            
                var data = db.States.Include("Cities").Select(d => new  Id = d.Id, Name = d.StateName, Cities = d.Cities.Select(x => new  Id=x.Id,Name=x.CityName) ).ToList();
                ViewBag.CityStateJson = new System.Web.Script.Serialization.javascriptSerializer().Serialize(data);
            
            ViewBag.Persons = GetPersons();
            return View();
        
    


@model IEnumerable<Person>
<div>
    <table>
        <tr>
            <th>
                First Name
            </th>
            <th>
                Last Name
            </th>
            <th>
                Email
            </th>
            <th>
                City
            </th>
            <th>
                State
            </th>
            <th>
                Edit
            </th>
        </tr>
    @if (Model.Count() == 0)
    
        <tr>
            <td colspan="6">
                <h3>No data available</h3>
            </td>
        </tr>
    
    else  
    foreach (var item in Model)  
        <tr data-id="@item.Id">
            <td data-id="fn">@item.FirstName</td>
            <td data-id="ln">@item.LastName</td>
            <td data-id="email">@item.Email</td>
            <td data-id="cn">@item.CityName<input type="hidden" value="@item.CityId" /></td>
            <td>@item.StateName</td>
            <td>
                @if (ViewBag.Title == "Home Ajax" || Request.IsAjaxRequest())
                
                    <a href="javascript:void(0);" onclick="Edit(this,@item.Id);">Update</a>
                    <span>@Ajax.ActionLink("Delete", "Delete", new  id = item.Id , new AjaxOptions OnSuccess="deleteSuccess",OnBegin="showLoader",OnComplete="hideLoader" )</span>

                
                else  
                    <span>@Html.ActionLink("Update", "Index", new  id = item.Id )</span>
                    <span>@Html.ActionLink("Delete", "Delete", new  id = item.Id )</span>
                

            </td>
        </tr>
    

    
        </table>
</div>

@model Person

@
    ViewBag.Title = "Home Ajax";
    IEnumerable<Person> persons = ViewBag.Persons;
    IEnumerable<SelectListItem> States = ViewBag.States;
    IEnumerable<SelectListItem> Cities = ViewBag.Cities;
    IEnumerable<State> fullStates=ViewBag.CityStates;



@section featured 
    <section class="featured">
        <div class="content-wrapper">
            <hgroup class="title">
                <h1>@ViewBag.Title.</h1>                
            </hgroup>            
        </div>
    </section>


@section styles
    <style type="text/css">
       td,th 
            border:1px solid;
            padding:5px 10px;
        

        select 
           padding:5px 2px;
           width:310px;
           font-size:16px;
        
    </style>


@section scripts
    @Scripts.Render("~/bundles/jqueryval")
    <script type="text/javascript">

        var jsonArray = @Html.Raw(ViewBag.CityStateJson)

        function clearValues() 
            $("input[type='text'],select").val('');
            $("input[type='hidden'][name='Id']").val(0);
        

        function loadCities(obj) 

            for (var i = 0; i < jsonArray.length; i++) 
                if (jsonArray[i].Id == parseInt($(obj).val())) 
                    fillCity(jsonArray[i].Cities);
                    break;
                
            
        

        function Edit(obj, Id) 
            //  alert("hi")
            $("input[type='hidden'][name='Id']").val(Id);
            var tr = $(obj).closest("tr");
            $("#txtfirstName").val($("td[data-id='fn']", tr).text().trim());
            $("#txtlastName").val($("td[data-id='ln']", tr).text().trim());
            $("#txtemail").val($("td[data-id='email']", tr).text().trim());
            var city = $("td[data-id='cn'] input[type='hidden']", tr).val();
            var state;
            for (var i = 0; i < jsonArray.length; i++) 
                for (var j = 0; j < jsonArray[i].Cities.length; j++) 
                    if (jsonArray[i].Cities[j].Id == parseInt(city)) 
                        state = jsonArray[i].Id;
                        break;
                    
                
                if (state) 
                    fillCity(jsonArray[i].Cities);
                    break;
                
            
            $("#ddlState").val(state);
            $("#ddlCity").val(city);
        

        function fillCity(obj) 
            var html = '<option value="0">Select City</option>';
            $(obj).each(function () 
                html += '<option value="' + this.Id + '">' + this.Name + '</option>'
            );
            $("#ddlCity").html(html);
        

        function deleteSuccess(responce) 
            alert("record deleted successfully");
            $("tr[data-id='" + responce + "']").remove();
        

        function insertSuccess() 
            alert("Record saved successfully");
            clearValues();
        

        function showLoader() 
            $("#overlay").show();
        
        function hideLoader() 
            $("#overlay").hide();
        
    </script>


<h3>Add Personal Detail</h3>
@using (Ajax.BeginForm("SavePersonDetail", "Home", new AjaxOptions  HttpMethod = "POST", UpdateTargetId = "personList" ,OnSuccess="insertSuccess",OnBegin="showLoader",OnComplete="hideLoader"))

    @Html.HiddenFor(m => m.Id);
<ol class="round">
    <li>       
        @Html.LabelFor(m => m.FirstName)
        @Html.TextBoxFor(m => m.FirstName, new  id = "txtfirstName" )
        @Html.ValidationMessageFor(m => m.FirstName)
    </li>
    <li>
    @Html.LabelFor(m => m.LastName)
        @Html.TextBoxFor(m => m.LastName, new  id = "txtlastName" )
        @Html.ValidationMessageFor(m => m.LastName)
        </li>
    <li>
       @Html.LabelFor(m => m.Email)
        @Html.TextBoxFor(m => m.Email, new  id = "txtemail" )
        @Html.ValidationMessageFor(m => m.Email)
    </li>

    <li>
        @Html.Label("State")
       @Html.DropDownList("State", States, "Select State", new  onchange = "loadCities(this)", id = "ddlState" )       
    </li>
    <li>
         @Html.LabelFor(m => m.CityId)
        @Html.DropDownListFor(m => m.CityId, Cities, "Select City", new  id = "ddlCity" )
        @Html.ValidationMessageFor(m => m.CityId)
    </li>
</ol>
    <input type="submit" value="Save" />
    <input type="button" value="Cancel" onclick="clearValues();"/>


   <h2>
        Person List
    </h2>
<div style="position:fixed;text-align:center;top:0;bottom:0;left:0;right:0;z-index:10;background-color:black;opacity:0.6;display:none;" id="overlay">
    <img style="position:relative;top:370px" src="~/Images/ajax-loader.gif" />
</div>
<div id="personList">
    @Html.Partial("_personDetail", persons)
    </div>

【问题讨论】:

定义“更好”。这在某种程度上不起作用吗?当第一个列表被选中时,使用 AJAX 获取第二个列表的内容似乎是合理的。你可以试试codereview.stackexchange.com 假设您的城市列表不是太大,我会在运行时将其全部加载,并且不会有 ajax 回调。有一个字典,其中包含每个州的键和一个城市列表作为值,而不是页面加载时的 javascript 对象,然后您的 loadCities 函数可以刷新列表而无需再次回调。 是的,这行得通,但我认为一定有更好的方法,像你们这样的专业人士总是有更好的方法,然后像我这样更新鲜 在使用.Select() 填充您的集合时,您不应该需要.ToList() @MariaPithia,您的方法很好而且相当标准。唯一的其他选择是从包含所有城市(包括州 ID)的集合的模型属性创建一个 javascript 数组,并循环通过它来更新第二个下拉列表 - 但这只会减慢页面的初始呈现速度。跨度> 【参考方案1】:

尽管我会推荐一些更好的做法,包括使用具有 StateIDCityIDStateListCityList 的属性的视图模型,但您使用 ajax 方法很好,以及使用 Unobtrusive JavaScript 而不是污染您的标记具有行为,并使用null 值而不是0 生成第一个(“请选择”)选项,因此它可以与[Required] 属性一起使用

HTML

@Html.DropDownList(m => m.StateID, States, "Select State") // remove the onchange
@Html.DropDownListFor(m => m.CityID, Cities, "Select City") // why change the default ID?

脚本

var url = '@Url.Action("GetCities", "Home")'; // use the helper (dont hard code)
var cities = $('#CityID'); // cache the element
$('#StateID').change(function() 
  $.getJSON(url,  id: $(this).val() , function(response) 
    // clear and add default (null) option
    cities.empty().append($('<option></option>').val('').text('Please select'));
    $.each(response, function(index, item) 
      cities.append($('<option></option>').val(item.Value).text(item.Text));
    );
  );
);

如果您要渲染多个项目(例如,您要求用户选择他们最近访问的 10 个城市),您可以缓存第一次调用的结果,以避免重复调用,因为他们的选择可能包括来自同一州的城市。

var cache = ;
$('#StateID').change(function() 
  var selectedState = $(this).val();
  if (cache[selectedState]) 
    // render the options from the cache
   else 
    $.getJSON(url,  id: selectedState , function(response) 
      // add to cache
      cache[selectedState] = response;
      .....
    );
  
);

最后,作为对不使用 ajax 的 cmets 的回应,您可以将所有城市传递给视图并将它们分配给 javascript 数组。如果你有几个国家,每个国家都有几个城市,我只会推荐这个。这是一个平衡轻微的额外初始加载时间与进行 ajax 调用的轻微延迟的问题。

在控制器中

model.CityList = db.Cities.Select(d => new  City = d.CountryID, Text = d.CityName, Value = d.Id ).ToList();

在视图中(脚本)

// assign all cities to javascript array
var allCities= JSON.parse('@Html.Raw(Json.Encode(Model.CityList))');
$('#StateID').change(function() 
  var selectedState = $(this).val();
  var cities = $.grep(allCities, function(item, index) 
    return item.CountryID == selectedState;
  );
  // build options based on value of cities
);

【讨论】:

先生,如果我有很多国家、很多州和城市,那么我应该采用哪种方法??? 第一个(即您当前的方法) 哦,好的,谢谢先生,我喜欢你的缓存方法,虽然我没有完全理解,但这个概念很好 这个想法是,如果您在第一个下拉列表中选择“A”(调用服务器获取 A 的城市),然后将其更改为“B”(调用服务器获取B)的城市然后将其更改回“A”,服务器将不会再次被调用,因为您已经在浏览器中缓存了“A”的所有城市 - 只是节省了不必要的调用。 @transformer,数据被缓存在一个 javascript 变量中,并且只在页面的生命周期内持续(这只是意味着如果您在一行中选择了一个特定的国家,则会进行 ajax 调用并且状态存储在客户端上,因此如果在另一行中选择相同的国家/地区,则无需再次调用服务器来获取状态。在这种情况下,用户创建新状态的可能性很小正在编辑数据:)【参考方案2】:

这是一种正确的方法,但您可以简化您的 javascript:

function loadCities(obj) 
    $.getJSON("/Home/GetCities", function (data) 
        var html = '<option value="0">Select City</option>';
        $(data).each(function () 
              html += '<option value="'+this.Value+'">'+this.Text+'</option>'
        );
        $("#ddlCity").html(html);
    );

进一步可能的简化: 在服务器端添加默认项(选择城市),这样您的 javascript 会更小。

【讨论】:

【参考方案3】:

假设城市列表不太长,我将在不刷新页面的情况下执行此操作。 我假设您可以创建一个 GetStatesAndCities 方法来返回一个字典。

public ActionResult Index()

  Dictionary<string, List<String>> statesAndCities = GetStatesAndCities();
  ViewBag.StatesAndCities = Json(statesAndCities);

然后在视图中:

var states = JSON.parse(@ViewBag.StatesAndCities);

function loadCities(obj) 
    var cities = states[$(obj).val()];
    var html = '<option value="0">Select City</option>';
    $(cities).each(function () 
        html += '<option value="'+this.Value+'">'+this.Text+'</option>'
    );
    $("#ddlCity").html(html);

这样,当状态改变时,城市字段立即更新,无需回调。

【讨论】:

【参考方案4】:

免责声明:这不是代码答案,还有很多其他答案。

我认为最好的方法是让自己乐于将 UI 页面与数据分开 => 将它们转换为 API 调用:

/GetCities /GetStates

现在您可以在 Razor 渲染页面时将选择项留空。并使用 Jquery/Bootstrap 插件创建 AJAX 选择框。

这样,当用户停止输入搜索时,该搜索字符串可以通过 AJAX 调用发送(例如:/GetStates?search=test),然后可以将一个小的结果集发送回网站。

这给出了:

服务端代码更好的分离 更好的用户体验。 较小的页面加载(因为您不再在用户请求页面时将所有选项发送给用户,仅当他打开选择框时)。

【讨论】:

【参考方案5】:

使用 Knockout 怎么样?

Knockout 是一个 JavaScript 库,可帮助您使用干净的底层数据模型创建丰富的响应式显示和编辑器用户界面

您必须为您的城市使用 ajax。但是淘汰赛就不需要写了

var html = '<option value="0">Select City</option>'; $(responce).each(function () html += '<option value="'+this.Value+'">'+this.Text+'</option>'); $("#ddlCity").html(html);

在您的 javascript.Knockout 中使其变得简单。

你可以简单地写:

        function CityModel() 
        var self = this; // that means this CityModel
        self.cities = ko.observableArray([]);
        self.getCities = function () 
            $.ajax(
                url: "/Home/GetCities",
                data:  id: $(obj).val() ,
                contentType: "application/json",
                success: self.cities
            );
        
    
    ko.applyBindings(new CityModel());

仅此而已。但是您必须将数据绑定到 html 元素中。 而不是使用: @Html.DropDownListFor(m =&gt; m.CityId, Cities, "Select City", new id="ddlCity")

你可以使用:

    <select data-bind="options:cities,optionsValue:"Id",optionsText:"CityName",optionsCaption:"Select City""></select>

或者你可以混合使用剃须刀和淘汰赛:

@Html.DropDownListFor(m => m.CityId, Cities, "Select City", new  id="ddlCity",data_bind:"options:cities,optionsValue:\"Id\",optionsText:\"CityName\"")

State 发生变化时,您还必须调用GetCities,您可以:

@Html.DropDownList("State", States, "Select State", new data_bind:"event:\"change\":\"$root.GetCities\"")

不要对 \"\" 的事情感到害怕,因为" 是一个转义字符,我们必须通过在它之前使用 \ 对剃须刀说我想使用 "。

你可以找到更多关于淘汰赛的信息:Knockout

并与剃须刀混合:Razor and Knockout

Ps:是的,使用淘汰赛将我们从 Razor 和 Mvc 中暂停。您必须编写另一个 ViewModel 。但像这种情况 ko 是有帮助的。混合剃须刀和淘汰赛是您的另一种选择。

【讨论】:

看到 jQuery 可以做到这一点,包括这两个库没有意义,为什么 Knockout 在这里是最佳的? 在您发表评论后,我注意到淘汰赛不是最佳选择,而是另一种选择。感谢您的反馈

以上是关于在 mvc 中加载 2 个下拉列表的更好方法的主要内容,如果未能解决你的问题,请参考以下文章

如何根据Angular 6同一行中的其他单元格值在AG-Grid选择下拉列表中加载不同的选项?

为啥在下拉列表中加载组件时不会触发 ngmodelChange 事件?

无法在angularjs中加载下拉列表的ng-model

在 Spring 中加载应用程序下拉/查找/参考数据的推荐方法

php ajax依赖下拉列表不从表中加载数据

如何在 AdminLte 多选下拉列表中加载项目以进行更新 - Laravel 8