Alan Tsai 的學習筆記


學而不思則罔,思而不學則殆,不思不學則“網貸” 為現任微軟最有價值專家 (MVP)、微軟認證講師 (MCT) 、Blogger、Youtuber:記錄軟體開發的點點滴滴 著重於微軟技術、C#、ASP .NET、Azure、DevOps、Docker、AI、Chatbot、Data Science

[iThome 第七屆鐵人賽 29] Javascript和Mvc溝通 - 實作篇

上一篇介紹完了在Mvc的框架裡面,用Json方式傳遞資料的問題之後,接下來將會看這些問題將會如何解決。

解決思路

首先,最大的問題是Json內建的Serialization是沒有辦法讓我們解決這些問題。因此,我們將會使用Json .Net作為Serialization的library。

再來,會建立一個Class繼承JsonResult,來對裡面做一些處理。不過,需要注意到,因為Mvc裡面的JsonResult寫的比較死,因此有些部分還是需要重複原始的JsonResult內容,讓整個使用起來和之前的Api一樣。

最後,會在之前建立的BaseController寫一些method,方便呼叫來建立新的JsonResult的class。

CoreJsonResult

先從客制的JsonResult開始,首先這個客制的JsonResult會記錄需要顯示的錯誤訊息,並且保留和之前JsonResult一樣的api,因此:

public class CoreJsonResult : JsonResult
{
    public IList<string> ErrorMessages { get; set; }

    public CoreJsonResult()
    {
        ErrorMessages = new List<string>();
    }

    public void AddError(string errorMessage)
    {
        ErrorMessages.Add(errorMessage);
    }

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet &&
            string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("GET access is not allowed.  Change the JsonRequestBehavior if you need GET access.");
        }

        var response = context.HttpContext.Response;
        
        response.ContentType = string.IsNullOrEmpty(ContentType) ? "application/json" : ContentType;

        if (ContentEncoding != null)
        {
            response.ContentEncoding = ContentEncoding;
        }

        SerializeData(response);
    }
}

再來SerializeData才是實際要序列化物件的時候要做的事情:

protected virtual void SerializeData(HttpResponseBase response)
{
    if (ErrorMessages.Any())
    {
        var originalData = Data;
        Data = new
        {
            Success = false,
            OriginalData = originalData,
            ErrorMessage = string.Join("\n", ErrorMessages),
            ErrorMessages = ErrorMessages.ToArray()
        };

        response.StatusCode = 400;
        response.TrySkipIisCustomErrors = true;
    }

    var settings = new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver(),

        Converters = new JsonConverter[]
        {
            new StringEnumConverter(), 
        }
    };

    response.Write(JsonConvert.SerializeObject(Data, settings));
}

可以看到,這一邊的序列化使用的就是Json .Net。那用Json .Net序列化的時候有增加一些設定:

  1. 使用了CamelCase作為序列化出來的參數樣式
  2. 遇到Enum的時候,序列化出來的是string的樣子而不是數字的值

最後,為了方便傳遞序列化物件的形態,可以在增加一個泛型的版本:

public class CoreJsonResult<T> : CoreJsonResult
{
    public new T Data
    {
        get { return (T)base.Data; }
        set { base.Data = value; }
    }
}

在BaseController增加呼叫方法

還記得之前有介紹過使用BaseController。在之後使用這個CoreJsonResult,當然不希望手動new這個物件出來,因此可以再Controller增加方法,讓使用CoreJsonResult變的更加的簡單。

public class BaseController : Controller
{
    protected CoreJsonResult JsonValidationError()
    {
        var result = new CoreJsonResult();

        foreach (var validationError in 
    ModelState.Values.SelectMany(v => v.Errors))
        {
            result.AddError(validationError.ErrorMessage);
        }

        return result;
    }

    protected CoreJsonResult JsonError(string errorMessage)
    {
        var result = new CoreJsonResult();

        result.AddError(errorMessage);

        return result;
    }

    protected CoreJsonResult<T> JsonSuccess<T>(T data)
    {
        return new CoreJsonResult<T> { Data = data };
    }
}

基本上有三個方便呼叫的方法:

  1. JsonValidationError - 這個是當ModelState驗證失敗的時候,呼叫的版本
  2. JsonError - 這個是當發生其他類型的錯誤的時候
  3. JsonSuccess - 這個是當要Json返回ViewModel的時候呼叫

呼叫的範例

再來看一下實際使用的時候的範例:

// POST: /Users/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Edit viewModel)
{
    if (!ModelState.IsValid)
    {
        return JsonValidationError();
    }

    if (viewModel.Id == null)
    {
        return JsonError("Id不能是空");
    }

    // 如果需要以Json格式返回viewModel的時候
    return JsonSuccess(viewModel);
}

這邊給了三個方法使用的時候和情況。可以注意到的是如果是手動回傳錯誤訊息(JsonValidationError或者是JsonError),返回的http status是400,因此在前端的Ajax,也是由 error方法執行。假設,今天是系統出錯了,例如丟出了exception,前端接到的還是error的方法,這樣error就是處理和錯誤有關的,不管是驗證錯誤還是不預期的錯誤。

結語

透過自訂的JsonResult,不只是在速度上面比原生的還快,還可以控制很多設定,例如使用camel case而不是用pascal case。

有了自訂的JsonResult,前端需要和後端溝通也變的更加簡單。


如果文章對您有幫助,就請我喝杯飲料吧
街口支付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