Browse Source

Merge pull request #10197 from abpframework/maliming/static-proxy

Remove the custom method in `HttpApi`.
pull/10199/head
liangshiwei 5 years ago
committed by GitHub
parent
commit
f32226f870
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/FileUploadInputDto.cs
  2. 3
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/IFileAppService.cs
  3. 51
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs
  4. 11
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/ImageFormatHelper.cs
  5. 9
      modules/blogging/src/Volo.Blogging.HttpApi.Client/ClientProxies/BlogFilesClientProxy.Generated.cs
  6. 69
      modules/blogging/src/Volo.Blogging.HttpApi.Client/ClientProxies/blogging-generate-proxy.json
  7. 48
      modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogFilesController.cs
  8. 6
      modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BloggingHttpApiModule.cs
  9. 12
      modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/FileUploadResult.cs
  10. 14
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/edit.js
  11. 14
      modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js
  12. 12
      modules/blogging/src/Volo.Blogging.Web/wwwroot/client-proxies/blogging-proxy.js

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

@ -1,13 +1,14 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Volo.Abp.Content;
namespace Volo.Blogging.Files namespace Volo.Blogging.Files
{ {
public class FileUploadInputDto public class FileUploadInputDto
{ {
[Required] [Required]
public byte[] Bytes { get; set; } public IRemoteStreamContent File { get; set; }
[Required] [Required]
public string Name { get; set; } public string Name { get; set; }
} }
} }

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

@ -1,5 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Content;
namespace Volo.Blogging.Files namespace Volo.Blogging.Files
{ {
@ -7,6 +8,8 @@ namespace Volo.Blogging.Files
{ {
Task<RawFileDto> GetAsync(string name); Task<RawFileDto> GetAsync(string name);
Task<IRemoteStreamContent> GetFileAsync(string name);
Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input); Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input);
} }
} }

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

@ -1,10 +1,12 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.IO; using System.IO;
using System.Net.Mime;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.BlobStoring; using Volo.Abp.BlobStoring;
using Volo.Abp.Content;
using Volo.Abp.Validation; using Volo.Abp.Validation;
using Volo.Blogging.Areas.Blog.Helpers; using Volo.Blogging.Areas.Blog.Helpers;
@ -30,26 +32,61 @@ namespace Volo.Blogging.Files
}; };
} }
public virtual async Task<IRemoteStreamContent> GetFileAsync(string name)
{
var fileStream = await BlobContainer.GetAsync(name);
return new RemoteStreamContent(fileStream, name, GetByExtension(Path.GetExtension(name)), disposeStream: true);
}
private static string GetByExtension(string extension)
{
extension = extension.RemovePreFix(".").ToLowerInvariant();
switch (extension)
{
case "png":
return "image/png";
case "gif":
return "image/gif";
case "jpg":
case "jpeg":
return "image/jpeg";
//TODO: Add other extensions too..
default:
return "application/octet-stream";
}
}
public virtual async Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input) public virtual async Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input)
{ {
if (input.Bytes.IsNullOrEmpty()) if (input.File == null)
{ {
ThrowValidationException("Bytes of file can not be null or empty!", "Bytes"); ThrowValidationException("Bytes of file can not be null or empty!", nameof(input.File));
} }
if (input.Bytes.Length > BloggingWebConsts.FileUploading.MaxFileSize) if (input.File.ContentLength > BloggingWebConsts.FileUploading.MaxFileSize)
{ {
throw new UserFriendlyException($"File exceeds the maximum upload size ({BloggingWebConsts.FileUploading.MaxFileSizeAsMegabytes} MB)!"); throw new UserFriendlyException($"File exceeds the maximum upload size ({BloggingWebConsts.FileUploading.MaxFileSizeAsMegabytes} MB)!");
} }
if (!ImageFormatHelper.IsValidImage(input.Bytes, FileUploadConsts.AllowedImageUploadFormats)) var position = input.File.GetStream().Position;
if (!ImageFormatHelper.IsValidImage(input.File.GetStream(), FileUploadConsts.AllowedImageUploadFormats))
{ {
throw new UserFriendlyException("Invalid image format!"); throw new UserFriendlyException("Invalid image format!");
} }
// IsValidImage may change the position of the stream
if (input.File.GetStream().CanSeek)
{
input.File.GetStream().Position = position;
}
var uniqueFileName = GenerateUniqueFileName(Path.GetExtension(input.Name)); var uniqueFileName = GenerateUniqueFileName(Path.GetExtension(input.Name));
await BlobContainer.SaveAsync(uniqueFileName, input.Bytes); await BlobContainer.SaveAsync(uniqueFileName, input.File.GetStream());
return new FileUploadOutputDto return new FileUploadOutputDto
{ {

11
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/ImageFormatHelper.cs

@ -7,20 +7,17 @@ namespace Volo.Blogging.Areas.Blog.Helpers
{ {
public class ImageFormatHelper public class ImageFormatHelper
{ {
public static ImageFormat GetImageRawFormat(byte[] fileBytes) public static ImageFormat GetImageRawFormat(Stream stream)
{ {
using (var memoryStream = new MemoryStream(fileBytes)) return System.Drawing.Image.FromStream(stream).RawFormat;
{
return System.Drawing.Image.FromStream(memoryStream).RawFormat;
}
} }
public static bool IsValidImage(byte[] fileBytes, ICollection<ImageFormat> validFormats) public static bool IsValidImage(Stream stream, ICollection<ImageFormat> validFormats)
{ {
// System.Drawing only works on windows => https://docs.microsoft.com/en-us/dotnet/api/system.drawing.image?view=net-5.0#remarks // System.Drawing only works on windows => https://docs.microsoft.com/en-us/dotnet/api/system.drawing.image?view=net-5.0#remarks
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
var imageFormat = GetImageRawFormat(fileBytes); var imageFormat = GetImageRawFormat(stream);
return validFormats.Contains(imageFormat); return validFormats.Contains(imageFormat);
} }

9
modules/blogging/src/Volo.Blogging.HttpApi.Client/ClientProxies/BlogFilesClientProxy.Generated.cs

@ -7,6 +7,7 @@ using Volo.Abp.Http.Modeling;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Http.Client.ClientProxying; using Volo.Abp.Http.Client.ClientProxying;
using Volo.Blogging.Files; using Volo.Blogging.Files;
using Volo.Abp.Content;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace Volo.Blogging.ClientProxies namespace Volo.Blogging.ClientProxies
@ -23,6 +24,14 @@ namespace Volo.Blogging.ClientProxies
}); });
} }
public virtual async Task<IRemoteStreamContent> GetFileAsync(string name)
{
return await RequestAsync<IRemoteStreamContent>(nameof(GetFileAsync), new ClientProxyRequestTypeValue
{
{ typeof(string), name }
});
}
public virtual async Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input) public virtual async Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input)
{ {
return await RequestAsync<FileUploadOutputDto>(nameof(CreateAsync), new ClientProxyRequestTypeValue return await RequestAsync<FileUploadOutputDto>(nameof(CreateAsync), new ClientProxyRequestTypeValue

69
modules/blogging/src/Volo.Blogging.HttpApi.Client/ClientProxies/blogging-generate-proxy.json

@ -51,9 +51,9 @@
"allowAnonymous": null, "allowAnonymous": null,
"implementFrom": "Volo.Blogging.Files.IFileAppService" "implementFrom": "Volo.Blogging.Files.IFileAppService"
}, },
"GetForWebAsyncByName": { "GetFileAsyncByName": {
"uniqueName": "GetForWebAsyncByName", "uniqueName": "GetFileAsyncByName",
"name": "GetForWebAsync", "name": "GetFileAsync",
"httpMethod": "GET", "httpMethod": "GET",
"url": "api/blogging/files/www/{name}", "url": "api/blogging/files/www/{name}",
"supportedVersions": [], "supportedVersions": [],
@ -82,17 +82,17 @@
} }
], ],
"returnValue": { "returnValue": {
"type": "Microsoft.AspNetCore.Mvc.FileResult", "type": "Volo.Abp.Content.IRemoteStreamContent",
"typeSimple": "Microsoft.AspNetCore.Mvc.FileResult" "typeSimple": "Volo.Abp.Content.IRemoteStreamContent"
}, },
"allowAnonymous": null, "allowAnonymous": null,
"implementFrom": "Volo.Blogging.BlogFilesController" "implementFrom": "Volo.Blogging.Files.IFileAppService"
}, },
"CreateAsyncByInput": { "CreateAsyncByInput": {
"uniqueName": "CreateAsyncByInput", "uniqueName": "CreateAsyncByInput",
"name": "CreateAsync", "name": "CreateAsync",
"httpMethod": "POST", "httpMethod": "POST",
"url": "api/blogging/files", "url": "api/blogging/files/images/upload",
"supportedVersions": [], "supportedVersions": [],
"parametersOnMethod": [ "parametersOnMethod": [
{ {
@ -107,60 +107,35 @@
"parameters": [ "parameters": [
{ {
"nameOnMethod": "input", "nameOnMethod": "input",
"name": "input", "name": "File",
"jsonName": null, "jsonName": null,
"type": "Volo.Blogging.Files.FileUploadInputDto", "type": "Volo.Abp.Content.IRemoteStreamContent",
"typeSimple": "Volo.Blogging.Files.FileUploadInputDto", "typeSimple": "Volo.Abp.Content.IRemoteStreamContent",
"isOptional": false, "isOptional": false,
"defaultValue": null, "defaultValue": null,
"constraintTypes": null, "constraintTypes": null,
"bindingSourceId": "Body", "bindingSourceId": "FormFile",
"descriptorName": "" "descriptorName": "input"
} },
],
"returnValue": {
"type": "Volo.Blogging.Files.FileUploadOutputDto",
"typeSimple": "Volo.Blogging.Files.FileUploadOutputDto"
},
"allowAnonymous": null,
"implementFrom": "Volo.Blogging.Files.IFileAppService"
},
"UploadImageByFile": {
"uniqueName": "UploadImageByFile",
"name": "UploadImage",
"httpMethod": "POST",
"url": "api/blogging/files/images/upload",
"supportedVersions": [],
"parametersOnMethod": [
{
"name": "file",
"typeAsString": "Microsoft.AspNetCore.Http.IFormFile, Microsoft.AspNetCore.Http.Features",
"type": "Microsoft.AspNetCore.Http.IFormFile",
"typeSimple": "Microsoft.AspNetCore.Http.IFormFile",
"isOptional": false,
"defaultValue": null
}
],
"parameters": [
{ {
"nameOnMethod": "file", "nameOnMethod": "input",
"name": "file", "name": "Name",
"jsonName": null, "jsonName": null,
"type": "Microsoft.AspNetCore.Http.IFormFile", "type": "System.String",
"typeSimple": "Microsoft.AspNetCore.Http.IFormFile", "typeSimple": "string",
"isOptional": false, "isOptional": false,
"defaultValue": null, "defaultValue": null,
"constraintTypes": null, "constraintTypes": null,
"bindingSourceId": "FormFile", "bindingSourceId": "ModelBinding",
"descriptorName": "" "descriptorName": "input"
} }
], ],
"returnValue": { "returnValue": {
"type": "Microsoft.AspNetCore.Mvc.JsonResult", "type": "Volo.Blogging.Files.FileUploadOutputDto",
"typeSimple": "Microsoft.AspNetCore.Mvc.JsonResult" "typeSimple": "Volo.Blogging.Files.FileUploadOutputDto"
}, },
"allowAnonymous": null, "allowAnonymous": null,
"implementFrom": "Volo.Blogging.BlogFilesController" "implementFrom": "Volo.Blogging.Files.IFileAppService"
} }
} }
}, },

48
modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogFilesController.cs

@ -1,11 +1,8 @@
using System.IO; using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Http; using Volo.Abp.Content;
using Volo.Blogging.Areas.Blog.Models;
using Volo.Blogging.Files; using Volo.Blogging.Files;
namespace Volo.Blogging namespace Volo.Blogging
@ -31,51 +28,16 @@ namespace Volo.Blogging
[HttpGet] [HttpGet]
[Route("www/{name}")] [Route("www/{name}")]
public async Task<FileResult> GetForWebAsync(string name) //TODO: output cache would be good public async Task<IRemoteStreamContent> GetFileAsync(string name)
{ {
var file = await _fileAppService.GetAsync(name); return await _fileAppService.GetFileAsync(name);
return File(
file.Bytes,
MimeTypes.GetByExtension(Path.GetExtension(name))
);
} }
[HttpPost] [HttpPost]
[Route("images/upload")]
public Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input) public Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input)
{ {
return _fileAppService.CreateAsync(input); return _fileAppService.CreateAsync(input);
} }
[HttpPost]
[Route("images/upload")]
public async Task<JsonResult> UploadImage(IFormFile file)
{
//TODO: localize exception messages
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!");
}
var output = await _fileAppService.CreateAsync(
new FileUploadInputDto
{
Bytes = file.GetAllBytes(),
Name = file.FileName
}
);
return Json(new FileUploadResult(output.WebUrl));
}
} }
} }

6
modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BloggingHttpApiModule.cs

@ -4,6 +4,7 @@ using Volo.Abp.Localization;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Blogging.Localization; using Volo.Blogging.Localization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Blogging.Files;
namespace Volo.Blogging namespace Volo.Blogging
{ {
@ -28,6 +29,11 @@ namespace Volo.Blogging
.Get<BloggingResource>() .Get<BloggingResource>()
.AddBaseTypes(typeof(AbpUiResource)); .AddBaseTypes(typeof(AbpUiResource));
}); });
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.FormBodyBindingIgnoredTypes.Add(typeof(FileUploadInputDto));
});
} }
} }
} }

12
modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/FileUploadResult.cs

@ -1,12 +0,0 @@
namespace Volo.Blogging.Areas.Blog.Models
{
public class FileUploadResult
{
public string FileUrl { get; set; }
public FileUploadResult(string fileUrl)
{
FileUrl = fileUrl;
}
}
}

14
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/edit.js

@ -13,15 +13,15 @@ $(function () {
var $postFormSubmitButton = $('#PostFormSubmitButton'); var $postFormSubmitButton = $('#PostFormSubmitButton');
var setCoverImage = function (file) { var setCoverImage = function (file) {
$postCoverImage.val(file.fileUrl); $postCoverImage.val(file.webUrl);
$coverImage.attr('src', file.fileUrl); $coverImage.attr('src', file.webUrl);
$postFormSubmitButton.removeAttr('disabled'); $postFormSubmitButton.removeAttr('disabled');
}; };
var uploadCoverImage = function (file) { var uploadCoverImage = function (file) {
var formData = new FormData(); var formData = new FormData();
formData.append('file', file); formData.append('file', file);
formData.append('name', file.name);
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/api/blogging/files/images/upload', url: '/api/blogging/files/images/upload',
@ -63,7 +63,7 @@ $(function () {
var uploadImage = function (file, callbackFn) { var uploadImage = function (file, callbackFn) {
var formData = new FormData(); var formData = new FormData();
formData.append('file', file); formData.append('file', file);
formData.append('name', file.name);
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/api/blogging/files/images/upload', url: '/api/blogging/files/images/upload',
@ -71,7 +71,7 @@ $(function () {
contentType: false, contentType: false,
processData: false, processData: false,
success: function (response) { success: function (response) {
callbackFn(response.fileUrl); callbackFn(response.webUrl);
}, },
}); });
}; };
@ -87,8 +87,8 @@ $(function () {
addImageBlobHook: function (blob, callback, source) { addImageBlobHook: function (blob, callback, source) {
var imageAltText = blob.name; var imageAltText = blob.name;
uploadImage(blob, function (fileUrl) { uploadImage(blob, function (webUrl) {
callback(fileUrl, imageAltText); callback(webUrl, imageAltText);
}); });
}, },
}, },

14
modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js

@ -13,8 +13,8 @@ $(function () {
var $postFormSubmitButton = $('#PostFormSubmitButton'); var $postFormSubmitButton = $('#PostFormSubmitButton');
var setCoverImage = function (file) { var setCoverImage = function (file) {
$postCoverImage.val(file.fileUrl); $postCoverImage.val(file.webUrl);
$coverImage.attr('src', file.fileUrl); $coverImage.attr('src', file.webUrl);
$coverImage.show(); $coverImage.show();
$postFormSubmitButton.removeAttr('disabled'); $postFormSubmitButton.removeAttr('disabled');
}; };
@ -22,7 +22,7 @@ $(function () {
var uploadCoverImage = function (file) { var uploadCoverImage = function (file) {
var formData = new FormData(); var formData = new FormData();
formData.append('file', file); formData.append('file', file);
formData.append('name', file.name);
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/api/blogging/files/images/upload', url: '/api/blogging/files/images/upload',
@ -47,7 +47,7 @@ $(function () {
var uploadImage = function (file, callbackFn) { var uploadImage = function (file, callbackFn) {
var formData = new FormData(); var formData = new FormData();
formData.append('file', file); formData.append('file', file);
formData.append('name', file.name);
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/api/blogging/files/images/upload', url: '/api/blogging/files/images/upload',
@ -55,7 +55,7 @@ $(function () {
contentType: false, contentType: false,
processData: false, processData: false,
success: function (response) { success: function (response) {
callbackFn(response.fileUrl); callbackFn(response.webUrl);
}, },
}); });
}; };
@ -70,8 +70,8 @@ $(function () {
addImageBlobHook: function (blob, callback, source) { addImageBlobHook: function (blob, callback, source) {
var imageAltText = blob.name; var imageAltText = blob.name;
uploadImage(blob, function (fileUrl) { uploadImage(blob, function (webUrl) {
callback(fileUrl, imageAltText); callback(webUrl, imageAltText);
}); });
}, },
}, },

12
modules/blogging/src/Volo.Blogging.Web/wwwroot/client-proxies/blogging-proxy.js

@ -18,7 +18,7 @@
}, ajaxParams)); }, ajaxParams));
}; };
volo.blogging.blogFiles.getForWeb = function(name, ajaxParams) { volo.blogging.blogFiles.getFile = function(name, ajaxParams) {
return abp.ajax($.extend(true, { return abp.ajax($.extend(true, {
url: abp.appPath + 'api/blogging/files/www/' + name + '', url: abp.appPath + 'api/blogging/files/www/' + name + '',
type: 'GET' type: 'GET'
@ -27,15 +27,7 @@
volo.blogging.blogFiles.create = function(input, ajaxParams) { volo.blogging.blogFiles.create = function(input, ajaxParams) {
return abp.ajax($.extend(true, { return abp.ajax($.extend(true, {
url: abp.appPath + 'api/blogging/files', url: abp.appPath + 'api/blogging/files/images/upload' + abp.utils.buildQueryString([{ name: 'name', value: input.name }]) + '',
type: 'POST',
data: JSON.stringify(input)
}, ajaxParams));
};
volo.blogging.blogFiles.uploadImage = function(file, ajaxParams) {
return abp.ajax($.extend(true, {
url: abp.appPath + 'api/blogging/files/images/upload',
type: 'POST' type: 'POST'
}, ajaxParams)); }, ajaxParams));
}; };

Loading…
Cancel
Save