2011年10月27日 星期四

ASP.NET MVC + JSON 自定義JsonResult 2


接續「ASP.NET MVC + JSON 自定義JsonResult 1」的內容,

上一篇文章的最後有說,Json.NET所提供的 JsonNetResult類別並不是很適合拿來使用,

而在「使用Entity Framework 將物件轉為JSON時遇到循環參考錯誤 3」裡面,我們也建立了兩個JavaScriptConverter類別,

所以我們就自己來建立一個自己定義的JsonResult,並且使用之前所建立EFJavaScriptSerializer,

這個 EFJavaScriptSerializer 可以分別依據狀況使用 EFSimpleJavaScriptConverter 或 EFJavaScriptConverter…


廢話不多說,直接來看做好的 CustomJsonResult 類別

using System;
using System.Text;
using System.Web;
using System.Web.Mvc;
namespace Test.Helper
{
  public class CustomJsonResult : JsonResult
  {
    public new Encoding ContentEncoding { get; set; }
    public int? MaxDepth { get; set; }
    public CustomJsonResult()
    {
      JsonRequestBehavior = JsonRequestBehavior.DenyGet;
    }
    public override void ExecuteResult(ControllerContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }
      HttpResponseBase response = context.HttpContext.Response;
      response.ContentType = !String.IsNullOrEmpty(ContentType) 
        ? ContentType 
        : "application/json";
      response.ContentEncoding = this.ContentEncoding == null
        ? System.Text.Encoding.UTF8
        : this.ContentEncoding;
      if (Data != null)
      {
        EFJavaScriptSerializer serializer = !this.MaxDepth.HasValue
          ? new EFJavaScriptSerializer()
          : new EFJavaScriptSerializer(this.MaxDepth.Value);
        
        response.Write(serializer.Serialize(Data));
      }
    }
  }
}

這個類別繼承 JsonResult,這個 CustomJsonResult 比原本的JsonResult 還多了一個 MaxDeprth 屬性,

這是可以在做序列化時,使用 EFJavaScriptConverter 去轉換資料到多少的深度,

預設的Content-Type為「application/json

預設的Encoding為「System.Text.Encoding.UTF8

最後建立的EFJavaScriptSerializer則會根據MaxDepth的數值來決定是要用哪一個 JavaScriptConverter。

 

EFJavaScriptSerializer類別

當沒有指定MaxDepth數值時,會使用EFSimpleJavaScriptConverter來做序列化,只會轉換物件本身的欄位,不包含關連資料。

當有指定MaxDepth時,則使用 EFJavaScriptConverter來做序列化,且依據深度來包含轉換多少深度的關連資料。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Script.Serialization;
 
namespace Test.Helper
{
    public class EFJavaScriptSerializer : JavaScriptSerializer
    {
        public EFJavaScriptSerializer()
        {
            RegisterConverters(new List<JavaScriptConverter> { new EFSimpleJavaScriptConverter() });
        }
 
        public EFJavaScriptSerializer(int maxDepth = 1, EFJavaScriptConverter parent = null)
        {
            if (maxDepth < 1)
            {
                RegisterConverters(new List<JavaScriptConverter> { new EFSimpleJavaScriptConverter() });
            }
            else
            {
                RegisterConverters(new List<JavaScriptConverter> { new EFJavaScriptConverter(maxDepth, parent) });
            }
        }
    }
}

 

使用情境一:

序列化一個從EF取出的Product物件,不指定深度,所以這個只會序列化Product物件自己的欄位資料。

public JsonResult Product(int? id)
{
  if (!id.HasValue)
  {
    Dictionary<string, string> jo = new Dictionary<string, string>();
    jo.Add("Msg", "請輸入產品ID編號.");
    return Json(jo);
  }
  else
  {
    ProductService service = new ProductService();
    var product = service.Single(id.Value);
    return new CustomJsonResult
    {
      Data = product,
      JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
  }
}

執行結果:

image

 

使用情境二:

序列化CategoryID為1的Product集合,不指定深度。

public JsonResult Product(int? id)
{
  if (!id.HasValue)
  {
    Dictionary<string, string> jo = new Dictionary<string, string>();
    jo.Add("Msg", "請輸入產品ID編號.");
    return Json(jo);
  }
  else
  {
    ProductService service = new ProductService();
    var products = service.FindBy(id.Value);
    return new CustomJsonResult
    {
      Data = products,
      JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
  }
}

執行結果:

image

image

 

使用情境三:

序列化一個從EF取出的Product物件,指定深度為1,所以會序列化Product物件自己的欄位資料以及關連的物件。

public JsonResult Product(int? id)
{
  if (!id.HasValue)
  {
    Dictionary<string, string> jo = new Dictionary<string, string>();
    jo.Add("Msg", "請輸入產品ID編號.");
    return Json(jo);
  }
  else
  {
    ProductService service = new ProductService();
    var product = service.Single(id.Value);
    return new CustomJsonResult
    {
      Data = product,
      MaxDepth = 1,
      JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
  }
}

執行結果:

image

 

使用情境四:

序列化CategoryID為1的Product集合,指定深度為1。

public JsonResult Product(int? id)
{
  if (!id.HasValue)
  {
    Dictionary<string, string> jo = new Dictionary<string, string>();
    jo.Add("Msg", "請輸入產品ID編號.");
    return Json(jo);
  }
  else
  {
    ProductService service = new ProductService();
    var products = service.FindBy(id.Value);
    return new CustomJsonResult
    {
      Data = products,
      MaxDepth = 1,
      JsonRequestBehavior = JsonRequestBehavior.AllowGet
    };
  }
}

執行結果:

image

image

 

在Controller中使用JsonResult的Action,在序列化物件的時候就可以使用CustomJsonResult類別。

 

如果說,每次使用JsonResult的Action於最後的回傳不想用 new CustomJsonResult{ … } 的方式,

可以另外建立一個BaseController類別,然後在這個BaseController類別中去建立CustomJson()方法

 

BaseController類別:

using System.Web.Mvc;
using Test.Helper;
namespace Test.Web
{
  public class BaseController : Controller
  {
    protected internal JsonResult CustomJson(object data)
    {
      return new CustomJsonResult { Data = data };
    }
    protected internal JsonResult CustomJson(object data, JsonRequestBehavior jsonRequestBehavior = JsonRequestBehavior.DenyGet)
    {
      return new CustomJsonResult { Data = data, JsonRequestBehavior = jsonRequestBehavior };
    }
    protected internal JsonResult CustomJson(object data, int maxDepth = 1)
    {
      return new CustomJsonResult { Data = data, MaxDepth = maxDepth };
    }
    protected internal JsonResult CustomJson(object data, int maxDepth = 1, JsonRequestBehavior jsonRequestBehavior = JsonRequestBehavior.DenyGet)
    {
      return new CustomJsonResult { Data = data, MaxDepth = maxDepth, JsonRequestBehavior = jsonRequestBehavior };
    }
  }
}

而修改後的Action:

沒有指定深度

public JsonResult Product(int? id)
{
  if (!id.HasValue)
  {
    Dictionary<string, string> jo = new Dictionary<string, string>();
    jo.Add("Msg", "請輸入產品ID編號.");
    return Json(jo);
  }
  else
  {
    ProductService service = new ProductService();
    var products = service.FindBy(id.Value);
    //return new CustomJsonResult
    //{
    //    Data = products,
    //    JsonRequestBehavior = JsonRequestBehavior.AllowGet
    //};
    return CustomJson(products, JsonRequestBehavior.AllowGet);
  }
}

指定深度

public JsonResult Product(int? id)
{
  if (!id.HasValue)
  {
    Dictionary<string, string> jo = new Dictionary<string, string>();
    jo.Add("Msg", "請輸入產品ID編號.");
    return Json(jo);
  }
  else
  {
    ProductService service = new ProductService();
    var products = service.FindBy(id.Value);
    //return new CustomJsonResult
    //{
    //    Data = products,
    //    MaxDepth = 1,
    //    JsonRequestBehavior = JsonRequestBehavior.AllowGet
    //};
    return CustomJson(products, 1, JsonRequestBehavior.AllowGet);
  }
}

這樣在return時就可以使用CustomJson(),不必使用 return new CustomJsonResult{ … }。

 

延伸閱讀:

ASP.NET MVC + JSON 自定義JsonResult 1

使用Entity Framework 將物件轉為JSON時遇到循環參考錯誤 3

 

參考連結:

Json.NET - ASP.NET MVC and Json.NET:JsonNetResult

Stackoverflow - Using JSON.net as default JSON serializer in ASP.NET MVC 3 - Is it possible?

Stackoverflow - Change Default JSON Serializer Used In ASP MVC3

 

以上

1 則留言:

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。