目录
概述——Blazor EditFormState控件
这是描述一组有用的Blazor Edit控件的系列文章中的第一篇,这些控件解决了开箱即用编辑体验中的一些当前缺点,而无需购买昂贵的工具包。

代码和示例
该存储库包含一个项目,该项目实现了本系列中所有文章的控件。你可以在这里找到它。
示例站点位于https://cec-blazor-database.azurewebsites.net/。
您可以在https://cec-blazor-database.azurewebsites.net//testeditor 中查看稍后描述的测试表单。
Repo是未来文章的一项正在进行的工作,因此会发生变化和发展。
Blazor编辑设置
首先,让我们看看当前的表单控件以及它们如何协同工作。一个经典的形式看起来像这样:
EditForm
EditForm是整体包装。它:
- 创建html Form上下文。
- 连接任何Submit按钮——即,在表单内将type设置为submit的按钮。
- 创建/管理EditContext.
- 级联EditContext. EditForm中的所有控件将被捕获并以一种或另一种方式使用它。
- 为提交过程的父控件提供回调委托——OnSubmit、OnValidSubmit和OnInvalidSubmit。
EditContext
EditContext是编辑过程核心的类,提供整体管理。它操作的数据类是model: 定义为object类型。它可以是任何对象,但实际上是某种类型的数据类。唯一的先决条件是表单中使用的字段被声明为public读/写属性。
该EditContext可以是:
- 直接作为EditContext参数传递给EditForm,
- 或者模型的对象实例被设置为Model参数并从EditForm中创建一个EditContext实例。
要记住的重要一点是,一旦创建了另一个对象,就不要更改它的EditContext模型。虽然有可能,但不建议这样做。如果模型需要改变,代码刷新整个表单:会更安全!
FieldIdentifier
FieldIdentifier类代表一个模型属性的部分“系列化”。EditContext通过他们的FieldIdentifier踪迹和识别单个属性。Model是拥有该属性的对象,FieldName是通过反射得到的属性名。
输入控件
InputText和InputNumber和其他InputBase控件捕获级联EditContext.。通过使用他们的FieldIdentifier调用NotifyFieldChanged,任何值的变动被向上推至EditContext。
重新审视EditContext
在内部EditContext维护一个FieldIdentifier列表。FieldIdentifier对象在各种方法和事件中传递以识别特定字段。调用NotifyFieldChanged将FieldIdentifier对象添加到列表中。每当调用NotifyFieldChanged时EditContext触发OnFieldChanged。
IsModified提供对列表或个人FieldIdentifier状态的访问。MarkAsUnmodified重置集合中的单个FieldIdentifier或全部FieldIdentifiers。
EditContext还包含管理验证的功能,但实际上并没有这样做。我们将在下一篇文章中介绍验证过程。
EditFormState 控件
该EditFormState控件与所有编辑表单控件一样,捕获级联的EditState 。它的作用是:
- 构建由Model公开的public属性列表并维护每个属性的编辑状态——原始值与编辑值的相等性检查。
- 在字段值的每次更改时更新状态。
- 通过readonly属性公开状态。
- 提供在编辑状态更新时触发的EventCallback委托。
在我们查看控件之前,让我们看一下模型——在我们的例子中,WeatherForecast——以及一些支持类。
WeatherForecast
WeatherForecast 是典型的数据类。
- 每个字段都声明为具有默认值的属性。
- Validate实现IValidation。暂时忽略这一点,我们将在下一篇文章中查看验证。我已经按照你在Repo代码中看到的方式展示了它。
public class WeatherForecast : IValidation { public int ID { get; set; } = -1; public DateTime Date { get; set; } = DateTime.Now; public int TemperatureC { get; set; } = 0; [NotMapped] public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); public string Summary { get; set; } = string.Empty; /// Ignore for now, but as you'll see it in the example repo it's shown public bool Validate(ValidationMessageStore validationMessageStore, string fieldname, object model = null) { .... } }
EditField
EditField 是我们从模型中“序列化”属性的类。
- 基本字段是记录——它们只能在初始化时设置。
- EditedValue 携带该字段的当前值。
- IsDirty测试Value和EditedValue之间的相等性。
public class EditField { public string FieldName { get; init; } public Guid GUID { get; init; } public object Value { get; init; } public object Model { get; init; } public object EditedValue { get; set; } public bool IsDirty { get { if (Value != null && EditedValue != null) return !Value.Equals(EditedValue); if (Value is null && EditedValue is null) return false; return true; } } public EditField(object model, string fieldName, object value) { this.Model = model; this.FieldName = fieldName; this.Value = value; this.EditedValue = value; this.GUID = Guid.NewGuid(); } public void Reset() => this.EditedValue = this.Value; }
EditFieldCollection
EditFieldCollection是EditField的IEnumerable集合。该类为集合提供了一组受控的setter和getter,并为IEnumerable接口实现了必要的方法。它还提供了一个IsDirty属性来公开集合的状态。
public class EditFieldCollection : IEnumerable { private List
_items = new List
(); public int Count => _items.Count; public Action
FieldValueChanged; public bool IsDirty => _items.Any(item => item.IsDirty); public void Clear() => _items.Clear(); public void ResetValues() => _items.ForEach(item => item.Reset()); public IEnumerator GetEnumerator() => new EditFieldCollectionEnumerator(_items); public T Get
(string FieldName) { var x = _items.FirstOrDefault(item => item.FieldName.Equals (FieldName, StringComparison.CurrentCultureIgnoreCase)); if (x != null && x.Value is T t) return t; return default; } public T GetEditValue
(string FieldName) { var x = _items.FirstOrDefault(item => item.FieldName.Equals (FieldName, StringComparison.CurrentCultureIgnoreCase)); if (x != null && x.EditedValue is T t) return t; return default; } public bool TryGet
(string FieldName, out T value) { value = default; var x = _items.FirstOrDefault(item => item.FieldName.Equals (FieldName, StringComparison.CurrentCultureIgnoreCase)); if (x != null && x.Value is T t) value = t; return x.Value != default; } public bool TryGetEditValue
(string FieldName, out T value) { value = default; var x = _items.FirstOrDefault(item => item.FieldName.Equals (FieldName, StringComparison.CurrentCultureIgnoreCase)); if (x != null && x.EditedValue is T t) value = t; return x.EditedValue != default; } public bool HasField(EditField field) => this.HasField(field.FieldName); public bool HasField(string FieldName) { var x = _items.FirstOrDefault(item => item.FieldName.Equals (FieldName, StringComparison.CurrentCultureIgnoreCase)); if (x is null | x == default) return false; return true; } public bool SetField(string FieldName, object value) { var x = _items.FirstOrDefault(item => item.FieldName.Equals (FieldName, StringComparison.CurrentCultureIgnoreCase)); if (x != null && x != default) { x.EditedValue = value; this.FieldValueChanged?.Invoke(this.IsDirty); return true; } return false; } public bool AddField(object model, string fieldName, object value) { this._items.Add(new EditField(model, fieldName, value)); return true; }
该Enumerator支持类。
public class EditFieldCollectionEnumerator : IEnumerator { private List
_items = new List
(); private int _cursor; object IEnumerator.Current { get { if ((_cursor < 0) || (_cursor == _items.Count)) throw new InvalidOperationException(); return _items[_cursor]; } } public EditFieldCollectionEnumerator(List
items) { this._items = items; _cursor = -1; } void IEnumerator.Reset() => _cursor = -1; bool IEnumerator.MoveNext() { if (_cursor < _items.Count) _cursor++; return (!(_cursor == _items.Count)); } } }
现在我们已经看到了支持类,转到主控件。
EditFormState
EditFormState被声明为一个组件并实现IDisposable。
public class EditFormState : ComponentBase, IDisposable
属性是:
- EditContext从级联中拿起。
- 向EditStateChanged父控件提供回调以告诉它编辑状态已更改。
- 为控件提供只读IsDirty属性,使用@ref检查控件状态。
- EditFields是我们填充并用于管理编辑状态的内部EditFieldCollection。
- disposedValue是IDisposable实现的一部分。
/// EditContext - cascaded from EditForm [CascadingParameter] public EditContext EditContext { get; set; } /// EventCallback for parent to link into for Edit State Change Events /// passes the current Dirty state [Parameter] public EventCallback
EditStateChanged { get; set; } /// Property to expose the Edit/Dirty state of the control public bool IsDirty => EditFields?.IsDirty ?? false; private EditFieldCollection EditFields = new EditFieldCollection(); private bool disposedValue;
当组件初始化时,它会捕获Model属性并填充EditFields初始数据。最后一步是连接EditContext.OnFieldChanged到FieldChanged,因此每当字段值更改时都会调用FieldChanged。
protected override Task OnInitializedAsync() { Debug.Assert(this.EditContext != null); if (this.EditContext != null) { // Populates the EditField Collection this.GetEditFields(); // Wires up to the EditContext OnFieldChanged event this.EditContext.OnFieldChanged += FieldChanged; } return Task.CompletedTask; } /// Method to populate the edit field collection protected void GetEditFields() { // Gets the model from the EditContext and populates the EditFieldCollection this.EditFields.Clear(); var model = this.EditContext.Model; var props = model.GetType().GetProperties(); foreach (var prop in props) { var value = prop.GetValue(model); EditFields.AddField(model, prop.Name, value); } }
该FieldChanged事件处理程序中从EditFields中查找EditFiel并通过调用SetField设置它的EditedValue。然后使用当前的dirty状态触发EditStateChanged回调。
/// Event Handler for Editcontext.OnFieldChanged private void FieldChanged(object sender, FieldChangedEventArgs e) { // Get the PropertyInfo object for the model property // Uses reflection to get property and value var prop = e.FieldIdentifier.Model.GetType().GetProperty(e.FieldIdentifier.FieldName); if (prop != null) { // Get the value for the property var value = prop.GetValue(e.FieldIdentifier.Model); // Sets the edit value in the EditField EditFields.SetField(e.FieldIdentifier.FieldName, value); // Invokes EditStateChanged this.EditStateChanged.InvokeAsync(EditFields?.IsDirty ?? false); } }
最后,我们有一些实用方法和IDisposable实现。
/// Method to Update the Edit State to current values public void UpdateState() { this.GetEditFields(); this.EditStateChanged.InvokeAsync(EditFields?.IsDirty ?? false); } // IDisposable Implementation protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { if (this.EditContext != null) this.EditContext.OnFieldChanged -= this.FieldChanged; } disposedValue = true; } } public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } }
一个简单的实现
为了测试组件,这里有一个简单的测试页面。

上下更改温度,您应该会看到状态按钮更改颜色和文本。
您可以在https://cec-blazor-database.azurewebsites.net/editstateeditor 上查看此示例。
@using Blazor.Database.Data @page "/test"
@code { protected bool _isDirty = false; protected string btncolour => _isDirty ? "btn-danger" : "btn-success"; protected string btntext => _isDirty ? "Dirty" : "Clean"; protected EditFormState editFormState { get; set; } private WeatherForecast Model = new WeatherForecast() { ID = 1, Date = DateTime.Now, TemperatureC = 22, Summary = "Balmy" }; private void HandleValidSubmit() { this.editFormState.UpdateState(); } private void EditStateChanged(bool editstate) => this._isDirty = editstate; }
总结
如果您之前没有实现过此类功能,则此控件的真正优势可能不会立即显现出来,但我们将在后续文章中使用它来构建编辑器表单。在接下来的文章看起来在验证过程中,如何构建一个简单的自定义验证。第三篇文章着眼于表单锁定,使用此控件作为流程的一部分。
如果您在以后发现这篇文章很好,最新版本将在此处提供。
https://www.codeproject.com/Articles//A-Blazor-Edit-Form-State-Control
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/229611.html原文链接:https://javaforall.net
