iT邦幫忙

DAY 11
1

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

傳遞資訊到前端的通用型服務

  • 分享至 

  • xImage
  •  

到目前為止,已經介紹了很多屬於基礎建設的部份。從核心的DI用Autofac開始,再到那裡都使用的到的Log服務。再來介紹了ViewModel和如何透過框架來讓對應變得更加簡單。最後介紹了透過Repository 和 Unit of work,使得實際的DAL層級能夠被abstraction出來。

在這一篇,將會介紹比較偏向Controller層級的內容,也就是每一個application一定會使用到的:如何提供一種一致性的服務用於從Server傳遞訊息到客戶端?

同步發表於我的部落格:http://alantsai2007.blogspot.tw/2014/10/BuildYourOwnApplicationFrameworkOnMvc-11-alertservice.html

如何從後端傳遞訊息到前端

所謂傳遞訊息到前端的意思是,通常來說每當一個功能執行完成之後,一定會提供一些feedback給user。例如,新增一筆資料成功,系統可能會用一個alert訊息來顯示「新增成功」。

因為這個功能非常通用,因此Mvc裡面有一個特殊的儲存空間叫做TempData。有別於ViewData只能保存一次的request值到View裡面,TempData 底層也是用Session來儲存,但是有別於Session在,當TempData的值被讀過,那一筆資料就會被清掉。

因此,TempData非常適合用來存這一種一次性的feedback訊息,顯示完之後就不需要了。

使用TempData的難處

TempData很適合存一次性資訊,但是實際在開發的時候不是那麼方便。在一個多人團隊開發的情況可能有以下幾個情況:

[*][*]在沒有講好的情況下,每個人使用的TempDataKey不一樣:開發者A可能使用message作為key,但是開發者B可能使用messages。
[*]要設定錯誤訊息的時候不方便:Action裡面設定TempData其實不是很好用。

因此這一次我們會建制一個AlertService來提供一個好用的傳遞訊息的方式。

AlertService功能概念

基本上我們會先建立一個用來封轉要顯示在前端的訊息ViewModel。這個ViewModel只有兩個Property,一個是Message:代表要顯示的訊息,還有一個是 AlertClass:也就是這個訊息要顯示的樣式。

為了能夠讓設定要傳遞的訊息更加簡單,將會建立一個AlertDecoratorActionResult,這個特別的ActionResult只是讓設定訊息更加的方便。同時,為了讓使用這個 ActionResult更加的方便,會在建立對應的4個Extension 方法,方便建立AlertViewModel。

最後,會建立屬於View的部份,並且使用bootstrap的alert container來顯示不同層級的訊息。

將會實作的Class Diagram

實作功能

有了上面的介紹,應該有了基本要建造的功能內容,就來看看如何實作。

AlertViewModel

首先先定義ViewModel的樣子:

/// <summary>
/// 用來代表一筆需要顯示的Alert資訊ViewModel
/// </summary>
public class AlertViewModel
{
    /// <summary>
    /// 儲存這個Alert資訊的Class - 用來區分呈現的顏色
    /// </summary>
    /// <value>
    /// Alert資訊的Class值
    /// </value>
    public string AlertClass { get; set; }
 
    /// <summary>
    /// Alert資訊的內容值
    /// </summary>
    /// <value>
    /// Alert資訊的值
    /// </value>
    public string Message { get; set; }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="AlertViewModel"/> class.
    /// </summary>
    /// <param name="alertClass">Alert資訊的Class值</param>
    /// <param name="message">Alert資訊的內容</param>
    public AlertViewModel(string alertClass, string message)
    {
        AlertClass = alertClass;
        Message = message;
    }
}

AlertDecoratorActionResult和方便使用的4個Extension方法

首先是AlertDecoratorActionResult:

/// <summary>
/// 一個Decorator Pattern的ActionResult,讓增加Alert訊息變的方便
/// </summary>
public class AlertDecoratorActionResult : ActionResult
{
    /// <summary>
    /// 取得或設定實際的ActionResult
    /// </summary>
    /// <value>
    /// 實際的ActionResult值
    /// </value>
    public ActionResult InnerResult { get; set; }
 
    /// <summary>
    /// 取得或設定Alert的Class
    /// </summary>
    /// <value>
    /// Alert的Class
    /// </value>
    public string AlertClass { get; set; }
 
    /// <summary>
    /// 取得或設定Alert的訊息
    /// </summary>
    /// <value>
    /// Alert的訊息
    /// </value>
    public string Message { get; set; }
 
    /// <summary>
    ///    Initializes a new instance of the 
    ///    <see cref="AlertDecoratorActionResult"/> class.
    /// </summary>
    /// <param name="inInnerResult">The in inner result.</param>
    /// <param name="inAlertClass">The in alert class.</param>
    /// <param name="inMessage">The in message.</param>
    public AlertDecoratorActionResult(ActionResult inInnerResult, 
        string inAlertClass, string inMessage)
    {
        this.InnerResult = inInnerResult;
        this.AlertClass = inAlertClass;
        this.Message = inMessage;
    }
 
    /// <summary>
    /// Enables processing of the result of an action 
    ///    method by a custom type that inherits from the
    ///    <see cref="T:System.Web.Mvc.ActionResult" /> class.
    /// </summary>
    /// <param name="context">The context in 
    ///   which the result is executed. 
    ///   The context information includes the controller, 
    ///   HTTP content, request context, and route data.</param>
    public override void ExecuteResult(ControllerContext context)
    {
        context.Controller.TempData.AddAlert(this.AlertClass, this.Message);
        this.InnerResult.ExecuteResult(context);
    }
}

AlertDecoratorActionResult基本上是實作了Decorator Pattern,基本上就是把實際要顯示的ActionResult包了一層,然後注入要顯示的訊息到TempData。

在上面的TempData.AddAlert()方法是客制的Extension方法,稍後會看到。

實際使用的時候不是很便利,因此會使用4個Extension方法來擴充,加上兩個方便存和取TempData裡面的Alert:

/// <summary>
/// Alert相關的Helper方法,方便呼叫使用
/// </summary>
public static class AlertExtension
{
    /// <summary>
    /// Alert存到TempData的Key值
    /// </summary>
    private static readonly string Alerts = "_Alerts";
 
    /// <summary>
    /// 取得目前所擁有的Alert清單。
    /// </summary>
    /// <param name="tempData">TempData</param>
    /// <returns>目前所還沒有顯示過的Alert清單</returns>
    public static List<AlertViewModel> GetAlerts(this TempDataDictionary tempData)
    {
        if (!tempData.ContainsKey(Alerts))
        {
            tempData[Alerts] = new List<AlertViewModel>();
        }
 
        return (List<AlertViewModel>)tempData[Alerts];
    }
 
    /// <summary>
    /// 增加一筆要顯示的Alert
    /// </summary>
    /// <param name="tempData">TempData</param>
    /// <param name="messageClass">這筆Alert的Class值</param>
    /// <param name="message">這筆Alert的訊息</param>
    public static void AddAlert(this TempDataDictionary tempData,
         string messageClass, string message)
    {
        var alerts = tempData.GetAlerts();
        alerts.Add(new AlertViewModel(messageClass, message));
    }
 
    /// <summary>
    /// 返回的ActionResult加上訊息屬於success Class的Helper
    /// </summary>
    /// <param name="result">ActionResult</param>
    /// <param name="message">要顯示的訊息</param>
    /// <returns>有增加Alert訊息的ActionResult</returns>
    public static ActionResult WithSuccess(this ActionResult result, 
     string message)
    {
        return new AlertDecoratorActionResult(result, 
      "alert-success", message);
    }
 
    /// <summary>
    /// 返回的ActionResult加上訊息屬於info Class的Helper
    /// </summary>
    /// <param name="result">ActionResult</param>
    /// <param name="message">要顯示的訊息</param>
    /// <returns>有增加Alert訊息的ActionResult</returns>
    public static ActionResult WithInfo(this ActionResult result, 
       string message)
    {
        return new AlertDecoratorActionResult(result,
        "alert-info", message);
    }
 
    /// <summary>
    /// 返回的ActionResult加上訊息屬於warning Class的Helper
    /// </summary>
    /// <param name="result">ActionResult</param>
    /// <param name="message">要顯示的訊息</param>
    /// <returns>有增加Alert訊息的ActionResult</returns>
    public static ActionResult WithWarning(this ActionResult result,
         string message)
    {
        return new AlertDecoratorActionResult(result, 
     "alert-warning", message);
    }
 
    /// <summary>
    /// 返回的ActionResult加上訊息屬於error Class的Helper
    /// </summary>
    /// <param name="result">ActionResult</param>
    /// <param name="message">要顯示的訊息</param>
    /// <returns>有增加Alert訊息的ActionResult</returns>
    public static ActionResult WithError(this ActionResult result,
       string message)
    {
        return new AlertDecoratorActionResult(result, 
     "alert-danger", message);
    }
}

上面可以看到我們有定義一個key代表AlertViewModel在TempData的位置。同時提供了AddAlert和GetAlerts這兩個TempData的Extension,方便取得實際的Alert。最後Alert訊息是List的形態,讓我們可以擁有多筆Alert訊息。

View的部份

這邊會定義一個PartialView叫做_Alert.cshtml,並且在_Layout裡面render出來。

@using Core.Alerts
 
@if (TempData.GetAlerts().Any())
{
    <script type="text/javascript">
        $(function() {
          alert('@String.Join("\\n", 
             TempData.GetAlerts().Select(x => x.Message.ToString()))');
       });
    </script>
}

在_Layout.cshtml快到body的地方:

....
 
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @Html.Partial("_Alert")
    @RenderSection("scripts", required: false)

.....

這邊我沒有實作配合AlertViewModel.AlertClass的值,搭配bootstrap,顯示有顏色的錯誤訊息,這個留個讀者去發揮。

如何使用

最後我們看一下會如何使用。

using Core.Alerts; // 需要加入Extension方法的namespace
 
.....
 
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Create backgroundKnowledge)
{
   ...
   // 新增成功,導回首頁
    return RedirectToAction("Index").WithSuccess("新增成功");
    
}

這個時候,只要新增成功,會導回首頁,並且顯示「新增成功」

結語

希望透過這一篇,讓未來在需要傳遞訊息到前端的時候變的更加方便。


上一篇
加上 Unit of Work,抽離Entity Framework的依賴就完美了
下一篇
BaseController的重要性
系列文
以Asp .Net MVC 5 為基礎,建立自己的程式開發框架30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
石頭
iT邦高手 1 級 ‧ 2017-08-03 13:21:43

這個裝飾者模式 用的真巧妙
學到了 謝謝^^

我要留言

立即登入留言