Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 記錄軟體開發的點點滴滴 著重於微軟技術、網頁開發、DevOps、C#, Asp .net Mvc、Azure、AI、Chatbot、Docker、Data Science

[iThome 第七屆鐵人賽 11] 傳遞資訊到前端的通用型服務

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

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

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

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

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

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

使用TempData的難處

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

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

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

AlertService功能概念

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

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

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

image

將會實作的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的位置。同時提供了AddAlertGetAlerts這兩個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)
</body>
.....

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

如何使用


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

using Core.Alerts; // 需要加入Extension方法的namespace

.....

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Create backgroundKnowledge)
{
   ...
	// 新增成功,導回首頁
    return RedirectToAction("Index").WithSuccess("新增成功");
   
}

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


結語


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


如果文章對您有幫助,就請我喝杯飲料吧
街口支付QR Code
街口支付QR Code
台灣 Pay QR Code
台灣 Pay QR Code
Line Pay 一卡通 QR Code
Line Pay 一卡通 QR Code
街口支付QR Code
支付寶QR Code
街口支付QR Code
微信支付QR Code
comments powered by Disqus