【SQL Server | Asp.net MVC】使用WebAPI存取FileTable

【SQL Server | Asp.net MVC】使用WebAPI存取FileTable
首先,先說明一下本篇案例吧!這邊規劃了四個FileTable出來,分別存放Document、Photo、Video以及其他類型的檔案;在檔案上傳的時候就會將使用者上傳的檔案做分類管理避免所有資料都存放在同一個FileTable中,日後再尋找檔案造成不便的困擾而且更好管理這些檔案!
在小弟的案例中,會有四個不同的存放路徑,在這樣的情形要怎麼去處理檔案上傳的路徑呢?或許有人會想寫四個API然後針對四個不同的路徑去做處理,但是這樣子實在太複雜了,後續要維護部好處理,所以小弟這邊就用了一個方法,利用一個stored procedure(下方程式碼),只要傳入FileTable名稱就可以取得相對應的檔案存放路徑,這樣就可以不用寫四個相同功能的API,節省了不少時間!

前一篇說明了要如何開啟與設定FileTable,本篇在此說明WebAPI的檔案存取

首先,先說明一下本篇案例吧!這邊規劃了四個FileTable出來,分別存放Document、Photo、Video以及其他類型的檔案;在檔案上傳的時候就會將使用者上傳的檔案做分類管理避免所有資料都存放在同一個FileTable中,日後再尋找檔案造成不便的困擾而且更好管理這些檔案!

在小弟的案例中,會有四個不同的存放路徑,在這樣的情形要怎麼去處理檔案上傳的路徑呢?或許有人會想寫四個API然後針對四個不同的路徑去做處理,但是這樣子實在太複雜了,後續要維護部好處理,所以小弟這邊就用了一個方法,利用一個stored procedure(下方程式碼),只要傳入FileTable名稱就可以取得相對應的檔案存放路徑,這樣就可以不用寫四個相同功能的API,節省了不少時間!


CREATE proc [dbo].[sp_SYS_GetUNCPath]
	@FileType varchar(4000)
AS
	SELECT FileTableRootPath (@FileType,1) AS UNCPath
GO

傳入的FileType則是FileTable名稱,而FileTableRootPath的預存程序則是在SQL Server 2012提供的,這個可以傳回特定的FileTable目錄或是目前資料庫的根目錄的UNC路徑,下圖是MSDN的介紹

image

接下來就是檔案上傳的部份了,因為小弟使用的.net Framework是4.0版本的,所以這邊不是使用非同步的方式上傳檔案

在寫API的時候有一個特性,Action開頭以GET、POST、PUT及DELETE的時候,會自動對應到相同的method稱為REST,Bruce有一篇介紹這邊附上連結,讓大家可以多學一個知識!

首先我們需要先寫好一個FileProcessServices的clss檔案,這個類中會有我們整個所需要的method

 


ModelsEntity db= new ModelsEntity();
 HttpResponseMessage ResponseValue = new HttpResponseMessage();
 /// <summary>
/// 擷取路徑
/// </summary>
/// <param name="FileType">FileTabe Name</param>
 /// <returns>File Table UNC Path</returns>
public String UNCPath(String FileType)
{
    return db.sp_SYS_GetUNCPath(FileType).FirstOrDefault();
}

接下來是API的部分


/// <summary>
/// 檔案上傳使用
/// </summary>
/// <param name="type">FileTable的資料表名稱</param>
/// <param name="id">檔案的放黨名</param>
/// <returns></returns>
public Task<HttpResponseMessage> Post(String type, String id)
{
    String FileName = "";//記錄回傳的檔案名稱
    FileProcessServices Services = new FileProcessServices();//檔案處裡的程序
    String UNCPath = Services.UNCPath(type);//取的FileTable UNC路徑
    MultipartFormDataStreamProvider Provider = new MultipartFormDataStreamProvider(UNCPath); 
    var Task = Request.Content.ReadAsMultipartAsync(Provider).ContinueWith<HttpResponseMessage>(t =>
    {
        if (t.IsFaulted || t.IsCanceled)
        {
            Request.CreateErrorResponse(HttpStatusCode.InternalServerError, t.Exception);
        }
        foreach (MultipartFileData file in Provider.FileData)
        {
            try
            {
                //使用這個上傳檔案的方法,不會有副檔名,所以我們在router中傳入一個附檔名的參數
                //產生新的檔案名稱
                String NewFile = System.IO.Path.ChangeExtension(file.LocalFileName, id);
                //更改檔案名稱
                File.Move(file.LocalFileName, NewFile);
                //只取得檔案名稱回傳
                FileName = System.IO.Path.GetFileName(NewFile);
            }
            catch (Exception ex)
            {
                //如果有出錯的狀況,就把上船的檔案刪除
                FileInfo DelFile = new FileInfo(UNCPath + "\\" + System.IO.Path.GetFileNameWithoutExtension(FileName));
                DelFile.Delete();
                return Request.CreateResponse(HttpStatusCode.BadRequest, "The file upload failure");
            }
        }
        return Request.CreateResponse(HttpStatusCode.OK, FileName);
    });

    return Task;
}

以上就可以把檔案上傳到FileTable中了!這時候就可以動手寫取得資料的部分了,在開始寫取得資料的API前,要先寫FileProcessServices中所需的Function

SL_MIMEType資料


/// <summary>
/// 產生放置檔案的容器
/// </summary>
/// <param name="FilePath">檔案名稱</param>
/// <returns></returns>
private StreamContent StreamContenter(String FilePath)
{
    return new StreamContent(this.FileStreamer(FilePath));
}
/// <summary>
/// 取得檔案內容
/// </summary>
/// <param name="FilePath">檔案名稱</param>
/// <returns></returns>
private FileStream FileStreamer(String FilePath)
{
    return new FileStream((FilePath), FileMode.Open);
}
/// <summary>
/// 取得MIME型態
/// </summary>
/// <param name="FileName">檔案名稱</param>
/// <returns></returns>
private System.Net.Http.Headers.MediaTypeHeaderValue MIMEType(String FileName)
{
    String Extension = Regex.Match(FileName, "[a-z]{0,255}$", RegexOptions.IgnoreCase).Value;
    return new System.Net.Http.Headers.MediaTypeHeaderValue(db.SL_MIMEType.Where(x => x.File_Extension.Equals(Extension)).Select(x => x.MIME).FirstOrDefault());
}
/// <summary>
/// 檔案顯示處理
/// </summary>
/// <param name="FileName">檔案名稱</param>
/// <returns>HttpResponseMessage</returns>
public HttpResponseMessage DisplayFile(String FileType, String FileName)
{
    String UNCPath = this.UNCPath(FileType);
    try
    {
        ResponseValue.Content = this.StreamContenter(UNCPath + @"\" + FileName);
        ResponseValue.Content.Headers.ContentType = this.MIMEType(FileName);
        return ResponseValue;
    }
    catch
    {
        var Path = HttpContext.Current.Server.MapPath("~/Photo");
        ResponseValue.Content = this.StreamContenter(Path + @"\" + "NotFound.png");
        ResponseValue.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/png");
        return ResponseValue;
    }
}

接下來是API的部分,Get的Action有兩個,兩者使用時機不一樣,第一個是使用者沒有上傳任何檔案時所顯示預設的資料

第二個則是讓以上傳的檔案可以正常的輸出,傳送到用戶端時,瀏覽器則會依照MIME的類型作處不同的處理(EX:檔案下載,或是顯示在網頁上)


/// <summary>
/// 取得檔案
/// </summary>
/// <param name="type">FileTable的名稱</param>
/// <returns></returns>
public HttpResponseMessage Get(String type)
{
    FileProcessServices FileServices = new FileProcessServices();
    return FileServices.Default();
}
/// <summary>
/// 取得檔案
/// </summary>
/// <param name="type">FileTable的名稱</param>
/// <param name="id">檔案名稱</param>
/// <returns></returns>
public HttpResponseMessage Get(String type, String id)
{
    FileProcessServices FileServices = new FileProcessServices();
    try
    {
        return FileServices.DisplayFile(type, id);
    }
    catch
    {
        return FileServices.Default();
    }
}

最後,則是檔案刪除

同樣我們先把FileProcessServices中先新增一個Method


/// <summary>
/// 檔案刪除
/// </summary>
/// <param name="FileType">檔案類型</param>
/// <param name="FileName">檔案名稱</param>
/// <returns></returns>
public HttpResponseMessage FileDelete(String FileType, String FileName)
{
    String UNCPath = this.UNCPath(FileType);
    try
    {
        FileInfo Del = new FileInfo(UNCPath + @"\" + FileName);
        Del.Delete();
        ResponseValue.StatusCode = System.Net.HttpStatusCode.OK;
        return ResponseValue;
    }
    catch
    {
        ResponseValue.StatusCode = System.Net.HttpStatusCode.NotFound;
        return ResponseValue;
    }
}

API的部分則讓他呼叫剛剛寫好的method即可


public HttpResponseMessage Delete(String type, String id)
{
    try
    {
        FileProcessServices FileServices = new FileProcessServices();
        return FileServices.FileDelete(type, id);
    }
    catch
    {
        return Request.CreateResponse(HttpStatusCode.NotFound, true);
    }
}

 

最後部屬到正式環境中,一定會有出錯的狀況!這絕對不是程式撰寫的問題,起因是平常使用的時候都是使用Administrator的帳號在存取FileTable上的資料,而部屬到正式環境中則是使用IIS的ApplicationPoolIdentity的內建帳戶執行,所以在正式環境中則一定會出錯,造成無法正常的存取,接下來就做幾項設定,就可以讓我們剛剛的API正常的運作了!

首先我們先到正式環境中的SQL Server中新增一個登入使用者,這個使用者是IIS的內建帳戶這個部分保哥之前有所介紹在此不再贅述,而IIS的使用帳戶則是這樣的顯示方式【IIS AppPool\應用程式集區名稱】,假設我們的站台名稱是WebServer,我們就在登入名稱中打上【IIS AppPool\WebServer】,在這邊只能用【手動輸】,如果您點下尋找的按鈕會找不到的!(小弟卡在這邊卡很久...希望大家不要跟我一樣傻傻的按下搜尋的按鈕)

image

接下來在使用者對應的頁簽中,設定您的資料庫,在資料庫腳色的部分則選擇【db_datawriter】即可

另外在資料庫的設定,則需要給他連結的權限

【資料庫】(右鍵)>【權限】>【尋找】>【IIS的站台名稱】>【點選連結屬性】

image

最後要在資料表需要設定刪除、更新、插入、選取等權限

image

如此一來就可以正常的使用FileTable的功能了!!

 

 

 

 

【參考資料】

http://www.dotblogs.com.tw/terrychuang/archive/2012/06/03/72586.aspx

http://technet.microsoft.com/zh-tw/library/gg492087.aspx

http://blog.kkbruce.net/2012/04/aspnet-web-api-2-rest.html#.UjH_N8bI18E

http://www.dotblogs.com.tw/skychang/archive/2013/07/15/110054.aspx

http://blog.miniasp.com/post/2009/09/09/Introduce-IIS-75-Application-Pool-Identity-and-Virtual-Account.aspx

http://www.books.com.tw/products/0010575816

 


 

大家好我是饅頭,希望大家喜歡我的文章

如果有錯誤的地方請不吝指教 ^_^