Progress Telerik 教程(三):数据绑定与数据源管理完全指南

作者:微信公众号:【架构师老卢】
9-25 18:7
22

Progress Telerik Ultimate Collection 2025 Q2下载地址 https://soft51.cc/software/175792580241152290

1. 数据绑定基础概念

数据绑定是现代UI框架的核心功能,它实现了数据与界面的自动同步。Telerik UI支持多种数据绑定方式,能够无缝集成各种数据源。

1.1 数据绑定类型

静态数据绑定:

  • 直接绑定到集合或数组
  • 适用于固定不变的数据

动态数据绑定:

  • 绑定到远程数据源
  • 支持CRUD操作
  • 实时数据更新

双向数据绑定:

  • 界面变化自动更新数据
  • 数据变化自动更新界面

1.2 支持的数据源类型

  • 内存集合:List、Array、ObservableCollection
  • 数据库:Entity Framework、ADO.NET
  • Web API:RESTful服务、GraphQL
  • JSON数据:本地JSON文件、Ajax请求
  • XML数据:本地XML、Web服务

2. 控件数据绑定方式

2.1 集合数据绑定

静态集合绑定

简单集合绑定:

@{
    var products = new[]
    {
        new { ID = 1, Name = "笔记本电脑", Price = 5999.99 },
        new { ID = 2, Name = "无线鼠标", Price = 199.99 },
        new { ID = 3, Name = "机械键盘", Price = 699.99 }
    };
}

@(Html.Kendo().Grid<object>()
    .Name("productGrid")
    .DataSource(dataSource => dataSource
        .Ajax()
        .Model(model => {
            model.Id("ID");
            model.Field("ID", typeof(int));
            model.Field("Name", typeof(string));
            model.Field("Price", typeof(decimal));
        })
        .Read(read => read.Action("GetProducts", "Home"))
    )
    .Columns(columns =>
    {
        columns.Bound("ID").Width(80);
        columns.Bound("Name").Title("产品名称").Width(200);
        columns.Bound("Price").Title("价格").Format("{0:C}").Width(150);
    })
)

强类型模型绑定:

// 模型定义
public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
    public DateTime CreatedDate { get; set; }
}
@(Html.Kendo().Grid<Product>()
    .Name("typedGrid")
    .DataSource(dataSource => dataSource
        .Ajax()
        .Model(model => model.Id(p => p.ID))
        .Read(read => read.Action("GetProducts", "Product"))
    )
    .Columns(columns =>
    {
        columns.Bound(p => p.ID).Width(80);
        columns.Bound(p => p.Name).Title("产品名称");
        columns.Bound(p => p.Price).Title("价格").Format("{0:C}");
        columns.Bound(p => p.Category).Title("分类");
    })
)

下拉框集合绑定

<!-- 静态数据绑定 -->
@(Html.Kendo().DropDownList()
    .Name("categoryList")
    .BindTo(new SelectList(new[]
    {
        new { Value = "electronics", Text = "电子产品" },
        new { Value = "clothing", Text = "服装" },
        new { Value = "books", Text = "图书" }
    }, "Value", "Text"))
    .OptionLabel("请选择分类")
)

<!-- 动态数据绑定 -->
@(Html.Kendo().DropDownList()
    .Name("dynamicCategoryList")
    .DataTextField("Name")
    .DataValueField("ID")
    .DataSource(source => source
        .Read(read => read.Action("GetCategories", "Product"))
    )
    .OptionLabel("加载中...")
)

2.2 数据库数据绑定

Entity Framework 集成

DbContext 配置:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options) { }

    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }
}

// 服务注册
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));

控制器实现:

[HttpGet]
public async Task<IActionResult> GetProducts([DataSourceRequest] DataSourceRequest request)
{
    using var context = new ApplicationDbContext();
    var products = context.Products.Include(p => p.Category);
    
    var result = await products.ToDataSourceResultAsync(request);
    return Json(result);
}

[HttpPost]
public async Task<IActionResult> CreateProduct([DataSourceRequest] DataSourceRequest request, Product product)
{
    if (ModelState.IsValid)
    {
        using var context = new ApplicationDbContext();
        context.Products.Add(product);
        await context.SaveChangesAsync();
    }

    return Json(new[] { product }.ToDataSourceResult(request, ModelState));
}

[HttpPost]
public async Task<IActionResult> UpdateProduct([DataSourceRequest] DataSourceRequest request, Product product)
{
    if (ModelState.IsValid)
    {
        using var context = new ApplicationDbContext();
        context.Entry(product).State = EntityState.Modified;
        await context.SaveChangesAsync();
    }

    return Json(new[] { product }.ToDataSourceResult(request, ModelState));
}

[HttpPost]
public async Task<IActionResult> DeleteProduct([DataSourceRequest] DataSourceRequest request, Product product)
{
    using var context = new ApplicationDbContext();
    var entity = context.Products.Find(product.ID);
    if (entity != null)
    {
        context.Products.Remove(entity);
        await context.SaveChangesAsync();
    }

    return Json(new[] { product }.ToDataSourceResult(request, ModelState));
}

Grid CRUD 配置:

@(Html.Kendo().Grid<Product>()
    .Name("crudGrid")
    .Columns(columns =>
    {
        columns.Bound(p => p.Name).Title("产品名称");
        columns.Bound(p => p.Price).Title("价格").Format("{0:C}");
        columns.Bound(p => p.Category).Title("分类");
        columns.Command(command => 
        {
            command.Edit();
            command.Destroy();
        }).Width(200);
    })
    .ToolBar(toolbar => toolbar.Create())
    .Editable(editable => editable.Mode(GridEditMode.InLine))
    .DataSource(dataSource => dataSource
        .Ajax()
        .PageSize(10)
        .Model(model =>
        {
            model.Id(p => p.ID);
            model.Field(p => p.ID).Editable(false);
        })
        .Create(create => create.Action("CreateProduct", "Product"))
        .Read(read => read.Action("GetProducts", "Product"))
        .Update(update => update.Action("UpdateProduct", "Product"))
        .Destroy(destroy => destroy.Action("DeleteProduct", "Product"))
    )
    .Pageable()
    .Sortable()
    .Filterable()
)

2.3 API 数据绑定

RESTful API 集成

API 控制器:

[ApiController]
[Route("api/[controller]")]
public class ProductsApiController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsApiController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet]
    public async Task<IActionResult> Get([DataSourceRequest] DataSourceRequest request)
    {
        var products = await _productService.GetAllAsync();
        var result = products.ToDataSourceResult(request);
        return Ok(result);
    }

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] Product product)
    {
        if (ModelState.IsValid)
        {
            await _productService.CreateAsync(product);
            return Ok(product);
        }
        return BadRequest(ModelState);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> Put(int id, [FromBody] Product product)
    {
        if (id != product.ID)
            return BadRequest();

        if (ModelState.IsValid)
        {
            await _productService.UpdateAsync(product);
            return Ok(product);
        }
        return BadRequest(ModelState);
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(int id)
    {
        await _productService.DeleteAsync(id);
        return Ok();
    }
}

客户端 API 绑定:

@(Html.Kendo().Grid<Product>()
    .Name("apiGrid")
    .DataSource(dataSource => dataSource
        .Ajax()
        .Transport(transport =>
        {
            transport.Read(read => read.Url("/api/products").Type(HttpVerbs.Get));
            transport.Create(create => create.Url("/api/products").Type(HttpVerbs.Post));
            transport.Update(update => update.Url("/api/products/{0}").Type(HttpVerbs.Put));
            transport.Destroy(destroy => destroy.Url("/api/products/{0}").Type(HttpVerbs.Delete));
            transport.ParameterMap(@<text>
                function(options, operation) {
                    if (operation !== "read" && options.models) {
                        return JSON.stringify(options.models[0]);
                    }
                    return options;
                }
            </text>);
        })
        .Schema(schema =>
        {
            schema.Model(model =>
            {
                model.Id(p => p.ID);
                model.Field(p => p.ID).Editable(false);
            });
            schema.Data("Data");
            schema.Total("Total");
        })
        .ServerOperation(false)
    )
)

外部 API 集成

public class ExternalApiService
{
    private readonly HttpClient _httpClient;

    public ExternalApiService(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<List<Product>> GetProductsFromExternalApiAsync()
    {
        var response = await _httpClient.GetStringAsync("https://api.example.com/products");
        var products = JsonSerializer.Deserialize<List<Product>>(response);
        return products;
    }
}

// 控制器使用
[HttpGet]
public async Task<IActionResult> GetExternalProducts([DataSourceRequest] DataSourceRequest request)
{
    var products = await _externalApiService.GetProductsFromExternalApiAsync();
    var result = products.ToDataSourceResult(request);
    return Json(result);
}

3. MVVM 模式下的数据交互

3.1 WPF 中的 MVVM 数据绑定

ViewModel 基类

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

产品管理 ViewModel

public class ProductViewModel : ViewModelBase
{
    private ObservableCollection<Product> _products;
    private Product _selectedProduct;
    private string _searchText;

    public ProductViewModel()
    {
        Products = new ObservableCollection<Product>();
        LoadProductsCommand = new RelayCommand(async () => await LoadProducts());
        AddProductCommand = new RelayCommand(AddProduct);
        DeleteProductCommand = new RelayCommand(DeleteProduct, () => SelectedProduct != null);
        SearchCommand = new RelayCommand(SearchProducts);
        
        LoadProducts();
    }

    public ObservableCollection<Product> Products
    {
        get => _products;
        set => SetProperty(ref _products, value);
    }

    public Product SelectedProduct
    {
        get => _selectedProduct;
        set
        {
            SetProperty(ref _selectedProduct, value);
            DeleteProductCommand.RaiseCanExecuteChanged();
        }
    }

    public string SearchText
    {
        get => _searchText;
        set => SetProperty(ref _searchText, value);
    }

    public ICommand LoadProductsCommand { get; }
    public ICommand AddProductCommand { get; }
    public ICommand DeleteProductCommand { get; }
    public ICommand SearchCommand { get; }

    private async Task LoadProducts()
    {
        try
        {
            var products = await _productService.GetAllAsync();
            Products.Clear();
            foreach (var product in products)
            {
                Products.Add(product);
            }
        }
        catch (Exception ex)
        {
            // 处理错误
            MessageBox.Show($"加载产品失败: {ex.Message}");
        }
    }

    private void AddProduct()
    {
        var newProduct = new Product
        {
            Name = "新产品",
            Price = 0,
            Category = "未分类"
        };
        Products.Add(newProduct);
        SelectedProduct = newProduct;
    }

    private async void DeleteProduct()
    {
        if (SelectedProduct == null) return;

        var result = MessageBox.Show(
            $"确定要删除产品 '{SelectedProduct.Name}' 吗?",
            "确认删除",
            MessageBoxButton.YesNo);

        if (result == MessageBoxResult.Yes)
        {
            try
            {
                await _productService.DeleteAsync(SelectedProduct.ID);
                Products.Remove(SelectedProduct);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"删除失败: {ex.Message}");
            }
        }
    }

    private void SearchProducts()
    {
        // 实现搜索逻辑
        if (string.IsNullOrWhiteSpace(SearchText))
        {
            LoadProducts();
            return;
        }

        var filteredProducts = Products
            .Where(p => p.Name.Contains(SearchText, StringComparison.OrdinalIgnoreCase))
            .ToList();

        Products.Clear();
        foreach (var product in filteredProducts)
        {
            Products.Add(product);
        }
    }
}

WPF 视图绑定

<Window x:Class="TelerikApp.Views.ProductWindow"
        xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
        DataContext="{Binding ProductViewModel, Source={StaticResource ViewModelLocator}}">
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- 搜索栏 -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="10">
            <TextBox Text="{Binding SearchText, UpdateSourceTrigger=PropertyChanged}" 
                     Width="200" Height="30" Margin="0,0,10,0"/>
            <telerik:RadButton Content="搜索" 
                             Command="{Binding SearchCommand}"
                             Width="80" Height="30"/>
        </StackPanel>

        <!-- 产品列表 -->
        <telerik:RadDataGrid Grid.Row="1" 
                           ItemsSource="{Binding Products}"
                           SelectedItem="{Binding SelectedProduct}"
                           AutoGenerateColumns="False"
                           Margin="10">
            <telerik:RadDataGrid.Columns>
                <telerik:DataGridTextColumn PropertyName="ID" 
                                          Header="ID" Width="80"/>
                <telerik:DataGridTextColumn PropertyName="Name" 
                                          Header="产品名称" Width="*"/>
                <telerik:DataGridTextColumn PropertyName="Price" 
                                          Header="价格" 
                                          CellContentFormat="{}{0:C}"/>
                <telerik:DataGridTextColumn PropertyName="Category" 
                                          Header="分类"/>
            </telerik:RadDataGrid.Columns>
        </telerik:RadDataGrid>

        <!-- 操作按钮 -->
        <StackPanel Grid.Row="2" Orientation="Horizontal" 
                    HorizontalAlignment="Right" Margin="10">
            <telerik:RadButton Content="刷新" 
                             Command="{Binding LoadProductsCommand}"
                             Width="80" Height="35" Margin="0,0,10,0"/>
            <telerik:RadButton Content="添加" 
                             Command="{Binding AddProductCommand}"
                             Width="80" Height="35" Margin="0,0,10,0"/>
            <telerik:RadButton Content="删除" 
                             Command="{Binding DeleteProductCommand}"
                             Width="80" Height="35"/>
        </StackPanel>
    </Grid>
</Window>

3.2 MVC 模式下的数据交互

控制器数据处理

public class ProductController : Controller
{
    private readonly IProductService _productService;
    private readonly IMapper _mapper;

    public ProductController(IProductService productService, IMapper mapper)
    {
        _productService = productService;
        _mapper = mapper;
    }

    public async Task<IActionResult> Index()
    {
        var products = await _productService.GetAllAsync();
        var viewModel = new ProductIndexViewModel
        {
            Products = products,
            Categories = await _productService.GetCategoriesAsync()
        };
        return View(viewModel);
    }

    [HttpPost]
    public async Task<IActionResult> GetProducts([DataSourceRequest] DataSourceRequest request)
    {
        try
        {
            var products = await _productService.GetAllAsync();
            var result = products.ToDataSourceResult(request);
            return Json(result);
        }
        catch (Exception ex)
        {
            return Json(new DataSourceResult 
            { 
                Errors = ex.Message 
            });
        }
    }

    [HttpPost]
    public async Task<IActionResult> CreateProduct([DataSourceRequest] DataSourceRequest request, 
                                                 ProductCreateDto dto)
    {
        if (ModelState.IsValid)
        {
            try
            {
                var product = _mapper.Map<Product>(dto);
                await _productService.CreateAsync(product);
                
                var resultDto = _mapper.Map<ProductDto>(product);
                return Json(new[] { resultDto }.ToDataSourceResult(request, ModelState));
            }
            catch (Exception ex)
            {
                ModelState.AddModelError("", ex.Message);
            }
        }

        return Json(new[] { dto }.ToDataSourceResult(request, ModelState));
    }
}

ViewModel 设计

public class ProductIndexViewModel
{
    public IEnumerable<Product> Products { get; set; }
    public IEnumerable<Category> Categories { get; set; }
    public ProductFilterModel Filter { get; set; } = new();
}

public class ProductFilterModel
{
    public string Name { get; set; }
    public string Category { get; set; }
    public decimal? MinPrice { get; set; }
    public decimal? MaxPrice { get; set; }
    public DateTime? StartDate { get; set; }
    public DateTime? EndDate { get; set; }
}

public class ProductCreateDto
{
    [Required(ErrorMessage = "产品名称不能为空")]
    public string Name { get; set; }

    [Required(ErrorMessage = "价格不能为空")]
    [Range(0.01, double.MaxValue, ErrorMessage = "价格必须大于0")]
    public decimal Price { get; set; }

    [Required(ErrorMessage = "分类不能为空")]
    public string Category { get; set; }

    public string Description { get; set; }
}

4. 综合实例:完整的产品管理系统

4.1 数据服务层

public interface IProductService
{
    Task<IEnumerable<Product>> GetAllAsync();
    Task<Product> GetByIdAsync(int id);
    Task<Product> CreateAsync(Product product);
    Task<Product> UpdateAsync(Product product);
    Task<bool> DeleteAsync(int id);
    Task<IEnumerable<Category>> GetCategoriesAsync();
}

public class ProductService : IProductService
{
    private readonly ApplicationDbContext _context;

    public ProductService(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<Product>> GetAllAsync()
    {
        return await _context.Products
            .Include(p => p.Category)
            .OrderBy(p => p.Name)
            .ToListAsync();
    }

    public async Task<Product> GetByIdAsync(int id)
    {
        return await _context.Products
            .Include(p => p.Category)
            .FirstOrDefaultAsync(p => p.ID == id);
    }

    public async Task<Product> CreateAsync(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
        return product;
    }

    public async Task<Product> UpdateAsync(Product product)
    {
        _context.Entry(product).State = EntityState.Modified;
        await _context.SaveChangesAsync();
        return product;
    }

    public async Task<bool> DeleteAsync(int id)
    {
        var product = await GetByIdAsync(id);
        if (product == null) return false;

        _context.Products.Remove(product);
        await _context.SaveChangesAsync();
        return true;
    }

    public async Task<IEnumerable<Category>> GetCategoriesAsync()
    {
        return await _context.Categories.OrderBy(c => c.Name).ToListAsync();
    }
}

4.2 完整的管理界面

@model ProductIndexViewModel

<div class="container-fluid">
    <div class="row">
        <div class="col-md-3">
            <h4>产品筛选</h4>
            <div class="card">
                <div class="card-body">
                    @using (Html.BeginForm("Index", "Product", FormMethod.Get))
                    {
                        <div class="mb-3">
                            <label>产品名称:</label>
                            @Html.Kendo().TextBoxFor(m => m.Filter.Name)
                                .Placeholder("输入产品名称")
                                .HtmlAttributes(new { @class = "form-control" })
                        </div>

                        <div class="mb-3">
                            <label>分类:</label>
                            @Html.Kendo().DropDownListFor(m => m.Filter.Category)
                                .DataTextField("Name")
                                .DataValueField("Name")
                                .BindTo(Model.Categories)
                                .OptionLabel("全部分类")
                                .HtmlAttributes(new { @class = "form-control" })
                        </div>

                        <div class="mb-3">
                            <label>价格范围:</label>
                            <div class="row">
                                <div class="col-6">
                                    @Html.Kendo().NumericTextBoxFor(m => m.Filter.MinPrice)
                                        .Format("c2")
                                        .Placeholder("最低价格")
                                </div>
                                <div class="col-6">
                                    @Html.Kendo().NumericTextBoxFor(m => m.Filter.MaxPrice)
                                        .Format("c2")
                                        .Placeholder("最高价格")
                                </div>
                            </div>
                        </div>

                        <button type="submit" class="btn btn-primary">筛选</button>
                        <a href="@Url.Action("Index")" class="btn btn-secondary">重置</a>
                    }
                </div>
            </div>
        </div>

        <div class="col-md-9">
            <h4>产品管理</h4>
            @(Html.Kendo().Grid<Product>()
                .Name("productGrid")
                .Columns(columns =>
                {
                    columns.Bound(p => p.ID).Width(80);
                    columns.Bound(p => p.Name).Title("产品名称");
                    columns.Bound(p => p.Price).Title("价格").Format("{0:C}").Width(120);
                    columns.Bound(p => p.Category).Title("分类").Width(120);
                    columns.Bound(p => p.CreatedDate).Title("创建时间")
                           .Format("{0:yyyy-MM-dd}").Width(120);
                    columns.Command(command =>
                    {
                        command.Edit().Text("编辑");
                        command.Destroy().Text("删除");
                    }).Width(160);
                })
                .ToolBar(toolbar => toolbar.Create().Text("新增产品"))
                .Editable(editable => editable.Mode(GridEditMode.PopUp)
                    .TemplateName("ProductEdit"))
                .DataSource(dataSource => dataSource
                    .Ajax()
                    .PageSize(20)
                    .Model(model =>
                    {
                        model.Id(p => p.ID);
                        model.Field(p => p.ID).Editable(false);
                        model.Field(p => p.CreatedDate).Editable(false);
                    })
                    .Create(create => create.Action("CreateProduct", "Product"))
                    .Read(read => read.Action("GetProducts", "Product"))
                    .Update(update => update.Action("UpdateProduct", "Product"))
                    .Destroy(destroy => destroy.Action("DeleteProduct", "Product"))
                    .Events(events => events.Error("onDataSourceError"))
                )
                .Pageable(pageable => pageable
                    .Refresh(true)
                    .PageSizes(true)
                    .ButtonCount(5))
                .Sortable()
                .Filterable()
                .Resizable(resize => resize.Columns(true))
            )
        </div>
    </div>
</div>

<script>
function onDataSourceError(e) {
    if (e.errors) {
        var message = "数据操作失败:\n";
        for (var error in e.errors) {
            message += error + "\n";
        }
        alert(message);
    }
}
</script>

总结

本教程深入介绍了Telerik UI的数据绑定与数据源管理:

  1. 数据绑定方式

    • 集合绑定:静态和动态数据集合
    • 数据库绑定:Entity Framework集成
    • API绑定:RESTful服务集成
  2. 架构模式支持

    • MVVM模式:完整的双向绑定支持
    • MVC模式:控制器和视图的数据交互
  3. 实际应用:通过完整的产品管理系统展示了数据绑定的最佳实践

掌握这些数据绑定技术,能够帮助您构建高效、可维护的数据驱动应用程序。

Progress Telerik Ultimate Collection 2025 Q2下载地址 https://soft51.cc/software/175792580241152290

相关留言评论
昵称:
邮箱:
阅读排行