iT邦幫忙

DAY 16
0

以Asp .Net MVC 5 為基礎,建立自己的程式開發框架系列 第 16

處理檔案上傳 2 - 放到Service層

在上一篇,介紹了如何處理檔案上傳的部分。但是,裡面處理上傳檔案的邏輯是放在Mvc的Action裡面。

這個有一些壞處,首先,和邏輯相關的不應該寫在Controller裡面,因為Controller的工作很簡單,就只是決定Model的資料,和顯示的View是哪一個。把處理這種邏輯放在Controller裡面破會了所謂的關注點分離(SoC)的概念。

再來屬於SoC的衍生,因為如果邏輯寫在了Controller裡面,未來如果要替換邏輯或者需要做一些測試的時候,更本不好做。加上,如果別的地方也需要同樣邏輯的時候,更本沒有辦法通用。

因此,這一篇將會介紹如何把邏輯抽到Service層裡面。

同步發表於我的部落格:http://alantsai2007.blogspot.com/2014/10/BuildYourOwnApplicationFrameworkOnMvc-16-FileProcess-InService.html

功能概述

基 本的邏輯在上一篇實作了在Controller裡面,現在要抽到Service裡面,尤其是通用型的Service(也就是 GenericService<T>),就要以能夠動態的角度去思考如何處理這部分的邏輯,因此,定下一些規則。如果進來的 ViewModel符合這些規則,就處理,要不然就不處理。

檔案處理的邏輯如果在仔細思考一下,基本上就是:

  1. 把HttpPostedFileBase檔案儲存 (在有檔案的情況下)
  2. 把儲存檔案的路徑放到要儲存到DB的欄位

如果要把上面的邏輯自動化的話,就變成:

  1. 找到這個ViewModel裡面是不是有HttpPostedFileBase - 並且裡面有檔案
  2. 把檔案儲存
  3. 把路勁給要存到DB的欄位即可 - 要如何知道是哪一個欄位?

關於第三點,就是這個框架處理要設定的規則,有符合規則就處理,要不然就不處理。

規則

HttpPostedFileBase這個欄位的名字是實際存到DB的欄位名字加上File。

舉例來說,如果實際存到DB的欄位名字是:CoverImg,那麼對應的HttpPostedFileBase名字就是CoverImgFile

有了這個規則,就可以把檔案上傳的部分移動到Service裡面。

功能實作

接下來就看看如何實際實作功能。

在Service裡面新增ViewModel的部分

/// <summary>
/// 依照某一個ViewModel的值,產生對應的Entity並且新增到資料庫
/// </summary>
/// <typeparam name="TViewModel">ViewModel的形態</typeparam>
/// <param name="viewModel">ViewModel的Reference</param>
/// <returns>是否儲存成功</returns>
public void CreateViewModelToDatabase<TViewModel>(TViewModel viewModel)
{
ProcessHttpPostFile(viewModel, @"D:\");

var entity = AutoMapper.Mapper.Map<T>(viewModel);

db.Repository<T>().Create(viewModel);

db.SaveChange();
}

上面,highlight起來就是會處理檔案上傳的部分。

這個方法基本上就是透過Reflection來找到HttpPostedFileBase,並且找到對應的檔案。如果有,就會做處理:

/// <summary>
/// 處理ViewModel裡面形態是HttpPostedFileBase的Property。
/// 會把此property的檔案儲存到某個路徑下,並且把儲存的路勁寫在對應的Property裡面
/// </summary>
/// <param name="viewModel">要處理的ViewModel</param>
/// <param name="basePath">檔案要儲存位置的base</param>
private void ProcessHttpPostFile(object viewModel, string basePath)
{
    var properties = viewModel.GetType()
          .GetProperties(BindingFlags.Instance |
         BindingFlags.Public | BindingFlags.FlattenHierarchy);
 
    foreach (var item in properties
        .Where(x => x.PropertyType == typeof(HttpPostedFileBase)))
    {
        var httpost = item.GetValue(viewModel) as HttpPostedFileBase;
 
        if (httpost != null
     && string.IsNullOrEmpty(httpost.FileName) == false)
        {
            // 如果postFile的property名稱後面一定會加File。例如:
          //CoverImgFile對應的string property名稱就是CoverImg。
            // 因此看看是否有property的名稱是postFile的名稱減去4(File是4個字)
            var fileNameProperty = properties
                .Where(x => x.Name == item.Name.Remove(item.Name.Count() - 4))
               .FirstOrDefault();
 
            if (fileNameProperty != null)
            {
                var savePath = Path.Combine(basePath, httpost.FileName);
 
                if (Directory.Exists(Path.GetDirectoryName(savePath)) == false)
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(savePath));
                }
 
                httpost.SaveAs(savePath);
 
                fileNameProperty.SetValue(viewModel, httpost.FileName);
            }
        }
    }
}

Controller的變化

把處理的部分移到Service之後,在Controller裡面就變成原來的樣子:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Create post)
{
    if (ModelState.IsValid)
    {
        service.CreateViewModelToDatabase(post);
        return RedirectToAction("Index");
    }
 
    return View(post);
}

結語

透過Service層,可以把一些通用的邏輯抽進去,避免掉Controller有太多邏輯,造成日後邏輯無法替換和重複使用。

在接下來還會介紹更多Service還能夠包括的功能,使得這些底層的內容還需要每一次都處理。


上一篇
處理檔案上傳
下一篇
資料驗證 - 思路篇
系列文
以Asp .Net MVC 5 為基礎,建立自己的程式開發框架30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言