Browse Source

Resolved #810: Blog Module's FilesController should support N-tier deployment.

pull/848/head
Halil ibrahim Kalkan 8 years ago
parent
commit
50dd44d1d0
  1. 7
      modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs
  2. 14
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/BloggingWebConsts.cs
  3. 13
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadInputDto.cs
  4. 7
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadOutputDto.cs
  5. 10
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/IFileAppService.cs
  6. 1
      modules/blogging/src/Volo.Blogging.Application/Volo.Blogging.Application.csproj
  7. 5
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/CommonOperations.cs
  8. 13
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/BlogFileOptions.cs
  9. 65
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs
  10. 20
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileUploadConsts.cs
  11. 0
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/ImageFormatHelper.cs
  12. 36
      modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/FilesController.cs
  13. 30
      modules/blogging/src/Volo.Blogging.Web/BloggingWebConsts.cs
  14. 72
      modules/blogging/src/Volo.Blogging.Web/Hosting/FileService.cs
  15. 42
      modules/blogging/src/Volo.Blogging.Web/Hosting/FormFileExtensions.cs
  16. 16
      modules/blogging/src/Volo.Blogging.Web/Hosting/IFileService.cs
  17. 1
      modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj

7
modules/blogging/app/Volo.BloggingTestApp/BloggingTestAppModule.cs

@ -31,6 +31,7 @@ using Volo.Abp.Threading;
using Volo.Abp.UI;
using Volo.Abp.VirtualFileSystem;
using Volo.Blogging;
using Volo.Blogging.Files;
using Volo.BloggingTestApp.EntityFrameworkCore;
using Volo.BloggingTestApp.MongoDb;
@ -109,6 +110,12 @@ namespace Volo.BloggingTestApp
{
options.DefaultThemeName = BasicTheme.Name;
});
Configure<BlogFileOptions>(options =>
{
options.FileUploadLocalFolder = Path.Combine(hostingEnvironment.WebRootPath, "files");
options.FileUploadUrlRoot = "/files/";
});
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)

14
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/BloggingWebConsts.cs

@ -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);
}
}
}

13
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadInputDto.cs

@ -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; }
}
}

7
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadOutputDto.cs

@ -0,0 +1,7 @@
namespace Volo.Blogging.Files
{
public class FileUploadOutputDto
{
public string Url { get; set; }
}
}

10
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/IFileAppService.cs

@ -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);
}
}

1
modules/blogging/src/Volo.Blogging.Application/Volo.Blogging.Application.csproj

@ -10,6 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="4.5.0" />
<ProjectReference Include="..\Volo.Blogging.Application.Contracts\Volo.Blogging.Application.Contracts.csproj" />
<ProjectReference Include="..\Volo.Blogging.Domain\Volo.Blogging.Domain.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />

5
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/CommonOperations.cs

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace Volo.Blogging
{

13
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/BlogFileOptions.cs

@ -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; }
}
}

65
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs

@ -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;
}
}
}

20
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileUploadConsts.cs

@ -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()));
}
}

0
modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/ImageFormatHelper.cs → modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/ImageFormatHelper.cs

36
modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/FilesController.cs

@ -1,31 +1,55 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Blogging.Areas.Blog.Models;
using Volo.Blogging.Files;
using Volo.Blogging.Hosting;
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")]
[Route("Blog/[controller]/[action]")]
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]
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));
}
}
}

30
modules/blogging/src/Volo.Blogging.Web/BloggingWebConsts.cs

@ -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);
}
}
}

72
modules/blogging/src/Volo.Blogging.Web/Hosting/FileService.cs

@ -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;
}
}
}

42
modules/blogging/src/Volo.Blogging.Web/Hosting/FormFileExtensions.cs

@ -2,56 +2,22 @@
using JetBrains.Annotations;
using Microsoft.AspNetCore.Http;
using Volo.Abp;
using Volo.Blogging.Areas.Blog.Helpers;
namespace Volo.Blogging.Hosting
{
public static class FormFileExtensions
{
public static byte[] AsBytes(this IFormFile file)
public static byte[] AsBytes(this IFormFile file) //TODO: Move to the framework (rename to GetBytes)
{
byte[] fileBytes;
using (var stream = file.OpenReadStream())
{
fileBytes = stream.GetAllBytes();
return stream.GetAllBytes();
}
return fileBytes;
}
public static void ValidateImage([CanBeNull] this IFormFile file, out byte[] fileBytes)
public static void ValidateImage([CanBeNull] this IFormFile file)
{
fileBytes = null;
if (file == null)
{
throw new UserFriendlyException("No file found!");
}
if (file.Length <= 0)
{
throw new UserFriendlyException("File is empty!");
}
if (!file.ContentType.Contains("image"))
{
throw new UserFriendlyException("Not a valid image!");
}
using (var stream = file.OpenReadStream())
{
fileBytes = stream.GetAllBytes();
}
if (!ImageFormatHelper.IsValidImage(fileBytes, BloggingWebConsts.FileUploading.AllowedImageUploadFormats))
{
throw new UserFriendlyException("Not a valid image format!");
}
if (file.Length > BloggingWebConsts.FileUploading.MaxFileSize)
{
throw new UserFriendlyException($"File exceeds the maximum upload size ({BloggingWebConsts.FileUploading.MaxFileSizeAsMegabytes} MB)!");
}
}
}
}

16
modules/blogging/src/Volo.Blogging.Web/Hosting/IFileService.cs

@ -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);
}
}

1
modules/blogging/src/Volo.Blogging.Web/Volo.Blogging.Web.csproj

@ -19,7 +19,6 @@
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
<ProjectReference Include="..\Volo.Blogging.HttpApi\Volo.Blogging.HttpApi.csproj" />
<PackageReference Include="CommonMark.NET" Version="0.15.1" />
<PackageReference Include="System.Drawing.Common" Version="4.5.0" />
</ItemGroup>
<ItemGroup>

Loading…
Cancel
Save