تنظیمات JSON در ASP.NET Web API
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: چهار دقیقه

ASP.NET Web API در سمت سرور، برای مدیریت ApiControllerها و در سمت کلاینت‌های دات نتی آن، برای مدیریت HttpClient، به صورت پیش فرض از JSON.NET استفاده می‌کند. در ادامه نگاهی خواهیم داشت به تنظیمات JSON در سرور و کلاینت‌های ASP.NET Web API.


آماده سازی یک مثال Self host

برای اینکه خروجی‌های JSON را بهتر و بدون نیاز به ابزار خاصی مشاهده کنیم، می‌توان یک پروژه‌ی کنسول جدید را آغاز کرده و سپس آن‌را تبدیل به Host مخصوص Web API کرد. برای اینکار تنها کافی است در کنسول پاور شل نیوگت دستور ذیل را صادر کنید:
 PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
سپس کنترلر Web API ما از کدهای ذیل تشکیل خواهد شد که در آن در متد Post، قصد داریم اصل محتوای دریافتی از کاربر را نمایش دهیم. توسط متد GetAll آن، خروجی نهایی JSON آن در سمت کاربر بررسی خواهد شد.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;

namespace WebApiSelfHostTests
{
    public class UsersController : ApiController
    {
        public IEnumerable<User> GetAllUsers()
        {
            return new[]
            {
                new User{ Id = 1, Name = "User 1", Type = UserType.Admin },
                new User{ Id = 2, Name = "User 2", Type = UserType.User }
            };
        }

        public async Task<HttpResponseMessage> Post(HttpRequestMessage request)
        {
            var jsonContent = await request.Content.ReadAsStringAsync();
            Console.WriteLine("JsonContent (Server Side): {0}", jsonContent);
            return new HttpResponseMessage(HttpStatusCode.Created);
        }
    }
}
که در آن شیء کاربر چنین ساختاری را دارد:
namespace WebApiSelfHostTests
{
    public enum UserType
    {
        User,
        Admin,
        Writer
    }

    public class User
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public UserType Type { set; get; }
    }
}
برای اعمال تنظیمات self host ابتدا نیاز است یک کلاس Startup مخصوص Owin را تهیه کرد:
using System.Web.Http;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Owin;

namespace WebApiSelfHostTests
{
    /// <summary>
    /// PM> Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
    /// </summary>
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            var config = new HttpConfiguration();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
                );

            appBuilder.UseWebApi(config);
        }
    }
}
که سپس با فراخوانی چند سطر ذیل، سبب راه اندازی سرور Web API، بدون نیاز به IIS خواهد شد:
 var server = WebApp.Start<Startup>(url: BaseAddress);


Console.WriteLine("Press Enter to quit.");
Console.ReadLine();
server.Dispose();
در ادامه اگر در سمت کلاینت، دستورات ذیل را برای دریافت لیست کاربران صادر کنیم:
 using (var client = new HttpClient())
{
   var response = client.GetAsync(BaseAddress + "api/users").Result;
   Console.WriteLine("Response: {0}", response);
   Console.WriteLine("JsonContent (Client Side): {0}", response.Content.ReadAsStringAsync().Result);
}
به این خروجی خواهیم رسید:
 JsonContent (Client Side): [{"Id":1,"Name":"User 1","Type":1},{"Id":2,"Name":"User 2","Type":0}]
همانطور که ملاحظه می‌کنید، مقدار Type مساوی صفر است. در اینجا چون Type را به صورت enum تعریف کرده‌ایم، به صورت پیش فرض مقدار عددی عضو انتخابی در JSON نهایی درج می‌گردد.


تنظیمات JSON سمت سرور Web API

برای تغییر این خروجی، در سمت سرور تنها کافی است به کلاس Startup مراجعه و HttpConfiguration را به صورت ذیل تنظیم کنیم:
    public class Startup
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            var config = new HttpConfiguration();
            config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings
            {
                Converters = { new StringEnumConverter() }
            };
در اینجا با انتخاب StringEnumConverter، سبب خواهیم شد تا کلیه مقادیر enum، دقیقا مساوی همان مقدار اصلی رشته‌ای آن‌ها در JSON نهایی درج شوند.
اینبار اگر برنامه را اجرا کنیم، چنین خروجی حاصل می‌گردد و در آن دیگر Type مساوی صفر نیست:
 JsonContent (Client Side): [{"Id":1,"Name":"User 1","Type":"Admin"},{"Id":2,"Name":"User 2","Type":"User"}]


تنظیمات JSON سمت کلاینت Web API

اکنون در سمت کلاینت قصد داریم اطلاعات یک کاربر را با فرمت JSON به سمت سرور ارسال کنیم. روش متداول آن توسط کتابخانه‌ی HttpClient، استفاده از متد PostAsJsonAsync است:
var user = new User
{
   Id = 1,
   Name = "User 1",
   Type = UserType.Writer
};

var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

var response = client.PostAsJsonAsync(BaseAddress + "api/users", user).Result;
Console.WriteLine("Response: {0}", response);
با این خروجی سمت سرور
 JsonContent (Server Side): {"Id":1,"Name":"User 1","Type":2}
در اینجا نیز Type به صورت عددی ارسال شده‌است. برای تغییر آن نیاز است به متدی با سطح پایین‌تر از PostAsJsonAsync مراجعه کنیم تا در آن بتوان JsonMediaTypeFormatter را مقدار دهی کرد:
            var jsonMediaTypeFormatter = new JsonMediaTypeFormatter
            {
                SerializerSettings = new JsonSerializerSettings
                {
                    Converters = { new StringEnumConverter() }
                }
            };
            var response = client.PostAsync(BaseAddress + "api/users", user, jsonMediaTypeFormatter).Result;
            Console.WriteLine("Response: {0}", response);
خاصیت SerializerSettings کلاس JsonMediaTypeFormatter برای اعمال تنظیمات JSON.NET پیش بینی شده‌است.
اینبار مقدار دریافتی در سمت سرور به صورت ذیل است و در آن، Type دیگر عددی نیست:
 JsonContent (Server Side): {"Id":1,"Name":"User 1","Type":"Writer"}

مثال کامل این بحث را از اینجا می‌توانید دریافت کنید:
UsersController.zip
  • #
    ‫۱۰ سال قبل، دوشنبه ۲۴ شهریور ۱۳۹۳، ساعت ۱۵:۱۵
    یک نکته‌ی تکمیلی
     اگر نمی‌خواهید یک وابستگی جدید را (Microsoft.AspNet.WebApi.Client) به پروژه اضافه کنید، کدهای ذیل همان کار HttpClient را برای ارسال اطلاعات، انجام می‌دهند. کلاس WebRequest آن در فضای نام System.Net موجود است :
    using System;
    using System.IO;
    using System.Net;
    using Newtonsoft.Json;
    
    namespace WebToolkit
    {
        public class SimpleHttp
        {
            public HttpStatusCode PostAsJson(string url, object data, JsonSerializerSettings settings)
            {
                if (string.IsNullOrWhiteSpace(url))
                    throw new ArgumentNullException("url");
    
                return PostAsJson(new Uri(url), data, settings);
            }
    
            public HttpStatusCode PostAsJson(Uri url, object data, JsonSerializerSettings settings)
            {
                if (url == null)
                    throw new ArgumentNullException("url");
    
                var postRequest = (HttpWebRequest)WebRequest.Create(url);
                postRequest.Method = "POST";
                postRequest.UserAgent = "SimpleHttp/1.0";
                postRequest.ContentType = "application/json; charset=utf-8";
    
                using (var stream = new StreamWriter(postRequest.GetRequestStream()))
                {
                    var serializer = JsonSerializer.Create(settings);
                    using (var writer = new JsonTextWriter(stream))
                    {
                        serializer.Serialize(writer, data);
                        writer.Flush();
                    }
                }
    
                using (var response = (HttpWebResponse)postRequest.GetResponse())
                {
                    return response.StatusCode;
                }
            }
        }
    }
  • #
    ‫۱۰ سال قبل، چهارشنبه ۲۶ شهریور ۱۳۹۳، ساعت ۲۲:۳۱
    سلام و وقت بخیر
    من وقتی میخوام اطلاعات یک فایل جیسون رو به آبجکت تبدیل کنم، با این خطا مواجه میشم:
    Additional text encountered after finished reading JSON content: ,. Path '', line 1, position 6982. 
    بعد از جستجو متوجه شدم که خطا به دلیل وجود کرکترهای کنترلی هست، پس فایل مذکور رو با روشهای زیر (هر کدام رو جداگانه تست کردم) تمیز کردم:
            public string RemoveControlCharacters1(string input)
            {
                string output = Regex.Replace(input, @"[\u0000-\u001F]", string.Empty);
                return output;
            }
    
            public string RemoveControlCharacters2(string input)
            {
                string output = new string(input.Where(c => !char.IsControl(c)).ToArray());
                return output;
            }
    
            public string RemoveControlCharacters3(string inString)
            {
                if (inString == null) return null;
                StringBuilder newString = new StringBuilder();
                char ch;
                for (int i = 0; i < inString.Length; i++)
                {
                    ch = inString[i];
                    if (!char.IsControl(ch))
                    {
                        newString.Append(ch);
                    }
                }
                return newString.ToString();
            }
    اما کماکان همان خطا را در زمان اجر میبینم.
    آیا مشکل چیز دیگری است؟
    پرسش: چطور میشود به جیسون دات نت گفت که اصلا کرکترهای کنترلی و یا چیزهایی را که ممکن است خطا ایجاد کنند، ندید بگیرد؟
    • #
      ‫۱۰ سال قبل، چهارشنبه ۲۶ شهریور ۱۳۹۳، ساعت ۲۲:۴۲
      با تنظیم eventArgs.ErrorContext.Handled = true از خطاهای موجود صرفنظر می‌شود:
      new JsonSerializerSettings
      {
        Error = (sender, eventArgs) =>
        {
            Debug.WriteLine(eventArgs.ErrorContext.Error.Message);
            //if an error happens we can mark it as handled, and it will continue
            eventArgs.ErrorContext.Handled = true;
         }
      }
      • #
        ‫۱۰ سال قبل، چهارشنبه ۲۶ شهریور ۱۳۹۳، ساعت ۲۲:۵۳
        سپاسگزارم از پاسخ سریع شما.
        ببخشید کد من به این شکل هست و نمیدونم کجا باید تغییرات رو اعمال کنم:
        var  items = JsonConvert.DeserializeObject<List<Classified>>(json);
  • #
    ‫۱۰ سال قبل، چهارشنبه ۲۶ شهریور ۱۳۹۳، ساعت ۲۳:۱۶
    سلام؛ من از کد زیر استفاده کردم
                    myUserApi.Id = UserId;
                    return new JsonNetResult
                    {
                        Data = myUserApi,
                        ContentType = "application/json"
                    };
    اما این خروجی تولید میشه
    {
      "$id": "1",
      "Settings": {
        "$id": "2",
        "ReferenceLoopHandling": 0,
        "MissingMemberHandling": 0,
        "ObjectCreationHandling": 0,
        "NullValueHandling": 0,
        "DefaultValueHandling": 0,
        "Converters": [],
        "PreserveReferencesHandling": 0,
        "TypeNameHandling": 0,
        "MetadataPropertyHandling": 0,
        "TypeNameAssemblyFormat": 0,
        "ConstructorHandling": 0,
        "ContractResolver": null,
        "ReferenceResolver": null,
        "TraceWriter": null,
        "Binder": null,
        "Error": null,
        "Context": {
          "$id": "3",
          "m_additionalContext": null,
          "m_state": 0
        },
        "DateFormatString": "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK",
        "MaxDepth": null,
        "Formatting": 0,
        "DateFormatHandling": 0,
        "DateTimeZoneHandling": 3,
        "DateParseHandling": 1,
        "FloatFormatHandling": 0,
        "FloatParseHandling": 0,
        "StringEscapeHandling": 0,
        "Culture": "(Default)",
        "CheckAdditionalContent": false
      },
      "ContentEncoding": null,
      "ContentType": "application/json",
      "Data": "{\"Id\":2}",
      "JsonRequestBehavior": 1,
      "MaxJsonLength": null,
      "RecursionLimit": null
    }