Blazor- EditForm InputCheckbox 可为 null 的 bools 问题变通
Blazor- EditForm InputCheckbox nullable bools issue work around
我正在尝试为 Blazor 上的 editform 创建一个来自 inputbase 的定制输入,但是我正在努力掌握它,因为我最近才在本周才开始使用 Blazor,而 C# 通常是本月。
我找到了
https://www.meziantou.net/creating-a-inputselect-component-for-enumerations-in-blazor.htm(或找到下面粘贴的代码)
并能够将它用于 inputselect 内的可为 null 的枚举,但是尝试将其复制为可为 null 的输入复选框无济于事。我想知道是否有人拥有 link 或者知道如何调整它以使其正常工作。
提前谢谢你,我几乎整天都在我的电脑上所以请随意提问,尽量不要责备我哈哈。
// file: Shared/InputSelectEnum.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;
using Humanizer;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;
// Inherit from InputBase so the hard work is already implemented
// Note that adding a constraint on TEnum (where T : Enum) doesn't work when used in the view, Razor raises an error at build time. Also, this would prevent using nullable types...
namespace OrderServiceFrontEnd.Shared
{
public sealed class InputSelectEnum<TEnum> : InputBase<TEnum>
{
// Generate html when the component is rendered.
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "select");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValueAsString));
builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder<string>(this, value => CurrentValueAsString = value, CurrentValueAsString, null));
// Add an option element per enum value
var enumType = GetEnumType();
foreach (TEnum value in Enum.GetValues(enumType))
{
builder.OpenElement(5, "option");
builder.AddAttribute(6, "value", value.ToString());
builder.AddContent(7, GetDisplayName(value));
builder.CloseElement();
}
builder.CloseElement(); // close the select element
}
protected override bool TryParseValueFromString(string value, out TEnum result, out string validationErrorMessage)
{
// Let's Blazor convert the value for us
if (BindConverter.TryConvertTo(value, CultureInfo.CurrentCulture, out TEnum parsedValue))
{
result = parsedValue;
validationErrorMessage = null;
return true;
}
// Map null/empty value to null if the bound object is nullable
if (string.IsNullOrEmpty(value))
{
var nullableType = Nullable.GetUnderlyingType(typeof(TEnum));
if (nullableType != null)
{
result = default;
validationErrorMessage = null;
return true;
}
}
// The value is invalid => set the error message
result = default;
validationErrorMessage = $"The {FieldIdentifier.FieldName} field is not valid.";
return false;
}
// Get the display text for an enum value:
// - Use the DisplayAttribute if set on the enum member, so this support localization
// - Fallback on Humanizer to decamelize the enum member name
private string GetDisplayName(TEnum value)
{
// Read the Display attribute name
var member = value.GetType().GetMember(value.ToString())[0];
var displayAttribute = member.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute != null)
return displayAttribute.GetName();
// Require the NuGet package Humanizer.Core
// <PackageReference Include = "Humanizer.Core" Version = "2.8.26" />
return value.ToString().Humanize();
}
// Get the actual enum type. It unwrap Nullable<T> if needed
// MyEnum => MyEnum
// MyEnum? => MyEnum
private Type GetEnumType()
{
var nullableType = Nullable.GetUnderlyingType(typeof(TEnum));
if (nullableType != null)
return nullableType;
return typeof(TEnum);
}
}
}
Blazor 组件:
<InputSelectEnum @bind-Value="@_Order.IOSAppDetails.PhasedRelease"/>
您可以继承 InputBase<bool?>
class 并使用一些附加属性处理绑定值。
在这个例子中,我没有使用 'code behind' 方法,尽管它看起来或多或少是一样的。
该组件位于名为 NullableBoolCheckBox.razor
的文件中
@inherits InputBase<bool?>
<input type="checkbox" value="@CurrentValue" @attributes="AdditionalAttributes" class="@CssClass" style="width:15px; height:15px;vertical-align:-2px;"
@onchange="EventCallback.Factory.CreateBinder<bool?>(this,OnChangeAction,this.CurrentValueAsBool)" />
<label @onclick="SetValueToNull" style="width:15px;height:15px;">[x]</label>
@code {
bool? _CurrentValueAsBool;
private bool? CurrentValueAsBool
{
get
{
if (string.IsNullOrEmpty(CurrentValueAsString))
_CurrentValueAsBool = null;
else
{
if (bool.TryParse(CurrentValueAsString, out bool _currentBool))
_CurrentValueAsBool = _currentBool;
else
_CurrentValueAsBool = null;
}
SetCheckBoxCheckedAttribute(_CurrentValueAsBool);
return _CurrentValueAsBool;
}
set => _CurrentValueAsBool = value;
}
void SetCheckBoxCheckedAttribute(bool? _currentValueAsBool)
{
bool _isChecked = _currentValueAsBool.HasValue ? _currentValueAsBool.Value : false;
var _attributes = AdditionalAttributes != null ? AdditionalAttributes.ToDictionary(kv => kv.Key, kv => kv.Value) : new Dictionary<string, object>(); ;
if (!_isChecked)
{
_ = _attributes.ContainsKey("checked") ? _attributes["checked"] = false : _attributes.TryAdd("checked", false);
}
else
{
_ = _attributes.ContainsKey("checked") ? _attributes["checked"] = true : _attributes.TryAdd("checked", true);
}
AdditionalAttributes = _attributes;
}
protected override bool TryParseValueFromString(string value, out bool? result, out string validationErrorMessage)
{
validationErrorMessage = null;
if (string.IsNullOrEmpty(value))
{
result = null;
}
else
{
if (bool.TryParse(value, out bool _result))
{
result = _result;
}
else
{
validationErrorMessage = "Unable to parse value!";
result = null;
return false;
}
}
return true;
}
private Action<Nullable<bool>> OnChangeAction { get => (_inputValue) => CurrentValueAsString = _inputValue.HasValue ? _inputValue.Value.ToString() : null; }
void SetValueToNull(MouseEventArgs e)
{
this.CurrentValueAsString = string.Empty;
}
}
它可以像任何其他组件一样使用。
例如:
<EditForm Model="someModel">
@*.more fields.*@
<label>Nullable value:</label><NullableBoolCheckBox @bind-Value="someModel.SomeNullBoolValue" />
<br />
<strong>value is: @(someModel.SomeNullBoolValue.HasValue?$"{someModel.SomeNullBoolValue}":"null")</strong>
@*.more fields.*@
</EditForm>
链接到表单的模型有一个 属性: public bool? SomeNullBoolValue { get; set; }
绑定到复选框。
看起来像这样:
如果您不想使用 [x] 标签重置值,您可能可以执行点击计数之类的操作来循环显示值 true, false, null
。
作为 [x] 标签的替代方法,将值设置为 null
:
如果您希望在 true, false, null
之间循环,您可以强制 OnChangeAction
将值设置为 null
(如果之前是 false
。
)
利用控件上的 hidden
属性来隐藏和显示 <input>
和 <label>
@inherits InputBase<bool?>
<input type="checkbox" value="@CurrentValue" @attributes="AdditionalAttributes" class="@CssClass" style="width:15px; height:15px;vertical-align:-2px;"
@onchange="EventCallback.Factory.CreateBinder<bool?>(this,OnChangeAction,this.CurrentValueAsBool)" hidden="@_HideCheckBox" />
<label @onclick="SetValueToTrue" hidden="@(!_HideCheckBox)" style="width:15px;height:15px;margin-left:-5px;">[?]</label>
@code {
bool _HideCheckBox { get; set; } = false;
bool? _CurrentValueAsBool;
private bool? CurrentValueAsBool
{
get
{
if (string.IsNullOrEmpty(CurrentValueAsString))
_CurrentValueAsBool = null;
else
{
if (bool.TryParse(CurrentValueAsString, out bool _currentBool))
_CurrentValueAsBool = _currentBool;
else
_CurrentValueAsBool = null;
}
SetCheckBoxCheckedAttribute(_CurrentValueAsBool);
return _CurrentValueAsBool;
}
set => _CurrentValueAsBool = value;
}
void SetCheckBoxCheckedAttribute(bool? _currentValueAsBool)
{
bool _isChecked = _currentValueAsBool.HasValue ? _currentValueAsBool.Value : false;
var _checkBoxAttributes = AdditionalAttributes != null ? AdditionalAttributes.ToDictionary(kv => kv.Key, kv => kv.Value) : new Dictionary<string, object>(); ;
if (!_isChecked)
{
_ = _checkBoxAttributes.ContainsKey("checked") ? _checkBoxAttributes["checked"] = false : _checkBoxAttributes.TryAdd("checked", false);
if (!_currentValueAsBool.HasValue)
_HideCheckBox = true;
}
else
{
_HideCheckBox = false;
_ = _checkBoxAttributes.ContainsKey("checked") ? _checkBoxAttributes["checked"] = true : _checkBoxAttributes.TryAdd("checked", true);
}
AdditionalAttributes = _checkBoxAttributes;
}
protected override bool TryParseValueFromString(string value, out bool? result, out string validationErrorMessage)
{
validationErrorMessage = null;
if (string.IsNullOrEmpty(value))
{
result = null;
}
else
{
if (bool.TryParse(value, out bool _result))
{
result = _result;
}
else
{
validationErrorMessage = "Unable to parse value!";
result = null;
return false;
}
}
return true;
}
private Action<Nullable<bool>> OnChangeAction
{
get => (_inputValue) =>
{
//ignore input value if previously false, to force it to null
if (this.CurrentValueAsString == bool.FalseString)
{
_inputValue = null;
this.CurrentValueAsString = string.Empty;
}
else
this.CurrentValueAsString = _inputValue.HasValue ? _inputValue.Value.ToString() : string.Empty;
};
}
void SetValueToTrue(MouseEventArgs e)
{
this.CurrentValueAsString = bool.TrueString;
}
}
您可以将 <label>
更改为 <image>
或使用字体图标使其更漂亮。
然后看起来像这样:
组件的使用保持不变。
@*.other fields.*@
<label>Nullable value:</label><NullableBoolCheckBox @bind-Value="someModel.SomeNullBoolValue" />
<br />
<strong>value is: @(someModel.SomeNullBoolValue.HasValue?$"{someModel.SomeNullBoolValue}":"null")</strong>
<br />
@*.other fields.*@
我正在尝试为 Blazor 上的 editform 创建一个来自 inputbase 的定制输入,但是我正在努力掌握它,因为我最近才在本周才开始使用 Blazor,而 C# 通常是本月。
我找到了 https://www.meziantou.net/creating-a-inputselect-component-for-enumerations-in-blazor.htm(或找到下面粘贴的代码) 并能够将它用于 inputselect 内的可为 null 的枚举,但是尝试将其复制为可为 null 的输入复选框无济于事。我想知道是否有人拥有 link 或者知道如何调整它以使其正常工作。
提前谢谢你,我几乎整天都在我的电脑上所以请随意提问,尽量不要责备我哈哈。
// file: Shared/InputSelectEnum.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Reflection;
using Humanizer;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Rendering;
// Inherit from InputBase so the hard work is already implemented
// Note that adding a constraint on TEnum (where T : Enum) doesn't work when used in the view, Razor raises an error at build time. Also, this would prevent using nullable types...
namespace OrderServiceFrontEnd.Shared
{
public sealed class InputSelectEnum<TEnum> : InputBase<TEnum>
{
// Generate html when the component is rendered.
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "select");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValueAsString));
builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder<string>(this, value => CurrentValueAsString = value, CurrentValueAsString, null));
// Add an option element per enum value
var enumType = GetEnumType();
foreach (TEnum value in Enum.GetValues(enumType))
{
builder.OpenElement(5, "option");
builder.AddAttribute(6, "value", value.ToString());
builder.AddContent(7, GetDisplayName(value));
builder.CloseElement();
}
builder.CloseElement(); // close the select element
}
protected override bool TryParseValueFromString(string value, out TEnum result, out string validationErrorMessage)
{
// Let's Blazor convert the value for us
if (BindConverter.TryConvertTo(value, CultureInfo.CurrentCulture, out TEnum parsedValue))
{
result = parsedValue;
validationErrorMessage = null;
return true;
}
// Map null/empty value to null if the bound object is nullable
if (string.IsNullOrEmpty(value))
{
var nullableType = Nullable.GetUnderlyingType(typeof(TEnum));
if (nullableType != null)
{
result = default;
validationErrorMessage = null;
return true;
}
}
// The value is invalid => set the error message
result = default;
validationErrorMessage = $"The {FieldIdentifier.FieldName} field is not valid.";
return false;
}
// Get the display text for an enum value:
// - Use the DisplayAttribute if set on the enum member, so this support localization
// - Fallback on Humanizer to decamelize the enum member name
private string GetDisplayName(TEnum value)
{
// Read the Display attribute name
var member = value.GetType().GetMember(value.ToString())[0];
var displayAttribute = member.GetCustomAttribute<DisplayAttribute>();
if (displayAttribute != null)
return displayAttribute.GetName();
// Require the NuGet package Humanizer.Core
// <PackageReference Include = "Humanizer.Core" Version = "2.8.26" />
return value.ToString().Humanize();
}
// Get the actual enum type. It unwrap Nullable<T> if needed
// MyEnum => MyEnum
// MyEnum? => MyEnum
private Type GetEnumType()
{
var nullableType = Nullable.GetUnderlyingType(typeof(TEnum));
if (nullableType != null)
return nullableType;
return typeof(TEnum);
}
}
}
Blazor 组件:
<InputSelectEnum @bind-Value="@_Order.IOSAppDetails.PhasedRelease"/>
您可以继承 InputBase<bool?>
class 并使用一些附加属性处理绑定值。
在这个例子中,我没有使用 'code behind' 方法,尽管它看起来或多或少是一样的。
该组件位于名为 NullableBoolCheckBox.razor
@inherits InputBase<bool?>
<input type="checkbox" value="@CurrentValue" @attributes="AdditionalAttributes" class="@CssClass" style="width:15px; height:15px;vertical-align:-2px;"
@onchange="EventCallback.Factory.CreateBinder<bool?>(this,OnChangeAction,this.CurrentValueAsBool)" />
<label @onclick="SetValueToNull" style="width:15px;height:15px;">[x]</label>
@code {
bool? _CurrentValueAsBool;
private bool? CurrentValueAsBool
{
get
{
if (string.IsNullOrEmpty(CurrentValueAsString))
_CurrentValueAsBool = null;
else
{
if (bool.TryParse(CurrentValueAsString, out bool _currentBool))
_CurrentValueAsBool = _currentBool;
else
_CurrentValueAsBool = null;
}
SetCheckBoxCheckedAttribute(_CurrentValueAsBool);
return _CurrentValueAsBool;
}
set => _CurrentValueAsBool = value;
}
void SetCheckBoxCheckedAttribute(bool? _currentValueAsBool)
{
bool _isChecked = _currentValueAsBool.HasValue ? _currentValueAsBool.Value : false;
var _attributes = AdditionalAttributes != null ? AdditionalAttributes.ToDictionary(kv => kv.Key, kv => kv.Value) : new Dictionary<string, object>(); ;
if (!_isChecked)
{
_ = _attributes.ContainsKey("checked") ? _attributes["checked"] = false : _attributes.TryAdd("checked", false);
}
else
{
_ = _attributes.ContainsKey("checked") ? _attributes["checked"] = true : _attributes.TryAdd("checked", true);
}
AdditionalAttributes = _attributes;
}
protected override bool TryParseValueFromString(string value, out bool? result, out string validationErrorMessage)
{
validationErrorMessage = null;
if (string.IsNullOrEmpty(value))
{
result = null;
}
else
{
if (bool.TryParse(value, out bool _result))
{
result = _result;
}
else
{
validationErrorMessage = "Unable to parse value!";
result = null;
return false;
}
}
return true;
}
private Action<Nullable<bool>> OnChangeAction { get => (_inputValue) => CurrentValueAsString = _inputValue.HasValue ? _inputValue.Value.ToString() : null; }
void SetValueToNull(MouseEventArgs e)
{
this.CurrentValueAsString = string.Empty;
}
}
它可以像任何其他组件一样使用。
例如:
<EditForm Model="someModel">
@*.more fields.*@
<label>Nullable value:</label><NullableBoolCheckBox @bind-Value="someModel.SomeNullBoolValue" />
<br />
<strong>value is: @(someModel.SomeNullBoolValue.HasValue?$"{someModel.SomeNullBoolValue}":"null")</strong>
@*.more fields.*@
</EditForm>
链接到表单的模型有一个 属性: public bool? SomeNullBoolValue { get; set; }
绑定到复选框。
看起来像这样:
如果您不想使用 [x] 标签重置值,您可能可以执行点击计数之类的操作来循环显示值 true, false, null
。
作为 [x] 标签的替代方法,将值设置为 null
:
如果您希望在 true, false, null
之间循环,您可以强制 OnChangeAction
将值设置为 null
(如果之前是 false
。
利用控件上的 hidden
属性来隐藏和显示 <input>
和 <label>
@inherits InputBase<bool?>
<input type="checkbox" value="@CurrentValue" @attributes="AdditionalAttributes" class="@CssClass" style="width:15px; height:15px;vertical-align:-2px;"
@onchange="EventCallback.Factory.CreateBinder<bool?>(this,OnChangeAction,this.CurrentValueAsBool)" hidden="@_HideCheckBox" />
<label @onclick="SetValueToTrue" hidden="@(!_HideCheckBox)" style="width:15px;height:15px;margin-left:-5px;">[?]</label>
@code {
bool _HideCheckBox { get; set; } = false;
bool? _CurrentValueAsBool;
private bool? CurrentValueAsBool
{
get
{
if (string.IsNullOrEmpty(CurrentValueAsString))
_CurrentValueAsBool = null;
else
{
if (bool.TryParse(CurrentValueAsString, out bool _currentBool))
_CurrentValueAsBool = _currentBool;
else
_CurrentValueAsBool = null;
}
SetCheckBoxCheckedAttribute(_CurrentValueAsBool);
return _CurrentValueAsBool;
}
set => _CurrentValueAsBool = value;
}
void SetCheckBoxCheckedAttribute(bool? _currentValueAsBool)
{
bool _isChecked = _currentValueAsBool.HasValue ? _currentValueAsBool.Value : false;
var _checkBoxAttributes = AdditionalAttributes != null ? AdditionalAttributes.ToDictionary(kv => kv.Key, kv => kv.Value) : new Dictionary<string, object>(); ;
if (!_isChecked)
{
_ = _checkBoxAttributes.ContainsKey("checked") ? _checkBoxAttributes["checked"] = false : _checkBoxAttributes.TryAdd("checked", false);
if (!_currentValueAsBool.HasValue)
_HideCheckBox = true;
}
else
{
_HideCheckBox = false;
_ = _checkBoxAttributes.ContainsKey("checked") ? _checkBoxAttributes["checked"] = true : _checkBoxAttributes.TryAdd("checked", true);
}
AdditionalAttributes = _checkBoxAttributes;
}
protected override bool TryParseValueFromString(string value, out bool? result, out string validationErrorMessage)
{
validationErrorMessage = null;
if (string.IsNullOrEmpty(value))
{
result = null;
}
else
{
if (bool.TryParse(value, out bool _result))
{
result = _result;
}
else
{
validationErrorMessage = "Unable to parse value!";
result = null;
return false;
}
}
return true;
}
private Action<Nullable<bool>> OnChangeAction
{
get => (_inputValue) =>
{
//ignore input value if previously false, to force it to null
if (this.CurrentValueAsString == bool.FalseString)
{
_inputValue = null;
this.CurrentValueAsString = string.Empty;
}
else
this.CurrentValueAsString = _inputValue.HasValue ? _inputValue.Value.ToString() : string.Empty;
};
}
void SetValueToTrue(MouseEventArgs e)
{
this.CurrentValueAsString = bool.TrueString;
}
}
您可以将 <label>
更改为 <image>
或使用字体图标使其更漂亮。
然后看起来像这样:
组件的使用保持不变。
@*.other fields.*@
<label>Nullable value:</label><NullableBoolCheckBox @bind-Value="someModel.SomeNullBoolValue" />
<br />
<strong>value is: @(someModel.SomeNullBoolValue.HasValue?$"{someModel.SomeNullBoolValue}":"null")</strong>
<br />
@*.other fields.*@