Added validation to admin pages
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEditContextDataAnnotationsExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fbc307cd57fb42fc4c7fb9795381958122734d3750f41b6c1735c7d132ecda70_003FEditContextDataAnnotationsExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb7208b3f72528d22781d25fde9a55271bdf2b5aade4f03b1324579a25493cd8_003FList_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValidationMessageStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003Ffc81648e473bb3cc818f71427c286ecddc3604d2f4c69c565205bb89e8b4ef4_003FValidationMessageStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue"><AssemblyExplorer>
|
||||
<Assembly Path="C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\7.0.16\ref\net7.0\System.ComponentModel.Annotations.dll" />
|
||||
<Assembly Path="C:\Users\Remote\.nuget\packages\blazorstrap\5.2.100.61524\lib\net7.0\BlazorStrap.dll" />
|
||||
|
||||
@@ -8,7 +8,7 @@ public class User : IPermissionOwner {
|
||||
[Key, Required, MinLength(36), MaxLength(36)]
|
||||
public Guid Id { get; init; }
|
||||
|
||||
[MaxLength(50)]
|
||||
[Required, MaxLength(50)]
|
||||
public string Username { get; set; }
|
||||
|
||||
[Required, MaxLength(50), EmailAddress]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace HopFrame.Web.Admin.Attributes.Members;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class AdminUniqueAttribute : Attribute;
|
||||
@@ -11,11 +11,12 @@ public interface IAdminPropertyGenerator<TProperty> {
|
||||
IAdminPropertyGenerator<TProperty> Ignore(bool ignore = true);
|
||||
IAdminPropertyGenerator<TProperty> Generated(bool generated = true);
|
||||
IAdminPropertyGenerator<TProperty> Bold(bool bold = true);
|
||||
IAdminPropertyGenerator<TProperty> Unique(bool unique = true);
|
||||
|
||||
IAdminPropertyGenerator<TProperty> DisplayName(string displayName);
|
||||
IAdminPropertyGenerator<TProperty> Description(string description);
|
||||
IAdminPropertyGenerator<TProperty> Prefix(string prefix);
|
||||
IAdminPropertyGenerator<TProperty> Validator(Func<object, bool> validator);
|
||||
IAdminPropertyGenerator<TProperty> Validator(Func<TProperty, string> validator);
|
||||
IAdminPropertyGenerator<TProperty> IsSelector<TSelector>();
|
||||
IAdminPropertyGenerator<TProperty> Parser<TModel>(Func<TModel, string, TProperty> parser);
|
||||
IAdminPropertyGenerator<TProperty> ParserForListType<TModel, TInnerProperty>(Func<TModel, string, TInnerProperty> parser);
|
||||
|
||||
@@ -51,6 +51,11 @@ internal sealed class AdminPropertyGenerator<TProperty>(string name, Type type)
|
||||
return this;
|
||||
}
|
||||
|
||||
public IAdminPropertyGenerator<TProperty> Unique(bool unique = true) {
|
||||
_property.Unique = unique;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IAdminPropertyGenerator<TProperty> DisplayName(string displayName) {
|
||||
_property.DisplayName = displayName;
|
||||
return this;
|
||||
@@ -66,8 +71,8 @@ internal sealed class AdminPropertyGenerator<TProperty>(string name, Type type)
|
||||
return this;
|
||||
}
|
||||
|
||||
public IAdminPropertyGenerator<TProperty> Validator(Func<object, bool> validator) {
|
||||
_property.Validator = validator;
|
||||
public IAdminPropertyGenerator<TProperty> Validator(Func<TProperty, string> validator) {
|
||||
_property.Validator = o => validator.Invoke((TProperty)o);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -115,6 +120,9 @@ internal sealed class AdminPropertyGenerator<TProperty>(string name, Type type)
|
||||
|
||||
if (attributes.Any(a => a is AdminUneditableAttribute))
|
||||
Editable(false);
|
||||
|
||||
if (attributes.Any(a => a is AdminUniqueAttribute))
|
||||
Unique();
|
||||
|
||||
if (attributes.Any(a => a is AdminIgnoreAttribute)) {
|
||||
var attribute = attributes.Single(a => a is AdminIgnoreAttribute) as AdminIgnoreAttribute;
|
||||
|
||||
@@ -17,12 +17,14 @@ public sealed class AdminPageProperty {
|
||||
public bool Bold { get; set; }
|
||||
public bool Required { get; set; }
|
||||
public bool Ignore { get; set; }
|
||||
public bool Unique { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Type Type { get; set; }
|
||||
|
||||
public Type SelectorType { get; set; }
|
||||
|
||||
public Func<object, bool> Validator { get; set; }
|
||||
public Func<object, string> Validator { get; set; }
|
||||
public Func<object, string, object> Parser { get; set; }
|
||||
|
||||
public object GetValue(object entry) {
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
<BSModalContent>
|
||||
@foreach (var prop in GetEditableProperties()) {
|
||||
@if (!_isEdit && prop.Generated) continue;
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
@if (IsListType(prop)) {
|
||||
<BSLabel>@prop.DisplayName</BSLabel>
|
||||
<BSListGroup>
|
||||
<BSListGroupItem>
|
||||
<BSListGroup IsFlush="true">
|
||||
@foreach (var element in GetListPropertyValues(prop).Select((e, i) => new {e, i})) {
|
||||
@foreach (var element in GetListPropertyValues(prop).Select((e, i) => new { e, i })) {
|
||||
<BSListGroupItem>
|
||||
<BSButton Color="BSColor.Danger" Size="Size.ExtraSmall" MarginEnd="Margins.Small" OnClick="() => DeleteListItem(prop, element.i)">
|
||||
<HopIconDisplay Type="HopIconDisplay.HopIcon.Cross"/>
|
||||
@@ -77,12 +77,17 @@
|
||||
else if (prop.Prefix is not null && !_isEdit) {
|
||||
<BSInputGroup>
|
||||
<span class="@BS.Input_Group_Text">@prop.Prefix</span>
|
||||
<input type="text" class="form-control" disabled="@IsDisabled(prop)" required="@IsRequired(prop)" value="@GetPropertyValue(prop)" @onchange="e => _values[prop] = prop.Prefix + e.Value"/>
|
||||
<input type="text" class="form-control" required="@IsRequired(prop)" disabled="@IsDisabled(prop)" value="@GetPropertyValue(prop)" @onchange="e => _values[prop] = prop.Prefix + e.Value"/>
|
||||
</BSInputGroup>
|
||||
}
|
||||
else {
|
||||
<BSLabel>@prop.DisplayName</BSLabel>
|
||||
<input type="@GetInputType(prop)" class="form-control" disabled="@IsDisabled(prop)" required="@IsRequired(prop)" value="@GetPropertyValue(prop)" @onchange="e => _values[prop] = e.Value"/>
|
||||
<input type="@GetInputType(prop)" class="form-control" required="@IsRequired(prop)" disabled="@IsDisabled(prop)" value="@GetPropertyValue(prop)" @onchange="e => _values[prop] = e.Value"/>
|
||||
@if (_validation[_validationIdentifiers[prop]].Any()) {
|
||||
<div class="invalid-feedback" style="display: block">
|
||||
@_validation[_validationIdentifiers[prop]].First()
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@@ -102,11 +107,15 @@
|
||||
@inject ITokenContext Auth
|
||||
|
||||
@code {
|
||||
#pragma warning disable CS4014
|
||||
|
||||
[Parameter]
|
||||
public Func<Task> ReloadDelegate { get; set; }
|
||||
|
||||
private BSModalBase _modal;
|
||||
private EditContext _context;
|
||||
private ValidationMessageStore _validation;
|
||||
private Dictionary<AdminPageProperty, FieldIdentifier> _validationIdentifiers;
|
||||
private IDictionary<AdminPageProperty, object> _values;
|
||||
private IModelRepository _repository;
|
||||
|
||||
@@ -126,11 +135,14 @@
|
||||
|
||||
_entry ??= Activator.CreateInstance(_currentPage.ModelType);
|
||||
_context = new EditContext(_entry);
|
||||
_validation = new ValidationMessageStore(_context);
|
||||
_validationIdentifiers = new Dictionary<AdminPageProperty, FieldIdentifier>();
|
||||
_context.OnValidationRequested += Validate;
|
||||
|
||||
_values = new Dictionary<AdminPageProperty, object>();
|
||||
foreach (var property in _currentPage.Properties) {
|
||||
_values.Add(property, property.GetValue(_entry));
|
||||
_validationIdentifiers.Add(property, new FieldIdentifier(_entry, property.Name));
|
||||
}
|
||||
|
||||
await _modal.ShowAsync();
|
||||
@@ -217,11 +229,23 @@
|
||||
}
|
||||
|
||||
private void Validate(object sender, ValidationRequestedEventArgs e) {
|
||||
_validation.Clear();
|
||||
foreach (var value in _values) {
|
||||
if (value.Key.Unique) {
|
||||
var repo = Provider.GetService(_currentPage.RepositoryProvider) as IModelRepository;
|
||||
var data = repo!.ReadAllO().GetAwaiter().GetResult();
|
||||
foreach (var entry in data) {
|
||||
var other = value.Key.GetValue(entry);
|
||||
if (!other.Equals(value.Value)) continue;
|
||||
_validation.Add(_validationIdentifiers[value.Key], $"This {value.Key.DisplayName ?? value.Key.Name} already exists!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (value.Key.Validator is null) continue;
|
||||
if (value.Key.Validator?.Invoke(value.Value) == true) continue;
|
||||
Console.WriteLine("INVALID");
|
||||
//TODO: implement validation
|
||||
var error = value.Key.Validator?.Invoke(value.Value);
|
||||
if (string.IsNullOrEmpty(error)) continue;
|
||||
_validation.Add(_validationIdentifiers[value.Key], error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +298,7 @@
|
||||
if (!_isEdit) {
|
||||
await _repository.CreateO(_entry);
|
||||
|
||||
await Alerts.FireAsync(new SweetAlertOptions {
|
||||
Alerts.FireAsync(new SweetAlertOptions {
|
||||
Title = "New entry added!",
|
||||
Icon = SweetAlertIcon.Success,
|
||||
ShowConfirmButton = false,
|
||||
@@ -284,12 +308,11 @@
|
||||
else {
|
||||
await _repository.UpdateO(_entry);
|
||||
|
||||
await Alerts.FireAsync(new SweetAlertOptions {
|
||||
Alerts.FireAsync(new SweetAlertOptions {
|
||||
Title = "Entry updated!",
|
||||
Icon = SweetAlertIcon.Success,
|
||||
ShowConfirmButton = false,
|
||||
Timer = 1500
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using HopFrame.Database.Models;
|
||||
using HopFrame.Security;
|
||||
using HopFrame.Web.Admin;
|
||||
@@ -23,7 +24,16 @@ public class HopAdminContext : AdminPagesContext {
|
||||
|
||||
generator.Page<User>().Property(u => u.Password)
|
||||
.DisplayInListing(false)
|
||||
.DisplayValueWhileEditing(false);
|
||||
.DisplayValueWhileEditing(false)
|
||||
.Validator(passwd => passwd.Length >= 8 ? null : "The password needs to be at least 8 characters long!");
|
||||
|
||||
generator.Page<User>().Property(u => u.Email)
|
||||
.Validator(email => Regex.Match(email, @"^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$").Success ? null : "Invalid E-Mail address!")
|
||||
.Unique();
|
||||
|
||||
generator.Page<User>().Property(u => u.Username)
|
||||
.Validator(uname => uname.Length >= 4 ? null : "The username needs to be at least 4 characters long!")
|
||||
.Unique();
|
||||
|
||||
generator.Page<User>().Property(u => u.CreatedAt)
|
||||
.Editable(false);
|
||||
|
||||
Reference in New Issue
Block a user