Asp.NET Core+ABP框架+IdentityServer4+MySQL+Ext JS之文件上传

2018-01-13 10:55:05来源:http://blog.csdn.net/tianxiaode/article/details/79048253作者:黄灯桥的专栏人点击

分享


媒体管理的主要难度就在于媒体文件的上传,由于使用了plupload
作为上传工具,因而整个实现流程还是比较简单。



由于媒体实体没有大文本数据,因而Get
和GetAll
方法使用相同的数据传输对象就可以,具体代码如下:


[AutoMapFrom(typeof(Media))]
public class MediaDto: EntityDto<long>
{
public string Filename { get; set; }
public string Description { get; set; }
public string Path { get; set; }
public MediaType Type { get; set; }
public int Size { get; set; }
public DateTime CreationTime { get; set; }
}


对于GetAll
方法,需要提供自定义排序和查询等功能,因而需自定义输入对象,代码如下:


public class GetAllMediaInputDto : PagedAndSortedResultRequestDto, IShouldNormalize
{
private readonly JObject _allowSorts = new JObject()
{
{ "creationTime", "CreationTime" },
{ "size", "Size" },
{ "description", "Description" }
};
public string Query { get; set; }
public int? Year { get; set; }
public int? Month { get; set; }
public int? Day { get; set; }
[Required]
public int[] Type { get; set; }
public string Sort { get; set; }
public void Normalize()
{
if (!string.IsNullOrEmpty(Sort))
{
Sorting = Helper.ExtJs.OrderBy(Sort, _allowSorts);
}
}
}


plupload
是一个个文件上传的,因而上传所用的数据传输对象,只接收一个文件就行了,代码如下:


public class CreateMediaDto
{
[Required]
public IFormFile File { get; set; }
}


要注意的是Asp.Net Core是接收文件是是否IFormFile
对象,不是使用HttpPostedFileBase



媒体的描述字段(Description
)允许更新,因而要写一个更新对象,代码如下:


[AutoMapTo(typeof(Media))]
public class UpdateMediaInputDto : EntityDto<long>
{
[MaxLength(Media.MaxDescriptionLength)]
public string Description { get; set; }
}

删除还是采取一次可删除多个记录的方式,需要重定义输入接口,代码如下:


public class DeleteMediaInputDto
{
public long[] Id { get; set; }
}

感觉这个可以做成一个通用类,就不用写n个那么麻烦了。


数据传输对象完成后,先定义一个服务接口,代码如下:


public interface IMediaAppService :IAsyncCrudAppService<MediaDto, long>
{
}

最后是完成服务类,代码如下:


[AbpAuthorize(PermissionNames.Pages_Articles)]
public class MediaAppService: AsyncCrudAppService<Media, MediaDto, long, GetAllMediaInputDto, CreateMediaDto, UpdateMediaInputDto, MediaDto>
{
public MediaAppService(IRepository<Media, long> repository) : base(repository)
{
}
public override async Task<PagedResultDto<MediaDto>> GetAll(GetAllMediaInputDto input)
{
CheckGetAllPermission();
var query = Repository.GetAll().Where(m=>input.Type.Contains((int)m.Type));
if (!string.IsNullOrEmpty(input.Query)) query = query.Where(m => m.Description.Contains(input.Query));
if (input.Year != null && input.Month != null)
{
query = query.Where(m => m.CreationTime.Year == input.Year && m.CreationTime.Month == input.Month);
}
if (input.Day != null)
{
query = query.Where(m => m.CreationTime.Day == input.Day);
}
var totalCount = await AsyncQueryableExecuter.CountAsync(query);
query = ApplySorting(query, input);
query = ApplyPaging(query, input);
var entities = await AsyncQueryableExecuter.ToListAsync(query);
return new PagedResultDto<MediaDto>(
totalCount,
entities.Select(MapToEntityDto).ToList()
);
}
public override async Task<MediaDto> Create([FromForm]CreateMediaDto input)
{
CheckCreatePermission();
var stream = input.File.OpenReadStream();
var allowImageFileType = await SettingManager.GetSettingValueAsync(AppSettingNames.AllowImageFileType);
var allowAudioFileType = await SettingManager.GetSettingValueAsync(AppSettingNames.AllowAudioFileType);
var allowVideoFileType = await SettingManager.GetSettingValueAsync(AppSettingNames.AllowVideoFileType);
var allowUploadSize = await SettingManager.GetSettingValueAsync<int>(AppSettingNames.AllowUploadSize);
var fileType = stream.GetFileType();
var ext = fileType?.Extension;
MediaType? type = null;
if (allowImageFileType.IndexOf($",{ext},", StringComparison.OrdinalIgnoreCase) >= 0)
{
type = MediaType.Image;
}
else if (allowAudioFileType.IndexOf($",{ext},", StringComparison.OrdinalIgnoreCase) >= 0)
{
type = MediaType.Audio;
}
else if (allowVideoFileType.IndexOf($",{ext},", StringComparison.OrdinalIgnoreCase) >= 0)
{
type = MediaType.Video;
}
if (type == null) throw new UserFriendlyException("fileTypeNotAllow");
if(stream.Length ==0 || stream.Length>allowUploadSize ) throw new UserFriendlyException("fileSizeNotAllow");
var filename = ShortGuid.ToShortGuid(Guid.NewGuid());
var path = $"{filename.Substring(0, 2)}/{filename.Substring(2, 2)}";
var dir = $"{Environment.CurrentDirectory}//wwwroot//upload//{path}";
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
using (var fileStream = new FileStream($"{dir}//{filename}.{ext}", FileMode.Create))
{
await input.File.CopyToAsync(fileStream);
}
if (type == MediaType.Image)
{
using (var image = Image.Load<Rgba32>($"{dir}//{filename}.{ext}"))
{
image.Mutate(x => x
.Resize(160, 160));
image.Save($"{dir}//thumbnail_{filename}.{ext}");
}
}
var entity = new Media()
{
Filename = $"{filename}.{ext}",
Description = input.File.FileName,
Size = (int)stream.Length,
Path = path,
Type = type ?? MediaType.Image,
CreatorUserId = AbpSession.UserId,
TenantId = AbpSession.TenantId ?? 1
};
await Repository.InsertAsync(entity);
await CurrentUnitOfWork.SaveChangesAsync();
return MapToEntityDto(entity);
}
public async Task Delete(DeleteMediaInputDto input)
{
CheckDeletePermission();
foreach (var inputId in input.Id)
{
await Repository.DeleteAsync(inputId);
}
}
public async Task<ListResultDto<ComboBoxItemDto>> GetDateList()
{
CheckGetAllPermission();
var query = await Repository.GetAllListAsync();
var list = from media in query
let year = media.CreationTime.Year
let month = media.CreationTime.Month
group media by new {year, month}
into g
orderby g.Key.year descending, g.Key.month descending
select new ComboBoxItemDto
{
Id = g.Key.year.ToString() + "," + g.Key.month.ToString(),
Text = g.Key.year.ToString() + "年" + g.Key.month.ToString() + "月"
};
var results =
new List<ComboBoxItemDto> {new ComboBoxItemDto("all", "全部"), new ComboBoxItemDto("today", "今天")};
var itemDtos = results.Union(list.ToList());
return new ListResultDto<ComboBoxItemDto>(itemDtos.ToList());
}
[NonAction]
public override Task Delete(EntityDto<long> input)
{
return base.Delete(input);
}
}


在Create
方法内,使用到了ABP框架的设置管理(SettingManager
),这个,我们需要在Core项目的Configuration
文件夹下定义,首先要在AppSettingNames
类中定义一些常量,代码如下:


public static class AppSettingNames
{
public const string UiTheme = "App.UiTheme";
public const string AllowImageFileType = "App.AllowImageFileType";
public const string AllowAudioFileType = "App.AllowAudioFileType";
public const string AllowVideoFileType = "App.AllowVideoFileType";
public const string AllowUploadSize = "App.AllowUploadSize";
}


这里的定义参考常量UiTheme
的定义来进行就行了。接下来是在AppSettingProvider
中添加设置,具体代码如下:


public class AppSettingProvider : SettingProvider
{
public override IEnumerable<SettingDefinition> GetSettingDefinitions(SettingDefinitionProviderContext context)
{
return new[]
{
new SettingDefinition(AppSettingNames.UiTheme, "red", scopes: SettingScopes.Application | SettingScopes.Tenant | SettingScopes.User, isVisibleToClients: true),
new SettingDefinition(AppSettingNames.AllowImageFileType,",jpg,gif,png,jpeg,",scopes:SettingScopes.All,isVisibleToClients:true),
new SettingDefinition(AppSettingNames.AllowAudioFileType,",mp3,flac,wav,",scopes:SettingScopes.All,isVisibleToClients:true),
new SettingDefinition(AppSettingNames.AllowVideoFileType,",mp4,m4v,flv,mov,",scopes:SettingScopes.All,isVisibleToClients:true),
new SettingDefinition(AppSettingNames.AllowUploadSize,"10485760",scopes:SettingScopes.All,isVisibleToClients:true),
};
}
}


有关SettingDefinition
的具体信息可参考文档《设置管理
》。



如果希望将设置定义在appsettings.json
中,可以在Web.Core项目中添加一个从SettingProvider
派生的类,然后在GetSettingDefinitions
方法内从appsettings.json
读取所需的配置再。最后调用Configuration.Settings.Providers.Add
方法添加配置。



在获取到允许的文件类型和文件大小后,就可通过Mime-Detective
包提供的扩展方法GetFileType
来获取文件的实际类型了。如果实际的扩展名在允许的文件类型内,则设置媒体文件的类型。如果文件的大小也在允许范围内,则调用ShortGuid.ToShortGuid
方法来生成一个文件名,并将文件名的前两个字符作为一级路径,第3、4位字符作为一个路径。在应用服务内,使用Environment.CurrentDirectory
来获取当前工作目录比较简单,也不需要做转换,可抛弃MapPath
方法。对于Asp.Net Core项目,直接访问路径是在wwwroot
文件夹,不能直接方在工作目录的根文件夹,这个一定要注意。创建存放文件夹后,就可调用CopyToAsync
方法将文件保存了。



由于ImageResizer
包不支持Asp.Net Core,而后续项目imageflow
虽然有测试版,但完全没文档说明如何去使用,只有暂时放下这东西,乖乖的创建一个缩略图了。在寻找可支持Asp.Net Core的图像处理库的时候,找到了《.NET Core Image Processing
.NET Core Image Processing这篇文章,介绍了很多处理图片的库,最终选择了SixLabors.ImageSharp
这个库。选择这个库的主要原因是不需要其他库的支持也可以支持Window和Linux环境,比较方便。


在保存缩略图后,就可创建实体并添加到数据库了,最后返回实体。



在媒体管理视图中,有个一个查询是根据日期来查询媒体的,而这需要服务器端返回根据年和月分组后的日期,因而需要创建一个GetDateList
方法。在方法内,使用了根据前端需要定义的一个下拉列表类,具体代码如下:


[Serializable]
public class ComboBoxItemDto
{
public string Text { get; set; }
public string Id { get; set; }
public ComboBoxItemDto()
{
}
public ComboBoxItemDto(string id, string text)
{
Text = text;
Id = id;
}
}

这个类是根据框架提供的下拉列表类修改属性后创建的。区域其实不大,但方便Ext JS使用。



在GetDateList
方法内先通过分组创建一个ComboBoxItemDto
列表,然后与额外的两个选项合并后返回客户端。


至此,后台代码就完成了,下面来完成客户端代码。



在客户端先把模型等与字段相关的地方修改好。接下来要修改的是视图模型内datelists
存储的访问地址,需要将地址datelist
修改为getdatelist



最关键的修改是要在视图控制器的onBeforeUpload
方法内,为plupload
的请求添加认证头,代码如下:


onBeforeUpload: function (cmp, uploader, file) {
var me = this,
tb = me.lookupReference('progressToolBar'),
progress = tb.down('progressbar');
uploader.setOption('headers',Ext.apply( {}, HEADERS.getHeaders()));
progress.setValue(0);
progress.updateText(Ext.String.format(I18N.Uploading, file.name, 0));
tb.show();
},


plupload
提供了setOption
方法来修改选择,把headers
选项加进去就行了,难度不大。如果不使用plupload
,而是使用Ajax提交,可以参考这个说明:https://stackoverflow.com/questions/37258612/upload-document-to-web-api



余下要修改的是onDescriptionEditComplete
方法,为Ajax请求配置method
为PUT
,将params
修改为jsonData
.。


至此,文件上传功能就已经完成了。



项目源代码:https://gitee.com/tianxiaode


最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台