Blazor编辑表单状态控件

目录

概述——Blazor EditFormState控件

代码和示例

Blazor编辑设置

EditForm

EditContext

FieldIdentifier

输入控件

重新审视EditContext

EditFormState 控件

WeatherForecast

EditField

EditFieldCollection

EditFormState

一个简单的实现

总结


概述——Blazor EditFormState控件

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

代码和示例

该存储库包含一个项目,该项目实现了本系列中所有文章的控件。你可以在这里找到它。

示例站点位于https://cec-blazor-database.azurewebsites.net/

您可以在https://cec-blazor-database.azurewebsites.net//testeditor 查看稍后描述的测试表单。

Repo是未来文章的一项正在进行的工作,因此会发生变化和发展。

Blazor编辑设置

首先,让我们看看当前的表单控件以及它们如何协同工作。一个经典的形式看起来像这样:

<EditForm Model="@exampleModel" OnValidSubmit="@HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <InputText id="name" @bind-Value="exampleModel.Name" />
    <ValidationMessage For="@(() => exampleModel.Name)" />

    <button type="submit">Submit</button>
</EditForm>

EditForm

EditForm是整体包装。它:

  1. 创建html Form上下文。
  2. 连接任何Submit按钮——即,在表单内将type设置为submit的按钮。
  3. 创建/管理EditContext.
  4. 级联EditContextEditForm中的所有控件将被捕获并以一种或另一种方式使用它。
  5. 为提交过程的父控件提供回调委托——OnSubmitOnValidSubmitOnInvalidSubmit

EditContext

EditContext是编辑过程核心的类,提供整体管理。它操作的数据类是model: 定义为object类型。它可以是任何对象,但实际上是某种类型的数据类。唯一的先决条件是表单中使用的字段被声明为public/写属性。

EditContext可以是:

  • 直接作为EditContext参数传递给EditForm
  • 或者模型的对象实例被设置为Model参数并从EditForm中创建一个EditContext实例。

要记住的重要一点是,一旦创建了另一个对象,就不要更改它的EditContext模型。虽然有可能,但不建议这样做。如果模型需要改变,代码刷新整个表单:会更安全!

FieldIdentifier

FieldIdentifier类代表一个模型属性的部分系列化EditContext通过他们的FieldIdentifier踪迹和识别单个属性。Model是拥有该属性的对象,FieldName是通过反射得到的属性名。

输入控件

InputTextInputNumber和其他InputBase控件捕获级联EditContext.。通过使用他们的FieldIdentifier调用NotifyFieldChanged,任何值的变动被向上推至EditContext

重新审视EditContext

在内部EditContext维护一个FieldIdentifier列表。FieldIdentifier对象在各种方法和事件中传递以识别特定字段。调用NotifyFieldChangedFieldIdentifier对象添加到列表中。每当调用NotifyFieldChangedEditContext触发OnFieldChanged

IsModified提供对列表或个人FieldIdentifier状态的访问。MarkAsUnmodified重置集合中的单个FieldIdentifier或全部FieldIdentifiers

EditContext还包含管理验证的功能,但实际上并没有这样做。我们将在下一篇文章中介绍验证过程。

EditFormState 控件

EditFormState控件与所有编辑表单控件一样,捕获级联的EditState 。它的作用是:

  1. 构建由Model公开的public属性列表并维护每个属性的编辑状态——原始值与编辑值的相等性检查。
  2. 在字段值的每次更改时更新状态。
  3. 通过readonly属性公开状态。
  4. 提供在编辑状态更新时触发的EventCallback委托。

在我们查看控件之前,让我们看一下模型——在我们的例子中,WeatherForecast——以及一些支持类。

WeatherForecast

WeatherForecast 是典型的数据类。

  1. 每个字段都声明为具有默认值的属性。
  2. 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 是我们从模型中序列化属性的类。

  1. 基本字段是记录——它们只能在初始化时设置。
  2. EditedValue 携带该字段的当前值。
  3. IsDirty测试ValueEditedValue之间的相等性。

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

EditFieldCollectionEditFieldIEnumerable集合。该类为集合提供了一组受控的settergetter,并为IEnumerable接口实现了必要的方法。它还提供了一个IsDirty属性来公开集合的状态。

public class EditFieldCollection : IEnumerable
{
    private List<EditField> _items = new List<EditField>();
    public int Count => _items.Count;
    public Action<bool> 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<T>(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<T>(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<T>(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<T>(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<EditField> _items = new List<EditField>();
        private int _cursor;

        object IEnumerator.Current
        {
            get
            {
                if ((_cursor < 0) || (_cursor == _items.Count))
                    throw new InvalidOperationException();
                return _items[_cursor];
            }
        }
        public EditFieldCollectionEnumerator(List<EditField> 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

属性是:

  1. EditContext从级联中拿起。
  2. EditStateChanged父控件提供回调以告诉它编辑状态已更改。
  3. 为控件提供只读IsDirty属性,使用@ref检查控件状态。
  4. EditFields是我们填充并用于管理编辑状态的内部EditFieldCollection
  5. disposedValueIDisposable实现的一部分。

/// 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<bool> 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.OnFieldChangedFieldChanged,因此每当字段值更改时都会调用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"

<EditForm Model="@Model" OnValidSubmit="@HandleValidSubmit">
    <EditFormState @ref="editFormState" EditStateChanged="this.EditStateChanged">
    </EditFormState>

    <label class="form-label">ID:</label> <InputNumber class="form-control" 
     @bind-Value="Model.ID" />
    <label class="form-label">Date:</label> <InputDate class="form-control" 
     @bind-Value="Model.Date" />
    <label class="form-label">Temp C:</label> <InputNumber class="form-control" 
     @bind-Value="Model.TemperatureC" />
    <label class="form-label">Summary:</label> <InputText class="form-control" 
     @bind-Value="Model.Summary" />

    <div class="text-right mt-2">
        <button class="btn @btncolour">@btntext</button>
        <button class="btn btn-primary" type="submit">Submit</button>
    </div>

    <div>
    </div>
</EditForm>
@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 = <span class="pl-pds">"Balmy"
    };

    private void HandleValidSubmit()
    {
        this.editFormState.UpdateState();
    }

    private void EditStateChanged(bool editstate)
        => this._isDirty = editstate;
}

总结

如果您之前没有实现过此类功能,则此控件的真正优势可能不会立即显现出来,但我们将在后续文章中使用它来构建编辑器表单。在接下来的文章看起来在验证过程中,如何构建一个简单的自定义验证。第三篇文章着眼于表单锁定,使用此控件作为流程的一部分。

如果您在以后发现这篇文章很好,最新版本将在此处提供

https://www.codeproject.com/Articles/5297299/A-Blazor-Edit-Form-State-Control

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值