Created
September 21, 2018 16:29
-
-
Save SteveSandersonMS/6ac9458e3fc5b49c199777627fff3fc8 to your computer and use it in GitHub Desktop.
Validation mockup A: explicit <ValidateXyz> components that take a Func<T>
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@* Unfortunately this has to be named BlazorForm, not Form, because we can't differentiate based on casing *@ | |
<form onsubmit=@HandleSubmit> | |
@ChildContent(_context) | |
</form> | |
@functions { | |
private FormContext _context = new FormContext(); | |
[Parameter] protected RenderFragment<FormContext> ChildContent { get; set; } | |
[Parameter] protected Action<FormContext> OnSubmit { get; set; } | |
public class FormContext | |
{ | |
private List<Func<bool>> _validations = new List<Func<bool>>(); | |
public void AddValidation(Func<bool> validation) | |
{ | |
_validations.Add(validation); | |
} | |
public bool Validate() | |
{ | |
var isValid = true; | |
foreach (var validation in _validations) | |
{ | |
isValid &= validation(); | |
} | |
return isValid; | |
} | |
} | |
public class ValidationGroup | |
{ | |
private List<Func<bool>> _lastResultAccessors = new List<Func<bool>>(); | |
public bool Valid | |
{ | |
get => _lastResultAccessors.All(x => x()); | |
} | |
public void AddValidation(Func<bool> lastResultAccessor) | |
{ | |
_lastResultAccessors.Add(lastResultAccessor); | |
} | |
} | |
void HandleSubmit() | |
{ | |
if (_context.Validate()) | |
{ | |
OnSubmit?.Invoke(_context); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@page "/" | |
<h1>Hello, world!</h1> | |
Welcome to your new app. | |
@* Would be good if we could put Content="form" here so all "context" below is replaced by "form" *@ | |
<BlazorForm OnSubmit=@HandleSubmit> | |
@* | |
If we implemented the ability to share data with descendants as in #1 (like React's 'context') then we could do this, | |
i.e., could eliminate the Form and Value params: | |
<InputText bind=@email> | |
<ValidateRequired /> | |
<ValidateEmail /> | |
</InputText> | |
*@ | |
<p> | |
@* Example of making use of validation group status *@ | |
<input bind=@firstName style="background-color: @(firstNameValidations.Valid ? "" : "#ffcccc")" /> | |
@* Specifying Value as a Func<T>, not T, is needed to be independent of render order *@ | |
<ValidateRequired Form=context Value=@(() => firstName)>Type a first name</ValidateRequired> | |
</p> | |
<p> | |
@* Can work with arbitrary inputs, as we're validating a Func<T>, not a UI element *@ | |
<input bind=@email /> | |
<ValidateRequired Form=context Value=@(() => email) /> | |
</p> | |
<button type="submit">Submit</button> | |
@** Can control where the validation messages get displayed, have custom rules, put them in groups *@ | |
<ValidateCustom Group=@firstNameValidations Form=context Rule=@(() => !string.IsNullOrWhiteSpace(firstName))>First name is required</ValidateCustom> | |
<ValidateCustom Group=@firstNameValidations Form=context Rule=@(() => firstName != null && firstName.Length < 5)>First name is too long</ValidateCustom> | |
</BlazorForm> | |
<pre>@log</pre> | |
@functions { | |
BlazorForm.ValidationGroup firstNameValidations = new BlazorForm.ValidationGroup(); | |
string log = ""; | |
string firstName = "Bert"; | |
string email = "[email protected]"; | |
void HandleSubmit(BlazorForm.FormContext context) | |
{ | |
log += "\nSubmitted for " + firstName; | |
StateHasChanged(); // Only needed for the log | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@if (!isValid) | |
{ | |
<span style="color:red"> | |
@* Use child content as message if specified, otherwise use default *@ | |
@if (ChildContent != null) | |
{ | |
@ChildContent | |
} | |
else | |
{ | |
<text>The valid is not valid</text> | |
} | |
</span> | |
} | |
@functions { | |
[Parameter] protected BlazorForm.FormContext Form { get; set; } | |
[Parameter] protected BlazorForm.ValidationGroup Group { get; set; } | |
[Parameter] protected Func<bool> Rule { get; set; } | |
[Parameter] protected RenderFragment ChildContent { get; set; } | |
private bool isValid = true; | |
protected override void OnInit() | |
{ | |
Form.AddValidation(() => | |
{ | |
if (Rule() != isValid) | |
{ | |
isValid = !isValid; | |
StateHasChanged(); | |
} | |
return isValid; | |
}); | |
Group?.AddValidation(() => isValid); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@* Example of specializing ValidateCustom with a pre-implemented rule *@ | |
<ValidateCustom Form=@Form Group=@Group Rule=@Rule ChildContent=@(ChildContent ?? DefaultMessage) /> | |
@functions { | |
[Parameter] protected BlazorForm.FormContext Form { get; set; } | |
[Parameter] protected BlazorForm.ValidationGroup Group { get; set; } | |
[Parameter] protected RenderFragment ChildContent { get; set; } | |
[Parameter] protected Func<string> Value { get; set; } | |
private void DefaultMessage(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder) | |
{ | |
builder.AddContent(0, "A value is required"); | |
} | |
private bool Rule() | |
=> !string.IsNullOrWhiteSpace(Value()); | |
} |
I did sort of component-based validation in my blog post http://www.hishambinateya.com/part1-validation-controls-using-blazor-basic-validation-controls
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Do you have any plans for generic Blazor components (like generic classes in C#)? To implement universal ValidateRequired component we need
[Parameter] protected Func<T> Value { get; set; }
instead of
[Parameter] protected Func<string> Value { get; set; }
and smarter
Rule
function. Otherwise we will have to createValidateStringRequired
,ValidateIntRequired
, etc.Also we should take into account that average HTML form in LOB application is quite complex. There are a lot of "noise" if we want to create a nice responsive layout which is also compliant with accessibility rules. With this syntax:
our forms will be even more complex and hard to maintain. Probably the biggest drawback of this solution is that we have to specify validation rules second time. In Blazor we have the same programming language on the server and on the client and we should be able to specify validation rules in DTO objects.