2015年9月2日 星期三

Angular + Asp.net MVC 多檔上傳

自從Html5 增加file的功能後,檔案上傳的功能簡化不少,剛好有案子正好需要上傳圖片,所以可以示範如何在Angular 上傳檔案後,再經由MVC後端接收圖片的教學,最後又透過Angular的特殊功能,把多張圖片展示在列表上。

一、HTML5 的語法
瀏覽器已經將上傳檔案的功能內建化,所以讓後端處理細節簡化不少,
加上multiple 允許多個檔案上傳,預設只能單檔上傳,所以multiple 屬性要加上

<input type="file" name="multipleFiles" multiple />

二、Angular的語法

傳統的網頁是經由post把網頁內的form 資訊往server後端傳遞,但是
Ajax已經是網頁必備的功能,只將部份訊息往後端傳遞,所以angular中宣告formData變數就是要把前端的訊息集中起來,再呼叫$http.postformData 送給asp.net MVC controller

Angular 有很多參數和函數,作者沒有深入研究,因為$http.post是非常方便的工具,$http Angular和遠端http Server 溝通的核心

var formData = new FormData();//傳送到後端的資訊
      //上傳多張圖片
                $.each($("input[name='multipleFiles']"), function (i, obj) {
                    $.each(obj.files, function (j, file) {                       
                        formData.append('photo[' + j + ']', file);  //以陣列方式傳遞多檔圖片或檔案
                    })
                });        
$http.post(‘../Test/AddReleaseBug’, formData, {
   headers: { 'Content-Type': undefined }
 }).then(function (res) {
 alert("儲存成功");
}));

  
三、Asp.net MVC

Asp.net MVC為了要接收來自前端的上傳的圖片,所以宣告
HttpPostedFileBase陣列的型態去接收檔案,就可以接收多個檔案,為了隔離網頁的呈現層(asp.net mvc)和企業邏輯,所以增加實體層entityTest 負責資料DB的取得,如果網頁架構較大時,可以增加控制層,負責更細節的邏輯控制,再由控制層再呼叫實體層,這就是早期的三層式架構(MVC)模式。

Asp.net Controller
      [HttpPost]
        public async Task<bool> AddReleaseBug( IEnumerable<HttpPostedFileBase>[] photo)
        {         
            string path = "bugFile";
            string strMultiFiles = "";         
           if (photo != null)
                {
                    foreach (IEnumerable<HttpPostedFileBase> uploadImage in photo)
                    {
                        strMultiFiles += await entityTestObj.fileUpload(uploadImage, path) + ",";
                    }
                    strMultiFiles = strMultiFiles.Substring(0, strMultiFiles.Length - 1);
                }
        }

實體層:
FileUpload 負責把檔案儲存到指定的路徑中,並把檔名回傳
  public async Task<string> fileUpload(IEnumerable<HttpPostedFileBase> addDownloadFile, string path)
        {
            try
            {
                var fileName = "";
                var httpPath = HttpContext.Current.Server.MapPath("~/" + path);
                Directory.CreateDirectory(httpPath); //路徑不存在自已建立
                if (addDownloadFile.Count() > 0)
                {
                    foreach (HttpPostedFileBase file in addDownloadFile)
                    {
                        fileName += Path.GetFileName(file.FileName) + ",";
                        var pathFileName = Path.Combine(httpPath, file.FileName);
                        file.SaveAs(pathFileName);
                    }
                    fileName = fileName.Substring(0, fileName.Length - 1);                  
                }
                return fileName;
            }
            catch (Exception ex)
            {
                writeObj.writeToFile("ManageFileVersion_" + DateTime.Now.ToString("yyyyMMdd"), HttpContext.Current.Server.MapPath("~"), "fileUpload error: " + ex.Message);
                throw new Exception(ex.Message);
            }
}

示範: photo陣列集中共有3個檔案上傳

四、Angular如何split(“,”)

有點偏離主題,因為延續多檔上傳的功能,我將多檔案名稱儲存在同一欄位
中,並以『,』隔離,所以如果要在前端呈現多個圖片時,要以逗號隔離,但是Angular 有支援split的功能,所以這是一個好用的工具喔。

<font color="blue">圖片</font>:<br />
<a href="~/bugFile/{{::bug.bugDocumentFile.split(',')[0] }}" arget="_blank">{{::bug.bugDocumentFile.split(',')[0] }}</a>
<a href="~/bugFile/{{::bug.bugDocumentFile.split(',')[1] }}" arget="_blank">{{::bug.bugDocumentFile.split(',')[1] }}</a>
<a href="~/bugFile/{{::bug.bugDocumentFile.split(',')[2] }}" arget="_blank">{{::bug.bugDocumentFile.split(',')[2] }}</a>
<a href="~/bugFile/{{::bug.bugDocumentFile.split(',')[3] }}" arget="_blank">{{::bug.bugDocumentFile.split(',')[3] }}</a>
<a href="~/bugFile/{{::bug.bugDocumentFile.split(',')[4] }}" arget="_blank">{{::bug.bugDocumentFile.split(',')[4] }}</a>     

如果以上有更好的建議的話,歡迎多多留言,希望在這共同的平台上面,大
家可以共同教學相長。

五、文獻參考
1.    https://docs.angularjs.org/api/ng/service/$http


2015年8月28日 星期五

介紹Angular的Directive

 不少人已經知道Directive的好處,可以在抽出共同的元素,但這是屬於靜態的頁面,另一種情況是希望控制事件放在Directive ,例如有一個下拉式選單,可以自動帶出所有診所的名稱,我們只要呼叫自訂的元素,就可以使用這個下拉式選單,我們就能重複使用元件而不是畫面。

一、建立網頁的範本

檔名: showClinicMenu.js

function showClinicMenu(app) {
var templateForm = '<input type="hidden" id={{name}}>';
templateForm += "<input class='form-control' id={{textname}}  ng-model='clinicName' type='text' ng-change='showClinicMenu(clinicName)'>";
    templateForm += "<div class='list-group'>";
    templateForm += "<a href='#' class='list-group-item'  ng-click='setClinicID(clinic.clinicIndex,clinic.clinicName)' ng-repeat='clinic in listClinics'>";
    templateForm += "{{clinic.clinicName}}";
    templateForm += "</a>";
    templateForm += " </div>";
}

    為了將某些元素抽出為共同的元素時,某些欄位名稱要由外部傳進來,所以要呼叫Angular的語法{{}},然後再由Angularcontroller決定變數的值,如果有相關的事件觸發,也能呼叫ng-click呼叫Angularcontroller的事件。

二、建立directive元素

function showClinicMenu(app) {
var templateForm = “<input t>……..”  //網頁的範本… 上面已說明過… 暫時跳過
app.directive("showclinicmenu", function () {
        return {
                restrict: 'E',               
                template: templateForm,
                controller: function ($scope, $attrs, $http) {
                    $scope.name = $attrs.name;  //對應外部的屬性name
                    $scope.textname = $attrs.textname; //對應外部的屬性textname
                    $scope.showClinicMenu = function myfunction(clinicName) {
                        $http.get('../api/ApiTest/listClinics/' + clinicName).then(function (value) {
                            $("#" + $attrs.name).empty();
                            $scope.listClinics = value.data;
                        });
                    }
                    $scope.setClinicID = function (clinicIndex, clinicName) {
                        $scope.listClinics = null; //清空資料
                        $scope.clinicName = clinicName;
                        $("#" + $attrs.name).val(clinicIndex);
                    }
                },
        }
});
}; //end showClinicMenu

    directive中需要用到Controller,要把屬性放在return {}範圍中,在controller中注入需要的元件$scope, $attrs, $http所以在範本建立的變數,就可以在controller的範圍中做變數宣告以及事件宣告,但是如果想外部傳入參數到controller 怎麼辦請大家耐心往下看吧… 

三、呼叫directive元素

<div class="form-group">
     <label>診所名稱</label>
     <showclinicmenu name="editClinicIndex" textname="editClinicName"></showclinicmenu>
</div>
<script src="~/Scripts/directive/showClinicMenu.js"></script>  //引入使用 directive 函式
<script>
var app = angular.module(modelName, []);;
showClinicMenu(app);  //呼叫相關的函式
</script>

不知道大家對 $attrs是否陌生? 這也是最近作者才發現到另一個好用的功能,當我們在用directive時是否需要傳遞參數進來,但又不知道怎麼做? $attrs 就是連結directiveView之間的連結,以上的範例是directive自已建立的Element,但多了兩個屬性(name, textname),原來我們在controller 看見的 $attr ,就可以把值傳入到directivecontroller

app.directive("showclinicmenu", function () {
        return {
                restrict: 'E',              
                controller: function ($scope, $attrs, $http) {
                    $scope.name = $attrs.name;  //對應外部的屬性name
                    $scope.textname = $attrs.textname; //對應外部的屬性textname                  
      }
});

    看完以上的範例後… 各位有更進一步的瞭解Angular 了嗎?  雖然Angular 提出不少方便的小功能,但還是要親身感受才能瞭解它的用意… 希望大家還喜歡我這篇文章,謝謝大家。

Angular另外補充:

l   如何將ng-model設成動態產生的變數名稱,以示簡易示範教學

步驟一: 建立空陣列,如果有預設值可以呼叫foreach迴圈,逐一填入對應值
$scope.dynamic = {}; //用來放置每個no-model 動態值   
angular.forEach(listAllBugs, function (bug, key) {
$scope.statusOptions = ["未完成", "已完成", "會議討論", "退回"];
                    var statusIndex =$scope.statusOptions (bug.status);
                    $scope.dynamic[bug.ikey] = options[statusIndex];
                    $scope.dynamic['estimate_' + rowIndex] = bug.estimatedDate;  //依序填入預設值
                    rowIndex++;
  })

步驟二:ng-model的命名方式:空陣列名稱[‘字串_+$index]
<tr ng-repeat-start="bug in listAllBugs|filter:searchReleaseText" class="active">
<input type="date" class="datepicker form-control"
ng-model="dynamic['estimate_'+ $index]"
id="estimate_{{::bug.ikey}}" value="{{dynamic['estimate_'+ $index]}}" />
 </tr>
以陣列方式將『字串和索引值』就能變成ng-model獨一無二的變數名稱,所以後續取值時,就要以迴圈方式,逐一取出變數值…