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 UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.DataModels
{
@ -7,6 +9,7 @@ namespace UserService.DatabaseLayer.DataModels
{
public static void Seed(this ModelBuilder modelBuilder)
{
if (modelBuilder == null) throw new ArgumentNullException(nameof(modelBuilder));
var groups = new OrganizationUnit { CommonName = "Groups", Id = -1 };
var users = new OrganizationUnit { CommonName = "Users", Id = -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 france = new OrganizationUnit{CommonName = "France" , Id = -3, ParentId = -2 };
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);
var secGroup = new SecurityGroup { CommonName = "Global Admin", Id = -8, ParentId = groups.Id };
modelBuilder.Entity<SecurityGroup>().HasData(secGroup);
@ -25,6 +28,7 @@ namespace UserService.DatabaseLayer.DataModels
public static void CreateRelations(this ModelBuilder modelBuilder)
{
if (modelBuilder == null) throw new ArgumentNullException(nameof(modelBuilder));
modelBuilder.Entity<UserMember>()
.HasKey(bc => new { bc.MemberId, bc.UserId });
modelBuilder.Entity<UserMember>()
@ -35,7 +39,6 @@ namespace UserService.DatabaseLayer.DataModels
.HasOne(bc => bc.Member)
.WithMany(c => c!.Members)
.HasForeignKey(bc => bc.MemberId);
modelBuilder.Entity<Node>()
.HasMany(c => c.Children)
.WithOne(e => e.Parent!)
@ -47,6 +50,7 @@ namespace UserService.DatabaseLayer.DataModels
{
public static IEnumerable<SecurityGroup> GetSecurityGroups(this User user)
{
if (user == null) throw new ArgumentNullException(nameof(user));
foreach (var userMember in user.MemberOf)
{
if (userMember.Member is SecurityGroup securityGroup)
@ -62,6 +66,7 @@ namespace UserService.DatabaseLayer.DataModels
{
public static IEnumerable<User> GetUsers(this SecurityGroup securityGroup)
{
if (securityGroup == null) throw new ArgumentNullException(nameof(securityGroup));
foreach (var userMember in securityGroup.Members)
{
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 UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.DataModels
{

View File

@ -9,16 +9,16 @@ using UserService.DatabaseLayer.DataModels;
namespace UserService.DatabaseLayer.Migrations
{
[DbContext(typeof(UserServiceDbContext))]
[Migration("20200725195658_initial")]
partial class initial
[Migration("20200821193933_InitialCreate")]
partial class InitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
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")
.ValueGeneratedOnAdd()
@ -47,7 +47,7 @@ namespace UserService.DatabaseLayer.Migrations
b.HasDiscriminator<string>("Discriminator").HasValue("Node");
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b =>
modelBuilder.Entity("UserService.Infrastructure.DataModels.UserMember", b =>
{
b.Property<int>("MemberId")
.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")
.HasColumnType("TEXT");
@ -79,9 +79,9 @@ namespace UserService.DatabaseLayer.Migrations
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")
.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");
@ -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")
.HasColumnType("TEXT");
@ -162,36 +162,36 @@ namespace UserService.DatabaseLayer.Migrations
{
Id = -7,
CommonName = "holger",
ParentId = -6,
ParentId = -2,
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")
.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")
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("UserService.DatabaseLayer.DataModels.User", "User")
b.HasOne("UserService.Infrastructure.DataModels.User", "User")
.WithMany("MemberOf")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.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()
.HasForeignKey("ManagerId");
});

View File

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

View File

@ -14,9 +14,9 @@ namespace UserService.DatabaseLayer.Migrations
{
#pragma warning disable 612, 618
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")
.ValueGeneratedOnAdd()
@ -45,7 +45,7 @@ namespace UserService.DatabaseLayer.Migrations
b.HasDiscriminator<string>("Discriminator").HasValue("Node");
});
modelBuilder.Entity("UserService.DatabaseLayer.DataModels.UserMember", b =>
modelBuilder.Entity("UserService.Infrastructure.DataModels.UserMember", b =>
{
b.Property<int>("MemberId")
.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")
.HasColumnType("TEXT");
@ -77,9 +77,9 @@ namespace UserService.DatabaseLayer.Migrations
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")
.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");
@ -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")
.HasColumnType("TEXT");
@ -160,36 +160,36 @@ namespace UserService.DatabaseLayer.Migrations
{
Id = -7,
CommonName = "holger",
ParentId = -6,
ParentId = -2,
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")
.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")
.HasForeignKey("MemberId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("UserService.DatabaseLayer.DataModels.User", "User")
b.HasOne("UserService.Infrastructure.DataModels.User", "User")
.WithMany("MemberOf")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.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()
.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.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using UserService.DatabaseLayer.DataModels;
using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.Repository
{
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)
{
_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();
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)
{
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)
{
await using var db = new UserServiceDbContext();
await _context(db).AddAsync(@entity, token);
await db.SaveChangesAsync(token);
await Context(db).AddAsync(@entity, token).ConfigureAwait(false);
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();
_context(db).Update(entity);
await db.SaveChangesAsync(token);
Context(db).Update(entity);
var items= await db.SaveChangesAsync(token).ConfigureAwait(false);
return items > 0;
}
public async Task DeleteAsync(T entity, CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
_context(db).Remove(entity);
await db.SaveChangesAsync(token);
Context(db).Remove(entity);
await db.SaveChangesAsync(token).ConfigureAwait(false);
}
}
}

View File

@ -1,18 +1,19 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using UserService.DatabaseLayer.DataModels;
using UserService.Infrastructure.DataModels;
namespace UserService.DatabaseLayer.Repository
{
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 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);
}

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>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>8</LangVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.6">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</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>
</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 NUnit.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UserService.DatabaseLayer.DataModels;
using UserService.Infrastructure.DataModels;
namespace UserService.Test
{
@ -21,10 +20,10 @@ namespace UserService.Test
public async Task Test1()
{
await using var db = new UserServiceDbContext();
var user = await db.Users.FirstOrDefaultAsync();
var secGroup = await db.SecurityGroups.FirstOrDefaultAsync();
var ous = await db.OrganizationUnits.ToListAsync();
var mo = await db.UserMembers.ToListAsync();
var user = await db.Users.FirstOrDefaultAsync().ConfigureAwait(false);
var secGroup = await db.SecurityGroups.FirstOrDefaultAsync().ConfigureAwait(false);
var ous = await db.OrganizationUnits.ToListAsync().ConfigureAwait(false);
var mo = await db.UserMembers.ToListAsync().ConfigureAwait(false);
var securityGroupsOfUser = user.GetSecurityGroups();
var usersOfSecurityGroup = secGroup.GetUsers();
@ -40,12 +39,11 @@ namespace UserService.Test
public async Task Test2()
{
await using var db = new UserServiceDbContext();
var ous = await db.OrganizationUnits.ToListAsync();
var ous = await db.OrganizationUnits.ToListAsync().ConfigureAwait(false);
var sb = new StringBuilder();
NewMethod(ous, null, 0, ref sb);
var result = sb.ToString();
Assert.Pass();
}
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>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<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="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>

Binary file not shown.

View File

@ -9,6 +9,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.Test", "UserSer
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UserService.DatabaseLayer", "UserService.DatabaseLayer\UserService.DatabaseLayer.csproj", "{4505C991-7E39-416F-94E5-D906DD0D90F9}"
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -1,5 +1,4 @@

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

View File

@ -1,11 +1,12 @@
@page "/directory"
@using UserService.DatabaseLayer.DataModels
@using UserService.DatabaseLayer.Repository
@inject IOrganizationUnitsRepository OuRepository
@inherits DirectoryBase
<h1>TODO</h1>
@if (_organizationUnits == null)
<Row>
<Column ColumnSize="ColumnSize.Is12">
<h1>TODO</h1>
</Column>
</Row>
@if (OrganizationUnits == null)
{
<p>
<em>Loading...</em>
@ -13,17 +14,20 @@
}
else
{
}
@code {
private IReadOnlyList<OrganizationUnit> _organizationUnits;
private OrganizationUnit _selectedOu;
protected override async Task OnInitializedAsync()
{
_organizationUnits = (await OuRepository.GetAllAsync().ConfigureAwait(false)).Where(x=> x.Parent is null).ToList();
}
<Row>
<Column ColumnSize="ColumnSize.Is4">
<TreeView Nodes="OrganizationUnits"
GetChildNodes="@(item => item?.Children)"
HasChildNodes="@(item => item?.Children?.Any() == true)"
@bind-SelectedNode="@SelectedNode">
<NodeContent>
<Icon Name="IconName.Folder" />
@context?.CommonName
</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"
@using UserService.DatabaseLayer.DataModels
@using UserService.DatabaseLayer.Repository
@using UserService.Infrastructure.DataModels
@inherits SecurityGroupsBase
<h1>Table of all security groups</h1>
@if (SecurityGroups is null)
@if (Members is null)
{
<p>
<em>Loading...</em>
</p>
<p>
<em>Loading...</em>
</p>
}
else
{
<MatTable Items="@SecurityGroups" class="mat-elevation-z5">
<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>
<TextEdit Placeholder="Search" Size="Size.Large" @bind-Text="@CustomFilterValue" />
</MatTableRow>
</MatTable>
<MatButton @onclick="@(e => EditSecurityGroup(new SecurityGroup()))">Create new group</MatButton>
}
<DataGrid
TItem="SecurityGroup"
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" />
<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>
<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>
}

View File

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

View File

@ -1,10 +1,10 @@
@page "/users"
@using UserService.DatabaseLayer.DataModels
@using UserService.Infrastructure.DataModels
@inherits UsersBase
<h1>List of all users</h1>
@if (Users == null)
@if (Members == null)
{
<p>
<em>Loading...</em>
@ -12,55 +12,92 @@
}
else
{
<MatTable Items="@Users" class="mat-elevation-z5">
<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>
<TextEdit Placeholder="Search" Size="Size.Large" @bind-Text="@CustomFilterValue" />
<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" />
<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>
<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>
}

View File

@ -1,44 +1,99 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using System;
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.Infrastructure;
using UserService.Infrastructure.DataModels;
namespace UserService.Pages
{
public class UsersBase : ComponentBase
public class UsersBase : MembersBase<User>
{
[Inject] private IUsersRepository UsersRepository { get; set; } = null!;
[Inject] private IOrganizationUnitsRepository OrganizationUnitsRepository { get; set; } = null!;
protected bool DialogIsOpen { get; set; }
protected User? UserToEdit { get; private set; }
protected IReadOnlyList<User>? Users { get; private set; }
protected IReadOnlyList<OrganizationUnit>? OrganizationUnits { get; private set; }
[Inject] private IJSRuntime JsRuntime { get; set; } = null!;
protected override async Task OnInitializedAsync()
{
Users = await UsersRepository.GetAllAsync();
Members = await UsersRepository.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;
UserToEdit = user;
if (arg is null) throw new ArgumentNullException(nameof(arg));
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;
await UsersRepository.UpdateAsync(UserToEdit).ConfigureAwait(false);
DialogIsOpen = false;
if (arg is null) throw new ArgumentNullException(nameof(arg));
var user = arg.Item;
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,33 +8,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<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 href="css/site.css" rel="stylesheet"/>
<script src="_content/MatBlazor/dist/matBlazor.js"></script>
<link href="_content/MatBlazor/dist/matBlazor.css" rel="stylesheet"/>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.12.0/css/all.css">
<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>
<body>
<app>
<component type="typeof(App)" render-mode="ServerPrerendered"/>
</app>
<app>
<component type="typeof(App)" render-mode="ServerPrerendered" />
</app>
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</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="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://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<script src="_framework/blazor.server.js"></script>
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.server.js"></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://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" 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="_content/Blazorise/blazorise.js"></script>
<script src="_content/Blazorise.Bootstrap/blazorise.bootstrap.js"></script>
</body>
</html>

View File

@ -1,49 +1,38 @@
<div class="top-row pl-4 navbar navbar-dark">
<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>
<Sidebar Data="@_sidebarInfo" />
@code {
private bool _collapseNavMenu = true;
private SidebarInfo _sidebarInfo;
private string NavMenuCssClass => _collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
protected override void OnInitialized()
{
_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.Hosting;
using Microsoft.Extensions.Configuration;
@ -26,12 +29,19 @@ namespace UserService
services.AddSingleton<IUsersRepository, UsersRepository>();
services.AddSingleton<ISecurityGroupsRepository, SecurityGroupsRepository>();
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.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (app == null) throw new ArgumentNullException(nameof(app));
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
@ -48,6 +58,10 @@ namespace UserService
app.UseRouting();
app.ApplicationServices
.UseBootstrapProviders()
.UseFontAwesomeIcons();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
@ -55,4 +69,4 @@ namespace UserService
});
}
}
}
}

View File

@ -3,14 +3,28 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MatBlazor" Version="2.6.2" />
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<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>
</Project>

View File

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