Image added (#434)
* Image added * ImageMaximumBytes * FileController remove Authorized * ApplicationCore.Constants.AuthorizationConstants.AUTH_KEY * SavePicture in the interface. * IFileSystem in Core * WebFileSystem in Infrastructure * PictureUri removed from UpdateCatalogItemRequest * Modal scroll fix
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
{
|
{
|
||||||
public class AuthorizationConstants
|
public class AuthorizationConstants
|
||||||
{
|
{
|
||||||
|
public const string AUTH_KEY = "AuthKeyOfDoomThatMustBeAMinimumNumberOfBytes";
|
||||||
|
|
||||||
// TODO: Don't use this in production
|
// TODO: Don't use this in production
|
||||||
public const string DEFAULT_PASSWORD = "Pass@word1";
|
public const string DEFAULT_PASSWORD = "Pass@word1";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ardalis.GuardClauses;
|
using System;
|
||||||
|
using Ardalis.GuardClauses;
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
@@ -54,5 +55,15 @@ namespace Microsoft.eShopWeb.ApplicationCore.Entities
|
|||||||
Guard.Against.Zero(catalogTypeId, nameof(catalogTypeId));
|
Guard.Against.Zero(catalogTypeId, nameof(catalogTypeId));
|
||||||
CatalogTypeId = catalogTypeId;
|
CatalogTypeId = catalogTypeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdatePictureUri(string pictureName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(pictureName))
|
||||||
|
{
|
||||||
|
PictureUri = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PictureUri = $"images\\products\\{pictureName}?{new DateTime().Ticks}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
9
src/ApplicationCore/Interfaces/IFileSystem.cs
Normal file
9
src/ApplicationCore/Interfaces/IFileSystem.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
|
||||||
|
{
|
||||||
|
public interface IFileSystem
|
||||||
|
{
|
||||||
|
Task<bool> SavePicture(string pictureName, string pictureBase64);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Blazored.LocalStorage" Version="2.1.6" />
|
<PackageReference Include="Blazored.LocalStorage" Version="2.1.6" />
|
||||||
|
<PackageReference Include="BlazorInputFile" Version="0.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="3.1.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="3.1.5" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="3.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="3.2.0" />
|
||||||
|
|||||||
25
src/BlazorAdmin/JavaScript/Css.cs
Normal file
25
src/BlazorAdmin/JavaScript/Css.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace BlazorAdmin.JavaScript
|
||||||
|
{
|
||||||
|
public class Css
|
||||||
|
{
|
||||||
|
private readonly IJSRuntime _jsRuntime;
|
||||||
|
|
||||||
|
public Css(IJSRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
_jsRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ShowBodyOverflow()
|
||||||
|
{
|
||||||
|
await _jsRuntime.InvokeAsync<string>(JSInteropConstants.ShowBodyOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> HideBodyOverflow()
|
||||||
|
{
|
||||||
|
return await _jsRuntime.InvokeAsync<string>(JSInteropConstants.HideBodyOverflow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,5 +10,7 @@ namespace BlazorAdmin.JavaScript
|
|||||||
public static string DeleteCookie => "deleteCookie";
|
public static string DeleteCookie => "deleteCookie";
|
||||||
public static string GetCookie => "getCookie";
|
public static string GetCookie => "getCookie";
|
||||||
public static string RouteOutside => "routeOutside";
|
public static string RouteOutside => "routeOutside";
|
||||||
|
public static string HideBodyOverflow => "hideBodyOverflow";
|
||||||
|
public static string ShowBodyOverflow => "showBodyOverflow";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@inject ILogger<Create> Logger
|
@inject ILogger<Create> Logger
|
||||||
@inject AuthService Auth
|
@inject AuthService Auth
|
||||||
|
@inject IJSRuntime JSRuntime
|
||||||
|
|
||||||
@inherits BlazorAdmin.Helpers.BlazorComponent
|
@inherits BlazorAdmin.Helpers.BlazorComponent
|
||||||
|
|
||||||
@@ -25,60 +26,80 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="form-group">
|
<div class="container">
|
||||||
<label class="control-label col-md-2">Name</label>
|
<div class="row">
|
||||||
<div class="col-md-12">
|
@if (HasPicture)
|
||||||
<InputText class="form-control" @bind-Value="_item.Name" />
|
{
|
||||||
<ValidationMessage For="(() => _item.Name)" />
|
<img class="col-md-6 esh-picture" src="@LoadPicture">
|
||||||
</div>
|
}
|
||||||
</div>
|
<div class="col-md-@(HasPicture?"6":"12")">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-md-6">Name</label>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<InputText class="form-control" @bind-Value="_item.Name" />
|
||||||
|
<ValidationMessage For="(() => _item.Name)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-md-2">Description</label>
|
<label class="control-label col-md-6">Description</label>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<InputText class="form-control" @bind-Value="_item.Description" />
|
<InputText class="form-control" @bind-Value="_item.Description" />
|
||||||
<ValidationMessage For="(() => _item.Description)" />
|
<ValidationMessage For="(() => _item.Description)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-md-6">Brand</label>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<InputSelect @bind-Value="_item.CatalogBrandId" class="form-control">
|
||||||
|
@foreach (var brand in Brands)
|
||||||
|
{
|
||||||
|
<option value="@brand.Id">@brand.Name</option>
|
||||||
|
}
|
||||||
|
</InputSelect>
|
||||||
|
<ValidationMessage For="(() => _item.CatalogBrandId)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-md-2">Brand</label>
|
<label class="control-label col-md-6">Type</label>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<InputSelect @bind-Value="_item.CatalogBrandId" class="form-control">
|
<InputSelect @bind-Value="_item.CatalogTypeId" class="form-control">
|
||||||
@foreach (var brand in Brands)
|
@foreach (var type in Types)
|
||||||
{
|
{
|
||||||
<option value="@brand.Id">@brand.Name</option>
|
<option value="@type.Id">@type.Name</option>
|
||||||
}
|
}
|
||||||
</InputSelect>
|
</InputSelect>
|
||||||
<ValidationMessage For="(() => _item.CatalogBrandId)" />
|
<ValidationMessage For="(() => _item.CatalogTypeId)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-md-2">Type</label>
|
<label class="control-label col-md-6">Price</label>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<InputSelect @bind-Value="_item.CatalogTypeId" class="form-control">
|
<InputNumber @bind-Value="_item.Price" class="form-control" />
|
||||||
@foreach (var type in Types)
|
<ValidationMessage For="(() => _item.Price)" />
|
||||||
{
|
</div>
|
||||||
<option value="@type.Id">@type.Name</option>
|
</div>
|
||||||
}
|
|
||||||
</InputSelect>
|
|
||||||
<ValidationMessage For="(() => _item.CatalogTypeId)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-md-2">Price</label>
|
<label class="control-label col-md-6">@_item.PictureName</label>
|
||||||
<div class="col-md-12">
|
<div class="row">
|
||||||
<InputNumber @bind-Value="_item.Price" class="form-control" />
|
<div class="col-md-6 esh-form-information">
|
||||||
<ValidationMessage For="(() => _item.Price)" />
|
<InputFile OnChange="AddFile" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-md-6 esh-form-information">
|
||||||
|
@if (HasPicture)
|
||||||
<div class="form-group">
|
{
|
||||||
<label class="control-label col-md-2">Picture name</label>
|
<button type="button" class="btn btn-danger" @onclick="RemoveImage">Remove Picture</button>
|
||||||
<div class="col-md-12 esh-form-information">
|
}
|
||||||
Uploading images not allowed for this version.
|
</div>
|
||||||
|
<span class="col-md-12" style="color: red;"> @_badFileMessage </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -111,35 +132,68 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<string> OnCloseClick { get; set; }
|
public EventCallback<string> OnCloseClick { get; set; }
|
||||||
|
|
||||||
|
private string LoadPicture => string.IsNullOrEmpty(_item.PictureBase64) ? string.Empty : $"data:image/png;base64, {_item.PictureBase64}";
|
||||||
|
private bool HasPicture => !string.IsNullOrEmpty(_item.PictureBase64);
|
||||||
|
private string _badFileMessage = string.Empty;
|
||||||
private string _modalDisplay = "none;";
|
private string _modalDisplay = "none;";
|
||||||
private string _modalClass = "";
|
private string _modalClass = "";
|
||||||
private bool _showCreateModal = false;
|
private bool _showCreateModal = false;
|
||||||
private readonly CreateCatalogItemRequest _item = new CreateCatalogItemRequest();
|
private CreateCatalogItemRequest _item = new CreateCatalogItemRequest();
|
||||||
|
|
||||||
private async Task CreateClick()
|
private async Task CreateClick()
|
||||||
{
|
{
|
||||||
await new BlazorAdmin.Services.CatalogItemServices.Create(Auth).HandleAsync(_item);
|
await new BlazorAdmin.Services.CatalogItemServices.Create(Auth).HandleAsync(_item);
|
||||||
await OnCloseClick.InvokeAsync(null);
|
await OnCloseClick.InvokeAsync(null);
|
||||||
Close();
|
await Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Open()
|
public async Task Open()
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Now loading... /Catalog/Create");
|
Logger.LogInformation("Now loading... /Catalog/Create");
|
||||||
|
|
||||||
_item.CatalogTypeId = Types.First().Id;
|
await new Css(JSRuntime).HideBodyOverflow();
|
||||||
_item.CatalogBrandId = Brands.First().Id;
|
|
||||||
|
_item = new CreateCatalogItemRequest
|
||||||
|
{
|
||||||
|
CatalogTypeId = Types.First().Id,
|
||||||
|
CatalogBrandId = Brands.First().Id
|
||||||
|
};
|
||||||
|
|
||||||
_modalDisplay = "block;";
|
_modalDisplay = "block;";
|
||||||
_modalClass = "Show";
|
_modalClass = "Show";
|
||||||
_showCreateModal = true;
|
_showCreateModal = true;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
private async Task Close()
|
||||||
{
|
{
|
||||||
|
await new Css(JSRuntime).ShowBodyOverflow();
|
||||||
_modalDisplay = "none";
|
_modalDisplay = "none";
|
||||||
_modalClass = "";
|
_modalClass = "";
|
||||||
_showCreateModal = false;
|
_showCreateModal = false;
|
||||||
OnCloseClick.InvokeAsync(null);
|
await OnCloseClick.InvokeAsync(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddFile(IFileListEntry[] files)
|
||||||
|
{
|
||||||
|
_badFileMessage = string.Empty;
|
||||||
|
|
||||||
|
var file = files.FirstOrDefault();
|
||||||
|
_item.PictureName = file?.Name;
|
||||||
|
_item.PictureBase64 = await CatalogItem.DataToBase64(file);
|
||||||
|
|
||||||
|
_badFileMessage = CatalogItem.IsValidImage(_item.PictureName, _item.PictureBase64);
|
||||||
|
if (!string.IsNullOrEmpty(_badFileMessage))
|
||||||
|
{
|
||||||
|
_item.PictureName = null;
|
||||||
|
_item.PictureBase64 = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveImage()
|
||||||
|
{
|
||||||
|
_item.PictureName = null;
|
||||||
|
_item.PictureBase64 = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@inject ILogger<Delete> Logger
|
@inject ILogger<Delete> Logger
|
||||||
@inject AuthService Auth
|
@inject AuthService Auth
|
||||||
|
@inject IJSRuntime JSRuntime
|
||||||
|
|
||||||
@inherits BlazorAdmin.Helpers.BlazorComponent
|
@inherits BlazorAdmin.Helpers.BlazorComponent
|
||||||
|
|
||||||
@@ -15,60 +16,62 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@if (_item == null)
|
@if (_item == null)
|
||||||
{
|
{
|
||||||
<Spinner></Spinner>
|
<Spinner></Spinner>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<img class="col-md-6 esh-picture" src="@($"{Auth.WebUrl}{_item.PictureUri}")">
|
@if (HasPicture)
|
||||||
|
{
|
||||||
|
<img class="col-md-6 esh-picture" src="@($"{Auth.WebUrl}{_item.PictureUri}")">
|
||||||
|
}
|
||||||
|
<dl class="col-md-@(HasPicture ? "6" : "12") dl-horizontal">
|
||||||
|
<dt>
|
||||||
|
Name
|
||||||
|
</dt>
|
||||||
|
|
||||||
<dl class="col-md-6 dl-horizontal">
|
<dd>
|
||||||
<dt>
|
@_item.Name
|
||||||
Name
|
</dd>
|
||||||
</dt>
|
|
||||||
|
|
||||||
<dd>
|
<dt>
|
||||||
@_item.Name
|
Description
|
||||||
</dd>
|
</dt>
|
||||||
|
|
||||||
<dt>
|
<dd>
|
||||||
Description
|
@_item.Description
|
||||||
</dt>
|
</dd>
|
||||||
|
|
||||||
<dd>
|
<dt>
|
||||||
@_item.Description
|
Brand
|
||||||
</dd>
|
</dt>
|
||||||
|
|
||||||
<dt>
|
<dd>
|
||||||
Brand
|
@Services.CatalogBrandServices.List.GetBrandName(Brands, _item.CatalogBrandId)
|
||||||
</dt>
|
</dd>
|
||||||
|
|
||||||
<dd>
|
<dt>
|
||||||
@Services.CatalogBrandServices.List.GetBrandName(Brands, _item.CatalogBrandId)
|
Type
|
||||||
</dd>
|
</dt>
|
||||||
|
|
||||||
<dt>
|
<dd>
|
||||||
Type
|
@Services.CatalogTypeServices.List.GetTypeName(Types, _item.CatalogTypeId)
|
||||||
</dt>
|
</dd>
|
||||||
|
<dt>
|
||||||
|
Price
|
||||||
|
</dt>
|
||||||
|
|
||||||
<dd>
|
<dd>
|
||||||
@Services.CatalogTypeServices.List.GetTypeName(Types, _item.CatalogTypeId)
|
@_item.Price
|
||||||
</dd>
|
</dd>
|
||||||
<dt>
|
</dl>
|
||||||
Price
|
</div>
|
||||||
</dt>
|
|
||||||
|
|
||||||
<dd>
|
|
||||||
@_item.Price
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@@ -96,6 +99,7 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<string> OnCloseClick { get; set; }
|
public EventCallback<string> OnCloseClick { get; set; }
|
||||||
|
|
||||||
|
private bool HasPicture => !string.IsNullOrEmpty(_item.PictureUri);
|
||||||
private string _modalDisplay = "none;";
|
private string _modalDisplay = "none;";
|
||||||
private string _modalClass = "";
|
private string _modalClass = "";
|
||||||
private bool _showDeleteModal = false;
|
private bool _showDeleteModal = false;
|
||||||
@@ -108,25 +112,30 @@
|
|||||||
await new BlazorAdmin.Services.CatalogItemServices.Delete(Auth).HandleAsync(id);
|
await new BlazorAdmin.Services.CatalogItemServices.Delete(Auth).HandleAsync(id);
|
||||||
|
|
||||||
await OnCloseClick.InvokeAsync(null);
|
await OnCloseClick.InvokeAsync(null);
|
||||||
Close();
|
await Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Open(int id)
|
public async Task Open(int id)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Now loading... /Catalog/Delete/{Id}", id);
|
Logger.LogInformation("Now loading... /Catalog/Delete/{Id}", id);
|
||||||
|
|
||||||
|
await new Css(JSRuntime).HideBodyOverflow();
|
||||||
|
|
||||||
_item = await new GetById(Auth).HandleAsync(id);
|
_item = await new GetById(Auth).HandleAsync(id);
|
||||||
|
|
||||||
_modalDisplay = "block;";
|
_modalDisplay = "block;";
|
||||||
_modalClass = "Show";
|
_modalClass = "Show";
|
||||||
_showDeleteModal = true;
|
_showDeleteModal = true;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
private async Task Close()
|
||||||
{
|
{
|
||||||
|
await new Css(JSRuntime).ShowBodyOverflow();
|
||||||
_modalDisplay = "none";
|
_modalDisplay = "none";
|
||||||
_modalClass = "";
|
_modalClass = "";
|
||||||
_showDeleteModal = false;
|
_showDeleteModal = false;
|
||||||
OnCloseClick.InvokeAsync(null);
|
await OnCloseClick.InvokeAsync(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
@inject ILogger<Details> Logger
|
@inject ILogger<Details> Logger
|
||||||
@inject AuthService Auth
|
@inject AuthService Auth
|
||||||
|
@inject IJSRuntime JSRuntime
|
||||||
|
|
||||||
@inherits BlazorAdmin.Helpers.BlazorComponent
|
@inherits BlazorAdmin.Helpers.BlazorComponent
|
||||||
|
|
||||||
@namespace BlazorAdmin.Pages.CatalogItemPage
|
@namespace BlazorAdmin.Pages.CatalogItemPage
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="modal @_modalClass" tabindex="-1" role="dialog" style="display:@_modalDisplay">
|
<div class="modal @_modalClass" tabindex="-1" role="dialog" style="display:@_modalDisplay">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -26,9 +25,13 @@
|
|||||||
{
|
{
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<img class="col-md-6 esh-picture" src="@($"{Auth.WebUrl}{_item.PictureUri}")">
|
@if (HasPicture)
|
||||||
|
{
|
||||||
|
<img class="col-md-6 esh-picture" src="@($"{Auth.WebUrl}{_item.PictureUri}")">
|
||||||
|
}
|
||||||
|
|
||||||
<dl class="col-md-6 dl-horizontal">
|
|
||||||
|
<dl class="col-md-@(HasPicture?"6":"12") dl-horizontal">
|
||||||
<dt>
|
<dt>
|
||||||
Name
|
Name
|
||||||
</dt>
|
</dt>
|
||||||
@@ -77,7 +80,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="Close">Close</button>
|
<button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="Close">Close</button>
|
||||||
<button class="btn btn-danger" @onclick="EditClick">
|
<button class="btn btn-primary" @onclick="EditClick">
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,6 +102,7 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> OnEditClick { get; set; }
|
public EventCallback<int> OnEditClick { get; set; }
|
||||||
|
|
||||||
|
private bool HasPicture => !string.IsNullOrEmpty(_item.PictureUri);
|
||||||
private string _modalDisplay = "none;";
|
private string _modalDisplay = "none;";
|
||||||
private string _modalClass = "";
|
private string _modalClass = "";
|
||||||
private bool _showDetailsModal = false;
|
private bool _showDetailsModal = false;
|
||||||
@@ -112,17 +116,24 @@
|
|||||||
|
|
||||||
public async Task Open(int id)
|
public async Task Open(int id)
|
||||||
{
|
{
|
||||||
|
|
||||||
Logger.LogInformation("Now loading... /Catalog/Details/{Id}", id);
|
Logger.LogInformation("Now loading... /Catalog/Details/{Id}", id);
|
||||||
|
|
||||||
|
await new Css(JSRuntime).HideBodyOverflow();
|
||||||
|
|
||||||
_item = await new GetById(Auth).HandleAsync(id);
|
_item = await new GetById(Auth).HandleAsync(id);
|
||||||
|
|
||||||
_modalDisplay = "block;";
|
_modalDisplay = "block;";
|
||||||
_modalClass = "Show";
|
_modalClass = "Show";
|
||||||
_showDetailsModal = true;
|
_showDetailsModal = true;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
public async Task Close()
|
||||||
{
|
{
|
||||||
|
await new Css(JSRuntime).ShowBodyOverflow();
|
||||||
|
|
||||||
_modalDisplay = "none";
|
_modalDisplay = "none";
|
||||||
_modalClass = "";
|
_modalClass = "";
|
||||||
_showDetailsModal = false;
|
_showDetailsModal = false;
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
@inject ILogger<Edit> Logger
|
@inject ILogger<Edit> Logger
|
||||||
@inject AuthService Auth
|
@inject AuthService Auth
|
||||||
|
@inject IJSRuntime JSRuntime
|
||||||
|
|
||||||
@inherits BlazorAdmin.Helpers.BlazorComponent
|
@inherits BlazorAdmin.Helpers.BlazorComponent
|
||||||
|
|
||||||
@namespace BlazorAdmin.Pages.CatalogItemPage
|
@namespace BlazorAdmin.Pages.CatalogItemPage
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="modal @_modalClass" tabindex="-1" role="dialog" style="display:@_modalDisplay">
|
<div class="modal @_modalClass" tabindex="-1" role="dialog" style="display:@_modalDisplay">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -26,60 +25,82 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="form-group">
|
<div class="container">
|
||||||
<label class="control-label col-md-2">Name</label>
|
<div class="row">
|
||||||
<div class="col-md-12">
|
@if (HasPicture)
|
||||||
<InputText class="form-control" @bind-Value="_item.Name" />
|
{
|
||||||
<ValidationMessage For="(() => _item.Name)" />
|
<img class="col-md-6 esh-picture" src="@LoadPicture">
|
||||||
</div>
|
}
|
||||||
</div>
|
<div class="col-md-@(HasPicture?"6":"12") ">
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-md-2">Description</label>
|
<label class="control-label col-md-6">Name</label>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<InputText class="form-control" @bind-Value="_item.Description" />
|
<InputText class="form-control" @bind-Value="_item.Name" />
|
||||||
<ValidationMessage For="(() => _item.Description)" />
|
<ValidationMessage For="(() => _item.Name)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-md-2">Brand</label>
|
<label class="control-label col-md-6">Description</label>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<InputSelect @bind-Value="_item.CatalogBrandId" class="form-control">
|
<InputText class="form-control" @bind-Value="_item.Description" />
|
||||||
@foreach (var brand in Brands)
|
<ValidationMessage For="(() => _item.Description)" />
|
||||||
{
|
</div>
|
||||||
<option value="@brand.Id">@brand.Name</option>
|
</div>
|
||||||
}
|
|
||||||
</InputSelect>
|
|
||||||
<ValidationMessage For="(() => _item.CatalogBrandId)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label class="control-label col-md-2">Type</label>
|
<div class="col-md-12">
|
||||||
<div class="col-md-12">
|
<div class="form-group">
|
||||||
<InputSelect @bind-Value="_item.CatalogTypeId" class="form-control">
|
<label class="control-label col-md-6">Brand</label>
|
||||||
@foreach (var type in Types)
|
<div class="col-md-12">
|
||||||
{
|
<InputSelect @bind-Value="_item.CatalogBrandId" class="form-control">
|
||||||
<option value="@type.Id">@type.Name</option>
|
@foreach (var brand in Brands)
|
||||||
}
|
{
|
||||||
</InputSelect>
|
<option value="@brand.Id">@brand.Name</option>
|
||||||
<ValidationMessage For="(() => _item.CatalogTypeId)" />
|
}
|
||||||
</div>
|
</InputSelect>
|
||||||
</div>
|
<ValidationMessage For="(() => _item.CatalogBrandId)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-md-2">Price</label>
|
<label class="control-label col-md-6">Type</label>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<InputNumber @bind-Value="_item.Price" class="form-control" />
|
<InputSelect @bind-Value="_item.CatalogTypeId" class="form-control">
|
||||||
<ValidationMessage For="(() => _item.Price)" />
|
@foreach (var type in Types)
|
||||||
</div>
|
{
|
||||||
</div>
|
<option value="@type.Id">@type.Name</option>
|
||||||
|
}
|
||||||
|
</InputSelect>
|
||||||
|
<ValidationMessage For="(() => _item.CatalogTypeId)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-md-2">Picture name</label>
|
<label class="control-label col-md-6">Price</label>
|
||||||
<div class="col-md-12 esh-form-information">
|
<div class="col-md-12">
|
||||||
Uploading images not allowed for this version.
|
<InputNumber @bind-Value="_item.Price" class="form-control" />
|
||||||
|
<ValidationMessage For="(() => _item.Price)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-md-6">@_item.PictureName</label>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 esh-form-information">
|
||||||
|
<InputFile OnChange="ChangeFile" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 esh-form-information">
|
||||||
|
@if (HasPicture)
|
||||||
|
{
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="RemoveImage">Remove Picture</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<span class="col-md-12" style="color: red;"> @_badFileMessage </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -111,6 +132,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<string> OnCloseClick { get; set; }
|
public EventCallback<string> OnCloseClick { get; set; }
|
||||||
|
|
||||||
|
private string LoadPicture => string.IsNullOrEmpty(_item.PictureBase64) ? string.IsNullOrEmpty(_item.PictureUri) ? string.Empty : $"{Auth.WebUrl}{_item.PictureUri}" : $"data:image/png;base64, {_item.PictureBase64}";
|
||||||
|
private bool HasPicture => !(string.IsNullOrEmpty(_item.PictureBase64) && string.IsNullOrEmpty(_item.PictureUri));
|
||||||
|
private string _badFileMessage = string.Empty;
|
||||||
private string _modalDisplay = "none;";
|
private string _modalDisplay = "none;";
|
||||||
private string _modalClass = "";
|
private string _modalClass = "";
|
||||||
private bool _showEditModal = false;
|
private bool _showEditModal = false;
|
||||||
@@ -119,25 +143,54 @@
|
|||||||
private async Task SaveClick()
|
private async Task SaveClick()
|
||||||
{
|
{
|
||||||
await new BlazorAdmin.Services.CatalogItemServices.Edit(Auth).HandleAsync(_item);
|
await new BlazorAdmin.Services.CatalogItemServices.Edit(Auth).HandleAsync(_item);
|
||||||
Close();
|
await Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Open(int id)
|
public async Task Open(int id)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("Now loading... /Catalog/Edit/{Id}", id);
|
Logger.LogInformation("Now loading... /Catalog/Edit/{Id}", id);
|
||||||
|
|
||||||
|
await new Css(JSRuntime).HideBodyOverflow();
|
||||||
|
|
||||||
_item = await new GetById(Auth).HandleAsync(id);
|
_item = await new GetById(Auth).HandleAsync(id);
|
||||||
|
|
||||||
_modalDisplay = "block;";
|
_modalDisplay = "block;";
|
||||||
_modalClass = "Show";
|
_modalClass = "Show";
|
||||||
_showEditModal = true;
|
_showEditModal = true;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Close()
|
private async Task Close()
|
||||||
{
|
{
|
||||||
|
await new Css(JSRuntime).ShowBodyOverflow();
|
||||||
|
|
||||||
_modalDisplay = "none";
|
_modalDisplay = "none";
|
||||||
_modalClass = "";
|
_modalClass = "";
|
||||||
_showEditModal = false;
|
_showEditModal = false;
|
||||||
OnCloseClick.InvokeAsync(null);
|
await OnCloseClick.InvokeAsync(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ChangeFile(IFileListEntry[] files)
|
||||||
|
{
|
||||||
|
_badFileMessage = string.Empty;
|
||||||
|
|
||||||
|
var file = files.FirstOrDefault();
|
||||||
|
_item.PictureName = file?.Name;
|
||||||
|
_item.PictureBase64 = await CatalogItem.DataToBase64(file);
|
||||||
|
|
||||||
|
_badFileMessage = CatalogItem.IsValidImage(_item.PictureName, _item.PictureBase64);
|
||||||
|
if (!string.IsNullOrEmpty(_badFileMessage))
|
||||||
|
{
|
||||||
|
_item.PictureName = null;
|
||||||
|
_item.PictureBase64 = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveImage()
|
||||||
|
{
|
||||||
|
_item.PictureName = null;
|
||||||
|
_item.PictureBase64 = null;
|
||||||
|
_item.PictureUri = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ else
|
|||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button @onclick="@(() => DeleteClick(item.Id))" @onclick:stopPropagation="true" class="btn btn-primary">
|
<button @onclick="@(() => DeleteClick(item.Id))" @onclick:stopPropagation="true" class="btn btn-danger">
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@@ -59,7 +59,7 @@ else
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<Details Brands="@catalogBrands" Types="@catalogTypes" @ref="DetailsComponent" OnEditClick="EditClick"></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" OnCloseClick="ReloadCatalogItems" @ref="EditComponent"></Edit>
|
||||||
<Create Brands="@catalogBrands" Types="@catalogTypes" OnCloseClick="ReloadCatalogItems" @ref="CreateComponent"></Create>
|
<Create Brands="@catalogBrands" Types="@catalogTypes" OnCloseClick="ReloadCatalogItems" @ref="CreateComponent"></Create>
|
||||||
<Delete Brands="@catalogBrands" Types="@catalogTypes" OnCloseClick="ReloadCatalogItems" @ref="DeleteComponent"></Delete>
|
<Delete Brands="@catalogBrands" Types="@catalogTypes" OnCloseClick="ReloadCatalogItems" @ref="DeleteComponent"></Delete>
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BlazorInputFile;
|
||||||
|
|
||||||
namespace BlazorAdmin.Services.CatalogItemServices
|
namespace BlazorAdmin.Services.CatalogItemServices
|
||||||
{
|
{
|
||||||
@@ -22,5 +26,57 @@ namespace BlazorAdmin.Services.CatalogItemServices
|
|||||||
public decimal Price { get; set; }
|
public decimal Price { get; set; }
|
||||||
|
|
||||||
public string PictureUri { get; set; }
|
public string PictureUri { get; set; }
|
||||||
|
public string PictureBase64 { get; set; }
|
||||||
|
public string PictureName { get; set; }
|
||||||
|
|
||||||
|
private const int ImageMinimumBytes = 512000;
|
||||||
|
|
||||||
|
public static string IsValidImage(string pictureName, string pictureBase64)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(pictureBase64))
|
||||||
|
{
|
||||||
|
return "File not found!";
|
||||||
|
}
|
||||||
|
var fileData = Convert.FromBase64String(pictureBase64);
|
||||||
|
|
||||||
|
if (fileData.Length <= 0)
|
||||||
|
{
|
||||||
|
return "File length is 0!";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileData.Length > ImageMinimumBytes)
|
||||||
|
{
|
||||||
|
return "Maximum length is 512KB";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsExtensionValid(pictureName))
|
||||||
|
{
|
||||||
|
return "File is not image";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> DataToBase64(IFileListEntry fileItem)
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(fileItem.Data);
|
||||||
|
|
||||||
|
await using var memStream = new MemoryStream();
|
||||||
|
await reader.BaseStream.CopyToAsync(memStream);
|
||||||
|
var fileData = memStream.ToArray();
|
||||||
|
var encodedBase64 = Convert.ToBase64String(fileData);
|
||||||
|
|
||||||
|
return encodedBase64;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsExtensionValid(string fileName)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(fileName);
|
||||||
|
|
||||||
|
return string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,5 +20,8 @@ namespace BlazorAdmin.Services.CatalogItemServices
|
|||||||
public decimal Price { get; set; } = 0;
|
public decimal Price { get; set; } = 0;
|
||||||
|
|
||||||
public string PictureUri { get; set; } = string.Empty;
|
public string PictureUri { get; set; } = string.Empty;
|
||||||
|
public string PictureBase64 { get; set; } = string.Empty;
|
||||||
|
public string PictureName { get; set; } = string.Empty;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,3 +16,4 @@
|
|||||||
@using BlazorAdmin.Services.CatalogTypeServices
|
@using BlazorAdmin.Services.CatalogTypeServices
|
||||||
@using BlazorAdmin.JavaScript
|
@using BlazorAdmin.JavaScript
|
||||||
@using BlazorShared.Authorization
|
@using BlazorShared.Authorization
|
||||||
|
@using BlazorInputFile
|
||||||
|
|||||||
@@ -150,6 +150,10 @@ admin {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.body-no-overflow {
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
.main .top-row:not(.auth) {
|
.main .top-row:not(.auth) {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
|
|
||||||
public static string GetWebUrl(bool inDocker) =>
|
public static string GetWebUrl(bool inDocker) =>
|
||||||
inDocker ? DOCKER_WEB_URL : WEB_URL;
|
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) =>
|
public static string GetOriginWebUrl(bool inDocker) =>
|
||||||
GetWebUrl(inDocker).TrimEnd('/');
|
GetWebUrl(inDocker).TrimEnd('/');
|
||||||
|
|||||||
12
src/Infrastructure/Data/FileItem.cs
Normal file
12
src/Infrastructure/Data/FileItem.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Microsoft.eShopWeb.Infrastructure.Data
|
||||||
|
{
|
||||||
|
public class FileItem
|
||||||
|
{
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
public string Ext { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public string DataBase64 { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.5" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.5" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
83
src/Infrastructure/Services/WebFileSystem.cs
Normal file
83
src/Infrastructure/Services/WebFileSystem.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
|
using Microsoft.eShopWeb.Infrastructure.Data;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.Infrastructure.Services
|
||||||
|
{
|
||||||
|
public class WebFileSystem: IFileSystem
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly string _url;
|
||||||
|
public const string AUTH_KEY = "AuthKeyOfDoomThatMustBeAMinimumNumberOfBytes";
|
||||||
|
|
||||||
|
public WebFileSystem(string url)
|
||||||
|
{
|
||||||
|
_url = url;
|
||||||
|
_httpClient = new HttpClient();
|
||||||
|
_httpClient.DefaultRequestHeaders.Add("auth-key", AUTH_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SavePicture(string pictureName, string pictureBase64)
|
||||||
|
{
|
||||||
|
if (!await UploadFile(pictureName, Convert.FromBase64String(pictureBase64)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> UploadFile(string fileName, byte[] fileData)
|
||||||
|
{
|
||||||
|
if (!fileData.IsValidImage(fileName))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await UploadToWeb(fileName, fileData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> UploadToWeb(string fileName, byte[] fileData)
|
||||||
|
{
|
||||||
|
var request = new FileItem
|
||||||
|
{
|
||||||
|
DataBase64 = Convert.ToBase64String(fileData),
|
||||||
|
FileName = fileName
|
||||||
|
};
|
||||||
|
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
using var message = await _httpClient.PostAsync(_url, content);
|
||||||
|
if (!message.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ImageValidators
|
||||||
|
{
|
||||||
|
private const int ImageMaximumBytes = 512000;
|
||||||
|
|
||||||
|
public static bool IsValidImage(this byte[] postedFile, string fileName)
|
||||||
|
{
|
||||||
|
return postedFile != null && postedFile.Length > 0 && postedFile.Length <= ImageMaximumBytes && IsExtensionValid(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsExtensionValid(string fileName)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(fileName);
|
||||||
|
|
||||||
|
return string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string PictureUri { get; set; }
|
public string PictureUri { get; set; }
|
||||||
|
public string PictureBase64 { get; set; }
|
||||||
|
public string PictureName { get; set; }
|
||||||
public decimal Price { get; set; }
|
public decimal Price { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using Ardalis.ApiEndpoints;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
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;
|
||||||
@@ -16,11 +18,13 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
|||||||
{
|
{
|
||||||
private readonly IAsyncRepository<CatalogItem> _itemRepository;
|
private readonly IAsyncRepository<CatalogItem> _itemRepository;
|
||||||
private readonly IUriComposer _uriComposer;
|
private readonly IUriComposer _uriComposer;
|
||||||
|
private readonly IFileSystem _webFileSystem;
|
||||||
|
|
||||||
public Create(IAsyncRepository<CatalogItem> itemRepository, IUriComposer uriComposer)
|
public Create(IAsyncRepository<CatalogItem> itemRepository, IUriComposer uriComposer, IFileSystem webFileSystem)
|
||||||
{
|
{
|
||||||
_itemRepository = itemRepository;
|
_itemRepository = itemRepository;
|
||||||
_uriComposer = uriComposer;
|
_uriComposer = uriComposer;
|
||||||
|
_webFileSystem = webFileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("api/catalog-items")]
|
[HttpPost("api/catalog-items")]
|
||||||
@@ -34,10 +38,20 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
|||||||
{
|
{
|
||||||
var response = new CreateCatalogItemResponse(request.CorrelationId());
|
var response = new CreateCatalogItemResponse(request.CorrelationId());
|
||||||
|
|
||||||
CatalogItem newItem = new CatalogItem(request.CatalogTypeId, request.CatalogBrandId, request.Description, request.Name, request.Price, request.PictureUri);
|
var newItem = new CatalogItem(request.CatalogTypeId, request.CatalogBrandId, request.Description, request.Name, request.Price, request.PictureUri);
|
||||||
|
|
||||||
newItem = await _itemRepository.AddAsync(newItem);
|
newItem = await _itemRepository.AddAsync(newItem);
|
||||||
|
|
||||||
|
if (newItem.Id != 0)
|
||||||
|
{
|
||||||
|
var picName = $"{newItem.Id}{Path.GetExtension(request.PictureName)}";
|
||||||
|
if (await _webFileSystem.SavePicture(picName, request.PictureBase64))
|
||||||
|
{
|
||||||
|
newItem.UpdatePictureUri(picName);
|
||||||
|
await _itemRepository.UpdateAsync(newItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var dto = new CatalogItemDto
|
var dto = new CatalogItemDto
|
||||||
{
|
{
|
||||||
Id = newItem.Id,
|
Id = newItem.Id,
|
||||||
@@ -51,5 +65,7 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
|||||||
response.CatalogItem = dto;
|
response.CatalogItem = dto;
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
|||||||
public string Description { get; set; }
|
public string Description { get; set; }
|
||||||
[Required]
|
[Required]
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string PictureUri { get; set; }
|
public string PictureBase64 { get; set; }
|
||||||
|
public string PictureName { get; set; }
|
||||||
[Range(0.01, 10000)]
|
[Range(0.01, 10000)]
|
||||||
public decimal Price { get; set; }
|
public decimal Price { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
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.Exceptions;
|
|
||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||||
using Swashbuckle.AspNetCore.Annotations;
|
using Swashbuckle.AspNetCore.Annotations;
|
||||||
using System;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
||||||
@@ -17,11 +15,13 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
|||||||
{
|
{
|
||||||
private readonly IAsyncRepository<CatalogItem> _itemRepository;
|
private readonly IAsyncRepository<CatalogItem> _itemRepository;
|
||||||
private readonly IUriComposer _uriComposer;
|
private readonly IUriComposer _uriComposer;
|
||||||
|
private readonly IFileSystem _webFileSystem;
|
||||||
|
|
||||||
public Update(IAsyncRepository<CatalogItem> itemRepository, IUriComposer uriComposer)
|
public Update(IAsyncRepository<CatalogItem> itemRepository, IUriComposer uriComposer, IFileSystem webFileSystem)
|
||||||
{
|
{
|
||||||
_itemRepository = itemRepository;
|
_itemRepository = itemRepository;
|
||||||
_uriComposer = uriComposer;
|
_uriComposer = uriComposer;
|
||||||
|
_webFileSystem = webFileSystem;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +42,19 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
|||||||
existingItem.UpdateBrand(request.CatalogBrandId);
|
existingItem.UpdateBrand(request.CatalogBrandId);
|
||||||
existingItem.UpdateType(request.CatalogTypeId);
|
existingItem.UpdateType(request.CatalogTypeId);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(request.PictureBase64))
|
||||||
|
{
|
||||||
|
existingItem.UpdatePictureUri(string.Empty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var picName = $"{existingItem.Id}{Path.GetExtension(request.PictureName)}";
|
||||||
|
if (await _webFileSystem.SavePicture($"{picName}", request.PictureBase64))
|
||||||
|
{
|
||||||
|
existingItem.UpdatePictureUri(picName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await _itemRepository.UpdateAsync(existingItem);
|
await _itemRepository.UpdateAsync(existingItem);
|
||||||
|
|
||||||
var dto = new CatalogItemDto
|
var dto = new CatalogItemDto
|
||||||
|
|||||||
25
src/PublicApi/ImageValidators.cs
Normal file
25
src/PublicApi/ImageValidators.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.PublicApi
|
||||||
|
{
|
||||||
|
public static class ImageValidators
|
||||||
|
{
|
||||||
|
private const int ImageMaximumBytes = 512000;
|
||||||
|
|
||||||
|
public static bool IsValidImage(this byte[] postedFile, string fileName)
|
||||||
|
{
|
||||||
|
return postedFile != null && postedFile.Length > 0 && postedFile.Length <= ImageMaximumBytes && IsExtensionValid(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsExtensionValid(string fileName)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(fileName);
|
||||||
|
|
||||||
|
return string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using Microsoft.eShopWeb.ApplicationCore.Services;
|
|||||||
using Microsoft.eShopWeb.Infrastructure.Data;
|
using Microsoft.eShopWeb.Infrastructure.Data;
|
||||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||||
using Microsoft.eShopWeb.Infrastructure.Logging;
|
using Microsoft.eShopWeb.Infrastructure.Logging;
|
||||||
|
using Microsoft.eShopWeb.Infrastructure.Services;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
@@ -88,6 +89,7 @@ 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"));
|
||||||
|
|
||||||
services.AddMemoryCache();
|
services.AddMemoryCache();
|
||||||
|
|
||||||
|
|||||||
38
src/Web/Controllers/FileController.cs
Normal file
38
src/Web/Controllers/FileController.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.eShopWeb.Web.ViewModels.File;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.Web.Controllers
|
||||||
|
{
|
||||||
|
[Route("[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
public class FileController : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpPost]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public IActionResult Upload(FileViewModel fileViewModel)
|
||||||
|
{
|
||||||
|
if (!Request.Headers.ContainsKey("auth-key") || Request.Headers["auth-key"].ToString() != ApplicationCore.Constants.AuthorizationConstants.AUTH_KEY)
|
||||||
|
{
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fileViewModel == null || string.IsNullOrEmpty(fileViewModel.DataBase64)) return BadRequest();
|
||||||
|
|
||||||
|
var fileData = Convert.FromBase64String(fileViewModel.DataBase64);
|
||||||
|
if (fileData.Length <= 0) return BadRequest();
|
||||||
|
|
||||||
|
var fullPath = Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot/images/products", fileViewModel.FileName);
|
||||||
|
if (System.IO.File.Exists(fullPath))
|
||||||
|
{
|
||||||
|
System.IO.File.Delete(fullPath);
|
||||||
|
}
|
||||||
|
System.IO.File.WriteAllBytes(fullPath, fileData);
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" />
|
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" />
|
||||||
</environment>
|
</environment>
|
||||||
<link href="css/admin.css" rel="stylesheet" />
|
<link href="css/admin.css" rel="stylesheet" />
|
||||||
|
<script src="_content/BlazorInputFile/inputfile.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
window.getCookie = (cname) => {
|
window.getCookie = (cname) => {
|
||||||
@@ -55,6 +55,14 @@
|
|||||||
window.location = path;
|
window.location = path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.hideBodyOverflow = () => {
|
||||||
|
document.body.classList.add("body-no-overflow");
|
||||||
|
};
|
||||||
|
|
||||||
|
window.showBodyOverflow = () => {
|
||||||
|
document.body.classList.remove("body-no-overflow");
|
||||||
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
14
src/Web/ViewModels/File/FileViewModel.cs
Normal file
14
src/Web/ViewModels/File/FileViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.eShopWeb.Web.ViewModels.File
|
||||||
|
{
|
||||||
|
public class FileViewModel
|
||||||
|
{
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public string DataBase64 { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user