《打造一个网站或者其他网络应用的文件管理接口(WebApi)第三章“多文件上传+数据库辅助存储”》
========================================================
作者:qiujuer
博客:blog.csdn.net/qiujuer
网站:www.qiujuer.net
开源库:Genius-Android
转载请注明出处: http://blog.csdn.net/qiujuer/article/details/41721165
========================================================
在上一章中我们讲解了怎么实现文件的上传;在文件上传到服务器后似乎不好管理;每次要下载一个文件也似乎不知从何处下手。
在这一章中将会讲解如何结合数据进行文件的上传存储,保证文件的存储足够安全以及足够简单;同时增加不重复保存同一个文件。
好了又到了我们的代码模块了。
先来看看这一次我们做了哪些文件的改动;当然是相对上一版本。
可以看出,共更改了6个文件;其中修改:Web、Upload、Resouce、ResouceApi;添加:HashUtils、WebResouceContext。
using System; using System.ComponentModel.DataAnnotations; namespace WebResource.Models { /// <summary> /// 文件存储信息表 /// </summary> public class Resource { /// <summary> /// Id 用于唯一标识 /// 存储时记录文件MD5 /// 在断点续传下存储文件名称+文件大小的MD5值 /// </summary> [Key] public string Id { get; set; } /// <summary> /// 文件名称 /// </summary> [Required] public string Name { get; set; } /// <summary> /// 文件大小 /// </summary> [Required] public long Size { get; set; } /// <summary> /// 当前存储位置 /// 断点续传下用于判断是否传输完成 /// </summary> [Required] public long Cursor { get; set; } /// <summary> /// 文件类型 /// </summary> [Required] public string Type { get; set; } /// <summary> /// 存储文件夹 /// </summary> [Required] public string Folder { get; set; } /// <summary> /// 点击数 /// </summary> [Required] public int Clicks { get; set; } /// <summary> /// 存储日期 /// </summary> [Required] public DateTime Published { get; set; } } }这个是在上一个版本基础上进行修改后的数据库表结构;在我看来一个文件的存储信息应该包含上面的信息;当然其中用于断点续传的Cursor属性暂时是虚的,没有投入实际使用;当然这个在后续版本将会详细讲解如何添加断点续传。针对文件的基本信息各位如有更好的方案还请提出来。
using System; using System.IO; using System.Security.Cryptography; namespace WebResource.Models { public class HashUtils { /// <summary> /// 获取文件的MD5-格式[大写] /// </summary> /// <param name="fileStream">文件FileStream</param> /// <returns>MD5Hash</returns> public static string GetMD5Hash(FileStream fileStream) { try { MD5 md5Provider = new MD5CryptoServiceProvider(); byte[] buffer = md5Provider.ComputeHash(fileStream); md5Provider.Clear(); return BitConverter.ToString(buffer).Replace("-", ""); } catch { return null; } } /// <summary> /// 获取字符串的MD5-格式[大写] /// </summary> /// <param name="str">字符串</param> /// <returns>MD5Hash</returns> public static string GetMD5Hash(string str) { try { MD5 md5Provider = new MD5CryptoServiceProvider(); byte[] buffer = md5Provider.ComputeHash(System.Text.Encoding.UTF8.GetBytes(str)); md5Provider.Clear(); return BitConverter.ToString(buffer).Replace("-", ""); } catch { return null; } } /// <summary> /// 计算Byte数组的MD5-格式[大写] /// </summary> /// <param name="bytes">byte[]</param> /// <returns>MD5Hash</returns> public static string GetMD5Hash(byte[] bytes) { try { MD5 md5Provider = new MD5CryptoServiceProvider(); byte[] buffer = md5Provider.ComputeHash(bytes); md5Provider.Clear(); return BitConverter.ToString(buffer).Replace("-", ""); } catch { return null; } } } }顾名思义,这个类就是用来进行计算MD5值而准备的;当然实际使用中只会使用第3个方法;前面两个只是一并写出来说不定以后要用上。
using System.Data.Entity; namespace WebResource.Models { public class WebResourceContext : DbContext { public WebResourceContext() : base("name=WebResourceContext") { } public System.Data.Entity.DbSet<WebResource.Models.Resource> Resources { get; set; } } }这个类简单明了;用过EF Db的应该一看就懂了;这个类是用来进行数据表查询使用的类;当然使用的是EF。
其中指定了"name=WebResourceContext",这个就是用来对应配置文件中的数据库连接字段的。下面就会讲解。
<?xml version="1.0" encoding="utf-8"?> <configuration> ... <connectionStrings> <add name="WebResourceContext" connectionString="Data Source=(localdb)\v11.0; Initial Catalog=WebResourceContext-20141204104407; Integrated Security=True; MultipleActiveResultSets=True; AttachDbFilename=|DataDirectory|WebResourceContext-20141204104407.mdf" providerName="System.Data.SqlClient" /> </connectionStrings> ... </configuration>在这个文件中,有很多的配置;但是其中简单的来说,就是上面的那一条语句;在这条语句中现在指定的是本地的数据库;你也可以指定一个远程的数据连接。
using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Web; using System.Web.Http; using System.Web.Http.Description; using WebResource.Models; namespace WebResource.Controllers { [RoutePrefix("Res")] public class ResourceApiController : ApiController { private WebResourceContext db = new WebResourceContext(); private static readonly long MEMORY_SIZE = 64 * 1024 * 1024; private static readonly string ROOT_PATH = HttpContext.Current.Server.MapPath("~/App_Data/"); /// <summary> /// Post File /// </summary> /// <param name="Id">Md5</param> /// <returns>Resource</returns> [HttpPost] [Route("Upload/{Id?}")] [ResponseType(typeof(Resource))] public async Task<IHttpActionResult> Post(string Id = null) { List<Resource> resources = new List<Resource>(); // multipart/form-data var provider = new MultipartMemoryStreamProvider(); await Request.Content.ReadAsMultipartAsync(provider); foreach (var item in provider.Contents) { if (item.Headers.ContentDisposition.FileName != null) { //Strem var ms = item.ReadAsStreamAsync().Result; using (var br = new BinaryReader(ms)) { if (ms.Length <= 0) break; var data = br.ReadBytes((int)ms.Length); //Md5 string id = HashUtils.GetMD5Hash(data); Resource temp = await db.Resources.FindAsync(id); if (temp == null) { //Create Resource resource = new Resource(); resource.Id = id; resource.Size = ms.Length; resource.Cursor = resource.Size; resource.Published = DateTime.Now; //Info FileInfo info = new FileInfo(item.Headers.ContentDisposition.FileName.Replace("\"", "")); resource.Type = info.Extension.Substring(1).ToLower(); resource.Name = info.Name.Substring(0, info.Name.LastIndexOf(".")); //Relative resource.Folder = DateTime.Now.ToString("yyyyMM/dd/", DateTimeFormatInfo.InvariantInfo); //Write try { string dirPath = Path.Combine(ROOT_PATH, resource.Folder); if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } File.WriteAllBytes(Path.Combine(dirPath, resource.Id), data); //Save To Datebase db.Resources.Add(resource); await db.SaveChangesAsync(); temp = await db.Resources.FindAsync(resource.Id); } catch { } } if (temp != null) resources.Add(temp); } } } if (resources.Count == 0) return BadRequest(); else if (resources.Count == 1) return Ok(resources.FirstOrDefault()); else return Ok(resources); } } }这个类才是本次讲解的核心部分。
在这个类中,首先看第一个修改的地方,我把API地址重定向为:[RoutePrefix("Res")],这个是为了方便以后使用,输入Res总是要比Resource要舒服些的。
另外我删除了Get()方法,因为这个版本中主要讲解上传;另外下载的话也将集合数据进行操作;所以在下一个版本将会添加新的Get()。
来看看现在的API接口:
现在只有一个接口了。
说说流程:
@{ ViewBag.Title = "Upload"; } <h2>Upload</h2> <div id="body"> <h1>多文件上传模式</h1> <section class="main-content clear-fix"> <form name="form1" method="post" enctype="multipart/form-data" action="/Res/Upload"> <fieldset> <legend>File Upload Example</legend> <div> <label for="caption">File1</label> <input name="file1" type="file" /> </div> <div> <label for="image1">File2</label> <input name="file2" type="file" /> </div> <div> <input type="submit" value="Submit" /> </div> </fieldset> </form> </section> </div>这个文件中只改动了一个地方,就是 action="/Res/Upload"。
终于到了这里了,说实话写一个这个挺累的。
运行:localhost:60586/Home/Upload
提交后:
数据库
文件
可以看见文件是按照:年月/日/文件MD5 进行存储的。
与往常一样。
[WebApi] 捣鼓一个资源管理器--多文件上传+数据库辅助
在下一章中将会讲解;怎么在这一章的基础上进行文件的访问。
========================================================
作者:qiujuer
博客:blog.csdn.net/qiujuer
网站:www.qiujuer.net
开源库:Genius-Android
转载请注明出处:http://blog.csdn.net/qiujuer/article/details/41721165
========================================================
[WebApi] 捣鼓一个资源管理器--多文件上传+数据库辅助
原文地址:http://blog.csdn.net/qiujuer/article/details/41721165