Blazorise #4

Merged
holger merged 11 commits from Blazorise into main 2020-08-21 22:26:39 +02:00
14 changed files with 290 additions and 238 deletions
Showing only changes of commit 17f3274abc - Show all commits

View File

@ -1,8 +1,8 @@
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;
@ -12,43 +12,46 @@ 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 readonly Func<UserServiceDbContext, DbSet<T>> Context;
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);
IQueryable<T> queryable = Context(db).Include(x => x.Parent);
if(queryable != null) queryable = queryable.Where(predicate);
return await queryable.ToListAsync(token);
} }
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);
} }
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);
await db.SaveChangesAsync(token); await db.SaveChangesAsync(token);
} }
public async Task UpdateAsync(T entity, CancellationToken token = default) public async Task 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); await db.SaveChangesAsync(token);
} }
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);
} }
} }

View File

@ -1,4 +1,5 @@
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;
@ -9,7 +10,7 @@ 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 UpdateAsync(T entity, CancellationToken token = default);

View File

@ -23,7 +23,7 @@ namespace UserService.DatabaseLayer.Repository
await using var db = new UserServiceDbContext(); await using var db = new UserServiceDbContext();
var result = new List<OrganizationUnit>(); var result = new List<OrganizationUnit>();
var rootOus = await _context(db) var rootOus = await Context(db)
.Include(x => x.Parent) .Include(x => x.Parent)
.ToListAsync(token); .ToListAsync(token);

View File

@ -1,11 +1,20 @@
@page "/directory" @page "/directory"
@using UserService.DatabaseLayer.DataModels
@using UserService.DatabaseLayer.Repository @using UserService.DatabaseLayer.Repository
@inject IOrganizationUnitsRepository OuRepository @inject IOrganizationUnitsRepository OuRepository
@inherits DirectoryBase
<h1>TODO</h1> @functions {
@if (_organizationUnits == null)
}
<Row>
<Column ColumnSize="ColumnSize.Is12">
<h1>TODO</h1>
</Column>
</Row>
@if (OrganizationUnits == null)
{ {
<p> <p>
<em>Loading...</em> <em>Loading...</em>
@ -13,17 +22,20 @@
} }
else else
{ {
<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>
@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();
}
} }

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using UserService.DatabaseLayer.DataModels;
using UserService.DatabaseLayer.Repository;
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)
{
Members = await UsersRepository.GetAllAsync();
}
[Inject] public IOrganizationUnitsRepository OuRepository { get; set; }
[Inject] public IUsersRepository UsersRepository { get; set; }
protected override async Task OnInitializedAsync()
{
OrganizationUnits = (await OuRepository.GetAllAsync().ConfigureAwait(false))
.Where(x => x.Parent is null)
.ToList();
}
}
}

View File

@ -12,7 +12,10 @@
} }
else else
{ {
<MatTable Items="@OrganizationUnits" class="mat-elevation-z5">
@*<MatTable Items="@OrganizationUnits" class="mat-elevation-z5">
<MatTableHeader> <MatTableHeader>
<th style="width: 20%">Common Name</th> <th style="width: 20%">Common Name</th>
<th style="width: 20%">Description</th> <th style="width: 20%">Description</th>
@ -33,12 +36,12 @@ else
<a href="organizationUnits" @onclick="@(e => Delete(context))">delete</a> <a href="organizationUnits" @onclick="@(e => Delete(context))">delete</a>
</td> </td>
</MatTableRow> </MatTableRow>
</MatTable> </MatTable>*@
<MatButton @onclick="@(e => Edit(new OrganizationUnit()))">Create organization unit</MatButton> <MatButton @onclick="@(e => Edit(new OrganizationUnit()))">Create organization unit</MatButton>
} }
<MatDialog @bind-IsOpen="@DialogIsOpen"> @*<MatDialog @bind-IsOpen="@DialogIsOpen">
@if (OuToEdit != null) @if (OuToEdit != null)
{ {
<MatDialogTitle>@(OuToEdit.Id == 0 ? "New" : "Edit") @OuToEdit.CommonName (@OuToEdit.Id)</MatDialogTitle> <MatDialogTitle>@(OuToEdit.Id == 0 ? "New" : "Edit") @OuToEdit.CommonName (@OuToEdit.Id)</MatDialogTitle>
@ -47,8 +50,8 @@ else
<p/> <p/>
<MatTextField Label="Description" @bind-Value="@OuToEdit.Description"/> <MatTextField Label="Description" @bind-Value="@OuToEdit.Description"/>
@*<MatTextField Label="Manager" @bind-Value="@OuToEdit.Manager"/>*@ @*<MatTextField Label="Manager" @bind-Value="@OuToEdit.Manager"/>*@
<p/> @*<p />
<MatSelectItem Items="@OrganizationUnits" Label="Parent" @bind-Value="@OuToEdit.Parent"/> <MatSelectItem Items="@OrganizationUnits" Label="Parent" @bind-Value="@OuToEdit.Parent" />
</MatDialogContent> </MatDialogContent>
} }
else else
@ -59,4 +62,4 @@ else
<MatButton OnClick="@(e => { DialogIsOpen = false; })">No Thanks</MatButton> <MatButton OnClick="@(e => { DialogIsOpen = false; })">No Thanks</MatButton>
<MatButton OnClick="@OkClick">OK</MatButton> <MatButton OnClick="@OkClick">OK</MatButton>
</MatDialogActions> </MatDialogActions>
</MatDialog> </MatDialog>*@

View File

@ -13,7 +13,7 @@
} }
else else
{ {
<MatTable Items="@SecurityGroups" class="mat-elevation-z5"> @*<MatTable Items="@SecurityGroups" class="mat-elevation-z5">
<MatTableHeader> <MatTableHeader>
<th style="width: 30%">Common Name</th> <th style="width: 30%">Common Name</th>
<th style="width: 20%">Description</th> <th style="width: 20%">Description</th>
@ -32,10 +32,10 @@ else
</MatTableRow> </MatTableRow>
</MatTable> </MatTable>
<MatButton @onclick="@(e => EditSecurityGroup(new SecurityGroup()))">Create new group</MatButton> <MatButton @onclick="@(e => EditSecurityGroup(new SecurityGroup()))">Create new group</MatButton>*@
} }
<MatDialog @bind-IsOpen="@DialogIsOpen"> @*<MatDialog @bind-IsOpen="@DialogIsOpen">
@if (SecurityGroupToEdit != null) @if (SecurityGroupToEdit != null)
{ {
<MatDialogTitle>@(SecurityGroupToEdit.Id == 0 ? "New" : "Edit") @SecurityGroupToEdit.CommonName (@SecurityGroupToEdit.Id)</MatDialogTitle> <MatDialogTitle>@(SecurityGroupToEdit.Id == 0 ? "New" : "Edit") @SecurityGroupToEdit.CommonName (@SecurityGroupToEdit.Id)</MatDialogTitle>
@ -56,4 +56,4 @@ else
<MatButton OnClick="@(e => { DialogIsOpen = false; })">No Thanks</MatButton> <MatButton OnClick="@(e => { DialogIsOpen = false; })">No Thanks</MatButton>
<MatButton OnClick="@OkClick">OK</MatButton> <MatButton OnClick="@OkClick">OK</MatButton>
</MatDialogActions> </MatDialogActions>
</MatDialog> </MatDialog>*@

View File

@ -12,7 +12,7 @@
} }
else else
{ {
<MatTable Items="@Users" class="mat-elevation-z5"> @*<MatTable Items="@Users" class="mat-elevation-z5">
<MatTableHeader> <MatTableHeader>
<th style="width: 20%">Common Name</th> <th style="width: 20%">Common Name</th>
<th style="width: 30%">Full name</th> <th style="width: 30%">Full name</th>
@ -35,10 +35,10 @@ else
</MatTableRow> </MatTableRow>
</MatTable> </MatTable>
<MatButton @onclick="@(e => EditUser(new User()))">Create user</MatButton> <MatButton @onclick="@(e => EditUser(new User()))">Create user</MatButton>*@
} }
<MatDialog @bind-IsOpen="@DialogIsOpen"> @*<MatDialog @bind-IsOpen="@DialogIsOpen">
@if (UserToEdit != null) @if (UserToEdit != null)
{ {
<MatDialogTitle>@(UserToEdit.Id == 0 ? "New" : "Edit") @UserToEdit.CommonName (@UserToEdit.Id)</MatDialogTitle> <MatDialogTitle>@(UserToEdit.Id == 0 ? "New" : "Edit") @UserToEdit.CommonName (@UserToEdit.Id)</MatDialogTitle>
@ -63,4 +63,4 @@ else
<MatButton OnClick="@(e => { DialogIsOpen = false; })">No Thanks</MatButton> <MatButton OnClick="@(e => { DialogIsOpen = false; })">No Thanks</MatButton>
<MatButton OnClick="@OkClick">OK</MatButton> <MatButton OnClick="@OkClick">OK</MatButton>
</MatDialogActions> </MatDialogActions>
</MatDialog> </MatDialog>*@

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,26 @@
<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 = "SidebarBrand",
To = "/"
}, Items = new List<SidebarItemInfo>
{
new SidebarItemInfo
{
Text = "Directory",
Icon = IconName.Folder,
To = "/directory"
}
}
};
} }
} }

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.Net.Http;
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,7 +29,13 @@ 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.
@ -48,6 +57,10 @@ namespace UserService
app.UseRouting(); app.UseRouting();
app.ApplicationServices
.UseBootstrapProviders()
.UseFontAwesomeIcons();
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapBlazorHub(); endpoints.MapBlazorHub();

View File

@ -5,12 +5,15 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="MatBlazor" Version="2.6.2" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\UserService.DatabaseLayer\UserService.DatabaseLayer.csproj" /> <ProjectReference Include="..\UserService.DatabaseLayer\UserService.DatabaseLayer.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Blazorise.Bootstrap" 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" />
</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