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.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
@ -12,43 +12,46 @@ namespace UserService.DatabaseLayer.Repository
{
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)
{
_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);
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)
{
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)
{
await using var db = new UserServiceDbContext();
await _context(db).AddAsync(@entity, token);
await Context(db).AddAsync(@entity, token);
await db.SaveChangesAsync(token);
}
public async Task UpdateAsync(T entity, CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
_context(db).Update(entity);
Context(db).Update(entity);
await db.SaveChangesAsync(token);
}
public async Task DeleteAsync(T entity, CancellationToken token = default)
{
await using var db = new UserServiceDbContext();
_context(db).Remove(entity);
Context(db).Remove(entity);
await db.SaveChangesAsync(token);
}
}

View File

@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Threading;
@ -9,7 +10,7 @@ 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);

View File

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

View File

@ -1,11 +1,20 @@
@page "/directory"
@using UserService.DatabaseLayer.DataModels
@using UserService.DatabaseLayer.Repository
@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>
<em>Loading...</em>
@ -13,17 +22,20 @@
}
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">
}
@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();
}
</Column>
</Row>
}

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

View File

@ -13,7 +13,7 @@
}
else
{
<MatTable Items="@SecurityGroups" class="mat-elevation-z5">
@*<MatTable Items="@SecurityGroups" class="mat-elevation-z5">
<MatTableHeader>
<th style="width: 30%">Common Name</th>
<th style="width: 20%">Description</th>
@ -32,10 +32,10 @@ else
</MatTableRow>
</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)
{
<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="@OkClick">OK</MatButton>
</MatDialogActions>
</MatDialog>
</MatDialog>*@

View File

@ -12,7 +12,7 @@
}
else
{
<MatTable Items="@Users" class="mat-elevation-z5">
@*<MatTable Items="@Users" class="mat-elevation-z5">
<MatTableHeader>
<th style="width: 20%">Common Name</th>
<th style="width: 30%">Full name</th>
@ -35,10 +35,10 @@ else
</MatTableRow>
</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)
{
<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="@OkClick">OK</MatButton>
</MatDialogActions>
</MatDialog>
</MatDialog>*@

View File

@ -8,21 +8,26 @@
<!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">
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
@ -31,10 +36,13 @@
</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>
<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,26 @@
<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 = "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 Blazorise;
using Blazorise.Bootstrap;
using Blazorise.Icons.FontAwesome;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
@ -26,7 +29,13 @@ 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.
@ -48,6 +57,10 @@ namespace UserService
app.UseRouting();
app.ApplicationServices
.UseBootstrapProviders()
.UseFontAwesomeIcons();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();

View File

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

View File

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