2017年1月20日 星期五

C# MVC jQuery FileUpload

範例套件下載:https://github.com/daniel-williams/jQuery-File-Upload.MVC4
參考畫面(Basic Plus UI):http://blueimp.github.io/jQuery-File-Upload/

jQuery.FileUpload有分很多版本,我使用的是Basic Plus UI這一個版本。雖然NuGet套件裡也有jQuery.FileUpload,不過跟範例的版本有落差,建議大家可以先用套件的檔案,再來做調整。

至於我修改了哪些地方呢?
  1. 範例是抓檔案列出清單,而我的專案需要配合DB,所以改成由DB中撈出資料後再去抓上傳的圖檔。
  2. 原來的檔案都是上傳到同一個地方,在這邊我把它們分門別類,依照模組(modelName)的不同上傳到不同的資料夾。
我們需要copy的檔案有:
  • IDGen.cs
  • ImageHandler.cs
  • MimeTypes.cs
  • ViewDataUploadFilesResult.cs
  • styles/FileUpload/全部  (在專案中要改放到Content/底下)
  • scripts/FileUpload/全部  (在專案中的Scripts開頭是大寫)
然後修改前4個檔案的namespace,改成自己的專案名稱,這樣等等就不需要多做using的動作了。

我們先從需要修改的檔案開始改起,首先是App_Start/RouteConfig.cs,增加綠色的部份
    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

      // configure HttpHandler for serving static images
      HttpMethodConstraint GetFileMethodConstraints = new HttpMethodConstraint(new string[] { "GET" });
      Route GetFileRoute = new Route(
          url: "Files/{id}/{filename}",
          routeHandler: new DobImageRouteHandler(),
          defaults: null,
          constraints: new RouteValueDictionary { { "httpMethod", GetFileMethodConstraints } }
      );
      routes.Add("GetFileRoute", GetFileRoute);


      //routes.MapRoute(
      //    name: "Get File",
      //    url: "Files/{id}/{filename}",
      //    defaults: new { controller = "Files", action = "Find" },
      //    constraints: new { httpMethod = new HttpMethodConstraint("GET") }
      //);

      routes.MapRoute(
          name: "Delete File",
          url: "Files/{id}",
          defaults: new { controller = "Files", action = "Delete" },
          constraints: new { httpMethod = new HttpMethodConstraint("DELETE") }
      );

      routes.MapRoute(
          name: "Get Json File List",
          url: "Files/{id}",
          defaults: new { controller = "Files", action = "List" },
          constraints: new { httpMethod = new HttpMethodConstraint("GET") }
      );

      routes.MapRoute(
          name: "Post Files",
          url: "Files/{id}",
          defaults: new { controller = "Files", action = "Uploads" },
          constraints: new { httpMethod = new HttpMethodConstraint("POST") }
      );

      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Login", id = UrlParameter.Optional }
      );

    }

再編輯BundleConfig.cs,加入綠色的部份
    public static void RegisterBundles(BundleCollection bundles)
    {
      ...(原本的內容)

      bundles.Add(new ScriptBundle("~/bundles/jqueryfileupload").Include(
                "~/Scripts/FileUpload/vendor/jquery.ui.widget.js",
                "~/Scripts/FileUpload/tmpl.js",
                "~/Scripts/FileUpload/load-image.js",
                "~/Scripts/FileUpload/canvas-to-blob.js",
                "~/Scripts/FileUpload/jquery.iframe-transport.js",
                "~/Scripts/FileUpload/jquery.fileupload.js",
                "~/Scripts/FileUpload/jquery.fileupload-fp.js",
                "~/Scripts/FileUpload/jquery.fileupload-ui.js"));
                //"~/Scripts/FileUpload/main.js"));

      bundles.Add(new StyleBundle("~/Content/jqueryfileupload").Include(
                  "~/Content/FileUpload/jquery.fileupload-ui.css"));

    }

打開你要加入此功能的頁面,加入以下內容,因為我沒有用範例的bootstrap套件,所以css的部份有些許不同,不然按鈕上的圖示會無法顯示。原本main.js的內容則是移到頁面中,因為要加上modelName的參數
<!-- 
加入前先檢查一下Views/Shared/_Layout.cshtml是不是有下列兩個參數了,否則會出錯
@RenderSection("scripts", required: false)
@RenderSection("styles", required: false)
//-->

@section scripts {
  @Scripts.Render("~/bundles/jqueryfileupload")
}

@section styles
{
  @Styles.Render("~/Content/jqueryfileupload")
}

<form id="fileupload" action="@Url.Action("Uploads","Files")" method="POST" enctype="multipart/form-data">
    @Html.Hidden("modelName", "Product")
    <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload -->
    <div class="row fileupload-buttonbar">
      <div class="col-md-7">
        <!-- The fileinput-button span is used to style the file input field as button -->
        <span class="btn btn-success fileinput-button">
          <i class="glyphicon glyphicon-plus"></i>
          <span>增加檔案...</span>
          <input type="file" name="files[]" multiple>
        </span>
        <button type="submit" class="btn btn-primary start">
          <i class="glyphicon glyphicon-upload"></i>
          <span>開始上傳</span>
        </button>
        <button type="reset" class="btn btn-warning cancel">
          <i class="glyphicon glyphicon-ban-circle"></i>
          <span>取消上傳</span>
        </button>
        <button type="button" class="btn btn-danger delete">
          <i class="glyphicon glyphicon-trash"></i>
          <span>刪除</span>
        </button>
        <input type="checkbox" class="toggle">
        <!-- The global file processing state -->
        <span class="fileupload-process"></span>
      </div>
      <!-- The global progress state -->
      <div class="col-md-3 fileupload-progress fade">
        <!-- The global progress bar -->
        <div class="progress progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100">
          <div class="progress-bar progress-bar-success" style="width:0%;"></div>
        </div>
        <!-- The extended global progress state -->
        <div class="progress-extended">&nbsp;</div>
      </div>
      <!-- go back pritave page -->
      <div class="col-md-2 text-right">
        <input type="button" value="返回" onclick="comeback();" class="btn btn-default" />
      </div>
    </div>
<!-- The table listing the files available for upload/download -->
<table role="presentation" class="table table-striped"><tbody class="files"></tbody></table>
  </form>
</div>

<!-- The template to display files available for upload -->
<script id="template-upload" type="text/x-tmpl">
  {% for (var i=0, file; file=o.files[i]; i++) { %}
  <tr class="template-upload fade">
    <td class="preview"><span class="fade"></span></td>
    <td class="name"><span>{%=file.name%}</span></td>
    <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
    {% if (file.error) { %}
    <td class="error" colspan="2"><span class="label label-important">Error</span> {%=file.error%}</td>
    {% } else if (o.files.valid && !i) { %}
    <td>
      <div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"><div class="bar" style="width:0%;"></div></div>
    </td>
    <td class="start">
      {% if (!o.options.autoUpload) { %}
      <button class="btn btn-primary">
        <i class="glyphicon glyphicon-upload"></i>
        <span>開始上傳</span>
      </button>
      {% } %}
    </td>
    {% } else { %}
    <td colspan="2"></td>
    {% } %}
    <td class="cancel">
      {% if (!i) { %}
      <button class="btn btn-warning">
        <i class="glyphicon glyphicon-ban-circle"></i>
        <span>取消上傳</span>
      </button>
      {% } %}
    </td>
  </tr>
  {% } %}
</script>


<!-- The template to display files available for download -->
<script id="template-download" type="text/x-tmpl">
  {% for (var i=0, file; file=o.files[i]; i++) { %}
  <tr class="template-download fade">
    {% if (file.error) { %}
    <td></td>
    <td class="name"><span>{%=file.name%}</span></td>
    <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
    <td class="error" colspan="2"><span class="label label-important">Error</span> {%=file.error%}</td>
    {% } else { %}
    <td class="preview">
      <a href="{%=file.url%}" title="{%=file.name%}" data-gallery="gallery" download="{%=file.name%}"><img src="{%=file.thumbnail_url%}" class="thum" /></a>
    </td>
    <td class="name">
      <a href="{%=file.url%}" title="{%=file.name%}" data-gallery="{%=file.thumbnail_url&&'gallery'%}" download="{%=file.name%}">{%=file.name%}</a>
    </td>
    <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
    <td colspan="2"></td>
    {% } %}
    <td class="delete">
      <button class="btn btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}" {% if (file.delete_with_credentials) { %} data-xhr-fields='{"withCredentials":true}' {% } %}>
        <i class="glyphicon glyphicon-trash"></i>
        <span>刪除</span>
      </button>
      <input type="checkbox" name="delete" value="1">
    </td>
  </tr>
  {% } %}
</script>

<script type="text/javascript">
  function comeback() {
    window.location.href = "@Url.Action("Index", "Product")";
  }

  $(document).ready(function () {
    'use strict';

    // Initialize the jQuery File Upload widget:
    $('#fileupload').fileupload({
      // Uncomment the following to send cross-domain cookies:
      //xhrFields: {withCredentials: true},

      url: '/Files/@Model?modelName=Product',
      acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
      maxFileSize: 3000000,
    });

    // Enable iframe cross-domain access via redirect option:
    $('#fileupload').fileupload(
        'option',
        'redirect',
        window.location.href.replace(
            /\/[^\/]*$/,
            '/cors/result.html?%s'
        )
    );

    // Load existing files:
    $.ajax({
      // Uncomment the following to send cross-domain cookies:
      //xhrFields: {withCredentials: true},
      url: $('#fileupload').fileupload('option', 'url'),
      dataType: 'json',
      context: $('#fileupload')[0]
    }).done(function (result) {
      $(this).fileupload('option', 'done')
          .call(this, null, { result: result });
    });

  });
</script>

增加一個Controller檔案,命名為FilesController.cs,copy範例裡的同名檔案貼上內容,然後把所缺的東西一一 using 進來。至於db的部份,則看個人是怎麼寫的自行加上,我有寫成一個class,所以是直接引入我寫的class
      protected connDB D = new connDB();
        //
        // GET: /Files/
      private string _Dir = "D:/project";
      private string _StorageRoot;
      private string StorageRoot
      {
        get { return _StorageRoot; }
      }

      public FilesController()
      {
        //string modelName = "Product";
        _StorageRoot = Path.Combine(_Dir + "/Files/");
      }

      [HttpGet]
      public ActionResult List(int id, string modelName)
      {
        D.Open();
        DataTable Table = D.GetTable("SELECT * FROM product_img WHERE product_id = '" + D.ToString(id) + "'");
        D.Close();

        var fileData = new List<ViewDataUploadFilesResult>();

        DirectoryInfo dir = new DirectoryInfo(StorageRoot + modelName);

        if (dir.Exists)
        {
          foreach (DataRow row in Table.Rows)
          {
            var fileNameEncoded = HttpUtility.HtmlEncode(row["filename"]);
            var relativePath = "/Files/" + modelName + "/" + fileNameEncoded;

            fileData.Add(new ViewDataUploadFilesResult()
            {
              url = relativePath,
              thumbnail_url = relativePath, //@"data:image/png;base64," + EncodeFile(fullName),
              name = fileNameEncoded,
              type = D.ToString(row["filetype"]),
              size = D.ToInt(row["filesize"]),
              delete_url = "/Files/" + id + "?filename=" + fileNameEncoded + "&modelName=" + modelName,
              delete_type = "DELETE"
            });
          }

          /*
          string[] extensions = MimeTypes.ImageMimeTypes.Keys.ToArray();

          FileInfo[] files = dir.EnumerateFiles()
                   .Where(f => extensions.Contains(f.Extension.ToLower()))
                   .ToArray();

          if (files.Length > 0)
          {
            foreach (FileInfo file in files)
            {
              var fileId = file.Name.Substring(0, 20);
              var fileNameEncoded = HttpUtility.HtmlEncode(file.Name.Substring(21));
              var relativePath = "/App_Data/" + modelName + "/" + fileNameEncoded;

              fileData.Add(new ViewDataUploadFilesResult()
              {
                url = relativePath,
                thumbnail_url = relativePath, //@"data:image/png;base64," + EncodeFile(fullName),
                name = fileNameEncoded,
                type = MimeTypes.ImageMimeTypes[file.Extension],
                size = Convert.ToInt32(file.Length),
                delete_url = relativePath,
                delete_type = "DELETE"
              });
            }
          }
          */
        }

        var serializer = new JavaScriptSerializer();
        serializer.MaxJsonLength = Int32.MaxValue;

        var result = new ContentResult
        {
          Content = "{\"files\":" + serializer.Serialize(fileData) + "}",
        };
        return result;
      }

      public ActionResult Find(string id, string filename, string modelName)
      {
        if (id == null || filename == null)
        {
          return HttpNotFound();
        }

        //var filePath = Path.Combine(_StorageRoot, id + "-" + filename);
        var filePath = Path.Combine("~/Files/" + modelName, filename); 

        FileStreamResult result = new FileStreamResult(new System.IO.FileStream(filePath, System.IO.FileMode.Open), GetMimeType(filePath));
        result.FileDownloadName = filename;

        return result;
      }

      [HttpPost]
      [AcceptVerbs(HttpVerbs.Post)]
      public ActionResult Uploads(int id, string modelName)
      {
        var fileData = new List<ViewDataUploadFilesResult>();
        D.Open();
        int maxid = D.ToInt(D.GetValue("SELECT MAX(orderid) FROM product_img WHERE product_id = '" + D.ToString(id) + "'"));
        maxid = (maxid < 1) ? 0 : maxid;
        
        foreach (string file in Request.Files)
        {
          UploadWholeFile(modelName, id, maxid, Request, fileData);
        }

        D.Close();

        var serializer = new JavaScriptSerializer();
        serializer.MaxJsonLength = Int32.MaxValue;

        var result = new ContentResult
        {
          Content = "{\"files\":" + serializer.Serialize(fileData) + "}",
        };
        return result;
      }

      private void UploadWholeFile(string modelName, int id, int maxid, HttpRequestBase request, List<ViewDataUploadFilesResult> statuses)
      {

        D.Open();
        for (int i = 0; i < request.Files.Count; i++)
        {

          HttpPostedFileBase file = request.Files[i];

          ++maxid;
          //upload files
          string tempName = IDGen.NewID() + "_";

          if (file.ContentLength == 0)
            continue;

          var fileName = tempName + Path.GetFileName(file.FileName);
          var fileNameEncoded = HttpUtility.HtmlEncode(fileName);
          var fullPath = Path.Combine(Server.MapPath("~/Files/" + modelName), fileName);

          file.SaveAs(fullPath);

          //save to db
          Dictionary<string, string> data = new Dictionary<string, string>();
          data["product_id"] = D.ToString(id);
          data["filename"] = D.ToString(fileNameEncoded);
          data["filesize"] = D.ToString(file.ContentLength);
          data["orderid"] = D.ToString(maxid);
          D.InsertDictionary("product_img", data);

          statuses.Add(new ViewDataUploadFilesResult()
          {
            url = "/Files/" + modelName + "/" + fileNameEncoded,
            thumbnail_url = "/Files/" + modelName + "/" + fileNameEncoded, //@"data:image/png;base64," + EncodeFile(fullName),
            name = fileNameEncoded,
            type = file.ContentType,
            size = file.ContentLength,
            delete_url = "/Files/" + id + "?filename=" + fileNameEncoded + "&modelName=" + modelName,
            delete_type = "DELETE"
          });
        }
      }

      /*
      private void UploadWholeFile(HttpRequestBase request, List<ViewDataUploadFilesResult> statuses)
      {
        for (int i = 0; i < request.Files.Count; i++)
        {
          HttpPostedFileBase file = request.Files[i];

          var fileId = IDGen.NewID();
          var fileName = Path.GetFileName(file.FileName);
          var fileNameEncoded = HttpUtility.HtmlEncode(fileName);
          var fullPath = Path.Combine(StorageRoot, fileId + "-" + fileName);

          file.SaveAs(fullPath);

          statuses.Add(new ViewDataUploadFilesResult()
          {
            url = "/Files/" + fileId + "/" + fileNameEncoded,
            thumbnail_url = "/Files/" + fileId + "/" + fileNameEncoded, //@"data:image/png;base64," + EncodeFile(fullName),
            name = fileNameEncoded,
            type = file.ContentType,
            size = file.ContentLength,
            delete_url = "/Files/" + fileId + "/" + fileNameEncoded,
            delete_type = "DELETE"
          });
        }
      }
      */
        
      [AcceptVerbs(HttpVerbs.Delete)]
      public ActionResult Delete(string id, string filename, string modelName)
      {
        if (id == null || filename == null || modelName == null)
        {
          return HttpNotFound();
        }

        var filePath = Path.Combine(_StorageRoot + modelName, filename);

        if (System.IO.File.Exists(filePath))
        {
          System.IO.File.Delete(filePath);
          D.Open();
          D.DataDelete("DELETE FROM product_img WHERE filename = '" + filename + "'");
          D.Close();
        }

        return RedirectToAction("FormImg", "Product");
      }

      private string EncodeFile(string fileName)
      {
        return Convert.ToBase64String(System.IO.File.ReadAllBytes(fileName));
      }

      private string GetMimeType(string filePath)
      {
        return GetMimeType(new FileInfo(filePath));
      }
      private string GetMimeType(FileInfo file)
      {
        return MimeTypes.ImageMimeTypes[file.Extension];
      }

在根目錄下新增一個Files的資料夾,全部都佈屬好後就啟動唄!

畫面會長的像這樣,當然如果你還沒上傳過東西,列表會是空的


希望大家都能順利coding。

沒有留言:

張貼留言