Hướng dẫn triển khai ReactiveUI cho ứng dụng WPF

Cài đặt gói thư viện

Để tích hợp ReactiveUI vào dự án, cần cài đặt các gói NuGet phù hợp với nền tảng mục tiêu. Đối với ứng dụng WPF, hãy đảm bảo cài đặt cả gói cốt lõi và gói hỗ trợ riêng cho WPF:

Install-Package ReactiveUI
Install-Package ReactiveUI.WPF

Xây dựng ViewModel

Các lớp ViewModel trong ReactiveUI cần kế thừa từ ReactiveObject để hỗ trợ thông báo thay đổi thuộc tính tự động.

public class UserSessionViewModel : ReactiveObject 
{ 
}

Thuộc tính đọc ghi

Sử dụng phương thức RaiseAndSetIfChanged để cập nhật giá trị và thông báo thay đổi:

private string _emailAddress;
public string EmailAddress 
{
    get => _emailAddress;
    set => this.RaiseAndSetIfChanged(ref _emailAddress, value);
}

Thuộc tính chỉ đọc

Đối với các thuộc tính dẫn xuất từ observable, sử dụng ObservableAsPropertyHelper:

private readonly ObservableAsPropertyHelper<string> _domainName;
public string DomainName => _domainName.Value;

// Trích xuất tên miền khi EmailAddress thay đổi
this.WhenAnyValue(x => x.EmailAddress)
    .Where(x => !string.IsNullOrEmpty(x) && x.Contains("@"))
    .Select(x => x.Split('@')[1])
    .ToProperty(this, x => x.DomainName, out _domainName);

Tối ưu hóa với Fody

Khi sử dụng thêm gói ReactiveUI.Fody, code có thể được viết gọn hơn nhờ weaving tự động:

[Reactive]
public string EmailAddress { get; set; }

public string DomainName { [ObservableAsProperty] get; }

this.WhenAnyValue(x => x.EmailAddress)
    .Where(x => !string.IsNullOrEmpty(x))
    .Select(x => x.Contains("@") ? x.Split('@')[1] : string.Empty)
    .ToPropertyEx(this, x => x.DomainName);

Xử lý lệnh Command

ReactiveUI cung cấp nhiều phương thức tĩnh để tạo ReactiveCommand tùy theo nhu cầu đồng bộ hay bất đồng bộ:

  • CreateFromObservable()
  • CreateFromTask()
  • Create()
  • CreateCombined()

Lệnh đồng bộ

ReactiveCommand<string, Unit> logCommand = ReactiveCommand.Create<string>(
    message => System.Diagnostics.Debug.WriteLine(message));
logCommand.Execute("Debug Info").Subscribe();

Lệnh bất đồng bộ

var fetchCommand = ReactiveCommand.CreateFromTask<Unit, int>(
    async _ => 
    {
        await Task.Delay(TimeSpan.FromSeconds(2));
        return 100;
    });
fetchCommand.Execute(Unit.Default).Subscribe();
fetchCommand.Subscribe(result => System.Diagnostics.Debug.WriteLine(result));

Điều kiện thực thi

Có thể định nghĩa điều kiện canExecute dựa trên trạng thái của các thuộc tính khác:

var canLogin = this.WhenAnyValue(
    x => x.EmailAddress, x => x.Password,
    (email, pass) => 
        !string.IsNullOrEmpty(email) && 
        pass.Length >= 6);
        
var loginCommand = ReactiveCommand.CreateFromTask(PerformLoginAsync, canLogin);

Liên kết giao diện

Trong code-behind của View (Window, Page, UserControl), khởi tạo ViewModel và gán vào DataContext:

<Window x:Class="ReactiveDemo.ShellView"
        ...>
<!-- Sử dụng binding XAML truyền thống -->
</Window>
public partial class ShellView : Window
{
    public ShellView()
    {
        InitializeComponent();
        DataContext = new UserSessionViewModel();
    }
}

Quản lý tập dữ liệu động

Để xử lý danh sách dữ liệu một cách reactive, nên sử dụng thư viện DynamicData đi kèm. Các loại集合 phổ biến là SourceListSourceCache.

// Danh sách nguồn nội bộ
SourceList<int> _sourceNumbers = new SourceList<int>();
// Collection chỉ đọc để binding
ReadOnlyObservableCollection<int> _filteredItems;
public ReadOnlyObservableCollection<int> FilteredItems => _filteredItems;

// Thao tác trên danh sách
_sourceNumbers.Add(10);
_sourceNumbers.Add(15);
_sourceNumbers.Remove(10);

// Biến đổi và binding
_sourceNumbers.Connect()
    .Transform(x => x * 2)
    .Filter(x => x % 3 == 0)
    .ObserveOn(RxApp.MainThreadScheduler)
    .Bind(out _filteredItems)
    .Subscribe();

Xác thực dữ liệu đầu vào

Chức năng validation yêu cầu cài đặt thêm gói ReactiveUI.Validation. ViewModel cần implement IValidatableViewModel hoặc kế thừa ReactiveValidationObject.

ReactiveValidationObject đã tích hợp sẵn INotifyDataErrorInfo giúp tương thích với cơ chế validation của WPF.

public class LoginViewModel : ReactiveObject, IValidatableViewModel
{    
    public ValidationContext ValidationContext { get; } = new ValidationContext();
    [Reactive] public string Email { get; set; }
    [Reactive] public string Password { get; set; }
    public ReactiveCommand<Unit, Unit> Submit { get; }

    public LoginViewModel()
    {
        this.ValidationRule(
            viewModel => viewModel.Email,
            email => !string.IsNullOrWhiteSpace(email) && email.Contains("@"),
            "Email format is invalid");
            
        var isValid = this.IsValid();
        Submit = ReactiveCommand.CreateFromTask(async unit => { }, isValid);
    }
}

Cách viết ngắn gọn hơn khi kế thừa ReactiveValidationObject:

public class LoginViewModel : ReactiveValidationObject<LoginViewModel>
{
    [Reactive]
    public string Email { get; set; } = string.Empty;

    public LoginViewModel()
    {
        this.ValidationRule(
            x => x.Email, 
            email => !string.IsNullOrWhiteSpace(email),
            "Email field is required.");
    }
}

Ghi nhật ký hệ thống

Để ghi log, cần tích hợp thêm các gói như Serilog. Các gói cần thiết bao gồm:

  • Serilog
  • Splat.Serilog
  • Serilog.Sinks.File

Cấu hình Logger trong quá trình khởi động ứng dụng:

using Serilog;
using Splat;
using Splat.Serilog;
using System.Windows;

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        Log.Logger = new LoggerConfiguration()
            .WriteTo.File("app-log-.txt", rollingInterval: RollingInterval.Day)
            .CreateLogger();
            
        Locator.CurrentMutable.UseSerilogFullLogger();
    }
}

Sử dụng Logger trong các class implements IEnableLogger:

using Splat;
using System.Windows;

public partial class ShellView : Window, IEnableLogger
{
    public ShellView()
    {
        InitializeComponent();
        this.Log().Info("ShellView loaded successfully.");
    }
}
2023-10-27 09:15:22.123 +00:00 [INF] ShellView loaded successfully.

Thẻ: reactiveui WPF mvvm dynamicdata serilog

Đăng vào ngày 12 tháng 7 lúc 22:36