Blazorise #4

Merged
holger merged 11 commits from Blazorise into main 2020-08-21 22:26:39 +02:00
28 changed files with 184 additions and 99 deletions
Showing only changes of commit 8fcd1c4c44 - Show all commits

View File

@ -1,6 +1,7 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Collections.Generic; using System.Collections.Generic;
using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.DataModels namespace UserService.DatabaseLayer.DataModels
{ {

View File

@ -1,62 +0,0 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace UserService.DatabaseLayer.DataModels
{
public class OrganizationUnit : Node
{
public Member? Manager { get; set; }
}
public class SecurityGroup : Member
{
}
public class User : Member
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public bool IsActive { get; set; }
public string FullName => $"{FirstName} {LastName}";
public IEnumerable<UserMember> MemberOf { get; set; } = new List<UserMember>();
}
public class UserMember
{
public int MemberId { get; set; }
public Member? Member { get; set; }
public int UserId { get; set; }
public User? User { get; set; }
}
public abstract class Member : Node
{
[EmailAddress]
public string? EMail { get; set; }
public ICollection<UserMember> Members { get; set; } = new List<UserMember>();
}
public abstract class Node : ICloneable
{
public int Id { get; set; }
[Required] public string CommonName { get; set; } = null!;
public string? Description { get; set; }
public ICollection<Node> Children { get; set; } = new List<Node>();
public Node? Parent { get; set; } //Parent
public int? ParentId { get; set; }
public override string ToString() => CommonName;
public int Level => Parent?.Level + 1 ?? 0;
/// <inheritdoc />
public virtual object Clone() => MemberwiseClone();
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.DataModels namespace UserService.DatabaseLayer.DataModels
{ {

View File

@ -1,8 +1,7 @@
// <auto-generated /> // <auto-generated />
using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using UserService.DatabaseLayer.DataModels; using UserService.DatabaseLayer.DataModels;
namespace UserService.DatabaseLayer.Migrations namespace UserService.DatabaseLayer.Migrations

View File

@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using UserService.DatabaseLayer.DataModels; using UserService.DatabaseLayer.DataModels;
using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.Repository namespace UserService.DatabaseLayer.Repository
{ {

View File

@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using UserService.DatabaseLayer.DataModels; using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.Repository namespace UserService.DatabaseLayer.Repository
{ {

View File

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using UserService.DatabaseLayer.DataModels; using UserService.DatabaseLayer.DataModels;
using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.Repository namespace UserService.DatabaseLayer.Repository
{ {

View File

@ -1,4 +1,4 @@
using UserService.DatabaseLayer.DataModels; using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.Repository namespace UserService.DatabaseLayer.Repository
{ {

View File

@ -1,4 +1,4 @@
using UserService.DatabaseLayer.DataModels; using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.Repository namespace UserService.DatabaseLayer.Repository
{ {

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>8</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
@ -18,4 +18,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UserService.Infrastructure\UserService.Infrastructure.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace UserService.Infrastructure.DataModels
{
public abstract class Member : Node
{
[EmailAddress]
public string? EMail { get; set; }
public ICollection<UserMember> Members { get; set; } = new List<UserMember>();
}
}

View File

@ -0,0 +1,24 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace UserService.Infrastructure.DataModels
{
public abstract class Node : ICloneable
{
public int Id { get; set; }
[Required] public string CommonName { get; set; } = null!;
public string? Description { get; set; }
public ICollection<Node> Children { get; set; } = new List<Node>();
public Node? Parent { get; set; } //Parent
public int? ParentId { get; set; }
public override string ToString() => CommonName;
public int Level => Parent?.Level + 1 ?? 0;
/// <inheritdoc />
public virtual object Clone() => MemberwiseClone();
}
}

View File

@ -0,0 +1,7 @@
namespace UserService.Infrastructure.DataModels
{
public class OrganizationUnit : Node
{
public Member? Manager { get; set; }
}
}

View File

@ -0,0 +1,6 @@
namespace UserService.Infrastructure.DataModels
{
public class SecurityGroup : Member
{
}
}

View File

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace UserService.Infrastructure.DataModels
{
public class User : Member
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public bool IsActive { get; set; }
public string FullName => $"{FirstName} {LastName}";
public IEnumerable<UserMember> MemberOf { get; set; } = new List<UserMember>();
}
}

View File

@ -0,0 +1,12 @@
namespace UserService.Infrastructure.DataModels
{
public class UserMember
{
public int MemberId { get; set; }
public Member? Member { get; set; }
public int UserId { get; set; }
public User? User { get; set; }
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using UserService.Infrastructure.DataModels;
namespace UserService.Infrastructure
{
public static class Validators
{
public static bool? ValidateEmail(string mailAddress)
{
if (string.IsNullOrEmpty(mailAddress)) return null;
return new EmailAddressAttribute().IsValid(mailAddress);
}
public static bool? ValidateCommonName(string commonName, IReadOnlyList<User> users)
{
if (string.IsNullOrEmpty(commonName)) return false;
return users.All(x => x.CommonName != commonName);
}
}
}

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using UserService.DatabaseLayer.DataModels; using UserService.DatabaseLayer.DataModels;
using UserService.Infrastructure.DataModels;
namespace UserService.Test namespace UserService.Test
{ {

View File

@ -4,6 +4,8 @@
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

Binary file not shown.

View File

@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig .editorconfig = .editorconfig
EndProjectSection EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserService.Infrastructure", "UserService.Infrastructure\UserService.Infrastructure.csproj", "{586BD023-5C7B-4D9C-A17E-A0D5CEDADD20}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -32,6 +34,10 @@ Global
{4505C991-7E39-416F-94E5-D906DD0D90F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {4505C991-7E39-416F-94E5-D906DD0D90F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4505C991-7E39-416F-94E5-D906DD0D90F9}.Release|Any CPU.ActiveCfg = Release|Any CPU {4505C991-7E39-416F-94E5-D906DD0D90F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4505C991-7E39-416F-94E5-D906DD0D90F9}.Release|Any CPU.Build.0 = Release|Any CPU {4505C991-7E39-416F-94E5-D906DD0D90F9}.Release|Any CPU.Build.0 = Release|Any CPU
{586BD023-5C7B-4D9C-A17E-A0D5CEDADD20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{586BD023-5C7B-4D9C-A17E-A0D5CEDADD20}.Debug|Any CPU.Build.0 = Debug|Any CPU
{586BD023-5C7B-4D9C-A17E-A0D5CEDADD20}.Release|Any CPU.ActiveCfg = Release|Any CPU
{586BD023-5C7B-4D9C-A17E-A0D5CEDADD20}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -2,8 +2,8 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using UserService.DatabaseLayer.DataModels;
using UserService.DatabaseLayer.Repository; using UserService.DatabaseLayer.Repository;
using UserService.Infrastructure.DataModels;
namespace UserService.Pages namespace UserService.Pages
{ {

View File

@ -1,5 +1,5 @@
@page "/securitygroups" @page "/securitygroups"
@using UserService.DatabaseLayer.DataModels @using UserService.Infrastructure.DataModels
@inherits SecurityGroupsBase @inherits SecurityGroupsBase
<h1>Table of all security groups</h1> <h1>Table of all security groups</h1>

View File

@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using UserService.DatabaseLayer.DataModels;
using UserService.DatabaseLayer.Repository; using UserService.DatabaseLayer.Repository;
using UserService.Infrastructure.DataModels;
namespace UserService.Pages namespace UserService.Pages
{ {

View File

@ -1,5 +1,5 @@
@page "/users" @page "/users"
@using UserService.DatabaseLayer.DataModels @using UserService.Infrastructure.DataModels
@inherits UsersBase @inherits UsersBase
<h1>List of all users</h1> <h1>List of all users</h1>
@ -12,7 +12,9 @@
} }
else else
{ {
<DataGrid TItem="User" RowSelectable="@(u => false)" Editable="true" EditMode="DataGridEditMode.Popup" RowRemoving="RowDeletingCallback" Data="@Users" RowInserted="RowInsertedCallback" RowUpdating="RowUpdatingCallback"> <TextEdit Placeholder="Search" Size="Size.Large" @bind-Text="@CustomFilterValue" />
<DataGrid TItem="User" RowSelectable="@(u => false)" CustomFilter="@OnCustomFilter" Sortable="true" Editable="true" EditMode="DataGridEditMode.Popup" RowRemoving="RowDeletingCallback" Data="@Users" RowInserted="RowInsertedCallback" RowInserting="RowInsertingCallback" RowUpdating="RowUpdatingCallback">
<DataGridCommandColumn TItem="User"> <DataGridCommandColumn TItem="User">
<NewCommandTemplate> <NewCommandTemplate>
<Button Color="Color.Success" Clicked="@context.Clicked" title="Create user"> <Button Color="Color.Success" Clicked="@context.Clicked" title="Create user">
@ -78,15 +80,12 @@ else
</DisplayTemplate> </DisplayTemplate>
<EditTemplate> <EditTemplate>
<Select TValue="int?" SelectedValue="@((int?)(context.CellValue))" SelectedValueChanged="@(v => context.CellValue = v)" > <Select TValue="int?" SelectedValue="@((int?)(context.CellValue))" SelectedValueChanged="@(v => context.CellValue = v)" >
@foreach (var item in OrganizationUnits) @foreach (var item in OrganizationUnits)
{ {
<SelectItem TValue="int" Value="@(item.Id)">@item.CommonName</SelectItem> <SelectItem TValue="int" Value="@(item.Id)">@item.CommonName</SelectItem>
} }
</Select> </Select>
</EditTemplate> </EditTemplate>
</DataGridSelectColumn> </DataGridSelectColumn>
<DataGridCheckColumn TItem="User" Field="@nameof(User.IsActive)" Caption="Active" Editable="true" /> <DataGridCheckColumn TItem="User" Field="@nameof(User.IsActive)" Caption="Active" Editable="true" />

View File

@ -1,14 +1,14 @@
using System; using System;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Blazorise; using Blazorise;
using Blazorise.DataGrid; using Blazorise.DataGrid;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using UserService.DatabaseLayer.DataModels;
using UserService.DatabaseLayer.Repository; using UserService.DatabaseLayer.Repository;
using UserService.Infrastructure;
using UserService.Infrastructure.DataModels;
namespace UserService.Pages namespace UserService.Pages
{ {
@ -21,6 +21,7 @@ namespace UserService.Pages
protected IReadOnlyList<User>? Users { get; private set; } protected IReadOnlyList<User>? Users { get; private set; }
protected IReadOnlyList<OrganizationUnit>? OrganizationUnits { get; private set; } protected IReadOnlyList<OrganizationUnit>? OrganizationUnits { get; private set; }
protected string? CustomFilterValue { get; set; }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -28,14 +29,24 @@ namespace UserService.Pages
OrganizationUnits = await OrganizationUnitsRepository.GetAllAsync().ConfigureAwait(false); OrganizationUnits = await OrganizationUnitsRepository.GetAllAsync().ConfigureAwait(false);
} }
protected async Task RowInsertedCallback(SavedRowItem<User, Dictionary<string, object>> arg) protected async Task RowInsertingCallback(CancellableRowChange<User, Dictionary<string, object>> arg)
{ {
if (arg == null) throw new ArgumentNullException(nameof(arg)); if (arg is null) throw new ArgumentNullException(nameof(arg));
var user = arg.Item; var mailValidation = Validators.ValidateEmail(arg.Values[nameof(User.EMail)]?.ToString());
user.ParentId = -2; var commonNameValidation = Validators.ValidateCommonName(arg.Values[nameof(User.CommonName)]?.ToString(), Users);
await UsersRepository.AddAsync(user).ConfigureAwait(false); if (mailValidation == true && commonNameValidation == true) return;
await JsRuntime.InvokeVoidAsync("alert", "User could not be added").ConfigureAwait(false);
arg.Cancel = true;
} }
protected async Task RowInsertedCallback(SavedRowItem<User, Dictionary<string, object>> arg)
{
if (arg is null) throw new ArgumentNullException(nameof(arg));
var user = arg.Item;
user.Parent = OrganizationUnits?.FirstOrDefault(x => x.Id == user.ParentId);
await UsersRepository.AddAsync(user).ConfigureAwait(false);
}
protected async Task RowDeletingCallback(CancellableRowChange<User> arg) protected async Task RowDeletingCallback(CancellableRowChange<User> arg)
{ {
if (arg == null) throw new ArgumentNullException(nameof(arg)); if (arg == null) throw new ArgumentNullException(nameof(arg));
@ -50,30 +61,26 @@ namespace UserService.Pages
arg.Cancel = true; arg.Cancel = true;
} }
protected static void ValidateCommonName(ValidatorEventArgs e) protected void ValidateCommonName(ValidatorEventArgs e)
{ {
if (e == null) throw new ArgumentNullException(nameof(e)); if (e == null) throw new ArgumentNullException(nameof(e));
var commonName = e.Value?.ToString(); var commonName = e.Value?.ToString();
var validationResult = Validators.ValidateCommonName(commonName, Users);
if (string.IsNullOrEmpty(commonName)) e.Status = validationResult == false ? ValidationStatus.Error : ValidationStatus.Success;
e.Status = ValidationStatus.Error;
else
e.Status = ValidationStatus.Success;
} }
protected static void ValidateEmail(ValidatorEventArgs e) protected static void ValidateEmail(ValidatorEventArgs e)
{ {
if (e == null) throw new ArgumentNullException(nameof(e)); if (e == null) throw new ArgumentNullException(nameof(e));
var email = e.Value?.ToString(); var email = e.Value?.ToString();
var validationResult = Validators.ValidateEmail(email);
if (string.IsNullOrEmpty(email)) e.Status = validationResult switch
e.Status = ValidationStatus.None;
else if (!new EmailAddressAttribute().IsValid(email))
{ {
e.Status = ValidationStatus.Error; null => ValidationStatus.None,
} false => ValidationStatus.Error,
else _ => ValidationStatus.Success
e.Status = ValidationStatus.Success; };
} }
protected async Task RowUpdatingCallback(CancellableRowChange<User, Dictionary<string, object>> arg) protected async Task RowUpdatingCallback(CancellableRowChange<User, Dictionary<string, object>> arg)
{ {
@ -83,5 +90,17 @@ namespace UserService.Pages
var result = await UsersRepository.UpdateAsync(user).ConfigureAwait(false); var result = await UsersRepository.UpdateAsync(user).ConfigureAwait(false);
arg.Cancel = !result; arg.Cancel = !result;
} }
protected bool OnCustomFilter(User model)
{
// We want to accept empty value as valid or otherwise
// datagrid will not show anything.
if (string.IsNullOrEmpty(CustomFilterValue) || CustomFilterValue.Length < 3) return true;
return
model?.FirstName?.Contains(CustomFilterValue, StringComparison.OrdinalIgnoreCase) == true
|| model?.LastName?.Contains(CustomFilterValue, StringComparison.OrdinalIgnoreCase) == true
|| model?.CommonName?.Contains(CustomFilterValue, StringComparison.OrdinalIgnoreCase) == true;
}
} }
} }

View File

@ -3,6 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -11,6 +12,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\UserService.DatabaseLayer\UserService.DatabaseLayer.csproj" /> <ProjectReference Include="..\UserService.DatabaseLayer\UserService.DatabaseLayer.csproj" />
<ProjectReference Include="..\UserService.Infrastructure\UserService.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>