Updating Blazor Admin (#442)

* Updating Blazor services

* Adding Settings and Refactoring Services

* WIP - Fighting with DI

* Configuring dependencies in both Web Startup and BlazorAdmin Program.cs has them working again.

* Everything works; need to optimize calls to ListBrands

* LocalStorageBrandService decorator working

* Added cache duration of 1 minute

* Refactoring to reduce token storage
Fixed issue with dropdowns binding to int

* Remove token stuff from login; moved to CustomAuthStateProvider

* Migrated CatalogTypes to separate service
Implemented cache decorator

* Ardalis/blazor refactor (#440)

* 1. Migrate CatalogItemServices -> CatalogItemService.
3. Add caching to CatalogItemService.

* change to $"Loading {key} from local storage" ?

* docker settings added. (#441)

* docker settings added.

* InDocker Removed

* InDocker removed from web startup.

* removed unused using

* no reload list if close without save

* startup patch for localhost

* file name fixed

* removed docker from launchSettings.

* Configure logging via appsettings

Co-authored-by: Shady Nagy <info@shadynagy.com>
This commit is contained in:
Steve Smith
2020-07-30 23:50:51 -04:00
committed by GitHub
parent 98fb0ee8ce
commit e9a9dc06d7
77 changed files with 865 additions and 533 deletions

View File

@@ -2,9 +2,8 @@ version: '3.4'
services: services:
eshopwebmvc: eshopwebmvc:
environment: environment:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Docker
- ASPNETCORE_URLS=http://+:80 - ASPNETCORE_URLS=http://+:80
- DOTNET_RUNNING_IN_CONTAINER=true
ports: ports:
- "5106:80" - "5106:80"
volumes: volumes:
@@ -12,9 +11,8 @@ services:
- ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro
eshoppublicapi: eshoppublicapi:
environment: environment:
- ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_ENVIRONMENT=Docker
- ASPNETCORE_URLS=http://+:80 - ASPNETCORE_URLS=http://+:80
- DOTNET_RUNNING_IN_CONTAINER=true
ports: ports:
- "5200:80" - "5200:80"
volumes: volumes:

View File

@@ -14,6 +14,7 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="3.1.5" /> <PackageReference Include="Microsoft.Extensions.Identity.Core" Version="3.1.5" />
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="3.1.6" />
<PackageReference Include="System.Net.Http.Json" Version="3.2.0" /> <PackageReference Include="System.Net.Http.Json" Version="3.2.0" />
</ItemGroup> </ItemGroup>

View File

@@ -1,31 +1,41 @@
using System; using BlazorAdmin.Services;
using BlazorAdmin.Services; using BlazorShared.Authorization;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Security.Claims; using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using BlazorShared.Authorization;
namespace BlazorAdmin namespace BlazorAdmin
{ {
public class CustomAuthStateProvider : AuthenticationStateProvider public class CustomAuthStateProvider : AuthenticationStateProvider
{ {
// TODO: Get Default Cache Duration from Config
private static readonly TimeSpan UserCacheRefreshInterval = TimeSpan.FromSeconds(60); private static readonly TimeSpan UserCacheRefreshInterval = TimeSpan.FromSeconds(60);
private readonly AuthService _authService; private readonly AuthService _authService;
private readonly HttpClient _httpClient;
private readonly ILogger<CustomAuthStateProvider> _logger; private readonly ILogger<CustomAuthStateProvider> _logger;
private DateTimeOffset _userLastCheck = DateTimeOffset.FromUnixTimeSeconds(0); private DateTimeOffset _userLastCheck = DateTimeOffset.FromUnixTimeSeconds(0);
private ClaimsPrincipal _cachedUser = new ClaimsPrincipal(new ClaimsIdentity()); private ClaimsPrincipal _cachedUser = new ClaimsPrincipal(new ClaimsIdentity());
public CustomAuthStateProvider(AuthService authService, ILogger<CustomAuthStateProvider> logger) public CustomAuthStateProvider(AuthService authService,
HttpClient httpClient,
ILogger<CustomAuthStateProvider> logger)
{ {
_authService = authService; _authService = authService;
_httpClient = httpClient;
_logger = logger; _logger = logger;
} }
public override async Task<AuthenticationState> GetAuthenticationStateAsync() => public override async Task<AuthenticationState> GetAuthenticationStateAsync()
new AuthenticationState(await GetUser(useCache: true)); {
return new AuthenticationState(await GetUser(useCache: true));
}
private async ValueTask<ClaimsPrincipal> GetUser(bool useCache = false) private async ValueTask<ClaimsPrincipal> GetUser(bool useCache = false)
{ {
@@ -47,16 +57,17 @@ namespace BlazorAdmin
try try
{ {
user = await _authService.GetTokenFromController(); _logger.LogInformation("Fetching user details from web api.");
user = await _httpClient.GetFromJsonAsync<UserInfo>("User");
} }
catch (Exception exc) catch (Exception exc)
{ {
_logger.LogWarning(exc, "Fetching user failed."); _logger.LogWarning(exc, "Fetching user failed.");
} }
if (user == null || !user.IsAuthenticated) if (user == null || !user.IsAuthenticated)
{ {
return new ClaimsPrincipal(new ClaimsIdentity()); return null;
} }
var identity = new ClaimsIdentity( var identity = new ClaimsIdentity(
@@ -72,6 +83,8 @@ namespace BlazorAdmin
} }
} }
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", user.Token);
return new ClaimsPrincipal(identity); return new ClaimsPrincipal(identity);
} }
} }

View File

@@ -1,6 +1,7 @@
@inject ILogger<Create> Logger @inject ILogger<Create> Logger
@inject AuthService Auth @inject AuthService Auth
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject ICatalogItemService CatalogItemService
@inherits BlazorAdmin.Helpers.BlazorComponent @inherits BlazorAdmin.Helpers.BlazorComponent
@@ -130,7 +131,7 @@
public IEnumerable<CatalogType> Types { get; set; } public IEnumerable<CatalogType> Types { get; set; }
[Parameter] [Parameter]
public EventCallback<string> OnCloseClick { get; set; } public EventCallback<string> OnSaveClick { get; set; }
private string LoadPicture => string.IsNullOrEmpty(_item.PictureBase64) ? string.Empty : $"data:image/png;base64, {_item.PictureBase64}"; private string LoadPicture => string.IsNullOrEmpty(_item.PictureBase64) ? string.Empty : $"data:image/png;base64, {_item.PictureBase64}";
private bool HasPicture => !string.IsNullOrEmpty(_item.PictureBase64); private bool HasPicture => !string.IsNullOrEmpty(_item.PictureBase64);
@@ -142,8 +143,8 @@
private async Task CreateClick() private async Task CreateClick()
{ {
await new BlazorAdmin.Services.CatalogItemServices.Create(Auth).HandleAsync(_item); await CatalogItemService.Create(_item);
await OnCloseClick.InvokeAsync(null); await OnSaveClick.InvokeAsync(null);
await Close(); await Close();
} }
@@ -172,7 +173,6 @@
_modalDisplay = "none"; _modalDisplay = "none";
_modalClass = ""; _modalClass = "";
_showCreateModal = false; _showCreateModal = false;
await OnCloseClick.InvokeAsync(null);
} }
private async Task AddFile(IFileListEntry[] files) private async Task AddFile(IFileListEntry[] files)

View File

@@ -1,6 +1,7 @@
@inject ILogger<Delete> Logger @inject ILogger<Delete> Logger
@inject AuthService Auth @inject AuthService Auth
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject ICatalogItemService CatalogItemService
@inherits BlazorAdmin.Helpers.BlazorComponent @inherits BlazorAdmin.Helpers.BlazorComponent
@@ -50,7 +51,7 @@
</dt> </dt>
<dd> <dd>
@Services.CatalogBrandServices.List.GetBrandName(Brands, _item.CatalogBrandId) @_item.CatalogBrand
</dd> </dd>
<dt> <dt>
@@ -58,7 +59,7 @@
</dt> </dt>
<dd> <dd>
@Services.CatalogTypeServices.List.GetTypeName(Types, _item.CatalogTypeId) @_item.CatalogType
</dd> </dd>
<dt> <dt>
Price Price
@@ -97,7 +98,7 @@
public IEnumerable<CatalogType> Types { get; set; } public IEnumerable<CatalogType> Types { get; set; }
[Parameter] [Parameter]
public EventCallback<string> OnCloseClick { get; set; } public EventCallback<string> OnSaveClick { get; set; }
private bool HasPicture => !string.IsNullOrEmpty(_item.PictureUri); private bool HasPicture => !string.IsNullOrEmpty(_item.PictureUri);
private string _modalDisplay = "none;"; private string _modalDisplay = "none;";
@@ -109,9 +110,9 @@
{ {
// TODO: Add some kind of "are you sure" check before this // TODO: Add some kind of "are you sure" check before this
await new BlazorAdmin.Services.CatalogItemServices.Delete(Auth).HandleAsync(id); await CatalogItemService.Delete(id);
await OnCloseClick.InvokeAsync(null); await OnSaveClick.InvokeAsync(null);
await Close(); await Close();
} }
@@ -121,7 +122,7 @@
await new Css(JSRuntime).HideBodyOverflow(); await new Css(JSRuntime).HideBodyOverflow();
_item = await new GetById(Auth).HandleAsync(id); _item = await CatalogItemService.GetById(id);
_modalDisplay = "block;"; _modalDisplay = "block;";
_modalClass = "Show"; _modalClass = "Show";
@@ -136,6 +137,5 @@
_modalDisplay = "none"; _modalDisplay = "none";
_modalClass = ""; _modalClass = "";
_showDeleteModal = false; _showDeleteModal = false;
await OnCloseClick.InvokeAsync(null);
} }
} }

View File

@@ -1,6 +1,7 @@
@inject ILogger<Details> Logger @inject ILogger<Details> Logger
@inject AuthService Auth @inject AuthService Auth
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject ICatalogItemService CatalogItemService
@inherits BlazorAdmin.Helpers.BlazorComponent @inherits BlazorAdmin.Helpers.BlazorComponent
@@ -53,7 +54,7 @@
</dt> </dt>
<dd> <dd>
@Services.CatalogBrandServices.List.GetBrandName(Brands, _item.CatalogBrandId) @_item.CatalogBrand
</dd> </dd>
<dt> <dt>
@@ -61,7 +62,7 @@
</dt> </dt>
<dd> <dd>
@Services.CatalogTypeServices.List.GetTypeName(Types, _item.CatalogTypeId) @_item.CatalogType
</dd> </dd>
<dt> <dt>
Price Price
@@ -108,10 +109,10 @@
private bool _showDetailsModal = false; private bool _showDetailsModal = false;
private CatalogItem _item = new CatalogItem(); private CatalogItem _item = new CatalogItem();
public void EditClick() public async Task EditClick()
{ {
OnEditClick.InvokeAsync(_item.Id); await OnEditClick.InvokeAsync(_item.Id);
Close(); await Close();
} }
public async Task Open(int id) public async Task Open(int id)
@@ -121,7 +122,7 @@
await new Css(JSRuntime).HideBodyOverflow(); await new Css(JSRuntime).HideBodyOverflow();
_item = await new GetById(Auth).HandleAsync(id); _item = await CatalogItemService.GetById(id);
_modalDisplay = "block;"; _modalDisplay = "block;";
_modalClass = "Show"; _modalClass = "Show";

View File

@@ -1,6 +1,7 @@
@inject ILogger<Edit> Logger @inject ILogger<Edit> Logger
@inject AuthService Auth @inject AuthService Auth
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject ICatalogItemService CatalogItemService
@inherits BlazorAdmin.Helpers.BlazorComponent @inherits BlazorAdmin.Helpers.BlazorComponent
@@ -54,12 +55,12 @@
<div class="form-group"> <div class="form-group">
<label class="control-label col-md-6">Brand</label> <label class="control-label col-md-6">Brand</label>
<div class="col-md-12"> <div class="col-md-12">
<InputSelect @bind-Value="_item.CatalogBrandId" class="form-control"> <CustomInputSelect @bind-Value="_item.CatalogBrandId" class="form-control">
@foreach (var brand in Brands) @foreach (var brand in Brands)
{ {
<option value="@brand.Id">@brand.Name</option> <option value="@brand.Id.ToString()">@brand.Name</option>
} }
</InputSelect> </CustomInputSelect>
<ValidationMessage For="(() => _item.CatalogBrandId)" /> <ValidationMessage For="(() => _item.CatalogBrandId)" />
</div> </div>
</div> </div>
@@ -67,12 +68,12 @@
<div class="form-group"> <div class="form-group">
<label class="control-label col-md-6">Type</label> <label class="control-label col-md-6">Type</label>
<div class="col-md-12"> <div class="col-md-12">
<InputSelect @bind-Value="_item.CatalogTypeId" class="form-control"> <CustomInputSelect @bind-Value="_item.CatalogTypeId" class="form-control">
@foreach (var type in Types) @foreach (var type in Types)
{ {
<option value="@type.Id">@type.Name</option> <option value="@type.Id">@type.Name</option>
} }
</InputSelect> </CustomInputSelect>
<ValidationMessage For="(() => _item.CatalogTypeId)" /> <ValidationMessage For="(() => _item.CatalogTypeId)" />
</div> </div>
</div> </div>
@@ -130,7 +131,7 @@
public IEnumerable<CatalogType> Types { get; set; } public IEnumerable<CatalogType> Types { get; set; }
[Parameter] [Parameter]
public EventCallback<string> OnCloseClick { get; set; } public EventCallback<string> OnSaveClick { get; set; }
private string LoadPicture => string.IsNullOrEmpty(_item.PictureBase64) ? string.IsNullOrEmpty(_item.PictureUri) ? string.Empty : $"{_item.PictureUri}" : $"data:image/png;base64, {_item.PictureBase64}"; private string LoadPicture => string.IsNullOrEmpty(_item.PictureBase64) ? string.IsNullOrEmpty(_item.PictureUri) ? string.Empty : $"{_item.PictureUri}" : $"data:image/png;base64, {_item.PictureBase64}";
private bool HasPicture => !(string.IsNullOrEmpty(_item.PictureBase64) && string.IsNullOrEmpty(_item.PictureUri)); private bool HasPicture => !(string.IsNullOrEmpty(_item.PictureBase64) && string.IsNullOrEmpty(_item.PictureUri));
@@ -142,7 +143,8 @@
private async Task SaveClick() private async Task SaveClick()
{ {
await new BlazorAdmin.Services.CatalogItemServices.Edit(Auth).HandleAsync(_item); await CatalogItemService.Edit(_item);
await OnSaveClick.InvokeAsync(null);
await Close(); await Close();
} }
@@ -152,7 +154,7 @@
await new Css(JSRuntime).HideBodyOverflow(); await new Css(JSRuntime).HideBodyOverflow();
_item = await new GetById(Auth).HandleAsync(id); _item = await CatalogItemService.GetById(id);
_modalDisplay = "block;"; _modalDisplay = "block;";
_modalClass = "Show"; _modalClass = "Show";
@@ -168,7 +170,6 @@
_modalDisplay = "none"; _modalDisplay = "none";
_modalClass = ""; _modalClass = "";
_showEditModal = false; _showEditModal = false;
await OnCloseClick.InvokeAsync(null);
} }
private async Task ChangeFile(IFileListEntry[] files) private async Task ChangeFile(IFileListEntry[] files)

View File

@@ -1,9 +1,6 @@
@page "/admin" @page "/admin"
@attribute [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)] @attribute [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)]
@inject AuthService Auth @inject AuthService Auth
@inject BlazorAdmin.Services.CatalogItemServices.ListPaged CatalogItemListPaged
@inject BlazorAdmin.Services.CatalogTypeServices.List TypeList
@inject BlazorAdmin.Services.CatalogBrandServices.List BrandList
@inherits BlazorAdmin.Helpers.BlazorComponent @inherits BlazorAdmin.Helpers.BlazorComponent
@namespace BlazorAdmin.Pages.CatalogItemPage @namespace BlazorAdmin.Pages.CatalogItemPage
@@ -42,8 +39,8 @@ else
<td> <td>
<img class="img-thumbnail" src="@($"{item.PictureUri}")"> <img class="img-thumbnail" src="@($"{item.PictureUri}")">
</td> </td>
<td>@Services.CatalogTypeServices.List.GetTypeName(catalogTypes, item.CatalogTypeId)</td> <td>@item.CatalogType</td>
<td>@Services.CatalogBrandServices.List.GetBrandName(catalogBrands, item.CatalogBrandId)</td> <td>@item.CatalogBrand</td>
<td>@item.Id</td> <td>@item.Id</td>
<td>@item.Name</td> <td>@item.Name</td>
<td>@item.Description</td> <td>@item.Description</td>
@@ -63,7 +60,7 @@ else
</table> </table>
<Details Brands="@catalogBrands" Types="@catalogTypes" OnEditClick="EditClick" @ref="DetailsComponent"></Details> <Details Brands="@catalogBrands" Types="@catalogTypes" OnEditClick="EditClick" @ref="DetailsComponent"></Details>
<Edit Brands="@catalogBrands" Types="@catalogTypes" OnCloseClick="ReloadCatalogItems" @ref="EditComponent"></Edit> <Edit Brands="@catalogBrands" Types="@catalogTypes" OnSaveClick="ReloadCatalogItems" @ref="EditComponent"></Edit>
<Create Brands="@catalogBrands" Types="@catalogTypes" OnCloseClick="ReloadCatalogItems" @ref="CreateComponent"></Create> <Create Brands="@catalogBrands" Types="@catalogTypes" OnSaveClick="ReloadCatalogItems" @ref="CreateComponent"></Create>
<Delete Brands="@catalogBrands" Types="@catalogTypes" OnCloseClick="ReloadCatalogItems" @ref="DeleteComponent"></Delete> <Delete Brands="@catalogBrands" Types="@catalogTypes" OnSaveClick="ReloadCatalogItems" @ref="DeleteComponent"></Delete>
} }

View File

@@ -1,14 +1,22 @@
using BlazorAdmin.Helpers; using BlazorAdmin.Helpers;
using BlazorAdmin.Services.CatalogBrandServices;
using BlazorAdmin.Services.CatalogItemServices;
using BlazorAdmin.Services.CatalogTypeServices;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlazorShared.Interfaces;
using BlazorShared.Models;
namespace BlazorAdmin.Pages.CatalogItemPage namespace BlazorAdmin.Pages.CatalogItemPage
{ {
public partial class List : BlazorComponent public partial class List : BlazorComponent
{ {
[Microsoft.AspNetCore.Components.Inject]
public ICatalogItemService CatalogItemService { get; set; }
[Microsoft.AspNetCore.Components.Inject]
public ICatalogBrandService CatalogBrandService { get; set; }
[Microsoft.AspNetCore.Components.Inject]
public ICatalogTypeService CatalogTypeService { get; set; }
private List<CatalogItem> catalogItems = new List<CatalogItem>(); private List<CatalogItem> catalogItems = new List<CatalogItem>();
private List<CatalogType> catalogTypes = new List<CatalogType>(); private List<CatalogType> catalogTypes = new List<CatalogType>();
private List<CatalogBrand> catalogBrands = new List<CatalogBrand>(); private List<CatalogBrand> catalogBrands = new List<CatalogBrand>();
@@ -22,9 +30,9 @@ namespace BlazorAdmin.Pages.CatalogItemPage
{ {
if (firstRender) if (firstRender)
{ {
catalogItems = await CatalogItemListPaged.HandleAsync(50); catalogItems = await CatalogItemService.List();
catalogTypes = await TypeList.HandleAsync(); catalogTypes = await CatalogTypeService.List();
catalogBrands = await BrandList.HandleAsync(); catalogBrands = await CatalogBrandService.List();
CallRequestRefresh(); CallRequestRefresh();
} }
@@ -54,7 +62,7 @@ namespace BlazorAdmin.Pages.CatalogItemPage
private async Task ReloadCatalogItems() private async Task ReloadCatalogItems()
{ {
catalogItems = await new BlazorAdmin.Services.CatalogItemServices.ListPaged(Auth).HandleAsync(50); catalogItems = await CatalogItemService.List();
StateHasChanged(); StateHasChanged();
} }
} }

View File

@@ -1,11 +1,14 @@
using BlazorAdmin.Services;
using Blazored.LocalStorage;
using BlazorShared;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System; using System;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using BlazorAdmin.Services;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
namespace BlazorAdmin namespace BlazorAdmin
{ {
@@ -16,18 +19,36 @@ namespace BlazorAdmin
var builder = WebAssemblyHostBuilder.CreateDefault(args); var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("admin"); builder.RootComponents.Add<App>("admin");
builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); var baseUrlConfig = new BaseUrlConfiguration();
builder.Configuration.Bind(BaseUrlConfiguration.CONFIG_NAME, baseUrlConfig);
builder.Services.AddScoped<BaseUrlConfiguration>(sp => baseUrlConfig);
builder.Services.AddSingleton<ILocalStorageService, LocalStorageService>(); builder.Services.AddScoped(sp => new HttpClient() { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddSingleton<AuthService>();
builder.Services.AddScoped<HttpService>();
builder.Services.AddScoped<ILocalStorageService, LocalStorageService>();
builder.Services.AddScoped<AuthService>();
builder.Services.AddAuthorizationCore(); builder.Services.AddAuthorizationCore();
builder.Services.AddSingleton<AuthenticationStateProvider, CustomAuthStateProvider>(); builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthStateProvider>();
builder.Services.AddSingleton(sp => (CustomAuthStateProvider)sp.GetRequiredService<AuthenticationStateProvider>()); builder.Services.AddScoped(sp => (CustomAuthStateProvider)sp.GetRequiredService<AuthenticationStateProvider>());
builder.Services.AddBlazorServices(); builder.Services.AddBlazorServices();
await builder.Build().RunAsync(); builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging"));
await ClearLocalStorageCache(builder.Services);
builder.Build().RunAsync();
}
private static async Task ClearLocalStorageCache(IServiceCollection services)
{
var sp = services.BuildServiceProvider();
var localStorageService = sp.GetRequiredService<ILocalStorageService>();
await localStorageService.RemoveItemAsync("brands");
} }
} }
} }

View File

@@ -1,11 +1,8 @@
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlazorAdmin.JavaScript; using BlazorAdmin.JavaScript;
using Blazored.LocalStorage; using Blazored.LocalStorage;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using BlazorShared.Authorization;
namespace BlazorAdmin.Services namespace BlazorAdmin.Services
{ {
@@ -14,143 +11,32 @@ namespace BlazorAdmin.Services
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly ILocalStorageService _localStorage; private readonly ILocalStorageService _localStorage;
private readonly IJSRuntime _jSRuntime; private readonly IJSRuntime _jSRuntime;
private static bool InDocker { get; set; }
public string ApiUrl => Constants.GetApiUrl(InDocker);
public bool IsLoggedIn { get; set; } public bool IsLoggedIn { get; set; }
public string UserName { get; set; }
public AuthService(HttpClient httpClient, ILocalStorageService localStorage, IJSRuntime jSRuntime) public AuthService(HttpClient httpClient,
ILocalStorageService localStorage,
IJSRuntime jSRuntime)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_localStorage = localStorage; _localStorage = localStorage;
_jSRuntime = jSRuntime; _jSRuntime = jSRuntime;
} }
public HttpClient GetHttpClient()
{
return _httpClient;
}
public async Task Logout() public async Task Logout()
{ {
await DeleteLocalStorage();
await DeleteCookies(); await DeleteCookies();
RemoveAuthorizationHeader();
UserName = null;
IsLoggedIn = false; IsLoggedIn = false;
await LogoutIdentityManager(); await LogoutIdentityManager();
} }
public async Task RefreshLoginInfo()
{
await SetLoginData();
}
public async Task RefreshLoginInfoFromCookie()
{
var token = await new Cookies(_jSRuntime).GetCookie("token");
await SaveTokenInLocalStorage(token);
var username = await new Cookies(_jSRuntime).GetCookie("username");
await SaveUsernameInLocalStorage(username);
var inDocker = await new Cookies(_jSRuntime).GetCookie("inDocker");
await SaveInDockerInLocalStorage(inDocker);
await RefreshLoginInfo();
}
public async Task<string> GetToken()
{
var token = await _localStorage.GetItemAsync<string>("authToken");
return token;
}
public async Task<UserInfo> GetTokenFromController()
{
return await _httpClient.GetFromJsonAsync<UserInfo>("User");
}
public async Task<string> GetUsername()
{
var username = await _localStorage.GetItemAsync<string>("username");
return username;
}
public async Task<bool> GetInDocker()
{
return (await _localStorage.GetItemAsync<string>("inDocker")).ToLower() == "true";
}
private async Task LogoutIdentityManager() private async Task LogoutIdentityManager()
{ {
await _httpClient.PostAsync("Identity/Account/Logout", null); await _httpClient.PostAsync("Identity/Account/Logout", null);
} }
private async Task DeleteLocalStorage()
{
await _localStorage.RemoveItemAsync("authToken");
await _localStorage.RemoveItemAsync("username");
await _localStorage.RemoveItemAsync("inDocker");
}
private async Task DeleteCookies() private async Task DeleteCookies()
{ {
await new Cookies(_jSRuntime).DeleteCookie("token"); await new Cookies(_jSRuntime).DeleteCookie("token");
await new Cookies(_jSRuntime).DeleteCookie("username");
await new Cookies(_jSRuntime).DeleteCookie("inDocker");
} }
private async Task SetLoginData()
{
IsLoggedIn = !string.IsNullOrEmpty(await GetToken());
UserName = await GetUsername();
InDocker = await GetInDocker();
await SetAuthorizationHeader();
}
private void RemoveAuthorizationHeader()
{
if (_httpClient.DefaultRequestHeaders.Contains("Authorization"))
{
_httpClient.DefaultRequestHeaders.Remove("Authorization");
}
}
private async Task SaveTokenInLocalStorage(string token)
{
if (string.IsNullOrEmpty(token))
{
return;
}
await _localStorage.SetItemAsync("authToken", token);
}
private async Task SaveUsernameInLocalStorage(string username)
{
if (string.IsNullOrEmpty(username))
{
return;
}
await _localStorage.SetItemAsync("username", username);
}
private async Task SaveInDockerInLocalStorage(string inDocker)
{
if (string.IsNullOrEmpty(inDocker))
{
return;
}
await _localStorage.SetItemAsync("inDocker", inDocker);
}
private async Task SetAuthorizationHeader()
{
var token = await GetToken();
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
} }
} }

View File

@@ -0,0 +1,19 @@
using System;
namespace BlazorAdmin.Services
{
public class CacheEntry<T>
{
public CacheEntry(T item)
{
Value = item;
}
public CacheEntry()
{
}
public T Value { get; set; }
public DateTime DateCreated { get; set; } = DateTime.UtcNow;
}
}

View File

@@ -0,0 +1,59 @@
using Blazored.LocalStorage;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorAdmin.Services
{
public class CachedCatalogBrandServiceDecorator : ICatalogBrandService
{
// TODO: Make a generic decorator for any LookupData type
private readonly ILocalStorageService _localStorageService;
private readonly CatalogBrandService _catalogBrandService;
private ILogger<CachedCatalogBrandServiceDecorator> _logger;
public CachedCatalogBrandServiceDecorator(ILocalStorageService localStorageService,
CatalogBrandService catalogBrandService,
ILogger<CachedCatalogBrandServiceDecorator> logger)
{
_localStorageService = localStorageService;
_catalogBrandService = catalogBrandService;
_logger = logger;
}
public async Task<CatalogBrand> GetById(int id)
{
return (await List()).FirstOrDefault(x => x.Id == id);
}
public async Task<List<CatalogBrand>> List()
{
string key = "brands";
var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<CatalogBrand>>>(key);
if (cacheEntry != null)
{
_logger.LogInformation("Loading brands from local storage.");
// TODO: Get Default Cache Duration from Config
if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow)
{
return cacheEntry.Value;
}
else
{
_logger.LogInformation("Cache expired; removing brands from local storage.");
await _localStorageService.RemoveItemAsync(key);
}
}
var brands = await _catalogBrandService.List();
var entry = new CacheEntry<List<CatalogBrand>>(brands);
await _localStorageService.SetItemAsync(key, entry);
return brands;
}
}
}

View File

@@ -0,0 +1,114 @@
using Blazored.LocalStorage;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorAdmin.Services
{
public class CachedCatalogItemServiceDecorator : ICatalogItemService
{
private readonly ILocalStorageService _localStorageService;
private readonly CatalogItemService _catalogItemService;
private ILogger<CachedCatalogItemServiceDecorator> _logger;
public CachedCatalogItemServiceDecorator(ILocalStorageService localStorageService,
CatalogItemService catalogItemService,
ILogger<CachedCatalogItemServiceDecorator> logger)
{
_localStorageService = localStorageService;
_catalogItemService = catalogItemService;
_logger = logger;
}
public async Task<List<CatalogItem>> ListPaged(int pageSize)
{
string key = "items";
var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<CatalogItem>>>(key);
if (cacheEntry != null)
{
_logger.LogInformation("Loading items from local storage.");
if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow)
{
return cacheEntry.Value;
}
else
{
_logger.LogInformation($"Loading {key} from local storage.");
await _localStorageService.RemoveItemAsync(key);
}
}
var items = await _catalogItemService.ListPaged(pageSize);
var entry = new CacheEntry<List<CatalogItem>>(items);
await _localStorageService.SetItemAsync(key, entry);
return items;
}
public async Task<List<CatalogItem>> List()
{
string key = "items";
var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<CatalogItem>>>(key);
if (cacheEntry != null)
{
_logger.LogInformation("Loading items from local storage.");
if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow)
{
return cacheEntry.Value;
}
else
{
_logger.LogInformation($"Loading {key} from local storage.");
await _localStorageService.RemoveItemAsync(key);
}
}
var items = await _catalogItemService.List();
var entry = new CacheEntry<List<CatalogItem>>(items);
await _localStorageService.SetItemAsync(key, entry);
return items;
}
public async Task<CatalogItem> GetById(int id)
{
return (await List()).FirstOrDefault(x => x.Id == id);
}
public async Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem)
{
var result = await _catalogItemService.Create(catalogItem);
await RefreshLocalStorageList();
return result;
}
public async Task<CatalogItem> Edit(CatalogItem catalogItem)
{
var result = await _catalogItemService.Edit(catalogItem);
await RefreshLocalStorageList();
return result;
}
public async Task<string> Delete(int id)
{
var result = await _catalogItemService.Delete(id);
await RefreshLocalStorageList();
return result;
}
private async Task RefreshLocalStorageList()
{
string key = "items";
await _localStorageService.RemoveItemAsync(key);
var items = await _catalogItemService.List();
var entry = new CacheEntry<List<CatalogItem>>(items);
await _localStorageService.SetItemAsync(key, entry);
}
}
}

View File

@@ -0,0 +1,57 @@
using Blazored.LocalStorage;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorAdmin.Services
{
public class CachedCatalogTypeServiceDecorator : ICatalogTypeService
{
// TODO: Make a generic decorator for any LookupData type
private readonly ILocalStorageService _localStorageService;
private readonly CatalogTypeService _catalogTypeService;
private ILogger<CachedCatalogTypeServiceDecorator> _logger;
public CachedCatalogTypeServiceDecorator(ILocalStorageService localStorageService,
CatalogTypeService catalogTypeService,
ILogger<CachedCatalogTypeServiceDecorator> logger)
{
_localStorageService = localStorageService;
_catalogTypeService = catalogTypeService;
_logger = logger;
}
public async Task<CatalogType> GetById(int id)
{
return (await List()).FirstOrDefault(x => x.Id == id);
}
public async Task<List<CatalogType>> List()
{
string key = "types";
var cacheEntry = await _localStorageService.GetItemAsync<CacheEntry<List<CatalogType>>>(key);
if (cacheEntry != null)
{
_logger.LogInformation("Loading types from local storage.");
if (cacheEntry.DateCreated.AddMinutes(1) > DateTime.UtcNow)
{
return cacheEntry.Value;
}
else
{
_logger.LogInformation("Cache expired; removing types from local storage.");
await _localStorageService.RemoveItemAsync(key);
}
}
var types = await _catalogTypeService.List();
var entry = new CacheEntry<List<CatalogType>>(types);
await _localStorageService.SetItemAsync(key, entry);
return types;
}
}
}

View File

@@ -0,0 +1,41 @@
using BlazorShared;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
namespace BlazorAdmin.Services
{
public class CatalogBrandService : ICatalogBrandService
{
// TODO: Make a generic service for any LookupData type
private readonly HttpClient _httpClient;
private readonly ILogger<CatalogBrandService> _logger;
private string _apiUrl;
public CatalogBrandService(HttpClient httpClient,
BaseUrlConfiguration baseUrlConfiguration,
ILogger<CatalogBrandService> logger)
{
_httpClient = httpClient;
_logger = logger;
_apiUrl = baseUrlConfiguration.ApiBase;
}
public async Task<CatalogBrand> GetById(int id)
{
return (await List()).FirstOrDefault(x => x.Id == id);
}
public async Task<List<CatalogBrand>> List()
{
_logger.LogInformation("Fetching brands from API.");
return (await _httpClient.GetFromJsonAsync<CatalogBrandResponse>($"{_apiUrl}catalog-brands"))?.CatalogBrands;
}
}
}

View File

@@ -1,8 +0,0 @@
namespace BlazorAdmin.Services.CatalogBrandServices
{
public class CatalogBrand
{
public int Id { get; set; }
public string Name { get; set; }
}
}

View File

@@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
namespace BlazorAdmin.Services.CatalogBrandServices
{
public class List
{
private readonly AuthService _authService;
private readonly HttpClient _httpClient;
public List(AuthService authService, HttpClient httpClient)
{
_authService = authService;
_httpClient = httpClient;
}
public async Task<List<CatalogBrand>> HandleAsync()
{
return (await _httpClient.GetFromJsonAsync<CatalogBrandResult>($"{_authService.ApiUrl}catalog-brands"))?.CatalogBrands;
}
public static string GetBrandName(IEnumerable<CatalogBrand> brands, int brandId)
{
var type = brands.FirstOrDefault(t => t.Id == brandId);
return type == null ? "None" : type.Name;
}
}
}

View File

@@ -0,0 +1,102 @@
using BlazorShared;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorAdmin.Services
{
public class CatalogItemService : ICatalogItemService
{
private readonly ICatalogBrandService _brandService;
private readonly ICatalogTypeService _typeService;
private readonly HttpService _httpService;
private readonly ILogger<CatalogItemService> _logger;
private string _apiUrl;
public CatalogItemService(ICatalogBrandService brandService,
ICatalogTypeService typeService,
HttpService httpService,
BaseUrlConfiguration baseUrlConfiguration,
ILogger<CatalogItemService> logger)
{
_brandService = brandService;
_typeService = typeService;
_httpService = httpService;
_logger = logger;
_apiUrl = baseUrlConfiguration.ApiBase;
}
public async Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem)
{
return (await _httpService.HttpPost<CreateCatalogItemResponse>("catalog-items", catalogItem)).CatalogItem;
}
public async Task<CatalogItem> Edit(CatalogItem catalogItem)
{
return (await _httpService.HttpPut<EditCatalogItemResult>("catalog-items", catalogItem)).CatalogItem;
}
public async Task<string> Delete(int catalogItemId)
{
return (await _httpService.HttpDelete<DeleteCatalogItemResponse>("catalog-items", catalogItemId)).Status;
}
public async Task<CatalogItem> GetById(int id)
{
var brandListTask = _brandService.List();
var typeListTask = _typeService.List();
var itemGetTask = _httpService.HttpGet<EditCatalogItemResult>($"catalog-items/{id}");
await Task.WhenAll(brandListTask, typeListTask, itemGetTask);
var brands = brandListTask.Result;
var types = typeListTask.Result;
var catalogItem = itemGetTask.Result.CatalogItem;
catalogItem.CatalogBrand = brands.FirstOrDefault(b => b.Id == catalogItem.CatalogBrandId)?.Name;
catalogItem.CatalogType = types.FirstOrDefault(t => t.Id == catalogItem.CatalogTypeId)?.Name;
return catalogItem;
}
public async Task<List<CatalogItem>> ListPaged(int pageSize)
{
_logger.LogInformation("Fetching catalog items from API.");
var brandListTask = _brandService.List();
var typeListTask = _typeService.List();
var itemListTask = _httpService.HttpGet<PagedCatalogItemResponse>($"catalog-items?PageSize=10");
await Task.WhenAll(brandListTask, typeListTask, itemListTask);
var brands = brandListTask.Result;
var types = typeListTask.Result;
var items = itemListTask.Result.CatalogItems;
foreach (var item in items)
{
item.CatalogBrand = brands.FirstOrDefault(b => b.Id == item.CatalogBrandId)?.Name;
item.CatalogType = types.FirstOrDefault(t => t.Id == item.CatalogTypeId)?.Name;
}
return items;
}
public async Task<List<CatalogItem>> List()
{
_logger.LogInformation("Fetching catalog items from API.");
var brandListTask = _brandService.List();
var typeListTask = _typeService.List();
//TODO: Need to change the api to support full list
var itemListTask = _httpService.HttpGet<PagedCatalogItemResponse>($"catalog-items?PageSize=100");
await Task.WhenAll(brandListTask, typeListTask, itemListTask);
var brands = brandListTask.Result;
var types = typeListTask.Result;
var items = itemListTask.Result.CatalogItems;
foreach (var item in items)
{
item.CatalogBrand = brands.FirstOrDefault(b => b.Id == item.CatalogBrandId)?.Name;
item.CatalogType = types.FirstOrDefault(t => t.Id == item.CatalogTypeId)?.Name;
}
return items;
}
}
}

View File

@@ -1,7 +0,0 @@
namespace BlazorAdmin.Services.CatalogItemServices
{
public class CreateCatalogItemResult
{
public CatalogItem CatalogItem { get; set; } = new CatalogItem();
}
}

View File

@@ -1,19 +0,0 @@
using System.Threading.Tasks;
namespace BlazorAdmin.Services.CatalogItemServices
{
public class Create
{
private readonly HttpService _httpService;
public Create(AuthService authService)
{
_httpService = new HttpService(authService.GetHttpClient(), authService.ApiUrl);
}
public async Task<CatalogItem> HandleAsync(CreateCatalogItemRequest catalogItem)
{
return (await _httpService.HttpPost<CreateCatalogItemResult>("catalog-items", catalogItem)).CatalogItem;
}
}
}

View File

@@ -1,7 +0,0 @@
namespace BlazorAdmin.Services.CatalogItemServices
{
public class DeleteCatalogItemResult
{
public string Status { get; set; } = "Deleted";
}
}

View File

@@ -1,19 +0,0 @@
using System.Threading.Tasks;
namespace BlazorAdmin.Services.CatalogItemServices
{
public class Delete
{
private readonly HttpService _httpService;
public Delete(AuthService authService)
{
_httpService = new HttpService(authService.GetHttpClient(), authService.ApiUrl);
}
public async Task<string> HandleAsync(int catalogItemId)
{
return (await _httpService.HttpDelete<DeleteCatalogItemResult>("catalog-items", catalogItemId)).Status;
}
}
}

View File

@@ -1,19 +0,0 @@
using System.Threading.Tasks;
namespace BlazorAdmin.Services.CatalogItemServices
{
public class Edit
{
private readonly HttpService _httpService;
public Edit(AuthService authService)
{
_httpService = new HttpService(authService.GetHttpClient(), authService.ApiUrl);
}
public async Task<CatalogItem> HandleAsync(CatalogItem catalogItem)
{
return (await _httpService.HttpPut<EditCatalogItemResult>("catalog-items", catalogItem)).CatalogItem;
}
}
}

View File

@@ -1,7 +0,0 @@
namespace BlazorAdmin.Services.CatalogItemServices
{
public class GetByIdCatalogItemResult
{
public CatalogItem CatalogItem { get; set; } = new CatalogItem();
}
}

View File

@@ -1,19 +0,0 @@
using System.Threading.Tasks;
namespace BlazorAdmin.Services.CatalogItemServices
{
public class GetById
{
private readonly HttpService _httpService;
public GetById(AuthService authService)
{
_httpService = new HttpService(authService.GetHttpClient(), authService.ApiUrl);
}
public async Task<CatalogItem> HandleAsync(int catalogItemId)
{
return (await _httpService.HttpGet<EditCatalogItemResult>($"catalog-items/{catalogItemId}")).CatalogItem;
}
}
}

View File

@@ -1,21 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BlazorAdmin.Services.CatalogItemServices
{
public class ListPaged
{
private readonly HttpService _httpService;
public ListPaged(AuthService authService)
{
_httpService = new HttpService(authService.GetHttpClient(), authService.ApiUrl);
}
public async Task<List<CatalogItem>> HandleAsync(int pageSize)
{
return (await _httpService.HttpGet<PagedCatalogItemResult>($"catalog-items?PageSize={pageSize}")).CatalogItems;
}
}
}

View File

@@ -0,0 +1,40 @@
using BlazorShared;
using BlazorShared.Interfaces;
using BlazorShared.Models;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
namespace BlazorAdmin.Services
{
public class CatalogTypeService : ICatalogTypeService
{
// TODO: Make a generic service for any LookupData type
private readonly HttpClient _httpClient;
private readonly ILogger<CatalogTypeService> _logger;
private string _apiUrl;
public CatalogTypeService(HttpClient httpClient,
BaseUrlConfiguration baseUrlConfiguration,
ILogger<CatalogTypeService> logger)
{
_httpClient = httpClient;
_logger = logger;
_apiUrl = baseUrlConfiguration.ApiBase;
}
public async Task<CatalogType> GetById(int id)
{
return (await List()).FirstOrDefault(x => x.Id == id);
}
public async Task<List<CatalogType>> List()
{
_logger.LogInformation("Fetching types from API.");
return (await _httpClient.GetFromJsonAsync<CatalogTypeResponse>($"{_apiUrl}catalog-types"))?.CatalogTypes;
}
}
}

View File

@@ -1,8 +0,0 @@
namespace BlazorAdmin.Services.CatalogTypeServices
{
public class CatalogType
{
public int Id { get; set; }
public string Name { get; set; }
}
}

View File

@@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
namespace BlazorAdmin.Services.CatalogTypeServices
{
public class List
{
private readonly AuthService _authService;
private readonly HttpClient _httpClient;
public List(AuthService authService, HttpClient httpClient)
{
_authService = authService;
_httpClient = httpClient;
}
public async Task<List<CatalogType>> HandleAsync()
{
return (await _httpClient.GetFromJsonAsync<CatalogTypeResult>($"{_authService.ApiUrl}catalog-types"))?.CatalogTypes;
}
public static string GetTypeName(IEnumerable<CatalogType> types, int typeId)
{
var type = types.FirstOrDefault(t => t.Id == typeId);
return type == null ? "None" : type.Name;
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Net.Http; using BlazorShared;
using System.Net.Http;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -10,10 +11,11 @@ namespace BlazorAdmin.Services
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly string _apiUrl; private readonly string _apiUrl;
public HttpService(HttpClient httpClient, string apiUrl)
public HttpService(HttpClient httpClient, BaseUrlConfiguration baseUrlConfiguration)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_apiUrl = apiUrl; _apiUrl = baseUrlConfiguration.ApiBase;
} }
public async Task<T> HttpGet<T>(string uri) public async Task<T> HttpGet<T>(string uri)

View File

@@ -1,22 +1,21 @@
using BlazorAdmin.Services.CatalogItemServices; using BlazorAdmin.Services;
using BlazorShared.Interfaces;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace BlazorAdmin namespace BlazorAdmin
{ {
public static class ServicesConfiguration public static class ServicesConfiguration
{ {
public static IServiceCollection AddBlazorServices(this IServiceCollection service) public static IServiceCollection AddBlazorServices(this IServiceCollection services)
{ {
service.AddScoped<Create>(); services.AddScoped<ICatalogBrandService, CachedCatalogBrandServiceDecorator>();
service.AddScoped<ListPaged>(); services.AddScoped<CatalogBrandService>();
service.AddScoped<Delete>(); services.AddScoped<ICatalogTypeService, CachedCatalogTypeServiceDecorator>();
service.AddScoped<Edit>(); services.AddScoped<CatalogTypeService>();
service.AddScoped<GetById>(); services.AddScoped<ICatalogItemService, CachedCatalogItemServiceDecorator>();
services.AddScoped<CatalogItemService>();
service.AddScoped<BlazorAdmin.Services.CatalogBrandServices.List>(); return services;
service.AddScoped<BlazorAdmin.Services.CatalogTypeServices.List>();
return service;
} }
} }
} }

View File

@@ -0,0 +1,38 @@
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorAdmin.Shared
{
/// <summary>
/// This is needed until 5.0 ships with native support
/// https://www.pragimtech.com/blog/blazor/inputselect-does-not-support-system.int32/
/// </summary>
/// <typeparam name="TValue"></typeparam>
public class CustomInputSelect<TValue> : InputSelect<TValue>
{
protected override bool TryParseValueFromString(string value, out TValue result,
out string validationErrorMessage)
{
if (typeof(TValue) == typeof(int))
{
if (int.TryParse(value, out var resultInt))
{
result = (TValue)(object)resultInt;
validationErrorMessage = null;
return true;
}
else
{
result = default;
validationErrorMessage =
$"The selected value {value} is not a valid number.";
return false;
}
}
else
{
return base.TryParseValueFromString(value, out result,
out validationErrorMessage);
}
}
}
}

View File

@@ -1,4 +1,4 @@
@inject AuthService Auth @inject AuthenticationStateProvider AuthStateProvider
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inherits BlazorAdmin.Helpers.BlazorLayoutComponent @inherits BlazorAdmin.Helpers.BlazorLayoutComponent
@@ -25,8 +25,9 @@
{ {
if (firstRender) if (firstRender)
{ {
await Auth.RefreshLoginInfoFromCookie(); var authState = await AuthStateProvider.GetAuthenticationStateAsync();
if (!Auth.IsLoggedIn)
if(authState.User == null)
{ {
await new Route(JSRuntime).RouteOutside("/Identity/Account/Login"); await new Route(JSRuntime).RouteOutside("/Identity/Account/Login");
} }
@@ -35,5 +36,4 @@
await base.OnAfterRenderAsync(firstRender); await base.OnAfterRenderAsync(firstRender);
} }
} }

View File

@@ -1,5 +1,4 @@
@inject AuthService Auth @inherits BlazorAdmin.Helpers.BlazorComponent
@inherits BlazorAdmin.Helpers.BlazorComponent
<div class="top-row pl-4 navbar navbar-dark"> <div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">eShopOnWeb Admin</a> <a class="navbar-brand" href="">eShopOnWeb Admin</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu"> <button class="navbar-toggler" @onclick="ToggleNavMenu">
@@ -14,28 +13,27 @@
<span class="oi oi-home" aria-hidden="true"></span> Home <span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink> </NavLink>
</li> </li>
<li class="nav-item px-3"> <AuthorizeView>
<NavLink class="nav-link" href="manage/my-account" Match="NavLinkMatch.All"> <Authorized>
<span class="oi oi-person" aria-hidden="true"></span> @Auth.UserName <li class="nav-item px-3">
</NavLink> <NavLink class="nav-link" href="manage/my-account" Match="NavLinkMatch.All">
</li> <span class="oi oi-person" aria-hidden="true"></span> @context.User.Identity.Name
<li class="nav-item px-3"> </NavLink>
@if (Auth.IsLoggedIn) </li>
{ <li class="nav-item px-3">
<NavLink class="nav-link" href="logout"> <NavLink class="nav-link" href="logout">
<span class="oi oi-account-logout" aria-hidden="true"></span> Logout <span class="oi oi-account-logout" aria-hidden="true"></span> Logout
</NavLink> </NavLink>
} </li>
</Authorized>
</li> </AuthorizeView>
</ul> </ul>
</div> </div>
@code { @code {
private bool collapseNavMenu = true; private bool collapseNavMenu = true;
public string UserName { get; set; }
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

View File

@@ -11,9 +11,8 @@
@using BlazorAdmin @using BlazorAdmin
@using BlazorAdmin.Shared @using BlazorAdmin.Shared
@using BlazorAdmin.Services @using BlazorAdmin.Services
@using BlazorAdmin.Services.CatalogBrandServices
@using BlazorAdmin.Services.CatalogItemServices
@using BlazorAdmin.Services.CatalogTypeServices
@using BlazorAdmin.JavaScript @using BlazorAdmin.JavaScript
@using BlazorShared.Authorization @using BlazorShared.Authorization
@using BlazorShared.Interfaces
@using BlazorInputFile @using BlazorInputFile
@using BlazorShared.Models

View File

@@ -0,0 +1,6 @@
{
"baseUrls": {
"apiBase": "http://localhost:5200/api/",
"webBase": "http://host.docker.internal:5106/"
}
}

View File

@@ -0,0 +1,14 @@
{
"baseUrls": {
"apiBase": "https://localhost:5099/api/",
"webBase": "https://localhost:44315/"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"System": "Warning"
}
}
}

View File

@@ -1,27 +0,0 @@
[
{
"date": "2018-05-06",
"temperatureC": 1,
"summary": "Freezing"
},
{
"date": "2018-05-07",
"temperatureC": 14,
"summary": "Bracing"
},
{
"date": "2018-05-08",
"temperatureC": -13,
"summary": "Freezing"
},
{
"date": "2018-05-09",
"temperatureC": -16,
"summary": "Balmy"
},
{
"date": "2018-05-10",
"temperatureC": -2,
"summary": "Chilly"
}
]

View File

@@ -6,23 +6,5 @@
{ {
public const string ADMINISTRATORS = "Administrators"; public const string ADMINISTRATORS = "Administrators";
} }
public static string GetApiUrl(bool inDocker) =>
inDocker ? DOCKER_API_URL : API_URL;
public static string GetWebUrl(bool inDocker) =>
inDocker ? DOCKER_WEB_URL : WEB_URL;
public static string GetWebUrlInternal(bool inDocker) =>
inDocker ? DOCKER_WEB_URL.Replace("localhost", "host.docker.internal") : WEB_URL;
public static string GetOriginWebUrl(bool inDocker) =>
GetWebUrl(inDocker).TrimEnd('/');
private const string API_URL = "https://localhost:5099/api/";
private const string DOCKER_API_URL = "http://localhost:5200/api/";
private const string WEB_URL = "https://localhost:44315/";
private const string DOCKER_WEB_URL = "http://localhost:5106/";
} }
} }

View File

@@ -8,6 +8,7 @@ namespace BlazorShared.Authorization
public bool IsAuthenticated { get; set; } public bool IsAuthenticated { get; set; }
public string NameClaimType { get; set; } public string NameClaimType { get; set; }
public string RoleClaimType { get; set; } public string RoleClaimType { get; set; }
public string Token { get; set; }
public IEnumerable<ClaimValue> Claims { get; set; } public IEnumerable<ClaimValue> Claims { get; set; }
} }
} }

View File

@@ -0,0 +1,10 @@
namespace BlazorShared
{
public class BaseUrlConfiguration
{
public const string CONFIG_NAME = "baseUrls";
public string ApiBase { get; set; }
public string WebBase { get; set; }
}
}

View File

@@ -6,4 +6,8 @@
<AssemblyName>BlazorShared</AssemblyName> <AssemblyName>BlazorShared</AssemblyName>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="BlazorInputFile" Version="0.2.0" />
</ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using BlazorShared.Models;
namespace BlazorShared.Interfaces
{
public interface ICatalogBrandService
{
Task<List<CatalogBrand>> List();
Task<CatalogBrand> GetById(int id);
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using BlazorShared.Models;
namespace BlazorShared.Interfaces
{
public interface ICatalogItemService
{
Task<CatalogItem> Create(CreateCatalogItemRequest catalogItem);
Task<CatalogItem> Edit(CatalogItem catalogItem);
Task<string> Delete(int id);
Task<CatalogItem> GetById(int id);
Task<List<CatalogItem>> ListPaged(int pageSize);
Task<List<CatalogItem>> List();
}
}

View File

@@ -0,0 +1,12 @@
using BlazorShared.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BlazorShared.Interfaces
{
public interface ICatalogTypeService
{
Task<List<CatalogType>> List();
Task<CatalogType> GetById(int id);
}
}

View File

@@ -0,0 +1,6 @@
namespace BlazorShared.Models
{
public class CatalogBrand : LookupData
{
}
}

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace BlazorAdmin.Services.CatalogBrandServices namespace BlazorShared.Models
{ {
public class CatalogBrandResult public class CatalogBrandResponse
{ {
public List<CatalogBrand> CatalogBrands { get; set; } = new List<CatalogBrand>(); public List<CatalogBrand> CatalogBrands { get; set; } = new List<CatalogBrand>();
} }

View File

@@ -4,15 +4,17 @@ using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using BlazorInputFile; using BlazorInputFile;
namespace BlazorAdmin.Services.CatalogItemServices namespace BlazorShared.Models
{ {
public class CatalogItem public class CatalogItem
{ {
public int Id { get; set; } public int Id { get; set; }
public int CatalogTypeId { get; set; } public int CatalogTypeId { get; set; }
public string CatalogType { get; set; } = "NotSet";
public int CatalogBrandId { get; set; } public int CatalogBrandId { get; set; }
public string CatalogBrand { get; set; } = "NotSet";
[Required(ErrorMessage = "The Name field is required")] [Required(ErrorMessage = "The Name field is required")]
public string Name { get; set; } public string Name { get; set; }
@@ -60,14 +62,17 @@ namespace BlazorAdmin.Services.CatalogItemServices
public static async Task<string> DataToBase64(IFileListEntry fileItem) public static async Task<string> DataToBase64(IFileListEntry fileItem)
{ {
using var reader = new StreamReader(fileItem.Data); using ( var reader = new StreamReader(fileItem.Data))
{
using (var memStream = new MemoryStream())
{
await reader.BaseStream.CopyToAsync(memStream);
var fileData = memStream.ToArray();
var encodedBase64 = Convert.ToBase64String(fileData);
await using var memStream = new MemoryStream(); return encodedBase64;
await reader.BaseStream.CopyToAsync(memStream); }
var fileData = memStream.ToArray(); }
var encodedBase64 = Convert.ToBase64String(fileData);
return encodedBase64;
} }
private static bool IsExtensionValid(string fileName) private static bool IsExtensionValid(string fileName)

View File

@@ -0,0 +1,6 @@
namespace BlazorShared.Models
{
public class CatalogType : LookupData
{
}
}

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace BlazorAdmin.Services.CatalogTypeServices namespace BlazorShared.Models
{ {
public class CatalogTypeResult public class CatalogTypeResponse
{ {
public List<CatalogType> CatalogTypes { get; set; } = new List<CatalogType>(); public List<CatalogType> CatalogTypes { get; set; } = new List<CatalogType>();
} }

View File

@@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace BlazorAdmin.Services.CatalogItemServices namespace BlazorShared.Models
{ {
public class CreateCatalogItemRequest public class CreateCatalogItemRequest
{ {

View File

@@ -0,0 +1,7 @@
namespace BlazorShared.Models
{
public class CreateCatalogItemResponse
{
public CatalogItem CatalogItem { get; set; } = new CatalogItem();
}
}

View File

@@ -0,0 +1,7 @@
namespace BlazorShared.Models
{
public class DeleteCatalogItemResponse
{
public string Status { get; set; } = "Deleted";
}
}

View File

@@ -1,4 +1,4 @@
namespace BlazorAdmin.Services.CatalogItemServices namespace BlazorShared.Models
{ {
public class EditCatalogItemResult public class EditCatalogItemResult
{ {

View File

@@ -0,0 +1,8 @@
namespace BlazorShared.Models
{
public abstract class LookupData
{
public int Id { get; set; }
public string Name { get; set; }
}
}

View File

@@ -1,8 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace BlazorAdmin.Services.CatalogItemServices namespace BlazorShared.Models
{ {
public class PagedCatalogItemResult public class PagedCatalogItemResponse
{ {
public List<CatalogItem> CatalogItems { get; set; } = new List<CatalogItem>(); public List<CatalogItem> CatalogItems { get; set; } = new List<CatalogItem>();
public int PageCount { get; set; } = 0; public int PageCount { get; set; } = 0;

View File

@@ -1,10 +1,8 @@
using System; using System.IO;
using System.IO;
using Ardalis.ApiEndpoints; using Ardalis.ApiEndpoints;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.ApplicationCore.Constants;
using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.Annotations;

View File

@@ -7,6 +7,7 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
namespace Microsoft.eShopWeb.PublicApi namespace Microsoft.eShopWeb.PublicApi
{ {
@@ -43,6 +44,13 @@ namespace Microsoft.eShopWeb.PublicApi
public static IHostBuilder CreateHostBuilder(string[] args) => public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args) Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((builderContext, config) =>
{
var env = builderContext.HostingEnvironment;
config
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder => .ConfigureWebHostDefaults(webBuilder =>
{ {
webBuilder.UseStartup<Startup>(); webBuilder.UseStartup<Startup>();

View File

@@ -25,13 +25,6 @@
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
}, },
"applicationUrl": "https://localhost:5099;http://localhost:5098" "applicationUrl": "https://localhost:5099;http://localhost:5098"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"publishAllPorts": true,
"useSSL": true
} }
} }
} }

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using AutoMapper; using AutoMapper;
using BlazorShared;
using BlazorShared.Authorization; using BlazorShared.Authorization;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
@@ -28,7 +29,6 @@ namespace Microsoft.eShopWeb.PublicApi
public class Startup public class Startup
{ {
private const string CORS_POLICY = "CorsPolicy"; private const string CORS_POLICY = "CorsPolicy";
public static bool InDocker => Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true";
public Startup(IConfiguration configuration) public Startup(IConfiguration configuration)
{ {
@@ -46,6 +46,11 @@ namespace Microsoft.eShopWeb.PublicApi
//ConfigureProductionServices(services); //ConfigureProductionServices(services);
} }
public void ConfigureDockerServices(IServiceCollection services)
{
ConfigureDevelopmentServices(services);
}
private void ConfigureInMemoryDatabases(IServiceCollection services) private void ConfigureInMemoryDatabases(IServiceCollection services)
{ {
services.AddDbContext<CatalogContext>(c => services.AddDbContext<CatalogContext>(c =>
@@ -90,7 +95,10 @@ namespace Microsoft.eShopWeb.PublicApi
services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>())); services.AddSingleton<IUriComposer>(new UriComposer(Configuration.Get<CatalogSettings>()));
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>(); services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>();
services.AddScoped<IFileSystem, WebFileSystem>(x => new WebFileSystem($"{Constants.GetWebUrlInternal(Startup.InDocker)}File"));
var baseUrlConfig = new BaseUrlConfiguration();
Configuration.Bind(BaseUrlConfiguration.CONFIG_NAME, baseUrlConfig);
services.AddScoped<IFileSystem, WebFileSystem>(x => new WebFileSystem($"{baseUrlConfig.WebBase}File"));
services.AddMemoryCache(); services.AddMemoryCache();
@@ -112,15 +120,12 @@ namespace Microsoft.eShopWeb.PublicApi
}; };
}); });
services.AddCors(options => services.AddCors(options =>
{ {
options.AddPolicy(name: CORS_POLICY, options.AddPolicy(name: CORS_POLICY,
builder => builder =>
{ {
builder.WithOrigins("http://localhost:44319", builder.WithOrigins(baseUrlConfig.WebBase.Replace("host.docker.internal", "localhost").TrimEnd('/'));
"https://localhost:44319",
Constants.GetOriginWebUrl(InDocker));
builder.AllowAnyMethod(); builder.AllowAnyMethod();
builder.AllowAnyHeader(); builder.AllowAnyHeader();
}); });

View File

@@ -1,4 +1,8 @@
{ {
"baseUrls": {
"apiBase": "https://localhost:5099/api/",
"webBase": "https://localhost:44315/"
},
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Information",

View File

@@ -0,0 +1,17 @@
{
"ConnectionStrings": {
"CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;",
"IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;"
},
"baseUrls": {
"apiBase": "http://localhost:5200/api/",
"webBase": "http://host.docker.internal:5106/"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@@ -1,4 +1,8 @@
{ {
"baseUrls": {
"apiBase": "https://localhost:5099/api/",
"webBase": "https://localhost:44315/"
},
"ConnectionStrings": { "ConnectionStrings": {
"CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;", "CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;",
"IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;"

View File

@@ -22,16 +22,12 @@ namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
private readonly SignInManager<ApplicationUser> _signInManager; private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<LoginModel> _logger; private readonly ILogger<LoginModel> _logger;
private readonly IBasketService _basketService; private readonly IBasketService _basketService;
private readonly AuthService _authService;
private readonly ITokenClaimsService _tokenClaimsService;
public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger, IBasketService basketService, AuthService authService, ITokenClaimsService tokenClaimsService) public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger, IBasketService basketService)
{ {
_signInManager = signInManager; _signInManager = signInManager;
_logger = logger; _logger = logger;
_basketService = basketService; _basketService = basketService;
_authService = authService;
_tokenClaimsService = tokenClaimsService;
} }
[BindProperty] [BindProperty]
@@ -88,8 +84,6 @@ namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
if (result.Succeeded) if (result.Succeeded)
{ {
var token = await _tokenClaimsService.GetTokenAsync(Input.Email);
CreateAuthCookie(Input.Email, token, Startup.InDocker);
_logger.LogInformation("User logged in."); _logger.LogInformation("User logged in.");
await TransferAnonymousBasketToUserAsync(Input.Email); await TransferAnonymousBasketToUserAsync(Input.Email);
return LocalRedirect(returnUrl); return LocalRedirect(returnUrl);
@@ -114,15 +108,6 @@ namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
return Page(); return Page();
} }
private void CreateAuthCookie(string username, string token, bool inDocker)
{
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTime.Today.AddYears(10);
Response.Cookies.Append("token", token, cookieOptions);
Response.Cookies.Append("username", username, cookieOptions);
Response.Cookies.Append("inDocker", inDocker.ToString(), cookieOptions);
}
private async Task TransferAnonymousBasketToUserAsync(string userName) private async Task TransferAnonymousBasketToUserAsync(string userName)
{ {
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME)) if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))

View File

@@ -1,8 +1,8 @@
<environment include="Development"> <environment include="Development,Docker">
<script src="~/Identity/lib/jquery-validation/dist/jquery.validate.js"></script> <script src="~/Identity/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script> <script src="~/Identity/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment> </environment>
<environment exclude="Development"> <environment exclude="Development,Docker">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js" <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js"
asp-fallback-src="~/Identity/lib/jquery-validation/dist/jquery.validate.min.js" asp-fallback-src="~/Identity/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator" asp-fallback-test="window.jQuery && window.jQuery.validator"

View File

@@ -4,6 +4,8 @@ using System.Security.Claims;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using BlazorShared.Authorization; using BlazorShared.Authorization;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using System.Threading.Tasks;
namespace Microsoft.eShopWeb.Web.Controllers namespace Microsoft.eShopWeb.Web.Controllers
{ {
@@ -11,13 +13,20 @@ namespace Microsoft.eShopWeb.Web.Controllers
[ApiController] [ApiController]
public class UserController : ControllerBase public class UserController : ControllerBase
{ {
private readonly ITokenClaimsService _tokenClaimsService;
public UserController(ITokenClaimsService tokenClaimsService)
{
_tokenClaimsService = tokenClaimsService;
}
[HttpGet] [HttpGet]
[Authorize] [Authorize]
[AllowAnonymous] [AllowAnonymous]
public IActionResult GetCurrentUser() => public async Task<IActionResult> GetCurrentUser() =>
Ok(User.Identity.IsAuthenticated ? CreateUserInfo(User) : UserInfo.Anonymous); Ok(User.Identity.IsAuthenticated ? await CreateUserInfo(User) : UserInfo.Anonymous);
private UserInfo CreateUserInfo(ClaimsPrincipal claimsPrincipal) private async Task<UserInfo> CreateUserInfo(ClaimsPrincipal claimsPrincipal)
{ {
if (!claimsPrincipal.Identity.IsAuthenticated) if (!claimsPrincipal.Identity.IsAuthenticated)
{ {
@@ -57,6 +66,9 @@ namespace Microsoft.eShopWeb.Web.Controllers
userInfo.Claims = claims; userInfo.Claims = claims;
} }
var token = await _tokenClaimsService.GetTokenAsync(claimsPrincipal.Identity.Name);
userInfo.Token = token;
return userInfo; return userInfo;
} }
} }

View File

@@ -16,10 +16,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>@ViewData["Title"] - Microsoft.eShopOnWeb</title> <title>@ViewData["Title"] - Microsoft.eShopOnWeb</title>
<base href="~/" /> <base href="~/" />
<environment include="Development"> <environment include="Development,Docker">
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" /> <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
</environment> </environment>
<environment exclude="Development"> <environment exclude="Development,Docker">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
asp-fallback-href="css/bootstrap/bootstrap.min.css" asp-fallback-href="css/bootstrap/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"

View File

@@ -7,6 +7,7 @@ using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
namespace Microsoft.eShopWeb.Web namespace Microsoft.eShopWeb.Web
{ {
@@ -42,6 +43,13 @@ namespace Microsoft.eShopWeb.Web
public static IHostBuilder CreateHostBuilder(string[] args) => public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args) Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((builderContext, config) =>
{
var env = builderContext.HostingEnvironment;
config
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
})
.ConfigureWebHostDefaults(webBuilder => .ConfigureWebHostDefaults(webBuilder =>
{ {
webBuilder.UseStartup<Startup>(); webBuilder.UseStartup<Startup>();

View File

@@ -25,13 +25,13 @@ using Blazored.LocalStorage;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using BlazorShared;
namespace Microsoft.eShopWeb.Web namespace Microsoft.eShopWeb.Web
{ {
public class Startup public class Startup
{ {
private IServiceCollection _services; private IServiceCollection _services;
public static bool InDocker => Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true";
public Startup(IConfiguration configuration) public Startup(IConfiguration configuration)
{ {
@@ -49,6 +49,15 @@ namespace Microsoft.eShopWeb.Web
//ConfigureProductionServices(services); //ConfigureProductionServices(services);
} }
public void ConfigureDockerServices(IServiceCollection services)
{
services.AddDataProtection()
.SetApplicationName("eshopwebmvc")
.PersistKeysToFileSystem(new DirectoryInfo(@"./"));
ConfigureDevelopmentServices(services);
}
private void ConfigureInMemoryDatabases(IServiceCollection services) private void ConfigureInMemoryDatabases(IServiceCollection services)
{ {
// use in-memory database // use in-memory database
@@ -88,12 +97,6 @@ namespace Microsoft.eShopWeb.Web
{ {
services.AddCookieSettings(); services.AddCookieSettings();
if (InDocker)
{
services.AddDataProtection()
.SetApplicationName("eshopwebmvc")
.PersistKeysToFileSystem(new DirectoryInfo(@"./"));
}
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => .AddCookie(options =>
@@ -141,15 +144,22 @@ namespace Microsoft.eShopWeb.Web
config.Path = "/allservices"; config.Path = "/allservices";
}); });
var baseUrlConfig = new BaseUrlConfiguration();
Configuration.Bind(BaseUrlConfiguration.CONFIG_NAME, baseUrlConfig);
services.AddScoped<BaseUrlConfiguration>(sp => baseUrlConfig);
// Blazor Admin Required Services for Prerendering // Blazor Admin Required Services for Prerendering
services.AddScoped<HttpClient>(s => new HttpClient services.AddScoped<HttpClient>(s => new HttpClient
{ {
BaseAddress = new Uri(BlazorShared.Authorization.Constants.GetWebUrl(InDocker)) BaseAddress = new Uri(baseUrlConfig.WebBase)
}); });
// add blazor services
services.AddBlazoredLocalStorage(); services.AddBlazoredLocalStorage();
services.AddServerSideBlazor(); services.AddServerSideBlazor();
services.AddScoped<AuthService>(); services.AddScoped<AuthService>();
services.AddScoped<HttpService>();
services.AddBlazorServices(); services.AddBlazorServices();
_services = services; // used to debug registered services _services = services; // used to debug registered services

View File

@@ -4,7 +4,7 @@
<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>@ViewData["Title"] - Microsoft.eShopOnWeb</title> <title>@ViewData["Title"] - Microsoft.eShopOnWeb</title>
<environment names="Development"> <environment names="Development,Docker">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" /> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/app.css" /> <link rel="stylesheet" href="~/css/app.css" />
<link rel="stylesheet" href="~/css/app.component.css" /> <link rel="stylesheet" href="~/css/app.component.css" />
@@ -49,7 +49,7 @@
</div> </div>
</footer> </footer>
</div> </div>
<environment names="Development"> <environment names="Development,Docker">
<script src="~/lib/jquery/jquery.js"></script> <script src="~/lib/jquery/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script> <script src="~/js/site.js" asp-append-version="true"></script>

View File

@@ -1,4 +1,4 @@
<environment names="Development"> <environment names="Development,Docker">
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script> <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script> <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment> </environment>

View File

@@ -1,4 +1,8 @@
{ {
"baseUrls": {
"apiBase": "https://localhost:5099/api/",
"webBase": "https://localhost:44315/"
},
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Debug", "Default": "Debug",

View File

@@ -0,0 +1,17 @@
{
"ConnectionStrings": {
"CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;",
"IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;"
},
"baseUrls": {
"apiBase": "http://localhost:5200/api/",
"webBase": "http://host.docker.internal:5106/"
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View File

@@ -1,4 +1,8 @@
{ {
"baseUrls": {
"apiBase": "https://localhost:5099/api/",
"webBase": "https://localhost:44315/"
},
"ConnectionStrings": { "ConnectionStrings": {
"CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;", "CatalogConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.CatalogDb;",
"IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;" "IdentityConnection": "Server=(localdb)\\mssqllocaldb;Integrated Security=true;Initial Catalog=Microsoft.eShopOnWeb.Identity;"

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB