Merge pull request #541 from sughosneo/feature/default-image-template
Removes the image upload functionality
This commit is contained in:
@@ -1,10 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Microsoft.eShopWeb.ApplicationCore.Interfaces
|
|
||||||
{
|
|
||||||
public interface IFileSystem
|
|
||||||
{
|
|
||||||
Task<bool> SavePicture(string pictureName, string pictureBase64, CancellationToken cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ namespace Microsoft.eShopWeb.ApplicationCore.Specifications
|
|||||||
Query
|
Query
|
||||||
.Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) &&
|
.Where(i => (!brandId.HasValue || i.CatalogBrandId == brandId) &&
|
||||||
(!typeId.HasValue || i.CatalogTypeId == typeId))
|
(!typeId.HasValue || i.CatalogTypeId == typeId))
|
||||||
.Paginate(skip, take);
|
.Skip(skip).Take(take);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,22 +81,6 @@
|
|||||||
<ValidationMessage For="(() => _item.Price)" />
|
<ValidationMessage For="(() => _item.Price)" />
|
||||||
</div>
|
</div>
|
||||||
</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">
|
|
||||||
<BlazorInputFile.InputFile OnChange="AddFile"/>
|
|
||||||
</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>
|
</div>
|
||||||
@@ -171,26 +155,4 @@
|
|||||||
_modalClass = "";
|
_modalClass = "";
|
||||||
_showCreateModal = false;
|
_showCreateModal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,22 +84,6 @@
|
|||||||
<ValidationMessage For="(() => _item.Price)" />
|
<ValidationMessage For="(() => _item.Price)" />
|
||||||
</div>
|
</div>
|
||||||
</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">
|
|
||||||
<BlazorInputFile.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>
|
</div>
|
||||||
@@ -170,27 +154,4 @@
|
|||||||
_modalClass = "";
|
_modalClass = "";
|
||||||
_showEditModal = false;
|
_showEditModal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
|
||||||
using Microsoft.eShopWeb.Infrastructure.Data;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
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, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(pictureBase64) || !await UploadFile(pictureName, Convert.FromBase64String(pictureBase64), cancellationToken))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> UploadFile(string fileName, byte[] fileData, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
if (!fileData.IsValidImage(fileName))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await UploadToWeb(fileName, fileData, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> UploadToWeb(string fileName, byte[] fileData, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var request = new FileItem
|
|
||||||
{
|
|
||||||
DataBase64 = Convert.ToBase64String(fileData),
|
|
||||||
FileName = fileName
|
|
||||||
};
|
|
||||||
var content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
|
|
||||||
|
|
||||||
using var message = await _httpClient.PostAsync(_url, content, cancellationToken);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,13 +19,11 @@ 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, IFileSystem webFileSystem)
|
public Create(IAsyncRepository<CatalogItem> itemRepository, IUriComposer uriComposer)
|
||||||
{
|
{
|
||||||
_itemRepository = itemRepository;
|
_itemRepository = itemRepository;
|
||||||
_uriComposer = uriComposer;
|
_uriComposer = uriComposer;
|
||||||
_webFileSystem = webFileSystem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("api/catalog-items")]
|
[HttpPost("api/catalog-items")]
|
||||||
@@ -45,13 +43,13 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints
|
|||||||
|
|
||||||
if (newItem.Id != 0)
|
if (newItem.Id != 0)
|
||||||
{
|
{
|
||||||
var picName = $"{newItem.Id}{Path.GetExtension(request.PictureName)}";
|
//We disabled the upload functionality and added a default/placeholder image to this sample due to a potential security risk
|
||||||
if (await _webFileSystem.SavePicture(picName, request.PictureBase64, cancellationToken))
|
// pointed out by the community. More info in this issue: https://github.com/dotnet-architecture/eShopOnWeb/issues/537
|
||||||
{
|
// In production, we recommend uploading to a blob storage and deliver the image via CDN after a verification process.
|
||||||
newItem.UpdatePictureUri(picName);
|
|
||||||
|
newItem.UpdatePictureUri("eCatalog-item-default.png");
|
||||||
await _itemRepository.UpdateAsync(newItem, cancellationToken);
|
await _itemRepository.UpdateAsync(newItem, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var dto = new CatalogItemDto
|
var dto = new CatalogItemDto
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,14 +18,11 @@ 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, IFileSystem webFileSystem)
|
public Update(IAsyncRepository<CatalogItem> itemRepository, IUriComposer uriComposer)
|
||||||
{
|
{
|
||||||
_itemRepository = itemRepository;
|
_itemRepository = itemRepository;
|
||||||
_uriComposer = uriComposer;
|
_uriComposer = uriComposer;
|
||||||
_webFileSystem = webFileSystem;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("api/catalog-items")]
|
[HttpPut("api/catalog-items")]
|
||||||
@@ -45,19 +42,6 @@ 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) && string.IsNullOrEmpty(request.PictureUri))
|
|
||||||
{
|
|
||||||
existingItem.UpdatePictureUri(string.Empty);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var picName = $"{existingItem.Id}{Path.GetExtension(request.PictureName)}";
|
|
||||||
if (await _webFileSystem.SavePicture($"{picName}", request.PictureBase64, cancellationToken))
|
|
||||||
{
|
|
||||||
existingItem.UpdatePictureUri(picName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await _itemRepository.UpdateAsync(existingItem, cancellationToken);
|
await _itemRepository.UpdateAsync(existingItem, cancellationToken);
|
||||||
|
|
||||||
var dto = new CatalogItemDto
|
var dto = new CatalogItemDto
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ namespace Microsoft.eShopWeb.PublicApi
|
|||||||
|
|
||||||
var baseUrlConfig = new BaseUrlConfiguration();
|
var baseUrlConfig = new BaseUrlConfiguration();
|
||||||
Configuration.Bind(BaseUrlConfiguration.CONFIG_NAME, baseUrlConfig);
|
Configuration.Bind(BaseUrlConfiguration.CONFIG_NAME, baseUrlConfig);
|
||||||
services.AddScoped<IFileSystem, WebFileSystem>(x => new WebFileSystem($"{baseUrlConfig.WebBase}File"));
|
|
||||||
|
|
||||||
services.AddMemoryCache();
|
services.AddMemoryCache();
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.eShopWeb.Web.ViewModels.File;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
src/Web/wwwroot/images/products/eCatalog-item-default.png
Normal file
BIN
src/Web/wwwroot/images/products/eCatalog-item-default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@@ -19,7 +19,6 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers
|
|||||||
private int _testTypeId = 2;
|
private int _testTypeId = 2;
|
||||||
private string _testDescription = "test description";
|
private string _testDescription = "test description";
|
||||||
private string _testName = "test name";
|
private string _testName = "test name";
|
||||||
private string _testUri = "test uri";
|
|
||||||
private decimal _testPrice = 1.23m;
|
private decimal _testPrice = 1.23m;
|
||||||
|
|
||||||
public CreateEndpoint(ApiTestFixture factory)
|
public CreateEndpoint(ApiTestFixture factory)
|
||||||
@@ -55,7 +54,6 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers
|
|||||||
Assert.Equal(_testTypeId, model.CatalogItem.CatalogTypeId);
|
Assert.Equal(_testTypeId, model.CatalogItem.CatalogTypeId);
|
||||||
Assert.Equal(_testDescription, model.CatalogItem.Description);
|
Assert.Equal(_testDescription, model.CatalogItem.Description);
|
||||||
Assert.Equal(_testName, model.CatalogItem.Name);
|
Assert.Equal(_testName, model.CatalogItem.Name);
|
||||||
Assert.Equal(_testUri, model.CatalogItem.PictureUri);
|
|
||||||
Assert.Equal(_testPrice, model.CatalogItem.Price);
|
Assert.Equal(_testPrice, model.CatalogItem.Price);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +65,6 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers
|
|||||||
CatalogTypeId = _testTypeId,
|
CatalogTypeId = _testTypeId,
|
||||||
Description = _testDescription,
|
Description = _testDescription,
|
||||||
Name = _testName,
|
Name = _testName,
|
||||||
PictureUri = _testUri,
|
|
||||||
Price = _testPrice
|
Price = _testPrice
|
||||||
};
|
};
|
||||||
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
|
var jsonContent = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json");
|
||||||
|
|||||||
Reference in New Issue
Block a user