下面先定义模型,尤其是Book类型。这个类型在UI中显示和编辑。为了支持数据绑定,在用户界面中更新的属性值需要实现变更通知。BookId属性只是显示,而不改变,所以变更通知不需要使用这个属性。SetProperty方法由基类BindableBase定义:
public class Book:BindableBase
{
public int BookId { get; set; }
private string _title;
public string Title
{
get => _title;
set => SetProperty(ref _title,value);
}
private string _publisher;
public string Publisher
{
get => _publisher;
set => SetProperty(ref _publisher,value);
}
public override string ToString() => Title;
}
1. 实现变更通知
XAML元素的对象源需要依赖属性或INotifyPropertyChanged,数据绑定时,才允许更改通知更新UI。有了模型类型,才能使用INotifyPropertyChanged,为了用于不同的项目,实现代码在Framewrok库项目的BindableBase内完成。INotifyPropertyChanged接口定义了PropertyChanged事件。为了触发更改通知,SetProperty方法实现为一个泛型函数,以支持任何属性类型。在触发通知之前,检查新值是否与当前值不同:
public class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this,new PropertyChangedEventArgs(propertyName));
}
protected bool SetProperty<T>(ref T item,T value,[CallerMemberName]string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(item,value))
{
return false;
}
item = value;
OnPropertyChanged(propertyName);
return true;
}
}
2. 使用Repository模式
接下来,需要一种方法来检索、更新和删除Book对象。使用EF Core可以在数据库中读写图书。虽然EF Core可以在通用Windows平台上访问,但通常这是一个后台任务,因此本章未涉及。为了使后端服务可以在客户端应用程序中访问,在服务器端选择Web API技术。在客户端应用程序中,最好能独立于数据存储。为此,定义Repository设计模式。Repository模式是模型和数据访问层之间的中介,它可以作为对象的内存集合。它抽象出了数据访问层,使单元测试方便。
泛型接口IQueryRepository定义的方法通过ID获取一项,或获取一个条目列表:
public interface IQueryRepository<T,in TKey> where T:class
{
Task<T> GetItemAsync(TKey id);
Task<IEnumerable<T>> GetItemsAsync();
}
泛型接口IUpdateRepository定义方法来添加、更新和删除条目:
public interface IUpdateRepository<T,in TKey> where T:class
{
Task<T> AddAsync(T item);
Task<T> UpdateAsync(T item);
Task<bool> DeleteAsync(TKey id);
}
IBooksRepository接口为泛型类型T定义Book类型,使前两个泛型接口更具体:
public interface IBooksRepository:IQueryRepository<Book,int>,
IUpdateRepository<Book,int>
{
}
使用这些接口,可以改变存储库。创建一个示例库SampleBooksRepository,它实现接口IBooksRepository,包含一个图书的初始列表:
public class BooksSampleRepository : IBooksRepository
{
private List<Book> _books;
public BooksSampleRepository() => InitSampleBooks();
private void InitSampleBooks()
{
_books = new List<Book>
{
new Book{ BookId = 1,Title="Professional C# 7 and .NET Core 2",
Publisher="Wrox Pres"},
new Book{BookId = 2,Title="Professional C# 6 and .NET Core 1.0",
Publisher="Wrox Pres"},
new Book{ BookId = 3,Title="Professional C# 5.0 and .NET Core 4.5.1",
Publisher="Wrox Pres"},
new Book{ BookId = 4,Title="Enterprise Services with the .NET Framework",
Publisher="AWL"},
};
}
public Task<Book> AddAsync(Book item)
{
item.BookId = _books.Select(b => b.BookId).Max() + 1;
_books.Add(item);
return Task.FromResult(item);
}
public Task<bool> DeleteAsync(int id)
{
//var bookToDelete = _books.SingleOrDefault(b => b.BookId == id);
var bookToDelete = _books.Find(b => b.BookId == id);
if (bookToDelete != null)
{
return Task.FromResult<bool>(_books.Remove(bookToDelete));
}
return Task.FromResult<bool>(false);
}
public Task<Book> GetItemAsync(int id) =>
Task.FromResult(_books.Find(b => b.BookId == id));
public Task<IEnumerable<Book>> GetItemsAsync() =>
Task.FromResult<IEnumerable<Book>>(_books);
public Task<Book> UpdateAsync(Book item)
{
var bookToUpdate = _books.Find(b => b.BookId == item.BookId);
int ix = _books.IndexOf(bookToUpdate);
_books[ix] = item;
return Task.FromResult(item);
}
}
注意:
存储库定义了异步方法,但这里不需要它们,因为书的检索和更新只在内存中进行。方法定义为异步,是因为用于访问ASP.NET Core API的存储库在本质上是异步的。