Merge pull request 'Blazorise' (#4) from Blazorise into main

Reviewed-on: #4
This commit is contained in:
Holger Börchers 2020-08-21 22:26:39 +02:00
commit e6822e0491
49 changed files with 860 additions and 637 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
[*.cs]
# CA1822: Member als statisch markieren
dotnet_diagnostic.CA1822.severity = suggestion

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ContentModelUserStore">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.UserService/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.UserService/riderModule.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RiderProjectSettingsUpdater">
<option name="vcsConfiguration" value="2" />
</component>
</project>

6
.idea/.idea.UserService/.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

15
.idea/.idea.UserService/riderModule.iml generated Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="RIDER_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$USER_HOME$/.nuget/packages/microsoft.net.test.sdk/16.7.0/build/netcoreapp2.1" />
<content url="file://$USER_HOME$/.nuget/packages/microsoft.testplatform.testhost/16.7.0/build/netcoreapp2.1/x64/testhost.dll" />
<content url="file://$USER_HOME$/.nuget/packages/microsoft.testplatform.testhost/16.7.0/build/netcoreapp2.1/x64/testhost.exe" />
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.17.0/build/netcoreapp2.1/NUnit3.TestAdapter.dll" />
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.17.0/build/netcoreapp2.1/NUnit3.TestAdapter.pdb" />
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.17.0/build/netcoreapp2.1/nunit.engine.api.dll" />
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.17.0/build/netcoreapp2.1/nunit.engine.core.dll" />
<content url="file://$USER_HOME$/.nuget/packages/nunit3testadapter/3.17.0/build/netcoreapp2.1/nunit.engine.dll" />
<content url="file://$MODULE_DIR$/../.." />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,5 +1,7 @@
using Microsoft.EntityFrameworkCore; using System;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic; using System.Collections.Generic;
using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.DataModels namespace UserService.DatabaseLayer.DataModels
{ {
@ -7,6 +9,7 @@ namespace UserService.DatabaseLayer.DataModels
{ {
public static void Seed(this ModelBuilder modelBuilder) public static void Seed(this ModelBuilder modelBuilder)
{ {
if (modelBuilder == null) throw new ArgumentNullException(nameof(modelBuilder));
var groups = new OrganizationUnit { CommonName = "Groups", Id = -1 }; var groups = new OrganizationUnit { CommonName = "Groups", Id = -1 };
var users = new OrganizationUnit { CommonName = "Users", Id = -2 }; var users = new OrganizationUnit { CommonName = "Users", Id = -2 };
var germany = new OrganizationUnit{CommonName = "Germany", Id = -6, ParentId = -2}; var germany = new OrganizationUnit{CommonName = "Germany", Id = -6, ParentId = -2};
@ -14,7 +17,7 @@ namespace UserService.DatabaseLayer.DataModels
var arizona = new OrganizationUnit{CommonName = "Arizona" , Id = -4, ParentId = -5 }; var arizona = new OrganizationUnit{CommonName = "Arizona" , Id = -4, ParentId = -5 };
var france = new OrganizationUnit{CommonName = "France" , Id = -3, ParentId = -2 }; var france = new OrganizationUnit{CommonName = "France" , Id = -3, ParentId = -2 };
modelBuilder.Entity<OrganizationUnit>().HasData(users, groups, germany, usa, arizona, france); modelBuilder.Entity<OrganizationUnit>().HasData(users, groups, germany, usa, arizona, france);
var user = new User { CommonName = "holger", IsActive = true, Id = -7, ParentId = germany.Id }; var user = new User { CommonName = Environment.UserName, IsActive = true, Id = -7, ParentId = users.Id };
modelBuilder.Entity<User>().HasData(user); modelBuilder.Entity<User>().HasData(user);
var secGroup = new SecurityGroup { CommonName = "Global Admin", Id = -8, ParentId = groups.Id }; var secGroup = new SecurityGroup { CommonName = "Global Admin", Id = -8, ParentId = groups.Id };
modelBuilder.Entity<SecurityGroup>().HasData(secGroup); modelBuilder.Entity<SecurityGroup>().HasData(secGroup);
@ -25,6 +28,7 @@ namespace UserService.DatabaseLayer.DataModels
public static void CreateRelations(this ModelBuilder modelBuilder) public static void CreateRelations(this ModelBuilder modelBuilder)
{ {
if (modelBuilder == null) throw new ArgumentNullException(nameof(modelBuilder));
modelBuilder.Entity<UserMember>() modelBuilder.Entity<UserMember>()
.HasKey(bc => new { bc.MemberId, bc.UserId }); .HasKey(bc => new { bc.MemberId, bc.UserId });
modelBuilder.Entity<UserMember>() modelBuilder.Entity<UserMember>()
@ -35,7 +39,6 @@ namespace UserService.DatabaseLayer.DataModels
.HasOne(bc => bc.Member) .HasOne(bc => bc.Member)
.WithMany(c => c!.Members) .WithMany(c => c!.Members)
.HasForeignKey(bc => bc.MemberId); .HasForeignKey(bc => bc.MemberId);
modelBuilder.Entity<Node>() modelBuilder.Entity<Node>()
.HasMany(c => c.Children) .HasMany(c => c.Children)
.WithOne(e => e.Parent!) .WithOne(e => e.Parent!)
@ -47,6 +50,7 @@ namespace UserService.DatabaseLayer.DataModels
{ {
public static IEnumerable<SecurityGroup> GetSecurityGroups(this User user) public static IEnumerable<SecurityGroup> GetSecurityGroups(this User user)
{ {
if (user == null) throw new ArgumentNullException(nameof(user));
foreach (var userMember in user.MemberOf) foreach (var userMember in user.MemberOf)
{ {
if (userMember.Member is SecurityGroup securityGroup) if (userMember.Member is SecurityGroup securityGroup)
@ -62,6 +66,7 @@ namespace UserService.DatabaseLayer.DataModels
{ {
public static IEnumerable<User> GetUsers(this SecurityGroup securityGroup) public static IEnumerable<User> GetUsers(this SecurityGroup securityGroup)
{ {
if (securityGroup == null) throw new ArgumentNullException(nameof(securityGroup));
foreach (var userMember in securityGroup.Members) foreach (var userMember in securityGroup.Members)
{ {
if (userMember.User is null) continue; if (userMember.User is null) continue;

View File

@ -1,79 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
namespace UserService.DatabaseLayer.DataModels
{
public class OrganizationUnit : Node
{
public Member? Manager { get; set; }
/// <inheritdoc />
public override string ToString()
{
var sb = new StringBuilder();
if (Level != 0)
{
sb.Append("|");
}
sb.Append('-', Level * 4);
return sb + CommonName;
}
}
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() => $"[{GetType().Name}] {Id:D5} {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

@ -9,16 +9,16 @@ using UserService.DatabaseLayer.DataModels;
namespace UserService.DatabaseLayer.Migrations namespace UserService.DatabaseLayer.Migrations
{ {
[DbContext(typeof(UserServiceDbContext))] [DbContext(typeof(UserServiceDbContext))]
[Migration("20200725195658_initial")] [Migration("20200821193933_InitialCreate")]
partial class initial partial class InitialCreate
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "3.1.6"); .HasAnnotation("ProductVersion", "3.1.7");
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.Node", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -47,7 +47,7 @@ namespace UserService.DatabaseLayer.Migrations
b.HasDiscriminator<string>("Discriminator").HasValue("Node"); b.HasDiscriminator<string>("Discriminator").HasValue("Node");
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.UserMember", b =>
{ {
b.Property<int>("MemberId") b.Property<int>("MemberId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -69,9 +69,9 @@ namespace UserService.DatabaseLayer.Migrations
}); });
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Member", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.Member", b =>
{ {
b.HasBaseType("UserService.DatabaseLayer.DataModels.Node"); b.HasBaseType("UserService.Infrastructure.DataModels.Node");
b.Property<string>("EMail") b.Property<string>("EMail")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -79,9 +79,9 @@ namespace UserService.DatabaseLayer.Migrations
b.HasDiscriminator().HasValue("Member"); b.HasDiscriminator().HasValue("Member");
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.OrganizationUnit", b =>
{ {
b.HasBaseType("UserService.DatabaseLayer.DataModels.Node"); b.HasBaseType("UserService.Infrastructure.DataModels.Node");
b.Property<int?>("ManagerId") b.Property<int?>("ManagerId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -127,9 +127,9 @@ namespace UserService.DatabaseLayer.Migrations
}); });
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.SecurityGroup", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.SecurityGroup", b =>
{ {
b.HasBaseType("UserService.DatabaseLayer.DataModels.Member"); b.HasBaseType("UserService.Infrastructure.DataModels.Member");
b.HasDiscriminator().HasValue("SecurityGroup"); b.HasDiscriminator().HasValue("SecurityGroup");
@ -142,9 +142,9 @@ namespace UserService.DatabaseLayer.Migrations
}); });
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.User", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.User", b =>
{ {
b.HasBaseType("UserService.DatabaseLayer.DataModels.Member"); b.HasBaseType("UserService.Infrastructure.DataModels.Member");
b.Property<string>("FirstName") b.Property<string>("FirstName")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -162,36 +162,36 @@ namespace UserService.DatabaseLayer.Migrations
{ {
Id = -7, Id = -7,
CommonName = "holger", CommonName = "holger",
ParentId = -6, ParentId = -2,
IsActive = true IsActive = true
}); });
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.Node", b =>
{ {
b.HasOne("UserService.DatabaseLayer.DataModels.Node", "Parent") b.HasOne("UserService.Infrastructure.DataModels.Node", "Parent")
.WithMany("Children") .WithMany("Children")
.HasForeignKey("ParentId"); .HasForeignKey("ParentId");
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.UserMember", b =>
{ {
b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Member") b.HasOne("UserService.Infrastructure.DataModels.Member", "Member")
.WithMany("Members") .WithMany("Members")
.HasForeignKey("MemberId") .HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("UserService.DatabaseLayer.DataModels.User", "User") b.HasOne("UserService.Infrastructure.DataModels.User", "User")
.WithMany("MemberOf") .WithMany("MemberOf")
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.OrganizationUnit", b =>
{ {
b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Manager") b.HasOne("UserService.Infrastructure.DataModels.Member", "Manager")
.WithMany() .WithMany()
.HasForeignKey("ManagerId"); .HasForeignKey("ManagerId");
}); });

View File

@ -2,7 +2,7 @@
namespace UserService.DatabaseLayer.Migrations namespace UserService.DatabaseLayer.Migrations
{ {
public partial class initial : Migration public partial class InitialCreate : Migration
{ {
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
@ -90,13 +90,13 @@ namespace UserService.DatabaseLayer.Migrations
migrationBuilder.InsertData( migrationBuilder.InsertData(
table: "Node", table: "Node",
columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "EMail" }, columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "EMail", "FirstName", "IsActive", "LastName" },
values: new object[] { -8, "Global Admin", null, "SecurityGroup", -1, null }); values: new object[] { -7, "holger", null, "User", -2, null, null, true, null });
migrationBuilder.InsertData( migrationBuilder.InsertData(
table: "Node", table: "Node",
columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "EMail", "FirstName", "IsActive", "LastName" }, columns: new[] { "Id", "CommonName", "Description", "Discriminator", "ParentId", "EMail" },
values: new object[] { -7, "holger", null, "User", -6, null, null, true, null }); values: new object[] { -8, "Global Admin", null, "SecurityGroup", -1, null });
migrationBuilder.InsertData( migrationBuilder.InsertData(
table: "Node", table: "Node",

View File

@ -14,9 +14,9 @@ namespace UserService.DatabaseLayer.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "3.1.6"); .HasAnnotation("ProductVersion", "3.1.7");
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.Node", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
@ -45,7 +45,7 @@ namespace UserService.DatabaseLayer.Migrations
b.HasDiscriminator<string>("Discriminator").HasValue("Node"); b.HasDiscriminator<string>("Discriminator").HasValue("Node");
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.UserMember", b =>
{ {
b.Property<int>("MemberId") b.Property<int>("MemberId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -67,9 +67,9 @@ namespace UserService.DatabaseLayer.Migrations
}); });
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Member", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.Member", b =>
{ {
b.HasBaseType("UserService.DatabaseLayer.DataModels.Node"); b.HasBaseType("UserService.Infrastructure.DataModels.Node");
b.Property<string>("EMail") b.Property<string>("EMail")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -77,9 +77,9 @@ namespace UserService.DatabaseLayer.Migrations
b.HasDiscriminator().HasValue("Member"); b.HasDiscriminator().HasValue("Member");
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.OrganizationUnit", b =>
{ {
b.HasBaseType("UserService.DatabaseLayer.DataModels.Node"); b.HasBaseType("UserService.Infrastructure.DataModels.Node");
b.Property<int?>("ManagerId") b.Property<int?>("ManagerId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -125,9 +125,9 @@ namespace UserService.DatabaseLayer.Migrations
}); });
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.SecurityGroup", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.SecurityGroup", b =>
{ {
b.HasBaseType("UserService.DatabaseLayer.DataModels.Member"); b.HasBaseType("UserService.Infrastructure.DataModels.Member");
b.HasDiscriminator().HasValue("SecurityGroup"); b.HasDiscriminator().HasValue("SecurityGroup");
@ -140,9 +140,9 @@ namespace UserService.DatabaseLayer.Migrations
}); });
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.User", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.User", b =>
{ {
b.HasBaseType("UserService.DatabaseLayer.DataModels.Member"); b.HasBaseType("UserService.Infrastructure.DataModels.Member");
b.Property<string>("FirstName") b.Property<string>("FirstName")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -160,36 +160,36 @@ namespace UserService.DatabaseLayer.Migrations
{ {
Id = -7, Id = -7,
CommonName = "holger", CommonName = "holger",
ParentId = -6, ParentId = -2,
IsActive = true IsActive = true
}); });
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.Node", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.Node", b =>
{ {
b.HasOne("UserService.DatabaseLayer.DataModels.Node", "Parent") b.HasOne("UserService.Infrastructure.DataModels.Node", "Parent")
.WithMany("Children") .WithMany("Children")
.HasForeignKey("ParentId"); .HasForeignKey("ParentId");
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.UserMember", b =>
{ {
b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Member") b.HasOne("UserService.Infrastructure.DataModels.Member", "Member")
.WithMany("Members") .WithMany("Members")
.HasForeignKey("MemberId") .HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("UserService.DatabaseLayer.DataModels.User", "User") b.HasOne("UserService.Infrastructure.DataModels.User", "User")
.WithMany("MemberOf") .WithMany("MemberOf")
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.OrganizationUnit", b => modelBuilder.Entity("UserService.Infrastructure.DataModels.OrganizationUnit", b =>
{ {
b.HasOne("UserService.DatabaseLayer.DataModels.Member", "Manager") b.HasOne("UserService.Infrastructure.DataModels.Member", "Manager")
.WithMany() .WithMany()
.HasForeignKey("ManagerId"); .HasForeignKey("ManagerId");
}); });

View File

@ -0,0 +1,17 @@
using System.Linq.Expressions;
// ReSharper disable once CheckNamespace
namespace System.Linq
{
public static class Queryable
{
/// <summary>
/// Makes a where filtering, if it is not null.
/// </summary>
public static IQueryable<TSource> WhereOrDefault<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>>? predicate)
{
if (source == null) throw new ArgumentNullException(nameof(source));
return predicate is null ? source : source.Where(predicate);
}
}
}

View File

@ -1,55 +1,57 @@
using System; #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading; 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
{ {
public class BaseRepository<T> where T : Node public class BaseRepository<T> where T : Node
{ {
protected readonly Func<UserServiceDbContext, DbSet<T>> _context; protected Func<UserServiceDbContext, DbSet<T>> Context { get; }
protected BaseRepository(Func<UserServiceDbContext, DbSet<T>> context) protected BaseRepository(Func<UserServiceDbContext, DbSet<T>> context)
{ {
_context = context; Context = context;
} }
public virtual async Task<IReadOnlyList<T>> GetAllAsync(CancellationToken token = default) public virtual async Task<IReadOnlyList<T>> GetAllAsync(Expression<Func<T, bool>>? predicate = null, CancellationToken token = default)
{ {
await using var db = new UserServiceDbContext(); await using var db = new UserServiceDbContext();
return await _context(db).Include(x => x.Parent).ToListAsync(token); return await Context(db).Include(x => x.Parent).WhereOrDefault(predicate).ToListAsync(token).ConfigureAwait(false);
} }
public async Task<T?> GetAsync(Expression<Func<T, bool>> predicate, CancellationToken token = default) public async Task<T?> GetAsync(Expression<Func<T, bool>> predicate, CancellationToken token = default)
{ {
await using var db = new UserServiceDbContext(); await using var db = new UserServiceDbContext();
return await _context(db).FirstOrDefaultAsync(predicate, token); return await Context(db).FirstOrDefaultAsync(predicate, token).ConfigureAwait(false);
} }
public async Task AddAsync(T entity, CancellationToken token = default) public async Task AddAsync(T entity, CancellationToken token = default)
{ {
await using var db = new UserServiceDbContext(); await using var db = new UserServiceDbContext();
await _context(db).AddAsync(@entity, token); await Context(db).AddAsync(@entity, token).ConfigureAwait(false);
await db.SaveChangesAsync(token); await db.SaveChangesAsync(token).ConfigureAwait(false);
} }
public async Task UpdateAsync(T entity, CancellationToken token = default) public async Task<bool> UpdateAsync(T entity, CancellationToken token = default)
{ {
await using var db = new UserServiceDbContext(); await using var db = new UserServiceDbContext();
_context(db).Update(entity); Context(db).Update(entity);
await db.SaveChangesAsync(token); var items= await db.SaveChangesAsync(token).ConfigureAwait(false);
return items > 0;
} }
public async Task DeleteAsync(T entity, CancellationToken token = default) public async Task DeleteAsync(T entity, CancellationToken token = default)
{ {
await using var db = new UserServiceDbContext(); await using var db = new UserServiceDbContext();
_context(db).Remove(entity); Context(db).Remove(entity);
await db.SaveChangesAsync(token); await db.SaveChangesAsync(token).ConfigureAwait(false);
} }
} }
} }

View File

@ -1,18 +1,19 @@
using System; #nullable enable
using System;
using System.Collections.Generic; 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
{ {
public interface IRepository<T> where T : Node public interface IRepository<T> where T : Node
{ {
Task<IReadOnlyList<T>> GetAllAsync(CancellationToken token = default); Task<IReadOnlyList<T>> GetAllAsync(Expression<Func<T, bool>>? predicate = null, CancellationToken token = default);
Task<T?> GetAsync(Expression<Func<T, bool>> predicate, CancellationToken token = default); Task<T?> GetAsync(Expression<Func<T, bool>> predicate, CancellationToken token = default);
Task AddAsync(T entity, CancellationToken token = default); Task AddAsync(T entity, CancellationToken token = default);
Task UpdateAsync(T entity, CancellationToken token = default); Task<bool> UpdateAsync(T entity, CancellationToken token = default);
Task DeleteAsync(T entity, CancellationToken token = default); Task DeleteAsync(T entity, CancellationToken token = default);
} }

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using UserService.DatabaseLayer.DataModels;
using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.Repository
{
public class OrganizationUnitsRepository : BaseRepository<OrganizationUnit>, IOrganizationUnitsRepository
{
public OrganizationUnitsRepository() : base(x => x.OrganizationUnits)
{
}
/// <inheritdoc cref="GetAllAsync" />
public override async Task<IReadOnlyList<OrganizationUnit>> GetAllAsync(
Expression<Func<OrganizationUnit, bool>>? predicate = null, CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
var result = new List<OrganizationUnit>();
var rootOus = await Context(db)
.Include(x => x.Parent)
.WhereOrDefault(predicate)
.ToListAsync(cancellationToken: token).ConfigureAwait(false);
IEnumerable<OrganizationUnit> Rec(Node node)
{
if (!(node is OrganizationUnit organizationUnit)) yield break;
yield return organizationUnit;
foreach (var ouChild in rootOus.Where(x => x.ParentId != null && x.ParentId == organizationUnit.Id))
{
foreach (var unit in Rec(ouChild))
{
yield return unit;
}
}
}
foreach (var ou in rootOus.Where(x => x.ParentId is null))
{
result.AddRange(Rec(ou));
}
return result;
}
}
}

View File

@ -1,121 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using UserService.DatabaseLayer.DataModels;
namespace UserService.DatabaseLayer.Repository
{
public class OrganizationUnitsRepository : BaseRepository<OrganizationUnit>, IOrganizationUnitsRepository
{
public OrganizationUnitsRepository() : base(x => x.OrganizationUnits)
{
}
/// <inheritdoc />
public override async Task<IReadOnlyList<OrganizationUnit>> GetAllAsync(CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
var result = new List<OrganizationUnit>();
var rootOus = await _context(db)
.Include(x => x.Parent)
.ToListAsync(token);
IEnumerable<OrganizationUnit> Rec(Node node)
{
if (!(node is OrganizationUnit organizationUnit)) yield break;
yield return organizationUnit;
foreach (var ouChild in rootOus.Where(x=>x.ParentId != null && x.ParentId == organizationUnit.Id))
{
foreach (var unit in Rec(ouChild))
{
yield return unit;
}
}
}
foreach (var ou in rootOus.Where(x=> x.ParentId is null))
{
result.AddRange(Rec(ou));
}
return result;
}
}
public class SecurityGroupsRepository : BaseRepository<SecurityGroup>, ISecurityGroupsRepository
{
public SecurityGroupsRepository() : base(x=> x.SecurityGroups)
{
}
}
public class UsersRepository : BaseRepository<User>, IUsersRepository
{
public UsersRepository() : base(x => x.Users)
{
}
}
public class NodesRepository : INodesRepository
{
public NodesRepository()
{
}
public static async IAsyncEnumerable<Node> GetNodesAsync([EnumeratorCancellation] CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
await foreach (var note in db.OrganizationUnits.AsAsyncEnumerable().WithCancellation(token))
{
yield return note;
}
await foreach (var node in db.SecurityGroups.AsAsyncEnumerable().WithCancellation(token))
{
yield return node;
}
await foreach (var node in db.Users.AsAsyncEnumerable().WithCancellation(token))
{
yield return node;
}
}
public async Task<IReadOnlyList<Node>> GetAllAsync(CancellationToken token = default)
{
var list = new List<Node>();
await foreach (var node in GetNodesAsync(token))
{
list.Add(node);
}
return list;
}
public Task<Node?> GetAsync(Expression<Func<Node, bool>> predicate, CancellationToken token = default)
{
throw new NotImplementedException();
}
public Task AddAsync(Node entity, CancellationToken token = default)
{
throw new NotImplementedException();
}
public Task UpdateAsync(Node entity, CancellationToken token = default)
{
throw new NotImplementedException();
}
public Task DeleteAsync(Node entity, CancellationToken token = default)
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,11 @@
using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.Repository
{
public class SecurityGroupsRepository : BaseRepository<SecurityGroup>, ISecurityGroupsRepository
{
public SecurityGroupsRepository() : base(x => x.SecurityGroups)
{
}
}
}

View File

@ -0,0 +1,11 @@
using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.Repository
{
public class UsersRepository : BaseRepository<User>, IUsersRepository
{
public UsersRepository() : base(x => x.Users)
{
}
}
}

View File

@ -2,16 +2,24 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>8</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.6"> <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UserService.Infrastructure\UserService.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace UserService.Infrastructure.DataModels
{
public abstract class Member : Node
{
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,20 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace UserService.Infrastructure.DataModels
{
public class User : Member
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public bool IsActive { get; set; }
[EmailAddress]
public string? EMail { 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,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UserService.Infrastructure.DataModels;
namespace UserService.Infrastructure
{
public static class NodeExtensions
{
public static void MapFields(this User user, Dictionary<string, object> values)
{
if (user == null) throw new ArgumentNullException(nameof(user));
if (values == null) throw new ArgumentNullException(nameof(values));
var properties = user.GetType().GetProperties();
foreach (var keyValuePair in values)
{
var propertyInfo = properties.FirstOrDefault(x => x.Name == keyValuePair.Key);
if (propertyInfo == null) continue;
propertyInfo.SetValue(user, keyValuePair.Value);
}
}
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</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, IEnumerable<Member> users)
{
if (string.IsNullOrEmpty(commonName)) return false;
return users.All(x => x.CommonName != commonName);
}
}
}

View File

@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NUnit.Framework; using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UserService.DatabaseLayer.DataModels; using UserService.DatabaseLayer.DataModels;
using UserService.Infrastructure.DataModels;
namespace UserService.Test namespace UserService.Test
{ {
@ -21,10 +20,10 @@ namespace UserService.Test
public async Task Test1() public async Task Test1()
{ {
await using var db = new UserServiceDbContext(); await using var db = new UserServiceDbContext();
var user = await db.Users.FirstOrDefaultAsync(); var user = await db.Users.FirstOrDefaultAsync().ConfigureAwait(false);
var secGroup = await db.SecurityGroups.FirstOrDefaultAsync(); var secGroup = await db.SecurityGroups.FirstOrDefaultAsync().ConfigureAwait(false);
var ous = await db.OrganizationUnits.ToListAsync(); var ous = await db.OrganizationUnits.ToListAsync().ConfigureAwait(false);
var mo = await db.UserMembers.ToListAsync(); var mo = await db.UserMembers.ToListAsync().ConfigureAwait(false);
var securityGroupsOfUser = user.GetSecurityGroups(); var securityGroupsOfUser = user.GetSecurityGroups();
var usersOfSecurityGroup = secGroup.GetUsers(); var usersOfSecurityGroup = secGroup.GetUsers();
@ -40,12 +39,11 @@ namespace UserService.Test
public async Task Test2() public async Task Test2()
{ {
await using var db = new UserServiceDbContext(); await using var db = new UserServiceDbContext();
var ous = await db.OrganizationUnits.ToListAsync(); var ous = await db.OrganizationUnits.ToListAsync().ConfigureAwait(false);
var sb = new StringBuilder(); var sb = new StringBuilder();
NewMethod(ous, null, 0, ref sb); NewMethod(ous, null, 0, ref sb);
var result = sb.ToString(); var result = sb.ToString();
Assert.Pass(); Assert.Pass();
} }
private static void NewMethod(IEnumerable<OrganizationUnit> ous, Node parent, int level, ref StringBuilder sb) private static void NewMethod(IEnumerable<OrganizationUnit> ous, Node parent, int level, ref StringBuilder sb)

View File

@ -1,15 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="nunit" Version="3.12.0" /> <PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

Binary file not shown.

View File

@ -9,6 +9,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Test", "UserSer
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.DatabaseLayer", "UserService.DatabaseLayer\UserService.DatabaseLayer.csproj", "{4505C991-7E39-416F-94E5-D906DD0D90F9}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.DatabaseLayer", "UserService.DatabaseLayer\UserService.DatabaseLayer.csproj", "{4505C991-7E39-416F-94E5-D906DD0D90F9}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A8D411B0-CD71-4448-9D4F-12898D0CCDD6}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
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
@ -27,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

@ -1,5 +1,4 @@
 <CascadingAuthenticationState>
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly"> <Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData"> <Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />

View File

@ -1,11 +1,12 @@
@page "/directory" @page "/directory"
@using UserService.DatabaseLayer.DataModels @inherits DirectoryBase
@using UserService.DatabaseLayer.Repository
@inject IOrganizationUnitsRepository OuRepository
<h1>TODO</h1> <Row>
<Column ColumnSize="ColumnSize.Is12">
@if (_organizationUnits == null) <h1>TODO</h1>
</Column>
</Row>
@if (OrganizationUnits == null)
{ {
<p> <p>
<em>Loading...</em> <em>Loading...</em>
@ -13,17 +14,20 @@
} }
else else
{ {
<Row>
<Column ColumnSize="ColumnSize.Is4">
} <TreeView Nodes="OrganizationUnits"
@code { GetChildNodes="@(item => item?.Children)"
private IReadOnlyList<OrganizationUnit> _organizationUnits; HasChildNodes="@(item => item?.Children?.Any() == true)"
private OrganizationUnit _selectedOu; @bind-SelectedNode="@SelectedNode">
<NodeContent>
protected override async Task OnInitializedAsync() <Icon Name="IconName.Folder" />
{ @context?.CommonName
_organizationUnits = (await OuRepository.GetAllAsync().ConfigureAwait(false)).Where(x=> x.Parent is null).ToList(); </NodeContent>
} </TreeView>
</Column>
<Column ColumnSize="ColumnSize.Is4">
</Column>
</Row>
} }

View File

@ -0,0 +1,47 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using UserService.DatabaseLayer.Repository;
using UserService.Infrastructure.DataModels;
namespace UserService.Pages
{
public class DirectoryBase : ComponentBase
{
private Node? _selectedNode;
public IReadOnlyList<Node>? OrganizationUnits { get; set; }
public Node? SelectedNode
{
get => _selectedNode;
set
{
if(Equals(_selectedNode, value)) return;
_selectedNode = value;
OnSelectedNodeChanged(value);
}
}
private async void OnSelectedNodeChanged(Node? value)
{
if (value == null) return;
Members = await UsersRepository.GetAllAsync(x => x.ParentId == value.Id).ConfigureAwait(false);
}
public IReadOnlyList<Member>? Members { get; set; }
[Inject] public IOrganizationUnitsRepository OuRepository { get; set; } = null!;
[Inject] public IUsersRepository UsersRepository { get; set; } = null!;
protected override async Task OnInitializedAsync()
{
OrganizationUnits = (await OuRepository.GetAllAsync().ConfigureAwait(false))
.Where(x => x.Parent is null)
.ToList();
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Blazorise;
using Blazorise.DataGrid;
using Microsoft.AspNetCore.Components;
using UserService.Infrastructure;
using UserService.Infrastructure.DataModels;
namespace UserService.Pages
{
public abstract class MembersBase<T> : ComponentBase where T : Member
{
protected IReadOnlyList<OrganizationUnit>? OrganizationUnits { get; set; }
protected IReadOnlyList<T>? Members { get; set; }
protected string? CustomFilterValue { get; set; }
protected abstract Task RowInsertingCallback(CancellableRowChange<T, Dictionary<string, object>> arg);
protected abstract Task RowInsertedCallback(SavedRowItem<T, Dictionary<string, object>> arg);
protected abstract Task RowDeletingCallback(CancellableRowChange<T> arg);
protected abstract Task RowUpdatingCallback(CancellableRowChange<T, Dictionary<string, object>> arg);
protected abstract bool OnCustomFilter(T model);
protected void ValidateCommonName(ValidatorEventArgs e)
{
if (e == null) throw new ArgumentNullException(nameof(e));
var commonName = e.Value?.ToString();
var validationResult = Validators.ValidateCommonName(commonName, Members ?? Enumerable.Empty<Member>());
e.Status = validationResult == false ? ValidationStatus.Error : ValidationStatus.Success;
}
}
}

View File

@ -1,62 +0,0 @@
@page "/organizationUnits"
@using UserService.DatabaseLayer.DataModels
@inherits OrganizationUnitsBase
<h1>List of all organization units</h1>
@if (OrganizationUnits is null)
{
<p>
<em>Loading...</em>
</p>
}
else
{
<MatTable Items="@OrganizationUnits" class="mat-elevation-z5">
<MatTableHeader>
<th style="width: 20%">Common Name</th>
<th style="width: 20%">Description</th>
<th style="width: 20%">Parent</th>
<th style="width: auto">Manager</th>
<th style="width: auto"> </th>
<th style="width: auto"> </th>
</MatTableHeader>
<MatTableRow>
<td>@context.CommonName</td>
<td>@context.Description</td>
<td>@context.Parent</td>
<td>@context.Manager</td>
<td>
<a href="organizationUnits" @onclick="@(e => Edit(context))">edit</a>
</td>
<td>
<a href="organizationUnits" @onclick="@(e => Delete(context))">delete</a>
</td>
</MatTableRow>
</MatTable>
<MatButton @onclick="@(e => Edit(new OrganizationUnit()))">Create organization unit</MatButton>
}
<MatDialog @bind-IsOpen="@DialogIsOpen">
@if (OuToEdit != null)
{
<MatDialogTitle>@(OuToEdit.Id == 0 ? "New" : "Edit") @OuToEdit.CommonName (@OuToEdit.Id)</MatDialogTitle>
<MatDialogContent>
<MatTextField Label="Common name" @bind-Value="@OuToEdit.CommonName" ReadOnly="@(OuToEdit.Id != 0)"/>
<p/>
<MatTextField Label="Description" @bind-Value="@OuToEdit.Description"/>
@*<MatTextField Label="Manager" @bind-Value="@OuToEdit.Manager"/>*@
<p/>
<MatSelectItem Items="@OrganizationUnits" Label="Parent" @bind-Value="@OuToEdit.Parent"/>
</MatDialogContent>
}
else
{
<MatDialogTitle>No securityGroup selected</MatDialogTitle>
}
<MatDialogActions>
<MatButton OnClick="@(e => { DialogIsOpen = false; })">No Thanks</MatButton>
<MatButton OnClick="@OkClick">OK</MatButton>
</MatDialogActions>
</MatDialog>

View File

@ -1,41 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using UserService.DatabaseLayer.DataModels;
using UserService.DatabaseLayer.Repository;
namespace UserService.Pages
{
public class OrganizationUnitsBase : ComponentBase
{
[Inject] private IOrganizationUnitsRepository OrganizationUnitsRepository { get; set; } = null!;
protected bool DialogIsOpen { get; set; }
protected OrganizationUnit? OuToEdit { get; private set; }
protected IReadOnlyList<OrganizationUnit>? OrganizationUnits { get; private set; }
protected override async Task OnInitializedAsync()
{
OrganizationUnits = await OrganizationUnitsRepository.GetAllAsync().ConfigureAwait(false);
}
protected void Edit(OrganizationUnit organizationUnit)
{
DialogIsOpen = true;
OuToEdit = organizationUnit;
}
protected async Task OkClick()
{
if (OuToEdit is null) return;
await OrganizationUnitsRepository.UpdateAsync(OuToEdit).ConfigureAwait(false);
DialogIsOpen = false;
}
protected async Task Delete(OrganizationUnit organizationUnit)
{
await OrganizationUnitsRepository.DeleteAsync(organizationUnit).ConfigureAwait(false);
}
}
}

View File

@ -1,59 +1,87 @@
@page "/securitygroups" @page "/securitygroups"
@using UserService.DatabaseLayer.DataModels @using UserService.Infrastructure.DataModels
@using UserService.DatabaseLayer.Repository
@inherits SecurityGroupsBase @inherits SecurityGroupsBase
<h1>Table of all security groups</h1> <h1>Table of all security groups</h1>
@if (SecurityGroups is null) @if (Members is null)
{ {
<p> <p>
<em>Loading...</em> <em>Loading...</em>
</p> </p>
} }
else else
{ {
<MatTable Items="@SecurityGroups" class="mat-elevation-z5"> <TextEdit Placeholder="Search" Size="Size.Large" @bind-Text="@CustomFilterValue" />
<MatTableHeader>
<th style="width: 30%">Common Name</th>
<th style="width: 20%">Description</th>
<th style="width: 20%">E-Mail</th>
<th style="width: 20%">Parent</th>
<th style="width: auto"> </th>
<th style="width: auto"> </th>
</MatTableHeader>
<MatTableRow>
<td>@context.CommonName</td>
<td>@context.Description</td>
<td>@context.EMail</td>
<td>@context.Parent</td>
<td><a href="securitygroups" @onclick="@(e => EditSecurityGroup(context))">edit</a></td>
<td><a href="securitygroups" @onclick="@(e => DeleteSecurityGroup(context))">delete</a></td>
</MatTableRow> <DataGrid
</MatTable> TItem="SecurityGroup"
<MatButton @onclick="@(e => EditSecurityGroup(new SecurityGroup()))">Create new group</MatButton> RowSelectable="@(u => false)"
CustomFilter="@OnCustomFilter"
Sortable="true"
Editable="true"
EditMode="DataGridEditMode.Inline"
RowRemoving="RowDeletingCallback"
Data="@Members"
RowInserted="RowInsertedCallback"
RowInserting="RowInsertingCallback"
RowUpdating="RowUpdatingCallback">
<DataGridCommandColumn TItem="SecurityGroup">
<NewCommandTemplate>
<Button Color="Color.Success" Clicked="@context.Clicked" title="Create security group">
<i class="fas fa-users"></i>
</Button>
</NewCommandTemplate>
<EditCommandTemplate>
<Button Color="Color.Primary" Clicked="@context.Clicked" title="Edit security group">
<i class="fa fa-user-edit"></i>
</Button>
</EditCommandTemplate>
<DeleteCommandTemplate>
<Button Color="Color.Danger" Clicked="@context.Clicked" title="Delete security group">
<i class="fa fa-user-minus"></i>
</Button>
</DeleteCommandTemplate>
<SaveCommandTemplate>
<Button Color="Color.Success" Clicked="@context.Clicked" title="Save security group">
<i class="fas fa-save"></i>
</Button>
</SaveCommandTemplate>
<CancelCommandTemplate>
<Button Color="Color.Danger" Clicked="@context.Clicked" title="Cancel editing">
<i class="far fa-times-circle"></i>
</Button>
</CancelCommandTemplate>
</DataGridCommandColumn>
<DataGridColumn TItem="SecurityGroup" Field="@nameof(SecurityGroup.Id)" Caption="#" Sortable="false" />
<DataGridColumn TItem="SecurityGroup" Field="@nameof(SecurityGroup.CommonName)" Caption="CN" CellsEditableOnEditCommand="false" Editable="true">
<EditTemplate>
<Validation Validator="@ValidateCommonName">
<TextEdit Placeholder="Enter common name" Text="@((string)(((CellEditContext)context).CellValue))" TextChanged="@(v=>((CellEditContext)context).CellValue=v)">
<Feedback>
<ValidationSuccess></ValidationSuccess>
<ValidationError>Please enter a valid common name!</ValidationError>
</Feedback>
</TextEdit>
</Validation>
</EditTemplate>
</DataGridColumn>
<DataGridSelectColumn TItem="SecurityGroup" Field="@nameof(SecurityGroup.ParentId)" Caption="Parent" Editable="true">
<DisplayTemplate>
@{
var name = ((SecurityGroup) context ).Parent?.CommonName ?? "-";
@name
}
</DisplayTemplate>
<EditTemplate>
<Select TValue="int?" SelectedValue="@((int?)(context.CellValue))" SelectedValueChanged="@(v => context.CellValue = v)" >
@foreach (var item in OrganizationUnits ?? Enumerable.Empty<OrganizationUnit>())
{
<SelectItem TValue="int" Value="@(item.Id)">@item.CommonName</SelectItem>
}
</Select>
</EditTemplate>
</DataGridSelectColumn>
</DataGrid>
} }
<MatDialog @bind-IsOpen="@DialogIsOpen">
@if (SecurityGroupToEdit != null)
{
<MatDialogTitle>@(SecurityGroupToEdit.Id == 0 ? "New" : "Edit") @SecurityGroupToEdit.CommonName (@SecurityGroupToEdit.Id)</MatDialogTitle>
<MatDialogContent>
<MatTextField Label="Common name" @bind-Value="@SecurityGroupToEdit.CommonName" ReadOnly="@(SecurityGroupToEdit.Id != 0)"></MatTextField>
<p />
<MatTextField Label="Description" @bind-Value="@SecurityGroupToEdit.Description"></MatTextField>
<MatTextField Label="E-Mail" @bind-Value="@SecurityGroupToEdit.EMail"></MatTextField>
<p />
<MatSelectItem Items="@OrganizationUnits" Label="Parent" @bind-Value="@SecurityGroupToEdit.Parent"></MatSelectItem>
</MatDialogContent>
}
else
{
<MatDialogTitle>No securityGroup selected</MatDialogTitle>
}
<MatDialogActions>
<MatButton OnClick="@(e => { DialogIsOpen = false; })">No Thanks</MatButton>
<MatButton OnClick="@OkClick">OK</MatButton>
</MatDialogActions>
</MatDialog>

View File

@ -1,47 +1,49 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Blazorise.DataGrid;
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
{ {
public class SecurityGroupsBase : ComponentBase public class SecurityGroupsBase : MembersBase<SecurityGroup>
{ {
[Inject] private ISecurityGroupsRepository? SecurityGroupsRepository { get; set; } [Inject] private ISecurityGroupsRepository SecurityGroupsRepository { get; set; } = null!;
[Inject] private IOrganizationUnitsRepository? OrganizationUnitsRepository { get; set; } [Inject] private IOrganizationUnitsRepository OrganizationUnitsRepository { get; set; } = null!;
protected bool DialogIsOpen;
protected SecurityGroup? SecurityGroupToEdit;
protected IReadOnlyList<SecurityGroup>? SecurityGroups;
protected IReadOnlyList<OrganizationUnit>? OrganizationUnits;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
SecurityGroups = await SecurityGroupsRepository.GetAllAsync().ConfigureAwait(false); Members = await SecurityGroupsRepository.GetAllAsync().ConfigureAwait(false);
OrganizationUnits = await OrganizationUnitsRepository.GetAllAsync().ConfigureAwait(false); OrganizationUnits = await OrganizationUnitsRepository.GetAllAsync().ConfigureAwait(false);
} }
protected void EditSecurityGroup(SecurityGroup securityGroup) /// <inheritdoc />
protected override async Task RowInsertingCallback(CancellableRowChange<SecurityGroup, Dictionary<string, object>> arg)
{ {
SecurityGroupToEdit = securityGroup;
DialogIsOpen = true;
} }
protected async Task OkClick() /// <inheritdoc />
protected override async Task RowInsertedCallback(SavedRowItem<SecurityGroup, Dictionary<string, object>> arg)
{ {
if (!(SecurityGroupToEdit is null))
{
await SecurityGroupsRepository.UpdateAsync(SecurityGroupToEdit).ConfigureAwait(false);
} }
DialogIsOpen = false; /// <inheritdoc />
protected override async Task RowDeletingCallback(CancellableRowChange<SecurityGroup> arg)
{
} }
protected async Task DeleteSecurityGroup(SecurityGroup securityGroup) /// <inheritdoc />
protected override async Task RowUpdatingCallback(CancellableRowChange<SecurityGroup, Dictionary<string, object>> arg)
{ {
await SecurityGroupsRepository.DeleteAsync(securityGroup).ConfigureAwait(false); }
/// <inheritdoc />
protected override bool OnCustomFilter(SecurityGroup model)
{
return true;
} }
} }
} }

View File

@ -1,10 +1,10 @@
@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>
@if (Users == null) @if (Members == null)
{ {
<p> <p>
<em>Loading...</em> <em>Loading...</em>
@ -12,55 +12,92 @@
} }
else else
{ {
<MatTable Items="@Users" class="mat-elevation-z5"> <TextEdit Placeholder="Search" Size="Size.Large" @bind-Text="@CustomFilterValue" />
<MatTableHeader>
<th style="width: 20%">Common Name</th>
<th style="width: 30%">Full name</th>
<th style="width: 20%">Description</th>
<th style="width: 20%">E-Mail</th>
<th style="width: 20%">Parent</th>
<th style="width: auto">Is active</th>
<th style="width: auto"> </th>
<th style="width: auto"> </th>
</MatTableHeader>
<MatTableRow>
<td>@context.CommonName</td>
<td>@context.FullName</td>
<td>@context.Description</td>
<td>@context.EMail</td>
<td>@context.Parent</td>
<td>@context.IsActive</td>
<td><a href="users" @onclick="@(e => EditUser(context))">edit</a></td>
<td><a href="users" @onclick="@(e => DeleteUser(context))">delete</a></td>
</MatTableRow>
</MatTable>
<MatButton @onclick="@(e => EditUser(new User()))">Create user</MatButton> <DataGrid
TItem="User"
RowSelectable="@(u => false)"
CustomFilter="@OnCustomFilter"
Sortable="true"
Editable="true"
EditMode="DataGridEditMode.Inline"
RowRemoving="RowDeletingCallback"
Data="@Members"
RowInserted="RowInsertedCallback"
RowInserting="RowInsertingCallback"
RowUpdating="RowUpdatingCallback">
<DataGridCommandColumn TItem="User">
<NewCommandTemplate>
<Button Color="Color.Success" Clicked="@context.Clicked" title="Create user">
<i class="fa fa-user-plus"></i>
</Button>
</NewCommandTemplate>
<EditCommandTemplate>
<Button Color="Color.Primary" Clicked="@context.Clicked" title="Edit user">
<i class="fa fa-user-edit"></i>
</Button>
</EditCommandTemplate>
<DeleteCommandTemplate>
<Button Color="Color.Danger" Clicked="@context.Clicked" title="Delete user">
<i class="fa fa-user-minus"></i>
</Button>
</DeleteCommandTemplate>
<SaveCommandTemplate>
<Button Color="Color.Success" Clicked="@context.Clicked" title="Save user">
<i class="fas fa-save"></i>
</Button>
</SaveCommandTemplate>
<CancelCommandTemplate>
<Button Color="Color.Danger" Clicked="@context.Clicked" title="Cancel editing">
<i class="far fa-times-circle"></i>
</Button>
</CancelCommandTemplate>
</DataGridCommandColumn>
<DataGridColumn TItem="User" Field="@nameof(User.Id)" Caption="#" Sortable="false" />
<DataGridColumn TItem="User" Field="@nameof(User.CommonName)" Caption="CN" CellsEditableOnEditCommand="false" Editable="true">
<EditTemplate>
<Validation Validator="@ValidateCommonName">
<TextEdit Placeholder="Enter common name" Text="@((string)(((CellEditContext)context).CellValue))" TextChanged="@(v=>((CellEditContext)context).CellValue=v)">
<Feedback>
<ValidationSuccess></ValidationSuccess>
<ValidationError>Please enter a valid common name!</ValidationError>
</Feedback>
</TextEdit>
</Validation>
</EditTemplate>
</DataGridColumn>
<DataGridColumn TItem="User" Field="@nameof(User.FirstName)" Caption="First Name" Editable="true" />
<DataGridColumn TItem="User" Field="@nameof(User.LastName)" Caption="Last Name" Editable="true"/>
<DataGridColumn TItem="User" Field="@nameof(User.EMail)" Caption="EMail" Editable="true">
<EditTemplate>
<Validation Validator="@ValidateEmail">
<TextEdit Placeholder="Enter email" Text="@((string)(((CellEditContext)context).CellValue))" TextChanged="@(v=>((CellEditContext)context).CellValue=v)">
<Feedback>
<ValidationNone>Please enter the email.</ValidationNone>
<ValidationSuccess>Email is ok.</ValidationSuccess>
<ValidationError>Enter valid email!</ValidationError>
</Feedback>
</TextEdit>
</Validation>
</EditTemplate>
</DataGridColumn>
<DataGridSelectColumn TItem="User" Field="@nameof(User.ParentId)" Caption="Parent" Editable="true">
<DisplayTemplate>
@{
var name = ((User) context ).Parent?.CommonName ?? "-";
@name
}
</DisplayTemplate>
<EditTemplate>
<Select TValue="int?" SelectedValue="@((int?)(context.CellValue))" SelectedValueChanged="@(v => context.CellValue = v)" >
@foreach (var item in OrganizationUnits ?? Enumerable.Empty<OrganizationUnit>())
{
<SelectItem TValue="int" Value="@(item.Id)">@item.CommonName</SelectItem>
}
</Select>
</EditTemplate>
</DataGridSelectColumn>
<DataGridCheckColumn TItem="User" Field="@nameof(User.IsActive)" Caption="Active" Editable="true" />
</DataGrid>
} }
<MatDialog @bind-IsOpen="@DialogIsOpen">
@if (UserToEdit != null)
{
<MatDialogTitle>@(UserToEdit.Id == 0 ? "New" : "Edit") @UserToEdit.CommonName (@UserToEdit.Id)</MatDialogTitle>
<MatDialogContent>
<MatTextField Label="Common name" @bind-Value="@UserToEdit.CommonName" ReadOnly="@(UserToEdit.Id != 0)" />
<MatSlideToggle Label="Is active" @bind-Value="@UserToEdit.IsActive" />
<p />
<MatTextField Label="First name" @bind-Value="@UserToEdit.FirstName" />
<MatTextField Label="Last name" @bind-Value="@UserToEdit.LastName" />
<p />
<MatTextField Label="Description" @bind-Value="@UserToEdit.Description" />
<MatTextField Label="E-Mail" @bind-Value="@UserToEdit.EMail" />
<p />
<MatSelectItem Items="@OrganizationUnits" TItem="Node" Label="Parent" @bind-Value="@UserToEdit.Parent" />
</MatDialogContent>
}
else
{
<MatDialogTitle>No securityGroup selected</MatDialogTitle>
}
<MatDialogActions>
<MatButton OnClick="@(e => { DialogIsOpen = false; })">No Thanks</MatButton>
<MatButton OnClick="@OkClick">OK</MatButton>
</MatDialogActions>
</MatDialog>

View File

@ -1,44 +1,99 @@
using System.Collections.Generic; using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using UserService.DatabaseLayer.DataModels; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Blazorise;
using Blazorise.DataGrid;
using Microsoft.JSInterop;
using UserService.DatabaseLayer.Repository; using UserService.DatabaseLayer.Repository;
using UserService.Infrastructure;
using UserService.Infrastructure.DataModels;
namespace UserService.Pages namespace UserService.Pages
{ {
public class UsersBase : ComponentBase public class UsersBase : MembersBase<User>
{ {
[Inject] private IUsersRepository UsersRepository { get; set; } = null!; [Inject] private IUsersRepository UsersRepository { get; set; } = null!;
[Inject] private IOrganizationUnitsRepository OrganizationUnitsRepository { get; set; } = null!; [Inject] private IOrganizationUnitsRepository OrganizationUnitsRepository { get; set; } = null!;
protected bool DialogIsOpen { get; set; } [Inject] private IJSRuntime JsRuntime { get; set; } = null!;
protected User? UserToEdit { get; private set; }
protected IReadOnlyList<User>? Users { get; private set; }
protected IReadOnlyList<OrganizationUnit>? OrganizationUnits { get; private set; }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
Users = await UsersRepository.GetAllAsync(); Members = await UsersRepository.GetAllAsync().ConfigureAwait(false);
OrganizationUnits = await OrganizationUnitsRepository.GetAllAsync().ConfigureAwait(false); OrganizationUnits = await OrganizationUnitsRepository.GetAllAsync().ConfigureAwait(false);
} }
protected void EditUser(User user) protected override async Task RowInsertingCallback(CancellableRowChange<User, Dictionary<string, object>> arg)
{ {
DialogIsOpen = true; if (arg is null) throw new ArgumentNullException(nameof(arg));
UserToEdit = user; var mailValidation = Validators.ValidateEmail(arg.Values[nameof(User.EMail)]?.ToString());
var commonNameValidation = Validators.ValidateCommonName(arg.Values[nameof(User.CommonName)]?.ToString(),
Members ?? Enumerable.Empty<User>());
if (mailValidation == true && commonNameValidation == true) return;
await JsRuntime.InvokeVoidAsync("alert", "User could not be added").ConfigureAwait(false);
arg.Cancel = true;
} }
protected async Task OkClick() protected override async Task RowInsertedCallback(SavedRowItem<User, Dictionary<string, object>> arg)
{ {
if (UserToEdit is null) return; if (arg is null) throw new ArgumentNullException(nameof(arg));
await UsersRepository.UpdateAsync(UserToEdit).ConfigureAwait(false); var user = arg.Item;
DialogIsOpen = false; await UsersRepository.AddAsync(user).ConfigureAwait(false);
user.Parent = OrganizationUnits?.FirstOrDefault(x => x.Id == user.ParentId);
} }
protected async Task DeleteUser(User user) protected override async Task RowDeletingCallback(CancellableRowChange<User> arg)
{ {
await UsersRepository.DeleteAsync(user).ConfigureAwait(false); if (arg == null) throw new ArgumentNullException(nameof(arg));
var confirmed = await JsRuntime.InvokeAsync<bool>("confirm",
$"You are about to delete the user {arg.Item.FullName}. Are you sure?").ConfigureAwait(false);
if (confirmed)
{
await UsersRepository.DeleteAsync(arg.Item).ConfigureAwait(false);
return;
}
arg.Cancel = true;
}
protected static void ValidateEmail(ValidatorEventArgs e)
{
if (e == null) throw new ArgumentNullException(nameof(e));
var email = e.Value?.ToString();
var validationResult = Validators.ValidateEmail(email);
e.Status = validationResult switch
{
null => ValidationStatus.None,
false => ValidationStatus.Error,
_ => ValidationStatus.Success
};
}
protected override async Task RowUpdatingCallback(CancellableRowChange<User, Dictionary<string, object>> arg)
{
if (arg == null) throw new ArgumentNullException(nameof(arg));
var user = arg.Item;
user.MapFields(arg.Values);
user.Parent = OrganizationUnits?.FirstOrDefault(x => x.Id == (int?) arg.Values[nameof(Node.ParentId)]);
var result = await UsersRepository.UpdateAsync(user).ConfigureAwait(false);
arg.Cancel = !result;
}
protected override bool OnCustomFilter(User model)
{
if (model == null) throw new ArgumentNullException(nameof(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);
} }
} }
} }

View File

@ -8,21 +8,26 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>UserService</title> <title>UserService</title>
<base href="~/"/> <base href="~/" />
<link href="css/site.css" rel="stylesheet" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link href="css/site.css" rel="stylesheet"/> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.12.0/css/all.css">
<script src="_content/MatBlazor/dist/matBlazor.js"></script>
<link href="_content/MatBlazor/dist/matBlazor.css" rel="stylesheet"/> <link href="_content/Blazorise/blazorise.css" rel="stylesheet" />
<link href="_content/Blazorise.Bootstrap/blazorise.bootstrap.css" rel="stylesheet" />
<link href="_content/Blazorise.Sidebar/blazorise.sidebar.css" rel="stylesheet" />
<link href="_content/Blazorise.TreeView/blazorise.treeview.css" rel="stylesheet" />
</head> </head>
<body> <body>
<app> <app>
<component type="typeof(App)" render-mode="ServerPrerendered"/> <component type="typeof(App)" render-mode="ServerPrerendered" />
</app> </app>
<div id="blazor-error-ui"> <div id="blazor-error-ui">
<environment include="Staging,Production"> <environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded. An error has occurred. This application may no longer respond until reloaded.
</environment> </environment>
@ -31,10 +36,13 @@
</environment> </environment>
<a href="" class="reload">Reload</a> <a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a> <a class="dismiss">🗙</a>
</div> </div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="_framework/blazor.server.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="_framework/blazor.server.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<script src="_content/Blazorise/blazorise.js"></script>
<script src="_content/Blazorise.Bootstrap/blazorise.bootstrap.js"></script>
</body> </body>
</html> </html>

View File

@ -1,49 +1,38 @@
<div class="top-row pl-4 navbar navbar-dark"> <Sidebar Data="@_sidebarInfo" />
<a class="navbar-brand" href="">UserService</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="users">
<span class="oi oi-plus" aria-hidden="true"></span> Users
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="securitygroups">
<span class="oi oi-plus" aria-hidden="true"></span> Security groups
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="organizationUnits">
<span class="oi oi-plus" aria-hidden="true"></span> Organization units
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="directory">
<span class="oi oi-plus" aria-hidden="true"></span> Directory
</NavLink>
</li>
</ul>
</div>
@code { @code {
private bool _collapseNavMenu = true; private SidebarInfo _sidebarInfo;
private string NavMenuCssClass => _collapseNavMenu ? "collapse" : null; protected override void OnInitialized()
private void ToggleNavMenu()
{ {
_collapseNavMenu = !_collapseNavMenu; _sidebarInfo = new SidebarInfo
{
Brand = new SidebarBrandInfo
{
Text = "User service",
To = "/"
},
Items = new List<SidebarItemInfo>
{
new SidebarItemInfo
{
Text = "Directory",
Icon = IconName.Folder,
To = "/directory"
},
new SidebarItemInfo
{
Text = "Users",
Icon = IconName.Users,
To = "/users"
},
new SidebarItemInfo
{
Text = "Security groups",
Icon = IconName.Book,
To = "/securitygroups"
}
}
};
} }
} }

View File

@ -1,16 +0,0 @@
<div class="alert alert-secondary mt-4" role="alert">
<span class="oi oi-pencil mr-2" aria-hidden="true"></span>
<strong>@Title</strong>
<span class="text-nowrap">
Please take our
<a target="_blank" class="font-weight-bold" href="https://go.microsoft.com/fwlink/?linkid=2112271">brief survey</a>
</span>
and tell us what you think.
</div>
@code {
// Demonstrates how a parent component can supply parameters
[Parameter]
public string Title { get; set; }
}

View File

@ -1,4 +1,7 @@
using System.Net.Http; using System;
using Blazorise;
using Blazorise.Bootstrap;
using Blazorise.Icons.FontAwesome;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -26,12 +29,19 @@ namespace UserService
services.AddSingleton<IUsersRepository, UsersRepository>(); services.AddSingleton<IUsersRepository, UsersRepository>();
services.AddSingleton<ISecurityGroupsRepository, SecurityGroupsRepository>(); services.AddSingleton<ISecurityGroupsRepository, SecurityGroupsRepository>();
services.AddSingleton<IOrganizationUnitsRepository, OrganizationUnitsRepository>(); services.AddSingleton<IOrganizationUnitsRepository, OrganizationUnitsRepository>();
services.AddScoped<HttpClient>(); services
.AddBlazorise(options =>
{
options.ChangeTextOnKeyPress = true; // optional
})
.AddBootstrapProviders()
.AddFontAwesomeIcons();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{ {
if (app == null) throw new ArgumentNullException(nameof(app));
if (env.IsDevelopment()) if (env.IsDevelopment())
{ {
app.UseDeveloperExceptionPage(); app.UseDeveloperExceptionPage();
@ -48,6 +58,10 @@ namespace UserService
app.UseRouting(); app.UseRouting();
app.ApplicationServices
.UseBootstrapProviders()
.UseFontAwesomeIcons();
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapBlazorHub(); endpoints.MapBlazorHub();

View File

@ -3,14 +3,28 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MatBlazor" Version="2.6.2" /> <None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup> </ItemGroup>
<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>
<PackageReference Include="Blazorise.Bootstrap" Version="0.9.1.2" />
<PackageReference Include="Blazorise.DataGrid" Version="0.9.1.2" />
<PackageReference Include="Blazorise.Icons.FontAwesome" Version="0.9.1.2" />
<PackageReference Include="Blazorise.Sidebar" Version="0.9.1.2" />
<PackageReference Include="Blazorise.TreeView" Version="0.9.1.2" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -7,4 +7,7 @@
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using UserService @using UserService
@using UserService.Shared @using UserService.Shared
@using MatBlazor @using Blazorise
@using Blazorise.Sidebar
@using Blazorise.TreeView
@using Blazorise.DataGrid