diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 32b0faa..dad72a5 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -2,9 +2,8 @@ version: '3.4' services: eshopwebmvc: environment: - - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_ENVIRONMENT=Docker - ASPNETCORE_URLS=http://+:80 - - DOTNET_RUNNING_IN_CONTAINER=true ports: - "5106:80" volumes: @@ -12,9 +11,8 @@ services: - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro eshoppublicapi: environment: - - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_ENVIRONMENT=Docker - ASPNETCORE_URLS=http://+:80 - - DOTNET_RUNNING_IN_CONTAINER=true ports: - "5200:80" volumes: diff --git a/src/BlazorAdmin/BlazorAdmin.csproj b/src/BlazorAdmin/BlazorAdmin.csproj index f02d879..c41d0af 100644 --- a/src/BlazorAdmin/BlazorAdmin.csproj +++ b/src/BlazorAdmin/BlazorAdmin.csproj @@ -14,6 +14,7 @@ + diff --git a/src/BlazorAdmin/CustomAuthStateProvider.cs b/src/BlazorAdmin/CustomAuthStateProvider.cs index af6092e..bdb8f9d 100644 --- a/src/BlazorAdmin/CustomAuthStateProvider.cs +++ b/src/BlazorAdmin/CustomAuthStateProvider.cs @@ -1,31 +1,41 @@ -using System; -using BlazorAdmin.Services; +using BlazorAdmin.Services; +using BlazorShared.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.Threading.Tasks; -using Microsoft.Extensions.Logging; -using BlazorShared.Authorization; namespace BlazorAdmin { public class CustomAuthStateProvider : AuthenticationStateProvider { + // TODO: Get Default Cache Duration from Config private static readonly TimeSpan UserCacheRefreshInterval = TimeSpan.FromSeconds(60); private readonly AuthService _authService; + private readonly HttpClient _httpClient; private readonly ILogger _logger; private DateTimeOffset _userLastCheck = DateTimeOffset.FromUnixTimeSeconds(0); private ClaimsPrincipal _cachedUser = new ClaimsPrincipal(new ClaimsIdentity()); - public CustomAuthStateProvider(AuthService authService, ILogger logger) + public CustomAuthStateProvider(AuthService authService, + HttpClient httpClient, + ILogger logger) { _authService = authService; + _httpClient = httpClient; _logger = logger; } - public override async Task GetAuthenticationStateAsync() => - new AuthenticationState(await GetUser(useCache: true)); + public override async Task GetAuthenticationStateAsync() + { + return new AuthenticationState(await GetUser(useCache: true)); + } private async ValueTask GetUser(bool useCache = false) { @@ -47,16 +57,17 @@ namespace BlazorAdmin try { - user = await _authService.GetTokenFromController(); + _logger.LogInformation("Fetching user details from web api."); + user = await _httpClient.GetFromJsonAsync("User"); } catch (Exception exc) { _logger.LogWarning(exc, "Fetching user failed."); } - + if (user == null || !user.IsAuthenticated) { - return new ClaimsPrincipal(new ClaimsIdentity()); + return null; } var identity = new ClaimsIdentity( @@ -72,6 +83,8 @@ namespace BlazorAdmin } } + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", user.Token); + return new ClaimsPrincipal(identity); } } diff --git a/src/BlazorAdmin/Pages/CatalogItemPage/Create.razor b/src/BlazorAdmin/Pages/CatalogItemPage/Create.razor index 101f7fd..be47dae 100644 --- a/src/BlazorAdmin/Pages/CatalogItemPage/Create.razor +++ b/src/BlazorAdmin/Pages/CatalogItemPage/Create.razor @@ -1,6 +1,7 @@ @inject ILogger Logger @inject AuthService Auth @inject IJSRuntime JSRuntime +@inject ICatalogItemService CatalogItemService @inherits BlazorAdmin.Helpers.BlazorComponent @@ -130,7 +131,7 @@ public IEnumerable Types { get; set; } [Parameter] - public EventCallback OnCloseClick { get; set; } + public EventCallback OnSaveClick { get; set; } private string LoadPicture => string.IsNullOrEmpty(_item.PictureBase64) ? string.Empty : $"data:image/png;base64, {_item.PictureBase64}"; private bool HasPicture => !string.IsNullOrEmpty(_item.PictureBase64); @@ -142,8 +143,8 @@ private async Task CreateClick() { - await new BlazorAdmin.Services.CatalogItemServices.Create(Auth).HandleAsync(_item); - await OnCloseClick.InvokeAsync(null); + await CatalogItemService.Create(_item); + await OnSaveClick.InvokeAsync(null); await Close(); } @@ -172,7 +173,6 @@ _modalDisplay = "none"; _modalClass = ""; _showCreateModal = false; - await OnCloseClick.InvokeAsync(null); } private async Task AddFile(IFileListEntry[] files) diff --git a/src/BlazorAdmin/Pages/CatalogItemPage/Delete.razor b/src/BlazorAdmin/Pages/CatalogItemPage/Delete.razor index e1a9c78..4afb1eb 100644 --- a/src/BlazorAdmin/Pages/CatalogItemPage/Delete.razor +++ b/src/BlazorAdmin/Pages/CatalogItemPage/Delete.razor @@ -1,6 +1,7 @@ @inject ILogger Logger @inject AuthService Auth @inject IJSRuntime JSRuntime +@inject ICatalogItemService CatalogItemService @inherits BlazorAdmin.Helpers.BlazorComponent @@ -50,7 +51,7 @@
- @Services.CatalogBrandServices.List.GetBrandName(Brands, _item.CatalogBrandId) + @_item.CatalogBrand
@@ -58,7 +59,7 @@
- @Services.CatalogTypeServices.List.GetTypeName(Types, _item.CatalogTypeId) + @_item.CatalogType
Price @@ -97,7 +98,7 @@ public IEnumerable Types { get; set; } [Parameter] - public EventCallback OnCloseClick { get; set; } + public EventCallback OnSaveClick { get; set; } private bool HasPicture => !string.IsNullOrEmpty(_item.PictureUri); private string _modalDisplay = "none;"; @@ -109,9 +110,9 @@ { // 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(); } @@ -121,7 +122,7 @@ await new Css(JSRuntime).HideBodyOverflow(); - _item = await new GetById(Auth).HandleAsync(id); + _item = await CatalogItemService.GetById(id); _modalDisplay = "block;"; _modalClass = "Show"; @@ -136,6 +137,5 @@ _modalDisplay = "none"; _modalClass = ""; _showDeleteModal = false; - await OnCloseClick.InvokeAsync(null); } } diff --git a/src/BlazorAdmin/Pages/CatalogItemPage/Details.razor b/src/BlazorAdmin/Pages/CatalogItemPage/Details.razor index fb3d518..4533f9b 100644 --- a/src/BlazorAdmin/Pages/CatalogItemPage/Details.razor +++ b/src/BlazorAdmin/Pages/CatalogItemPage/Details.razor @@ -1,6 +1,7 @@ @inject ILogger
Logger @inject AuthService Auth @inject IJSRuntime JSRuntime +@inject ICatalogItemService CatalogItemService @inherits BlazorAdmin.Helpers.BlazorComponent @@ -53,7 +54,7 @@
- @Services.CatalogBrandServices.List.GetBrandName(Brands, _item.CatalogBrandId) + @_item.CatalogBrand
@@ -61,7 +62,7 @@
- @Services.CatalogTypeServices.List.GetTypeName(Types, _item.CatalogTypeId) + @_item.CatalogType
Price @@ -108,10 +109,10 @@ private bool _showDetailsModal = false; private CatalogItem _item = new CatalogItem(); - public void EditClick() + public async Task EditClick() { - OnEditClick.InvokeAsync(_item.Id); - Close(); + await OnEditClick.InvokeAsync(_item.Id); + await Close(); } public async Task Open(int id) @@ -121,7 +122,7 @@ await new Css(JSRuntime).HideBodyOverflow(); - _item = await new GetById(Auth).HandleAsync(id); + _item = await CatalogItemService.GetById(id); _modalDisplay = "block;"; _modalClass = "Show"; diff --git a/src/BlazorAdmin/Pages/CatalogItemPage/Edit.razor b/src/BlazorAdmin/Pages/CatalogItemPage/Edit.razor index 101200a..2fa0214 100644 --- a/src/BlazorAdmin/Pages/CatalogItemPage/Edit.razor +++ b/src/BlazorAdmin/Pages/CatalogItemPage/Edit.razor @@ -1,6 +1,7 @@ @inject ILogger Logger @inject AuthService Auth @inject IJSRuntime JSRuntime +@inject ICatalogItemService CatalogItemService @inherits BlazorAdmin.Helpers.BlazorComponent @@ -54,12 +55,12 @@
- + @foreach (var brand in Brands) { - + } - +
@@ -67,12 +68,12 @@
- + @foreach (var type in Types) { } - +
@@ -130,7 +131,7 @@ public IEnumerable Types { get; set; } [Parameter] - public EventCallback OnCloseClick { get; set; } + public EventCallback 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 bool HasPicture => !(string.IsNullOrEmpty(_item.PictureBase64) && string.IsNullOrEmpty(_item.PictureUri)); @@ -142,7 +143,8 @@ private async Task SaveClick() { - await new BlazorAdmin.Services.CatalogItemServices.Edit(Auth).HandleAsync(_item); + await CatalogItemService.Edit(_item); + await OnSaveClick.InvokeAsync(null); await Close(); } @@ -152,7 +154,7 @@ await new Css(JSRuntime).HideBodyOverflow(); - _item = await new GetById(Auth).HandleAsync(id); + _item = await CatalogItemService.GetById(id); _modalDisplay = "block;"; _modalClass = "Show"; @@ -168,7 +170,6 @@ _modalDisplay = "none"; _modalClass = ""; _showEditModal = false; - await OnCloseClick.InvokeAsync(null); } private async Task ChangeFile(IFileListEntry[] files) diff --git a/src/BlazorAdmin/Pages/CatalogItemPage/List.razor b/src/BlazorAdmin/Pages/CatalogItemPage/List.razor index cae60cb..2672f0e 100644 --- a/src/BlazorAdmin/Pages/CatalogItemPage/List.razor +++ b/src/BlazorAdmin/Pages/CatalogItemPage/List.razor @@ -1,9 +1,6 @@ @page "/admin" @attribute [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)] @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 @namespace BlazorAdmin.Pages.CatalogItemPage @@ -42,8 +39,8 @@ else - @Services.CatalogTypeServices.List.GetTypeName(catalogTypes, item.CatalogTypeId) - @Services.CatalogBrandServices.List.GetBrandName(catalogBrands, item.CatalogBrandId) + @item.CatalogType + @item.CatalogBrand @item.Id @item.Name @item.Description @@ -63,7 +60,7 @@ else
- - - + + + } diff --git a/src/BlazorAdmin/Pages/CatalogItemPage/List.razor.cs b/src/BlazorAdmin/Pages/CatalogItemPage/List.razor.cs index 18d434c..9b12a73 100644 --- a/src/BlazorAdmin/Pages/CatalogItemPage/List.razor.cs +++ b/src/BlazorAdmin/Pages/CatalogItemPage/List.razor.cs @@ -1,14 +1,22 @@ using BlazorAdmin.Helpers; -using BlazorAdmin.Services.CatalogBrandServices; -using BlazorAdmin.Services.CatalogItemServices; -using BlazorAdmin.Services.CatalogTypeServices; using System.Collections.Generic; using System.Threading.Tasks; +using BlazorShared.Interfaces; +using BlazorShared.Models; namespace BlazorAdmin.Pages.CatalogItemPage { 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 catalogItems = new List(); private List catalogTypes = new List(); private List catalogBrands = new List(); @@ -22,9 +30,9 @@ namespace BlazorAdmin.Pages.CatalogItemPage { if (firstRender) { - catalogItems = await CatalogItemListPaged.HandleAsync(50); - catalogTypes = await TypeList.HandleAsync(); - catalogBrands = await BrandList.HandleAsync(); + catalogItems = await CatalogItemService.List(); + catalogTypes = await CatalogTypeService.List(); + catalogBrands = await CatalogBrandService.List(); CallRequestRefresh(); } @@ -54,7 +62,7 @@ namespace BlazorAdmin.Pages.CatalogItemPage private async Task ReloadCatalogItems() { - catalogItems = await new BlazorAdmin.Services.CatalogItemServices.ListPaged(Auth).HandleAsync(50); + catalogItems = await CatalogItemService.List(); StateHasChanged(); } } diff --git a/src/BlazorAdmin/Program.cs b/src/BlazorAdmin/Program.cs index a2adbee..89080ad 100644 --- a/src/BlazorAdmin/Program.cs +++ b/src/BlazorAdmin/Program.cs @@ -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.Net.Http; 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 { @@ -16,18 +19,36 @@ namespace BlazorAdmin var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("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(sp => baseUrlConfig); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); + builder.Services.AddScoped(sp => new HttpClient() { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + + builder.Services.AddScoped(); + + builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddAuthorizationCore(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => (CustomAuthStateProvider)sp.GetRequiredService()); + builder.Services.AddScoped(); + builder.Services.AddScoped(sp => (CustomAuthStateProvider)sp.GetRequiredService()); 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(); + + await localStorageService.RemoveItemAsync("brands"); } } } diff --git a/src/BlazorAdmin/Services/AuthService.cs b/src/BlazorAdmin/Services/AuthService.cs index f426dbe..5c0721a 100644 --- a/src/BlazorAdmin/Services/AuthService.cs +++ b/src/BlazorAdmin/Services/AuthService.cs @@ -1,11 +1,8 @@ using System.Net.Http; -using System.Net.Http.Headers; -using System.Net.Http.Json; using System.Threading.Tasks; using BlazorAdmin.JavaScript; using Blazored.LocalStorage; using Microsoft.JSInterop; -using BlazorShared.Authorization; namespace BlazorAdmin.Services { @@ -14,143 +11,32 @@ namespace BlazorAdmin.Services private readonly HttpClient _httpClient; private readonly ILocalStorageService _localStorage; private readonly IJSRuntime _jSRuntime; - private static bool InDocker { get; set; } - - public string ApiUrl => Constants.GetApiUrl(InDocker); 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; _localStorage = localStorage; _jSRuntime = jSRuntime; } - public HttpClient GetHttpClient() - { - return _httpClient; - } - public async Task Logout() { - await DeleteLocalStorage(); await DeleteCookies(); - RemoveAuthorizationHeader(); - UserName = null; IsLoggedIn = false; 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 GetToken() - { - - var token = await _localStorage.GetItemAsync("authToken"); - return token; - } - - public async Task GetTokenFromController() - { - return await _httpClient.GetFromJsonAsync("User"); - } - - public async Task GetUsername() - { - var username = await _localStorage.GetItemAsync("username"); - return username; - } - - public async Task GetInDocker() - { - return (await _localStorage.GetItemAsync("inDocker")).ToLower() == "true"; - } - private async Task LogoutIdentityManager() { 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() { 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); - } - } } diff --git a/src/BlazorAdmin/Services/CacheEntry.cs b/src/BlazorAdmin/Services/CacheEntry.cs new file mode 100644 index 0000000..97abd8e --- /dev/null +++ b/src/BlazorAdmin/Services/CacheEntry.cs @@ -0,0 +1,19 @@ +using System; + +namespace BlazorAdmin.Services +{ + public class CacheEntry + { + public CacheEntry(T item) + { + Value = item; + } + public CacheEntry() + { + + } + + public T Value { get; set; } + public DateTime DateCreated { get; set; } = DateTime.UtcNow; + } +} diff --git a/src/BlazorAdmin/Services/CachedCatalogBrandServiceDecorator.cs b/src/BlazorAdmin/Services/CachedCatalogBrandServiceDecorator.cs new file mode 100644 index 0000000..b076490 --- /dev/null +++ b/src/BlazorAdmin/Services/CachedCatalogBrandServiceDecorator.cs @@ -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 _logger; + + public CachedCatalogBrandServiceDecorator(ILocalStorageService localStorageService, + CatalogBrandService catalogBrandService, + ILogger logger) + { + _localStorageService = localStorageService; + _catalogBrandService = catalogBrandService; + _logger = logger; + + } + + public async Task GetById(int id) + { + return (await List()).FirstOrDefault(x => x.Id == id); + } + + public async Task> List() + { + string key = "brands"; + var cacheEntry = await _localStorageService.GetItemAsync>>(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>(brands); + await _localStorageService.SetItemAsync(key, entry); + return brands; + } + } +} diff --git a/src/BlazorAdmin/Services/CachedCatalogItemServiceDecorator.cs b/src/BlazorAdmin/Services/CachedCatalogItemServiceDecorator.cs new file mode 100644 index 0000000..885cb94 --- /dev/null +++ b/src/BlazorAdmin/Services/CachedCatalogItemServiceDecorator.cs @@ -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 _logger; + + public CachedCatalogItemServiceDecorator(ILocalStorageService localStorageService, + CatalogItemService catalogItemService, + ILogger logger) + { + _localStorageService = localStorageService; + _catalogItemService = catalogItemService; + _logger = logger; + } + + public async Task> ListPaged(int pageSize) + { + string key = "items"; + var cacheEntry = await _localStorageService.GetItemAsync>>(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>(items); + await _localStorageService.SetItemAsync(key, entry); + return items; + } + + public async Task> List() + { + string key = "items"; + var cacheEntry = await _localStorageService.GetItemAsync>>(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>(items); + await _localStorageService.SetItemAsync(key, entry); + return items; + } + + public async Task GetById(int id) + { + return (await List()).FirstOrDefault(x => x.Id == id); + } + + public async Task Create(CreateCatalogItemRequest catalogItem) + { + var result = await _catalogItemService.Create(catalogItem); + await RefreshLocalStorageList(); + + return result; + } + + public async Task Edit(CatalogItem catalogItem) + { + var result = await _catalogItemService.Edit(catalogItem); + await RefreshLocalStorageList(); + + return result; + } + + public async Task 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>(items); + await _localStorageService.SetItemAsync(key, entry); + } + } +} diff --git a/src/BlazorAdmin/Services/CachedCatalogTypeServiceDecorator.cs b/src/BlazorAdmin/Services/CachedCatalogTypeServiceDecorator.cs new file mode 100644 index 0000000..86c0cc0 --- /dev/null +++ b/src/BlazorAdmin/Services/CachedCatalogTypeServiceDecorator.cs @@ -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 _logger; + + public CachedCatalogTypeServiceDecorator(ILocalStorageService localStorageService, + CatalogTypeService catalogTypeService, + ILogger logger) + { + _localStorageService = localStorageService; + _catalogTypeService = catalogTypeService; + _logger = logger; + } + + public async Task GetById(int id) + { + return (await List()).FirstOrDefault(x => x.Id == id); + } + + public async Task> List() + { + string key = "types"; + var cacheEntry = await _localStorageService.GetItemAsync>>(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>(types); + await _localStorageService.SetItemAsync(key, entry); + return types; + } + } +} diff --git a/src/BlazorAdmin/Services/CatalogBrandService.cs b/src/BlazorAdmin/Services/CatalogBrandService.cs new file mode 100644 index 0000000..263d6ea --- /dev/null +++ b/src/BlazorAdmin/Services/CatalogBrandService.cs @@ -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 _logger; + private string _apiUrl; + + public CatalogBrandService(HttpClient httpClient, + BaseUrlConfiguration baseUrlConfiguration, + ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + _apiUrl = baseUrlConfiguration.ApiBase; + } + + public async Task GetById(int id) + { + return (await List()).FirstOrDefault(x => x.Id == id); + } + + public async Task> List() + { + _logger.LogInformation("Fetching brands from API."); + return (await _httpClient.GetFromJsonAsync($"{_apiUrl}catalog-brands"))?.CatalogBrands; + } + } +} diff --git a/src/BlazorAdmin/Services/CatalogBrandServices/List.CatalogBrand.cs b/src/BlazorAdmin/Services/CatalogBrandServices/List.CatalogBrand.cs deleted file mode 100644 index b8d8a5b..0000000 --- a/src/BlazorAdmin/Services/CatalogBrandServices/List.CatalogBrand.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BlazorAdmin.Services.CatalogBrandServices -{ - public class CatalogBrand - { - public int Id { get; set; } - public string Name { get; set; } - } -} diff --git a/src/BlazorAdmin/Services/CatalogBrandServices/List.cs b/src/BlazorAdmin/Services/CatalogBrandServices/List.cs deleted file mode 100644 index 454840a..0000000 --- a/src/BlazorAdmin/Services/CatalogBrandServices/List.cs +++ /dev/null @@ -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> HandleAsync() - { - return (await _httpClient.GetFromJsonAsync($"{_authService.ApiUrl}catalog-brands"))?.CatalogBrands; - } - - public static string GetBrandName(IEnumerable brands, int brandId) - { - var type = brands.FirstOrDefault(t => t.Id == brandId); - - return type == null ? "None" : type.Name; - } - - } -} diff --git a/src/BlazorAdmin/Services/CatalogItemService.cs b/src/BlazorAdmin/Services/CatalogItemService.cs new file mode 100644 index 0000000..a33afae --- /dev/null +++ b/src/BlazorAdmin/Services/CatalogItemService.cs @@ -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 _logger; + private string _apiUrl; + + public CatalogItemService(ICatalogBrandService brandService, + ICatalogTypeService typeService, + HttpService httpService, + BaseUrlConfiguration baseUrlConfiguration, + ILogger logger) + { + _brandService = brandService; + _typeService = typeService; + + _httpService = httpService; + _logger = logger; + _apiUrl = baseUrlConfiguration.ApiBase; + } + + public async Task Create(CreateCatalogItemRequest catalogItem) + { + return (await _httpService.HttpPost("catalog-items", catalogItem)).CatalogItem; + } + + public async Task Edit(CatalogItem catalogItem) + { + return (await _httpService.HttpPut("catalog-items", catalogItem)).CatalogItem; + } + + public async Task Delete(int catalogItemId) + { + return (await _httpService.HttpDelete("catalog-items", catalogItemId)).Status; + } + + public async Task GetById(int id) + { + var brandListTask = _brandService.List(); + var typeListTask = _typeService.List(); + var itemGetTask = _httpService.HttpGet($"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> ListPaged(int pageSize) + { + _logger.LogInformation("Fetching catalog items from API."); + + var brandListTask = _brandService.List(); + var typeListTask = _typeService.List(); + var itemListTask = _httpService.HttpGet($"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() + { + _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($"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; + } + } +} diff --git a/src/BlazorAdmin/Services/CatalogItemServices/Create.CreateCatalogItemResult.cs b/src/BlazorAdmin/Services/CatalogItemServices/Create.CreateCatalogItemResult.cs deleted file mode 100644 index 37cf03f..0000000 --- a/src/BlazorAdmin/Services/CatalogItemServices/Create.CreateCatalogItemResult.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BlazorAdmin.Services.CatalogItemServices -{ - public class CreateCatalogItemResult - { - public CatalogItem CatalogItem { get; set; } = new CatalogItem(); - } -} diff --git a/src/BlazorAdmin/Services/CatalogItemServices/Create.cs b/src/BlazorAdmin/Services/CatalogItemServices/Create.cs deleted file mode 100644 index 74b9cc8..0000000 --- a/src/BlazorAdmin/Services/CatalogItemServices/Create.cs +++ /dev/null @@ -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 HandleAsync(CreateCatalogItemRequest catalogItem) - { - return (await _httpService.HttpPost("catalog-items", catalogItem)).CatalogItem; - } - } -} diff --git a/src/BlazorAdmin/Services/CatalogItemServices/Delete.DeleteCatalogItemResult.cs b/src/BlazorAdmin/Services/CatalogItemServices/Delete.DeleteCatalogItemResult.cs deleted file mode 100644 index 8436815..0000000 --- a/src/BlazorAdmin/Services/CatalogItemServices/Delete.DeleteCatalogItemResult.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BlazorAdmin.Services.CatalogItemServices -{ - public class DeleteCatalogItemResult - { - public string Status { get; set; } = "Deleted"; - } -} diff --git a/src/BlazorAdmin/Services/CatalogItemServices/Delete.cs b/src/BlazorAdmin/Services/CatalogItemServices/Delete.cs deleted file mode 100644 index 86f82b9..0000000 --- a/src/BlazorAdmin/Services/CatalogItemServices/Delete.cs +++ /dev/null @@ -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 HandleAsync(int catalogItemId) - { - return (await _httpService.HttpDelete("catalog-items", catalogItemId)).Status; - } - } -} diff --git a/src/BlazorAdmin/Services/CatalogItemServices/Edit.cs b/src/BlazorAdmin/Services/CatalogItemServices/Edit.cs deleted file mode 100644 index a5fd6d3..0000000 --- a/src/BlazorAdmin/Services/CatalogItemServices/Edit.cs +++ /dev/null @@ -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 HandleAsync(CatalogItem catalogItem) - { - return (await _httpService.HttpPut("catalog-items", catalogItem)).CatalogItem; - } - } -} diff --git a/src/BlazorAdmin/Services/CatalogItemServices/GetById.GetByIdCatalogItemResult.cs b/src/BlazorAdmin/Services/CatalogItemServices/GetById.GetByIdCatalogItemResult.cs deleted file mode 100644 index e7e9f1d..0000000 --- a/src/BlazorAdmin/Services/CatalogItemServices/GetById.GetByIdCatalogItemResult.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BlazorAdmin.Services.CatalogItemServices -{ - public class GetByIdCatalogItemResult - { - public CatalogItem CatalogItem { get; set; } = new CatalogItem(); - } -} diff --git a/src/BlazorAdmin/Services/CatalogItemServices/GetById.cs b/src/BlazorAdmin/Services/CatalogItemServices/GetById.cs deleted file mode 100644 index 01347f3..0000000 --- a/src/BlazorAdmin/Services/CatalogItemServices/GetById.cs +++ /dev/null @@ -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 HandleAsync(int catalogItemId) - { - return (await _httpService.HttpGet($"catalog-items/{catalogItemId}")).CatalogItem; - } - } -} diff --git a/src/BlazorAdmin/Services/CatalogItemServices/ListPaged.cs b/src/BlazorAdmin/Services/CatalogItemServices/ListPaged.cs deleted file mode 100644 index 85a1e10..0000000 --- a/src/BlazorAdmin/Services/CatalogItemServices/ListPaged.cs +++ /dev/null @@ -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> HandleAsync(int pageSize) - { - return (await _httpService.HttpGet($"catalog-items?PageSize={pageSize}")).CatalogItems; - } - - } -} \ No newline at end of file diff --git a/src/BlazorAdmin/Services/CatalogTypeService.cs b/src/BlazorAdmin/Services/CatalogTypeService.cs new file mode 100644 index 0000000..a41fc7d --- /dev/null +++ b/src/BlazorAdmin/Services/CatalogTypeService.cs @@ -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 _logger; + private string _apiUrl; + + public CatalogTypeService(HttpClient httpClient, + BaseUrlConfiguration baseUrlConfiguration, + ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + _apiUrl = baseUrlConfiguration.ApiBase; + } + + public async Task GetById(int id) + { + return (await List()).FirstOrDefault(x => x.Id == id); + } + + public async Task> List() + { + _logger.LogInformation("Fetching types from API."); + return (await _httpClient.GetFromJsonAsync($"{_apiUrl}catalog-types"))?.CatalogTypes; + } + } +} diff --git a/src/BlazorAdmin/Services/CatalogTypeServices/List.CatalogType.cs b/src/BlazorAdmin/Services/CatalogTypeServices/List.CatalogType.cs deleted file mode 100644 index 0a22de3..0000000 --- a/src/BlazorAdmin/Services/CatalogTypeServices/List.CatalogType.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BlazorAdmin.Services.CatalogTypeServices -{ - public class CatalogType - { - public int Id { get; set; } - public string Name { get; set; } - } -} diff --git a/src/BlazorAdmin/Services/CatalogTypeServices/List.cs b/src/BlazorAdmin/Services/CatalogTypeServices/List.cs deleted file mode 100644 index 174fb80..0000000 --- a/src/BlazorAdmin/Services/CatalogTypeServices/List.cs +++ /dev/null @@ -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> HandleAsync() - { - return (await _httpClient.GetFromJsonAsync($"{_authService.ApiUrl}catalog-types"))?.CatalogTypes; - } - - public static string GetTypeName(IEnumerable types, int typeId) - { - var type = types.FirstOrDefault(t => t.Id == typeId); - - return type == null ? "None" : type.Name; - } - - } -} diff --git a/src/BlazorAdmin/Services/HttpService.cs b/src/BlazorAdmin/Services/HttpService.cs index 7344864..489a7a4 100644 --- a/src/BlazorAdmin/Services/HttpService.cs +++ b/src/BlazorAdmin/Services/HttpService.cs @@ -1,4 +1,5 @@ -using System.Net.Http; +using BlazorShared; +using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -10,10 +11,11 @@ namespace BlazorAdmin.Services private readonly HttpClient _httpClient; private readonly string _apiUrl; - public HttpService(HttpClient httpClient, string apiUrl) + + public HttpService(HttpClient httpClient, BaseUrlConfiguration baseUrlConfiguration) { _httpClient = httpClient; - _apiUrl = apiUrl; + _apiUrl = baseUrlConfiguration.ApiBase; } public async Task HttpGet(string uri) diff --git a/src/BlazorAdmin/ServicesConfiguration.cs b/src/BlazorAdmin/ServicesConfiguration.cs index dc13468..fb4302b 100644 --- a/src/BlazorAdmin/ServicesConfiguration.cs +++ b/src/BlazorAdmin/ServicesConfiguration.cs @@ -1,22 +1,21 @@ -using BlazorAdmin.Services.CatalogItemServices; +using BlazorAdmin.Services; +using BlazorShared.Interfaces; using Microsoft.Extensions.DependencyInjection; namespace BlazorAdmin { public static class ServicesConfiguration { - public static IServiceCollection AddBlazorServices(this IServiceCollection service) + public static IServiceCollection AddBlazorServices(this IServiceCollection services) { - service.AddScoped(); - service.AddScoped(); - service.AddScoped(); - service.AddScoped(); - service.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - service.AddScoped(); - service.AddScoped(); - - return service; + return services; } } } diff --git a/src/BlazorAdmin/Shared/CustomInputSelect.cs b/src/BlazorAdmin/Shared/CustomInputSelect.cs new file mode 100644 index 0000000..238a79a --- /dev/null +++ b/src/BlazorAdmin/Shared/CustomInputSelect.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Components.Forms; + +namespace BlazorAdmin.Shared +{ + /// + /// This is needed until 5.0 ships with native support + /// https://www.pragimtech.com/blog/blazor/inputselect-does-not-support-system.int32/ + /// + /// + public class CustomInputSelect : InputSelect + { + 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); + } + } + } +} diff --git a/src/BlazorAdmin/Shared/MainLayout.razor b/src/BlazorAdmin/Shared/MainLayout.razor index c9438fa..7070aae 100644 --- a/src/BlazorAdmin/Shared/MainLayout.razor +++ b/src/BlazorAdmin/Shared/MainLayout.razor @@ -1,4 +1,4 @@ -@inject AuthService Auth +@inject AuthenticationStateProvider AuthStateProvider @inject IJSRuntime JSRuntime @inherits BlazorAdmin.Helpers.BlazorLayoutComponent @@ -25,8 +25,9 @@ { if (firstRender) { - await Auth.RefreshLoginInfoFromCookie(); - if (!Auth.IsLoggedIn) + var authState = await AuthStateProvider.GetAuthenticationStateAsync(); + + if(authState.User == null) { await new Route(JSRuntime).RouteOutside("/Identity/Account/Login"); } @@ -35,5 +36,4 @@ await base.OnAfterRenderAsync(firstRender); } - } diff --git a/src/BlazorAdmin/Shared/NavMenu.razor b/src/BlazorAdmin/Shared/NavMenu.razor index 129eb57..49c44cd 100644 --- a/src/BlazorAdmin/Shared/NavMenu.razor +++ b/src/BlazorAdmin/Shared/NavMenu.razor @@ -1,5 +1,4 @@ -@inject AuthService Auth -@inherits BlazorAdmin.Helpers.BlazorComponent +@inherits BlazorAdmin.Helpers.BlazorComponent @code { private bool collapseNavMenu = true; - public string UserName { get; set; } private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; diff --git a/src/BlazorAdmin/_Imports.razor b/src/BlazorAdmin/_Imports.razor index 3d938ec..c53df61 100644 --- a/src/BlazorAdmin/_Imports.razor +++ b/src/BlazorAdmin/_Imports.razor @@ -11,9 +11,8 @@ @using BlazorAdmin @using BlazorAdmin.Shared @using BlazorAdmin.Services -@using BlazorAdmin.Services.CatalogBrandServices -@using BlazorAdmin.Services.CatalogItemServices -@using BlazorAdmin.Services.CatalogTypeServices @using BlazorAdmin.JavaScript @using BlazorShared.Authorization +@using BlazorShared.Interfaces @using BlazorInputFile +@using BlazorShared.Models diff --git a/src/BlazorAdmin/wwwroot/appsettings.Docker.json b/src/BlazorAdmin/wwwroot/appsettings.Docker.json new file mode 100644 index 0000000..98445e3 --- /dev/null +++ b/src/BlazorAdmin/wwwroot/appsettings.Docker.json @@ -0,0 +1,6 @@ +{ + "baseUrls": { + "apiBase": "http://localhost:5200/api/", + "webBase": "http://host.docker.internal:5106/" + } +} \ No newline at end of file diff --git a/src/BlazorAdmin/wwwroot/appsettings.json b/src/BlazorAdmin/wwwroot/appsettings.json new file mode 100644 index 0000000..cefa244 --- /dev/null +++ b/src/BlazorAdmin/wwwroot/appsettings.json @@ -0,0 +1,14 @@ +{ + "baseUrls": { + "apiBase": "https://localhost:5099/api/", + "webBase": "https://localhost:44315/" + }, + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "System": "Warning" + } + } +} \ No newline at end of file diff --git a/src/BlazorAdmin/wwwroot/sample-data/weather.json b/src/BlazorAdmin/wwwroot/sample-data/weather.json deleted file mode 100644 index 06463c0..0000000 --- a/src/BlazorAdmin/wwwroot/sample-data/weather.json +++ /dev/null @@ -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" - } -] diff --git a/src/BlazorShared/Authorization/Constants.cs b/src/BlazorShared/Authorization/Constants.cs index d787c83..4ddc121 100644 --- a/src/BlazorShared/Authorization/Constants.cs +++ b/src/BlazorShared/Authorization/Constants.cs @@ -6,23 +6,5 @@ { 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/"; - } } diff --git a/src/BlazorShared/Authorization/UserInfo.cs b/src/BlazorShared/Authorization/UserInfo.cs index 19cbd11..9ae24b8 100644 --- a/src/BlazorShared/Authorization/UserInfo.cs +++ b/src/BlazorShared/Authorization/UserInfo.cs @@ -8,6 +8,7 @@ namespace BlazorShared.Authorization public bool IsAuthenticated { get; set; } public string NameClaimType { get; set; } public string RoleClaimType { get; set; } + public string Token { get; set; } public IEnumerable Claims { get; set; } } } diff --git a/src/BlazorShared/BaseUrlConfiguration.cs b/src/BlazorShared/BaseUrlConfiguration.cs new file mode 100644 index 0000000..419735f --- /dev/null +++ b/src/BlazorShared/BaseUrlConfiguration.cs @@ -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; } + } +} diff --git a/src/BlazorShared/BlazorShared.csproj b/src/BlazorShared/BlazorShared.csproj index 07b614b..0e231e9 100644 --- a/src/BlazorShared/BlazorShared.csproj +++ b/src/BlazorShared/BlazorShared.csproj @@ -6,4 +6,8 @@ BlazorShared + + + + diff --git a/src/BlazorShared/Interfaces/ICatalogBrandService.cs b/src/BlazorShared/Interfaces/ICatalogBrandService.cs new file mode 100644 index 0000000..ba2f920 --- /dev/null +++ b/src/BlazorShared/Interfaces/ICatalogBrandService.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using BlazorShared.Models; + +namespace BlazorShared.Interfaces +{ + public interface ICatalogBrandService + { + Task> List(); + Task GetById(int id); + } +} diff --git a/src/BlazorShared/Interfaces/ICatalogItemService.cs b/src/BlazorShared/Interfaces/ICatalogItemService.cs new file mode 100644 index 0000000..3b277d3 --- /dev/null +++ b/src/BlazorShared/Interfaces/ICatalogItemService.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using BlazorShared.Models; + +namespace BlazorShared.Interfaces +{ + public interface ICatalogItemService + { + Task Create(CreateCatalogItemRequest catalogItem); + Task Edit(CatalogItem catalogItem); + Task Delete(int id); + Task GetById(int id); + Task> ListPaged(int pageSize); + Task> List(); + } +} diff --git a/src/BlazorShared/Interfaces/ICatalogTypeService.cs b/src/BlazorShared/Interfaces/ICatalogTypeService.cs new file mode 100644 index 0000000..437ff34 --- /dev/null +++ b/src/BlazorShared/Interfaces/ICatalogTypeService.cs @@ -0,0 +1,12 @@ +using BlazorShared.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace BlazorShared.Interfaces +{ + public interface ICatalogTypeService + { + Task> List(); + Task GetById(int id); + } +} diff --git a/src/BlazorShared/Models/CatalogBrand.cs b/src/BlazorShared/Models/CatalogBrand.cs new file mode 100644 index 0000000..631a23b --- /dev/null +++ b/src/BlazorShared/Models/CatalogBrand.cs @@ -0,0 +1,6 @@ +namespace BlazorShared.Models +{ + public class CatalogBrand : LookupData + { + } +} diff --git a/src/BlazorAdmin/Services/CatalogBrandServices/List.CatalogBrandResult.cs b/src/BlazorShared/Models/CatalogBrandResponse.cs similarity index 62% rename from src/BlazorAdmin/Services/CatalogBrandServices/List.CatalogBrandResult.cs rename to src/BlazorShared/Models/CatalogBrandResponse.cs index 88493ea..ba01032 100644 --- a/src/BlazorAdmin/Services/CatalogBrandServices/List.CatalogBrandResult.cs +++ b/src/BlazorShared/Models/CatalogBrandResponse.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -namespace BlazorAdmin.Services.CatalogBrandServices +namespace BlazorShared.Models { - public class CatalogBrandResult + public class CatalogBrandResponse { public List CatalogBrands { get; set; } = new List(); } diff --git a/src/BlazorAdmin/Services/CatalogItemServices/CatalogItem.cs b/src/BlazorShared/Models/CatalogItem.cs similarity index 79% rename from src/BlazorAdmin/Services/CatalogItemServices/CatalogItem.cs rename to src/BlazorShared/Models/CatalogItem.cs index a732225..ddd0d00 100644 --- a/src/BlazorAdmin/Services/CatalogItemServices/CatalogItem.cs +++ b/src/BlazorShared/Models/CatalogItem.cs @@ -4,15 +4,17 @@ using System.IO; using System.Threading.Tasks; using BlazorInputFile; -namespace BlazorAdmin.Services.CatalogItemServices +namespace BlazorShared.Models { public class CatalogItem { public int Id { get; set; } public int CatalogTypeId { get; set; } + public string CatalogType { get; set; } = "NotSet"; public int CatalogBrandId { get; set; } + public string CatalogBrand { get; set; } = "NotSet"; [Required(ErrorMessage = "The Name field is required")] public string Name { get; set; } @@ -60,14 +62,17 @@ namespace BlazorAdmin.Services.CatalogItemServices public static async Task 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(); - await reader.BaseStream.CopyToAsync(memStream); - var fileData = memStream.ToArray(); - var encodedBase64 = Convert.ToBase64String(fileData); - - return encodedBase64; + return encodedBase64; + } + } } private static bool IsExtensionValid(string fileName) diff --git a/src/BlazorShared/Models/CatalogType.cs b/src/BlazorShared/Models/CatalogType.cs new file mode 100644 index 0000000..ed7dad9 --- /dev/null +++ b/src/BlazorShared/Models/CatalogType.cs @@ -0,0 +1,6 @@ +namespace BlazorShared.Models +{ + public class CatalogType : LookupData + { + } +} diff --git a/src/BlazorAdmin/Services/CatalogTypeServices/List.CatalogTypeResult.cs b/src/BlazorShared/Models/CatalogTypeResponse.cs similarity index 62% rename from src/BlazorAdmin/Services/CatalogTypeServices/List.CatalogTypeResult.cs rename to src/BlazorShared/Models/CatalogTypeResponse.cs index a897967..664732e 100644 --- a/src/BlazorAdmin/Services/CatalogTypeServices/List.CatalogTypeResult.cs +++ b/src/BlazorShared/Models/CatalogTypeResponse.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -namespace BlazorAdmin.Services.CatalogTypeServices +namespace BlazorShared.Models { - public class CatalogTypeResult + public class CatalogTypeResponse { public List CatalogTypes { get; set; } = new List(); } diff --git a/src/BlazorAdmin/Services/CatalogItemServices/Create.CreateCatalogItemRequest.cs b/src/BlazorShared/Models/CreateCatalogItemRequest.cs similarity index 94% rename from src/BlazorAdmin/Services/CatalogItemServices/Create.CreateCatalogItemRequest.cs rename to src/BlazorShared/Models/CreateCatalogItemRequest.cs index 1c4a343..0894109 100644 --- a/src/BlazorAdmin/Services/CatalogItemServices/Create.CreateCatalogItemRequest.cs +++ b/src/BlazorShared/Models/CreateCatalogItemRequest.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace BlazorAdmin.Services.CatalogItemServices +namespace BlazorShared.Models { public class CreateCatalogItemRequest { diff --git a/src/BlazorShared/Models/CreateCatalogItemResponse.cs b/src/BlazorShared/Models/CreateCatalogItemResponse.cs new file mode 100644 index 0000000..e51d3ff --- /dev/null +++ b/src/BlazorShared/Models/CreateCatalogItemResponse.cs @@ -0,0 +1,7 @@ +namespace BlazorShared.Models +{ + public class CreateCatalogItemResponse + { + public CatalogItem CatalogItem { get; set; } = new CatalogItem(); + } +} diff --git a/src/BlazorShared/Models/DeleteCatalogItemResponse.cs b/src/BlazorShared/Models/DeleteCatalogItemResponse.cs new file mode 100644 index 0000000..3cd1e22 --- /dev/null +++ b/src/BlazorShared/Models/DeleteCatalogItemResponse.cs @@ -0,0 +1,7 @@ +namespace BlazorShared.Models +{ + public class DeleteCatalogItemResponse + { + public string Status { get; set; } = "Deleted"; + } +} diff --git a/src/BlazorAdmin/Services/CatalogItemServices/Edit.EditCatalogItemResult.cs b/src/BlazorShared/Models/EditCatalogItemResponse.cs similarity index 70% rename from src/BlazorAdmin/Services/CatalogItemServices/Edit.EditCatalogItemResult.cs rename to src/BlazorShared/Models/EditCatalogItemResponse.cs index 9f303eb..ed690d6 100644 --- a/src/BlazorAdmin/Services/CatalogItemServices/Edit.EditCatalogItemResult.cs +++ b/src/BlazorShared/Models/EditCatalogItemResponse.cs @@ -1,4 +1,4 @@ -namespace BlazorAdmin.Services.CatalogItemServices +namespace BlazorShared.Models { public class EditCatalogItemResult { diff --git a/src/BlazorShared/Models/LookupData.cs b/src/BlazorShared/Models/LookupData.cs new file mode 100644 index 0000000..53e029b --- /dev/null +++ b/src/BlazorShared/Models/LookupData.cs @@ -0,0 +1,8 @@ +namespace BlazorShared.Models +{ + public abstract class LookupData +{ + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/src/BlazorAdmin/Services/CatalogItemServices/ListPaged.PagedCatalogItemResult.cs b/src/BlazorShared/Models/PagedCatalogItemResponse.cs similarity index 67% rename from src/BlazorAdmin/Services/CatalogItemServices/ListPaged.PagedCatalogItemResult.cs rename to src/BlazorShared/Models/PagedCatalogItemResponse.cs index 531e78a..3395b1c 100644 --- a/src/BlazorAdmin/Services/CatalogItemServices/ListPaged.PagedCatalogItemResult.cs +++ b/src/BlazorShared/Models/PagedCatalogItemResponse.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; -namespace BlazorAdmin.Services.CatalogItemServices +namespace BlazorShared.Models { - public class PagedCatalogItemResult + public class PagedCatalogItemResponse { public List CatalogItems { get; set; } = new List(); public int PageCount { get; set; } = 0; diff --git a/src/PublicApi/CatalogItemEndpoints/Create.cs b/src/PublicApi/CatalogItemEndpoints/Create.cs index 68a6b2c..4b946b8 100644 --- a/src/PublicApi/CatalogItemEndpoints/Create.cs +++ b/src/PublicApi/CatalogItemEndpoints/Create.cs @@ -1,10 +1,8 @@ -using System; -using System.IO; +using System.IO; using Ardalis.ApiEndpoints; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.eShopWeb.ApplicationCore.Constants; using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Swashbuckle.AspNetCore.Annotations; diff --git a/src/PublicApi/Program.cs b/src/PublicApi/Program.cs index 9d9b469..86d7537 100644 --- a/src/PublicApi/Program.cs +++ b/src/PublicApi/Program.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; namespace Microsoft.eShopWeb.PublicApi { @@ -43,6 +44,13 @@ namespace Microsoft.eShopWeb.PublicApi public static IHostBuilder CreateHostBuilder(string[] 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 => { webBuilder.UseStartup(); diff --git a/src/PublicApi/Properties/launchSettings.json b/src/PublicApi/Properties/launchSettings.json index 098f415..fbed524 100644 --- a/src/PublicApi/Properties/launchSettings.json +++ b/src/PublicApi/Properties/launchSettings.json @@ -25,13 +25,6 @@ "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "https://localhost:5099;http://localhost:5098" - }, - "Docker": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", - "publishAllPorts": true, - "useSSL": true } } } \ No newline at end of file diff --git a/src/PublicApi/Startup.cs b/src/PublicApi/Startup.cs index f0d0a20..ae00094 100644 --- a/src/PublicApi/Startup.cs +++ b/src/PublicApi/Startup.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Text; using AutoMapper; +using BlazorShared; using BlazorShared.Authorization; using MediatR; using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -28,7 +29,6 @@ namespace Microsoft.eShopWeb.PublicApi public class Startup { private const string CORS_POLICY = "CorsPolicy"; - public static bool InDocker => Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true"; public Startup(IConfiguration configuration) { @@ -46,6 +46,11 @@ namespace Microsoft.eShopWeb.PublicApi //ConfigureProductionServices(services); } + public void ConfigureDockerServices(IServiceCollection services) + { + ConfigureDevelopmentServices(services); + } + private void ConfigureInMemoryDatabases(IServiceCollection services) { services.AddDbContext(c => @@ -90,7 +95,10 @@ namespace Microsoft.eShopWeb.PublicApi services.AddSingleton(new UriComposer(Configuration.Get())); services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); services.AddScoped(); - services.AddScoped(x => new WebFileSystem($"{Constants.GetWebUrlInternal(Startup.InDocker)}File")); + + var baseUrlConfig = new BaseUrlConfiguration(); + Configuration.Bind(BaseUrlConfiguration.CONFIG_NAME, baseUrlConfig); + services.AddScoped(x => new WebFileSystem($"{baseUrlConfig.WebBase}File")); services.AddMemoryCache(); @@ -112,15 +120,12 @@ namespace Microsoft.eShopWeb.PublicApi }; }); - services.AddCors(options => { options.AddPolicy(name: CORS_POLICY, builder => { - builder.WithOrigins("http://localhost:44319", - "https://localhost:44319", - Constants.GetOriginWebUrl(InDocker)); + builder.WithOrigins(baseUrlConfig.WebBase.Replace("host.docker.internal", "localhost").TrimEnd('/')); builder.AllowAnyMethod(); builder.AllowAnyHeader(); }); diff --git a/src/PublicApi/appsettings.Development.json b/src/PublicApi/appsettings.Development.json index 8983e0f..4af1b00 100644 --- a/src/PublicApi/appsettings.Development.json +++ b/src/PublicApi/appsettings.Development.json @@ -1,4 +1,8 @@ { + "baseUrls": { + "apiBase": "https://localhost:5099/api/", + "webBase": "https://localhost:44315/" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/src/PublicApi/appsettings.Docker.json b/src/PublicApi/appsettings.Docker.json new file mode 100644 index 0000000..f3bcd06 --- /dev/null +++ b/src/PublicApi/appsettings.Docker.json @@ -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" + } + } +} diff --git a/src/PublicApi/appsettings.json b/src/PublicApi/appsettings.json index fab9773..13b6949 100644 --- a/src/PublicApi/appsettings.json +++ b/src/PublicApi/appsettings.json @@ -1,4 +1,8 @@ { + "baseUrls": { + "apiBase": "https://localhost:5099/api/", + "webBase": "https://localhost:44315/" + }, "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;" diff --git a/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs index 4c775f6..820136e 100644 --- a/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -22,16 +22,12 @@ namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account private readonly SignInManager _signInManager; private readonly ILogger _logger; private readonly IBasketService _basketService; - private readonly AuthService _authService; - private readonly ITokenClaimsService _tokenClaimsService; - public LoginModel(SignInManager signInManager, ILogger logger, IBasketService basketService, AuthService authService, ITokenClaimsService tokenClaimsService) + public LoginModel(SignInManager signInManager, ILogger logger, IBasketService basketService) { _signInManager = signInManager; _logger = logger; _basketService = basketService; - _authService = authService; - _tokenClaimsService = tokenClaimsService; } [BindProperty] @@ -88,8 +84,6 @@ namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account if (result.Succeeded) { - var token = await _tokenClaimsService.GetTokenAsync(Input.Email); - CreateAuthCookie(Input.Email, token, Startup.InDocker); _logger.LogInformation("User logged in."); await TransferAnonymousBasketToUserAsync(Input.Email); return LocalRedirect(returnUrl); @@ -114,15 +108,6 @@ namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account 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) { if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME)) diff --git a/src/Web/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml b/src/Web/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml index bacc0ae..ea2a0df 100644 --- a/src/Web/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml +++ b/src/Web/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml @@ -1,8 +1,8 @@ - + - + diff --git a/src/Web/Views/Shared/_ValidationScriptsPartial.cshtml b/src/Web/Views/Shared/_ValidationScriptsPartial.cshtml index 27e0ea7..df42c5e 100644 --- a/src/Web/Views/Shared/_ValidationScriptsPartial.cshtml +++ b/src/Web/Views/Shared/_ValidationScriptsPartial.cshtml @@ -1,4 +1,4 @@ - + diff --git a/src/Web/appsettings.Development.json b/src/Web/appsettings.Development.json index e203e94..bd6e47b 100644 --- a/src/Web/appsettings.Development.json +++ b/src/Web/appsettings.Development.json @@ -1,4 +1,8 @@ { + "baseUrls": { + "apiBase": "https://localhost:5099/api/", + "webBase": "https://localhost:44315/" + }, "Logging": { "LogLevel": { "Default": "Debug", diff --git a/src/Web/appsettings.Docker.json b/src/Web/appsettings.Docker.json new file mode 100644 index 0000000..0b4b57a --- /dev/null +++ b/src/Web/appsettings.Docker.json @@ -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" + } + } +} diff --git a/src/Web/appsettings.json b/src/Web/appsettings.json index dd8d5ff..c2bc659 100644 --- a/src/Web/appsettings.json +++ b/src/Web/appsettings.json @@ -1,4 +1,8 @@ { + "baseUrls": { + "apiBase": "https://localhost:5099/api/", + "webBase": "https://localhost:44315/" + }, "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;" diff --git a/src/Web/wwwroot/css/site.min.css b/src/Web/wwwroot/css/site.min.css index dd35a47..50f91d7 100644 --- a/src/Web/wwwroot/css/site.min.css +++ b/src/Web/wwwroot/css/site.min.css @@ -1 +1 @@ -@font-face{font-family:Montserrat;font-weight:400;src:url(".../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%;bottom:0}.esh-app-footer-brand{height:50px;width:230px}.esh-app-header{margin:15px}.esh-app-wrapper{display:flex;min-height:100vh;flex-direction:column;justify-content:space-between}.esh-header{background-color:#00a69c;height:4rem}.esh-header-back{color:rgba(255,255,255,.5);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-header-back:hover{color:#fff;transition:color .35s}.esh-identity{line-height:3rem;position:relative;text-align:right}.esh-identity-section{display:inline-block;width:100%}.esh-identity-name{display:inline-block}.esh-identity-name--upper{text-transform:uppercase}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem}}.esh-identity-image{display:inline-block}.esh-identity-drop{background:#fff;height:10px;width:10rem;overflow:hidden;padding:.5rem;position:absolute;right:0;top:2.5rem;transition:height .35s}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:10rem;transition:height .35s}.esh-identity-item{cursor:pointer;transition:color .35s}.esh-identity-item:hover{color:#75b918;transition:color .35s}.esh-pager-wrapper{padding-top:1rem;text-align:center}.esh-pager-item{margin:0 5vw}.esh-pager-item.is-disabled{opacity:.5;pointer-events:none}.esh-pager-item--navigable{cursor:pointer;display:inline-block}.esh-pager-item--navigable:hover{color:#83d01b}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 2.5vw}}.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-items-margin-left1{margin-left:1px}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus-badge-inoperative{background-color:#f00;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s}.esh-catalog-hero{background-image:url("../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{-webkit-appearance:none;background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;min-width:140px;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-left:.5rem;margin-top:.65rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../images/arrow-down.png");content:'';height:7px;position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;margin-top:-1.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{margin-bottom:1.5rem;text-align:center;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:0;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{font-size:28px;font-weight:900;text-align:center}.esh-catalog-price::before{content:'$'}.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s} \ No newline at end of file +@font-face{font-family:Montserrat;font-weight:400;src:url(".../fonts/Montserrat-Regular.eot?") format("eot"),url("../fonts/Montserrat-Regular.woff") format("woff"),url("../fonts/Montserrat-Regular.ttf") format("truetype"),url("../fonts/Montserrat-Regular.svg#Montserrat") format("svg")}@font-face{font-family:Montserrat;font-weight:700;src:url("../fonts/Montserrat-Bold.eot?") format("eot"),url("../fonts/Montserrat-Bold.woff") format("woff"),url("../fonts/Montserrat-Bold.ttf") format("truetype"),url("../fonts/Montserrat-Bold.svg#Montserrat") format("svg")}html,body{font-family:Montserrat,sans-serif;font-size:16px;font-weight:400;z-index:10}*,*::after,*::before{box-sizing:border-box}.preloading{color:#00a69c;display:block;font-size:1.5rem;left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}select::-ms-expand{display:none}@media screen and (min-width:992px){.form-input{max-width:360px;width:360px}}.form-input{border-radius:0;height:45px;padding:10px}.form-input-small{max-width:100px !important}.form-input-medium{width:150px !important}.alert{padding-left:0}.alert-danger{background-color:transparent;border:0;color:#fb0d0d;font-size:12px}a,a:active,a:hover,a:visited{color:#000;text-decoration:none;transition:color .35s}a:hover,a:active{color:#75b918;transition:color .35s}.esh-app-footer{background-color:#000;border-top:1px solid #eee;margin-top:2.5rem;padding-bottom:2.5rem;padding-top:2.5rem;width:100%;bottom:0}.esh-app-footer-brand{height:50px;width:230px}.esh-app-header{margin:15px}.esh-app-wrapper{display:flex;min-height:100vh;flex-direction:column;justify-content:space-between}.esh-header{background-color:#00a69c;height:4rem}.esh-header-back{color:rgba(255,255,255,.5);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-header-back:hover{color:#fff;transition:color .35s}.esh-identity{line-height:3rem;position:relative;text-align:right}.esh-identity-section{display:inline-block;width:100%}.esh-identity-name{display:inline-block}.esh-identity-name--upper{text-transform:uppercase}@media screen and (max-width:768px){.esh-identity-name{font-size:.85rem}}.esh-identity-image{display:inline-block}.esh-identity-drop{background:#fff;height:10px;width:10rem;overflow:hidden;padding:.5rem;position:absolute;right:0;top:2.5rem;transition:height .35s}.esh-identity:hover .esh-identity-drop{border:1px solid #eee;height:14rem;transition:height .35s;z-index:10}.esh-identity-item{cursor:pointer;transition:color .35s}.esh-identity-item:hover{color:#75b918;transition:color .35s}.esh-pager-wrapper{padding-top:1rem;text-align:center}.esh-pager-item{margin:0 5vw}.esh-pager-item.is-disabled{opacity:.5;pointer-events:none}.esh-pager-item--navigable{cursor:pointer;display:inline-block}.esh-pager-item--navigable:hover{color:#83d01b}@media screen and (max-width:1280px){.esh-pager-item{font-size:.85rem}}@media screen and (max-width:1024px){.esh-pager-item{margin:0 2.5vw}}.esh-basket{min-height:80vh}.esh-basket-titles{padding-bottom:1rem;padding-top:2rem}.esh-basket-titles--clean{padding-bottom:0;padding-top:0}.esh-basket-title{text-transform:uppercase}.esh-basket-items--border{border-bottom:1px solid #eee;padding:.5rem 0}.esh-basket-items--border:last-of-type{border-color:transparent}.esh-basket-items-margin-left1{margin-left:1px}.esh-basket-item{font-size:1rem;font-weight:300}.esh-basket-item--middle{line-height:8rem}@media screen and (max-width:1024px){.esh-basket-item--middle{line-height:1rem}}.esh-basket-item--mark{color:#00a69c}.esh-basket-image{height:8rem}.esh-basket-input{line-height:1rem;width:100%}.esh-basket-checkout{background-color:#83d01b;border:0;border-radius:0;color:#fff;display:inline-block;font-size:1rem;font-weight:400;margin-top:1rem;padding:1rem 1.5rem;text-align:center;text-transform:uppercase;transition:all .35s}.esh-basket-checkout:hover{background-color:#4a760f;transition:all .35s}.esh-basket-checkout:visited{color:#fff}.esh-basketstatus{cursor:pointer;display:inline-block;float:right;position:relative;transition:all .35s}.esh-basketstatus.is-disabled{opacity:.5;pointer-events:none}.esh-basketstatus-image{height:36px;margin-top:.5rem}.esh-basketstatus-badge{background-color:#83d01b;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus-badge-inoperative{background-color:#f00;border-radius:50%;color:#fff;display:block;height:1.5rem;left:50%;position:absolute;text-align:center;top:0;transform:translateX(-38%);transition:all .35s;width:1.5rem}.esh-basketstatus:hover .esh-basketstatus-badge{background-color:transparent;color:#75b918;transition:all .35s}.esh-catalog-hero{background-image:url("../images/main_banner.png");background-size:cover;height:260px;width:100%}.esh-catalog-title{position:relative;top:74.28571px}.esh-catalog-filters{background-color:#00a69c;height:65px}.esh-catalog-filter{-webkit-appearance:none;background-color:transparent;border-color:#00d9cc;color:#fff;cursor:pointer;margin-right:1rem;margin-top:.5rem;min-width:140px;outline-color:#83d01b;padding-bottom:0;padding-left:.5rem;padding-right:.5rem;padding-top:1.5rem}.esh-catalog-filter option{background-color:#00a69c}.esh-catalog-label{display:inline-block;position:relative;z-index:0}.esh-catalog-label::before{color:rgba(255,255,255,.5);content:attr(data-title);font-size:.65rem;margin-left:.5rem;margin-top:.65rem;position:absolute;text-transform:uppercase;z-index:1}.esh-catalog-label::after{background-image:url("../images/arrow-down.png");content:'';height:7px;position:absolute;right:1.5rem;top:2.5rem;width:10px;z-index:1}.esh-catalog-send{background-color:#83d01b;color:#fff;cursor:pointer;font-size:1rem;margin-top:-1.5rem;transition:all .35s}.esh-catalog-send:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-items{margin-top:1rem}.esh-catalog-item{margin-bottom:1.5rem;text-align:center;width:33%;display:inline-block;float:none !important}@media screen and (max-width:1024px){.esh-catalog-item{width:50%}}@media screen and (max-width:768px){.esh-catalog-item{width:100%}}.esh-catalog-thumbnail{max-width:370px;width:100%}.esh-catalog-button{background-color:#83d01b;border:0;color:#fff;cursor:pointer;font-size:1rem;height:3rem;margin-top:1rem;transition:all .35s;width:80%}.esh-catalog-button.is-disabled{opacity:.5;pointer-events:none}.esh-catalog-button:hover{background-color:#4a760f;transition:all .35s}.esh-catalog-name{font-size:1rem;font-weight:300;margin-top:.5rem;text-align:center;text-transform:uppercase}.esh-catalog-price{font-size:28px;font-weight:900;text-align:center}.esh-catalog-price::before{content:'$'}.esh-orders{min-height:80vh;overflow-x:hidden}.esh-orders-header{background-color:#00a69c;height:4rem}.esh-orders-back{color:rgba(255,255,255,.4);line-height:4rem;text-decoration:none;text-transform:uppercase;transition:color .35s}.esh-orders-back:hover{color:#fff;transition:color .35s}.esh-orders-titles{padding-bottom:1rem;padding-top:2rem}.esh-orders-title{text-transform:uppercase}.esh-orders-items{height:2rem;line-height:2rem;position:relative}.esh-orders-items:nth-of-type(2n+1):before{background-color:#eef;content:'';height:100%;left:0;margin-left:-100vw;position:absolute;top:0;width:200vw;z-index:-1}.esh-orders-item{font-weight:300}.esh-orders-item--hover{opacity:0;pointer-events:none}.esh-orders-items:hover .esh-orders-item--hover{opacity:1;pointer-events:all}.esh-orders-link{color:#83d01b;text-decoration:none;transition:color .35s}.esh-orders-link:hover{color:#75b918;transition:color .35s}.esh-orders-detail-section{padding-bottom:30px}.esh-orders-detail-title{font-size:25px} \ No newline at end of file diff --git a/src/Web/wwwroot/images/products/5.jpg b/src/Web/wwwroot/images/products/5.jpg new file mode 100644 index 0000000..690649b Binary files /dev/null and b/src/Web/wwwroot/images/products/5.jpg differ