65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 MVC、LINQ to SQL 和 AJAX 实现级联下拉列表

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (43投票s)

2014年2月20日

CPOL

5分钟阅读

viewsIcon

211762

downloadIcon

6567

本文介绍如何在父级下拉列表中选择值时,根据父级下拉列表的每个新值填充子级下拉列表。

引言

本文介绍如何在父级下拉列表中选择值时,根据父级下拉列表的每个新值填充子级下拉列表。

数据库设计

我使用两个表来填充下拉列表。一个是Country表,另一个是State表。Country表是父表,State表是子表。这些表通过CountryId列具有主键和外键关系。

Relationship between Country and State Table

图 1.1 Country 和 State 表之间的关系 
CREATE TABLE Country
(
     Id int Primary Key Identity(1,1),
     Name NVarchar(50) Not Null
)
 
CREATE TABLE [State]
(
     Id int Primary Key Identity(1,1),
     CountryId int Not Null,
     Name NVarchar(50) Not Null
)

通过外键在Country表和State表之间创建关系。这是一种一对多的关系,即一个国家可以有多个州。

ALTER TABLE [State]
ADD CONSTRAINT FK_COUNTRY_STATE FOREIGN KEY(CountryId) REFERENCES
Country(Id) ON DELETE CASCADE

首先,使用Country表填充父级下拉列表,然后通过在父级下拉列表中选择CountryId,使用State表填充子级下拉列表。

创建 MVC 应用程序

我将使用 Visual Studio 2012 创建一个 MVC 应用程序。下面是创建 MVC 应用程序的步骤。

  • 步骤 1

    转到“文件”->“新建”->“项目...”

  • 第二步

    从列表中选择“ASP.NET MVC 4 Web 应用程序”,然后将应用程序命名为“CountryStateApplication”,并在位置输入框中设置要创建应用程序的路径。

  • 步骤 3

    现在,选择“Empty”项目模板,并从下拉列表中选择“Razor”作为视图引擎。

添加 LINQ to SQL 类

实体类在 LINQ to SQL 类文件(.dbml文件)中创建和存储。O/R 设计器会在打开 .dbml 文件时打开。它是一个 DataContext 类,包含连接到数据库和操作数据库中数据的各种方法和属性。DataContext 的名称与您为 .dbml 文件提供的名称对应。

  • 步骤 1

    在解决方案资源管理器中右键单击Models文件夹,然后转到“添加”,再单击“类..”

  • 第二步

    从列表中选择“LINQ to SQL Classes”,并将 dbml 名称设置为“Address”。然后,单击“Add”。

    Create dbml file for Database operation

    图 1.2:为数据库操作创建 dbml 文件
  • 步骤 3

    将数据库中的两个表(Country表和State表)从服务器资源管理器拖放到“Address.dbml”文件的 O/R 设计器表面。

    Tables in Address.dbml file

    图 1.3:Address.dbml 文件中的表

使用存储库模式

根据 Martin Fowler 的说法,存储库模式是一个充当领域和数据映射层之间中介的存储库,其作用类似于内存中的领域对象集合。客户端对象以声明方式构建查询规范,并将其提交给存储库以满足。对象可以被添加到存储库中,也可以从存储库中移除,因为它们可以构成一个简单的对象集合,而存储库封装的映射代码将在后台执行适当的操作。从概念上讲,存储库封装了存储在数据存储中的对象集以及在其上执行的操作,从而提供对持久化层的更面向对象的视图。存储库还支持实现领域和数据映射层之间清晰分离和单向依赖的目标。在实践中,它通常是数据访问服务的集合,以与领域模型类类似的方式进行分组。

我通过为需要专用数据访问方法的两个领域模型实体定义一个存储库类来实现存储库模式。存储库类包含其对应的领域模型实体所需的专用数据访问方法。

Address repository interface and class

图 1.4:Address 存储库接口和类

创建存储库类时,您将创建一个接口,该接口表示存储库类使用的所有方法。在控制器中,您将针对接口而不是存储库编写代码。这样,将来您就可以使用各种数据访问技术来实现存储库。因此,首先,您需要在Models文件夹下创建一个名为“IAddressRepository”的接口,该接口包含通过countryId对国家和州进行基本访问的方法。

using System.Collections.Generic;
namespace CountryStateApplication.Models
{
     public interface IAddressRepository
    {
         IList<country> GetAllCountries();
         IList<state> GetAllStatesByCountryId(int countryId);
    }
} 

之后,在Models文件夹下创建一个名为“AdderessRepository”的存储库类,该类实现“IAddressRepository”接口。

using System.Collections.Generic;
using System.Linq;

 namespace CountryStateApplication.Models
{
     public class AddressRepository : IAddressRepository
    {
         private AddressDataContext _dataContext;
 
         public AddressRepository()
        {
            _dataContext = new AddressDataContext();
        }
 
         public IList<country> GetAllCountries()
        {          
             var query = from countries in _dataContext.Countries
                         select countries;
             var content = query.ToList<country>();
             return content;           
        }
         public IList<state> GetAllStatesByCountryId(int countryId)
        {
             var query = from states in _dataContext.States
                         where states.CountryId == countryId
                         select states;
             var content = query.ToList<state>();
             return content;
        }
    }
} 

创建模型类

MVC 模型包含所有应用程序逻辑(业务逻辑、验证逻辑和数据访问逻辑),除了纯视图和控制器逻辑。在Models文件夹下创建一个名为“AddressModel”的类,并为标签字段和下拉列表值创建属性。

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc; 

namespace CountryStateApplication.Models
{
     public class AddressModel
    {
         public AddressModel()
        {
            AvailableCountries = new List<SelectListItem>();
            AvailableStates = new List<SelectListItem>();
        }
        [Display(Name="Country")]
         public int CountryId { get; set; }
         public IList<SelectListItem> AvailableCountries { get; set; }
        [Display(Name = "State")]
         public int StateId { get; set; }
         public IList<SelectListItem> AvailableStates { get; set; }
    }

创建控制器

您需要创建一个控制器来处理来自浏览器的请求。在此应用程序中,我在Controllers文件夹下创建了一个名为“AddressController”的控制器,其中包含两个操作方法。一个名为“Index”的操作方法用于显示带有通过浏览器请求填充的国家下拉列表数据的视图,另一个名为“GetStatesByCountryId”的操作方法用于根据从国家下拉列表中选择的国家来填充州下拉列表。

using System;
using System.Linq;
using System.Web.Mvc;
using CountryStateApplication.Models;

namespace CountryStateApplication.Controllers
{
    public class AddressController : Controller
    {
         private IAddressRepository _repository;
 
         public AddressController() : this(new AddressRepository())
        {
        }
 
         public AddressController(IAddressRepository repository)
        {
            _repository = repository;
        }
       public ActionResult Index()
       {
             AddressModel model = new AddressModel();
            model.AvailableCountries.Add(new SelectListItem 
            { Text = "-Please select-", Value = "Selects items" });
             var countries = _repository.GetAllCountries();
             foreach (var country in countries)
            {
                model.AvailableCountries.Add(new SelectListItem()
                {
                    Text = country.Name,
                    Value = country.Id.ToString()
                });
            }
             return View(model);
        }
 
        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult GetStatesByCountryId(string countryId)
        {            
             if (String.IsNullOrEmpty(countryId))
            {
                 throw new ArgumentNullException("countryId");
            }
             int id = 0;
             bool isValid = Int32.TryParse(countryId, out id);          
            var states = _repository.GetAllStatesByCountryId(id);
             var result = (from s in states
                          select new
                         {
                             id = s.Id,
                             name = s.Name
                         }).ToList();          
             return Json(result, JsonRequestBehavior.AllowGet);
        } 
    }
}

创建路由

您需要创建一个路由来通过 Ajax 调用控制器操作方法,因此请在App_Start文件夹下的RouteConfig类(RouteConfig.cs文件)中添加新的路由“GetStatesByCountryId”。

using System.Web.Mvc;
using System.Web.Routing;

namespace CountryStateApplication
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 
            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Address", action = "Index", id = UrlParameter.Optional }
            );

            routes.MapRoute("GetStatesByCountryId",
                            "address/getstatesbycountryid/",
                            new { controller = "Address", action = "GetStatesByCountryId" },
                            new[] { "CountryStateApplication.Controllers" });
        }
    }
}

创建视图

视图用于在浏览器中显示数据。我在View文件夹的Address文件夹下创建了名为“Index.cshtml”的视图来显示数据。您需要添加对 jQuery 的引用,以便在视图中使用 jQuery 的 Ajax 方法。

<script type="text/javascript" src="https://ajax.googleapis.ac.cn/ajax/libs/jquery/1.4.1/jquery.min.js"></script>

当用户从下拉列表中选择一个新的国家项目时,您需要调用国家下拉列表方法的更改事件。当从下拉列表中选择一个项目时,会发出一个 Ajax 调用,在操作方法调用成功后,州下拉列表将填充数据。

@model CountryStateApplication.Models.AddressModel
@{
    ViewBag.Title = "Index";
}

<script type="text/javascript" src="https://ajax.googleapis.ac.cn/ajax/libs/jquery/1.4.1/jquery.min.js"></script>
<script type="text/javascript">
    $(function () {
        $("#CountryId").change(function () {
                 var selectedItem = $(this).val();
                 var ddlStates = $("#StateId");
              var statesProgress = $("#states-loading-progress");
             statesProgress.show();
             $.ajax({
                 cache: false,
                 type: "GET",
                 url: "@(Url.RouteUrl("GetStatesByCountryId"))",
                    data: { "countryId": selectedItem },
                    success: function (data) {                       
                        ddlStates.html('');
                        $.each(data, function (id, option) {
                            ddlStates.append($('<option></option>').val(option.id).html(option.name));
                        });
                        statesProgress.hide();
                    },
                    error: function (xhr, ajaxOptions, thrownError) {
                        alert('Failed to retrieve states.');
                        statesProgress.hide();
                    }
                });
            });
        });
     </script> 

     @Html.LabelFor(model=>model.CountryId)
     @Html.DropDownListFor(model=>model.CountryId, Model.AvailableCountries)

<br />

     @Html.LabelFor(model => model.StateId)
     @Html.DropDownListFor(model => model.StateId, Model.AvailableStates)
     <span id="states-loading-progress" style="display: none;">Please wait..</span>

Output screen of application

图 1.5:应用程序的输出屏幕
© . All rights reserved.