mirror of https://github.com/abpframework/abp.git
17 changed files with 185 additions and 167 deletions
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Blogging |
||||
|
{ |
||||
|
public class BloggingWebConsts |
||||
|
{ |
||||
|
public class FileUploading |
||||
|
{ |
||||
|
public const int MaxFileSize = 5242880; //5MB
|
||||
|
|
||||
|
public static int MaxFileSizeAsMegabytes => Convert.ToInt32((MaxFileSize / 1024f) / 1024f); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace Volo.Blogging.Files |
||||
|
{ |
||||
|
public class FileUploadInputDto |
||||
|
{ |
||||
|
[Required] |
||||
|
public byte[] Bytes { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
public string Name { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Volo.Blogging.Files |
||||
|
{ |
||||
|
public class FileUploadOutputDto |
||||
|
{ |
||||
|
public string Url { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
|
||||
|
namespace Volo.Blogging.Files |
||||
|
{ |
||||
|
public interface IFileAppService : IApplicationService |
||||
|
{ |
||||
|
Task<FileUploadOutputDto> UploadAsync(FileUploadInputDto input); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
namespace Volo.Blogging.Files |
||||
|
{ |
||||
|
/* TODO: |
||||
|
* - It is not to have different options for all different modules. We should find a more generic way. |
||||
|
* - Actually, it is not good to assume to save to a local folder. Instead, use file storage once implemented. |
||||
|
*/ |
||||
|
public class BlogFileOptions |
||||
|
{ |
||||
|
public string FileUploadLocalFolder { get; set; } |
||||
|
|
||||
|
public string FileUploadUrlRoot { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using System.IO; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
using Volo.Abp.Validation; |
||||
|
using Volo.Blogging.Areas.Blog.Helpers; |
||||
|
|
||||
|
namespace Volo.Blogging.Files |
||||
|
{ |
||||
|
public class FileAppService : ApplicationService, IFileAppService |
||||
|
{ |
||||
|
public BlogFileOptions Options { get; } |
||||
|
|
||||
|
public FileAppService(IOptions<BlogFileOptions> options) |
||||
|
{ |
||||
|
Options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual Task<FileUploadOutputDto> UploadAsync(FileUploadInputDto input) |
||||
|
{ |
||||
|
if (input.Bytes.IsNullOrEmpty()) |
||||
|
{ |
||||
|
ThrowValidationException("Bytes can not be null or empty!", "Bytes"); |
||||
|
} |
||||
|
|
||||
|
if (input.Bytes.Length > BloggingWebConsts.FileUploading.MaxFileSize) |
||||
|
{ |
||||
|
throw new UserFriendlyException($"File exceeds the maximum upload size ({BloggingWebConsts.FileUploading.MaxFileSizeAsMegabytes} MB)!"); |
||||
|
} |
||||
|
|
||||
|
if (!ImageFormatHelper.IsValidImage(input.Bytes, FileUploadConsts.AllowedImageUploadFormats)) |
||||
|
{ |
||||
|
throw new UserFriendlyException("Not a valid image format!"); |
||||
|
} |
||||
|
|
||||
|
var uniqueFileName = GenerateUniqueFileName(Path.GetExtension(input.Name)); |
||||
|
var filePath = Path.Combine(Options.FileUploadLocalFolder, uniqueFileName); |
||||
|
|
||||
|
File.WriteAllBytes(filePath, input.Bytes); //TODO: Previously was using WriteAllBytesAsync, but it's only in .netcore.
|
||||
|
|
||||
|
return Task.FromResult(new FileUploadOutputDto |
||||
|
{ |
||||
|
Url = Options.FileUploadUrlRoot.EnsureEndsWith('/') + uniqueFileName |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private static void ThrowValidationException(string message, string memberName) |
||||
|
{ |
||||
|
throw new AbpValidationException(message, |
||||
|
new List<ValidationResult> |
||||
|
{ |
||||
|
new ValidationResult(message, new[] {memberName}) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected virtual string GenerateUniqueFileName(string extension, string prefix = null, string postfix = null) |
||||
|
{ |
||||
|
return prefix + GuidGenerator.Create().ToString("N") + postfix + extension; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Collections.ObjectModel; |
||||
|
using System.Drawing.Imaging; |
||||
|
using System.Linq; |
||||
|
|
||||
|
namespace Volo.Blogging.Files |
||||
|
{ |
||||
|
public class FileUploadConsts |
||||
|
{ |
||||
|
public static readonly ICollection<ImageFormat> AllowedImageUploadFormats = new Collection<ImageFormat> |
||||
|
{ |
||||
|
ImageFormat.Jpeg, |
||||
|
ImageFormat.Png, |
||||
|
ImageFormat.Gif, |
||||
|
ImageFormat.Bmp |
||||
|
}; |
||||
|
|
||||
|
public static string AllowedImageFormatsJoint => string.Join(",", AllowedImageUploadFormats.Select(x => x.ToString())); |
||||
|
} |
||||
|
} |
||||
@ -1,31 +1,55 @@ |
|||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Microsoft.AspNetCore.Http; |
using Microsoft.AspNetCore.Http; |
||||
using Microsoft.AspNetCore.Mvc; |
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp; |
||||
using Volo.Abp.AspNetCore.Mvc; |
using Volo.Abp.AspNetCore.Mvc; |
||||
using Volo.Blogging.Areas.Blog.Models; |
using Volo.Blogging.Areas.Blog.Models; |
||||
|
using Volo.Blogging.Files; |
||||
using Volo.Blogging.Hosting; |
using Volo.Blogging.Hosting; |
||||
|
|
||||
namespace Volo.Blogging.Areas.Blog.Controllers |
namespace Volo.Blogging.Areas.Blog.Controllers |
||||
{ |
{ |
||||
|
//TODO: This may be moved to HttpApi project since it may be needed by a SPA too.
|
||||
[Area("Blog")] |
[Area("Blog")] |
||||
[Route("Blog/[controller]/[action]")]
|
[Route("Blog/[controller]/[action]")]
|
||||
public class FilesController : AbpController |
public class FilesController : AbpController |
||||
{ |
{ |
||||
private readonly IFileService _fileService; |
private readonly IFileAppService _fileAppService; |
||||
|
|
||||
public FilesController(IFileService fileService) |
public FilesController(IFileAppService fileAppService) |
||||
{ |
{ |
||||
_fileService = fileService; |
_fileAppService = fileAppService; |
||||
} |
} |
||||
|
|
||||
[HttpPost] |
[HttpPost] |
||||
public async Task<JsonResult> UploadImage(IFormFile file) |
public async Task<JsonResult> UploadImage(IFormFile file) |
||||
{ |
{ |
||||
file.ValidateImage(out var fileBytes); |
//TODO: localize exception messages
|
||||
|
|
||||
var fileUrl = await _fileService.SaveFileAsync(fileBytes, file.FileName); |
if (file == null) |
||||
|
{ |
||||
|
throw new UserFriendlyException("No file found!"); |
||||
|
} |
||||
|
|
||||
return Json(new FileUploadResult(fileUrl)); |
if (file.Length <= 0) |
||||
|
{ |
||||
|
throw new UserFriendlyException("File is empty!"); |
||||
|
} |
||||
|
|
||||
|
if (!file.ContentType.Contains("image")) |
||||
|
{ |
||||
|
throw new UserFriendlyException("Not a valid image!"); |
||||
|
} |
||||
|
|
||||
|
var output = await _fileAppService.UploadAsync( |
||||
|
new FileUploadInputDto |
||||
|
{ |
||||
|
Bytes = file.AsBytes(), |
||||
|
Name = file.FileName |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
return Json(new FileUploadResult(output.Url)); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -1,30 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Collections.ObjectModel; |
|
||||
using System.Drawing.Imaging; |
|
||||
using System.Linq; |
|
||||
|
|
||||
namespace Volo.Blogging |
|
||||
{ |
|
||||
public class BloggingWebConsts |
|
||||
{ |
|
||||
public class FileUploading |
|
||||
{ |
|
||||
public const string DefaultFileUploadFolderName = "files"; |
|
||||
|
|
||||
public static readonly ICollection<ImageFormat> AllowedImageUploadFormats = new Collection<ImageFormat> |
|
||||
{ |
|
||||
ImageFormat.Jpeg, |
|
||||
ImageFormat.Png, |
|
||||
ImageFormat.Gif, |
|
||||
ImageFormat.Bmp |
|
||||
}; |
|
||||
|
|
||||
public static string AllowedImageFormatsJoint => string.Join(",", AllowedImageUploadFormats.Select(x => x.ToString())); |
|
||||
|
|
||||
public const int MaxFileSize = 5242880; //5MB
|
|
||||
|
|
||||
public static int MaxFileSizeAsMegabytes => Convert.ToInt32((MaxFileSize / 1024f) / 1024f); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,72 +0,0 @@ |
|||||
using System.IO; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.AspNetCore.Hosting; |
|
||||
using Microsoft.AspNetCore.Http; |
|
||||
using Volo.Abp; |
|
||||
using Volo.Abp.DependencyInjection; |
|
||||
using Volo.Abp.Guids; |
|
||||
|
|
||||
namespace Volo.Blogging.Hosting |
|
||||
{ |
|
||||
public class FileService : IFileService, ITransientDependency |
|
||||
{ |
|
||||
private readonly IHostingEnvironment _hostingEnvironment; |
|
||||
private readonly IGuidGenerator _guidGenerator; |
|
||||
|
|
||||
public FileService(IHostingEnvironment hostingEnvironment, IGuidGenerator guidGenerator) |
|
||||
{ |
|
||||
_hostingEnvironment = hostingEnvironment; |
|
||||
_guidGenerator = guidGenerator; |
|
||||
} |
|
||||
|
|
||||
public string FileUploadDirectory |
|
||||
{ |
|
||||
get |
|
||||
{ |
|
||||
var uploadDirectory = Path.Combine(_hostingEnvironment.WebRootPath, BloggingWebConsts.FileUploading.DefaultFileUploadFolderName); |
|
||||
if (!Directory.Exists(uploadDirectory)) |
|
||||
{ |
|
||||
Directory.CreateDirectory(uploadDirectory); |
|
||||
} |
|
||||
|
|
||||
return uploadDirectory; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public string GenerateUniqueFileName(string extension, string prefix = null, string postfix = null) |
|
||||
{ |
|
||||
return prefix + _guidGenerator.Create().ToString("N") + postfix + extension; |
|
||||
} |
|
||||
|
|
||||
public async Task<string> SaveFormFileAndGetUrlAsync(IFormFile file) |
|
||||
{ |
|
||||
var uniqueFileName = await SaveFileInternalAsync(file.FileName, file.AsBytes()); |
|
||||
return GetFileUrl(uniqueFileName); |
|
||||
} |
|
||||
|
|
||||
public async Task<string> SaveFileAsync(byte[] fileBytes, string originalFileName) |
|
||||
{ |
|
||||
if (fileBytes == null || fileBytes.Length == 0) |
|
||||
{ |
|
||||
throw new UserFriendlyException("File is empty!"); |
|
||||
} |
|
||||
|
|
||||
var uniqueFileName = await SaveFileInternalAsync(originalFileName, fileBytes); |
|
||||
return GetFileUrl(uniqueFileName); |
|
||||
} |
|
||||
|
|
||||
private static string GetFileUrl(string uniqueFileName) |
|
||||
{ |
|
||||
return "/" + BloggingWebConsts.FileUploading.DefaultFileUploadFolderName + "/" + uniqueFileName; |
|
||||
} |
|
||||
|
|
||||
private async Task<string> SaveFileInternalAsync(string originalFileName, byte[] fileBytes) |
|
||||
{ |
|
||||
var uniqueFileName = GenerateUniqueFileName(Path.GetExtension(originalFileName)); |
|
||||
var filePath = Path.Combine(FileUploadDirectory, uniqueFileName); |
|
||||
File.WriteAllBytes(filePath, fileBytes); //TODO: Previously was using WriteAllBytesAsync, but it's only in .netcore.
|
|
||||
return uniqueFileName; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -1,16 +0,0 @@ |
|||||
using System.Threading.Tasks; |
|
||||
using Microsoft.AspNetCore.Http; |
|
||||
|
|
||||
namespace Volo.Blogging.Hosting |
|
||||
{ |
|
||||
public interface IFileService |
|
||||
{ |
|
||||
string FileUploadDirectory { get; } |
|
||||
|
|
||||
string GenerateUniqueFileName(string extension, string prefix = null, string postfix = null); |
|
||||
|
|
||||
Task<string> SaveFormFileAndGetUrlAsync(IFormFile file); |
|
||||
|
|
||||
Task<string> SaveFileAsync(byte[] fileBytes, string originalFileName); |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue