.Net Core之MongoDB存储文件

1 篇文章 0 订阅
1 篇文章 0 订阅

.Net Core之MongoDB存储文件

MongoDB提供了GridFS来存储文件,我们这里就讨论采用GridFS存储文件的方案

这里主要使用MongoDB.Driver相关库

  • MongoDB的一些基本使用:

    public class MongoFileRepo
        {
            private IMongoClient _client;
            private IMongoDatabase _db;
            private readonly IGridFSBucket bucket;
            //注入相关配置
            public MongoFileRepo(IOptions<Settings> settings)
            {
                _client = new MongoClient(settings.Value.ConnectionString_MongoDB);
                _db = _client.GetDatabase("MongoDB");
                bucket = new GridFSBucket(_db);
            }
    
            public ObjectId GetInternalId(string id)
            {
                if (!ObjectId.TryParse(id, out ObjectId internalId))
                    internalId = ObjectId.Empty;
    
                return internalId;
            }
    
            public async Task<GridFSFileInfo> GetFileById(string id)
            {
                var filter = Builders<GridFSFileInfo>.Filter.Eq("_id", GetInternalId(id));
                return await bucket.Find(filter).FirstOrDefaultAsync();
            }
    
            public async Task<GridFSFileInfo> GetFileById(ObjectId id)
            {
                var filter = Builders<GridFSFileInfo>.Filter.Eq("_id", id);
                return await bucket.Find(filter).FirstOrDefaultAsync();
            }
    
            public async Task<ObjectId> UploadFile(string fileName, Stream source)
            {
                var id = await bucket.UploadFromStreamAsync(fileName, source);
                return id;
            }
    
            public async Task<GridFSDownloadStream<ObjectId>> DownloadFileStreamSeekable(string id)
            {
                var options = new GridFSDownloadOptions
                {
                    Seekable = true
                };
                return await bucket.OpenDownloadStreamAsync(GetInternalId(id), options);
            }
    
            public async Task<GridFSDownloadStream<ObjectId>> DownloadFileStreamSeekable(ObjectId id)
            {
                var options = new GridFSDownloadOptions
                {
                    Seekable = true
                };
                return await bucket.OpenDownloadStreamAsync(id, options);
            }
    
            public async Task<GridFSDownloadStream<ObjectId>> DownloadFileStream(string id)
            {
                return await bucket.OpenDownloadStreamAsync(GetInternalId(id));
            }
    
            public async Task<GridFSDownloadStream<ObjectId>> DownloadFileStream(ObjectId id)
            {
                return await bucket.OpenDownloadStreamAsync(id);
            }
    
            public async Task DeleteFile(string id)
            {
                await bucket.DeleteAsync(GetInternalId(id));
            }
    
            public async Task DeleteFile(ObjectId id)
            {
                await bucket.DeleteAsync(id);
            }
    
            public async Task RenameFile(string id, string newFilename)
            {
                await bucket.RenameAsync(GetInternalId(id), newFilename);
            }
    
            public async Task RenameFile(ObjectId id, string newFilename)
            {
                await bucket.RenameAsync(id, newFilename);
            }
        }
    
  • MongoDB属于开箱即用,但 .Net Core下的可配置化的文件上传还是没那么简洁的,需要做一些自定义的工作,但是微软官方也是提供了配置方案:

    public static class FileStreamingHelper
    {
        private static readonly FormOptions _defaultFormOptions = new FormOptions();
    
        public static async Task<FormValueProvider> StreamFile(this HttpRequest request, string targetFilePath)
        {
            if (!MultipartRequestHelper.IsMultipartContentType(request.ContentType))
            {
                throw new Exception($"Expected a multipart request, but got {request.ContentType}");
            }
    
            // Used to accumulate all the form url encoded key value pairs in the 
            // request.
            var formAccumulator = new KeyValueAccumulator();
    
            var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
            var reader = new MultipartReader(boundary, request.Body);
    
            var section = await reader.ReadNextSectionAsync();
            while (section != null)
            {
                ContentDispositionHeaderValue contentDisposition;
                var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
    
                if (hasContentDispositionHeader)
                {
                    if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
                    {
                        FileMultipartSection currentFile = section.AsFileSection();
                        string time = DateTime.Now.ToString("yyyyMMddHHmmssffff");
                        string filePath = Path.Combine(targetFilePath, time + "_" + currentFile.FileName);
    
                        using (var targetStream = File.Create(filePath))
                        {
                            Console.WriteLine("{0} is uploading", currentFile.FileName);
                            await section.Body.CopyToAsync(targetStream).ConfigureAwait(false);
                        }
                    }
                    else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
                    {
                        // Content-Disposition: form-data; name="key"
                        //
                        // value
    
                        // Do not limit the key name length here because the 
                        // multipart headers length limit is already in effect.
                        var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
                        var encoding = GetEncoding(section);
                        using (var streamReader = new StreamReader(
                            section.Body,
                            encoding,
                            detectEncodingFromByteOrderMarks: true,
                            bufferSize: 1024,
                            leaveOpen: true))
                        {
                            // The value length limit is enforced by MultipartBodyLengthLimit
                            var value = await streamReader.ReadToEndAsync();
                            if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
                            {
                                value = String.Empty;
                            }
                            formAccumulator.Append(key.Value, value);
    
                            if (formAccumulator.ValueCount > _defaultFormOptions.ValueCountLimit)
                            {
                                throw new InvalidDataException($"Form key count limit {_defaultFormOptions.ValueCountLimit} exceeded.");
                            }
                        }
                    }
                }
    
                // Drains any remaining section body that has not been consumed and
                // reads the headers for the next section.
                section = await reader.ReadNextSectionAsync();
            }
    
            // Bind form data to a model
            var formValueProvider = new FormValueProvider(
                BindingSource.Form,
                new FormCollection(formAccumulator.GetResults()),
                CultureInfo.CurrentCulture);
    
            return formValueProvider;
        }
    
        private static Encoding GetEncoding(MultipartSection section)
        {
            MediaTypeHeaderValue mediaType;
            var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
            // UTF-7 is insecure and should not be honored. UTF-8 will succeed in 
            // most cases.
            if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
            {
                return Encoding.UTF8;
            }
            return mediaType.Encoding;
        }
    }
    
    public static class MultipartRequestHelper
    {
    // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
    // The spec says 70 characters is a reasonable limit.
    public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
    {
        var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
        if (string.IsNullOrWhiteSpace(boundary))
        {
            throw new InvalidDataException("Missing content-type boundary.");
        }
    
        if (boundary.Length > lengthLimit)
        {
            throw new InvalidDataException(
                $"Multipart boundary length limit {lengthLimit} exceeded.");
        }
    
        return boundary;
    }
    
    public static bool IsMultipartContentType(string contentType)
    {
        return !string.IsNullOrEmpty(contentType)
                && contentType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0;
    }
    
    public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
    {
        // Content-Disposition: form-data; name="key";
        return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && string.IsNullOrEmpty(contentDisposition.FileName.Value)
                && string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
    }
    
    public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
    {
        // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
        return contentDisposition != null
                && contentDisposition.DispositionType.Equals("form-data")
                && (!string.IsNullOrEmpty(contentDisposition.FileName.Value)
                    || !string.IsNullOrEmpty(contentDisposition.FileNameStar.Value));
    }
    
    public static Encoding GetEncoding(MultipartSection section)
    {
        MediaTypeHeaderValue mediaType;
        var hasMediaTypeHeader = MediaTypeHeaderValue.TryParse(section.ContentType, out mediaType);
        // UTF-7 is insecure and should not be honored. UTF-8 will succeed in 
        // most cases.
        if (!hasMediaTypeHeader || Encoding.UTF7.Equals(mediaType.Encoding))
        {
            return Encoding.UTF8;
        }
        return mediaType.Encoding;
    }
    }
    
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
    {
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            var factories = context.ValueProviderFactories;
            factories.RemoveType<FormValueProviderFactory>();
            factories.RemoveType<JQueryFormValueProviderFactory>();
        }
    
        public void OnResourceExecuted(ResourceExecutedContext context)
        {
        }
    }
    
  • 完成以上准备工作,便可以实现文件上传和下载的接口了:

    /// <summary>
    /// 上传文件
    /// </summary>
    /// <returns></returns>
    [HttpPost]
    [DisableRequestSizeLimit]
    [DisableFormValueModelBinding]
    public async Task<IActionResult> Upload()
    {
        //检查ContentType
        if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
        {
            return BadRequest("shoule_be_multipart");
        }
    
        FormOptions _defaultFormOptions = new FormOptions();
        var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
        var reader = new MultipartReader(boundary, Request.Body);
    
        var section = await reader.ReadNextSectionAsync();
        while (section != null)
        {
            //把Form的栏位內容逐一取出
            var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out ContentDispositionHeaderValue contentDisposition);
    
            if (hasContentDispositionHeader)
            {
                //按文件和键值对分类处理
                if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
                {
                    FileMultipartSection currentFile = section.AsFileSection();
                    //存储文件到Mongo
                    var id = await _mongoRepo.UploadFile(currentFile.FileName, section.Body);
                }
            }
            section = await reader.ReadNextSectionAsync();
        }
    
        return Ok();
    }
    
    
    /// <summary>
    /// 下载文件
    /// </summary>
    [HttpGet("{id}/")]
    public async Task<IActionResult> Download(int id)
    {
        var fileInfo = await _mongoRepo.GetFileById(id);
        if (fileInfo == null)
        {
            return NotFound();
        }
    
        return File(await _mongoRepo.DownloadFileStream(mongoId), "application/octet-stream", fileInfo.Filename);
    }
    
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值