ASP.NET MVC Урок B. Json

от автора

Цель урока. Научиться работать с форматом json. Инструменты по работе с json. Написание сторонних запросов, авторизация через получение данных от facebook и vkontakte. Ajax в работе с json (авторизацию переписать). API сайта.

Json и Json.net

Json – это текстовый формат данных, основанный на Javascript.
Пример данных в Json:

{             "firstName": "Иван",             "lastName": "Иванов",             "address": {                 "streetAddress": "Московское ш., 101, кв.101",                 "city": "Ленинград",                 "postalCode": 101101             },    "phoneNumbers": [        "812 123-1234",        "916 123-4567"    	]   } 

В своей практике я работал с json с такими приложениями как yandex.maps api, facebook api, vk api, bronni api (это такой туристический портал), и даже при работе с биткоин-кошельком. Для этого используется JSON.net библиотека от http://james.newtonking.com/pages/json-net.aspx.
Изучим ее подробнее:

  • Установим
  • Изучим преобразования из json в объекты и назад
  • Десериализация из сложных форматов
  • Работа с facebook API (пример) — авторизация

Устрановим

PM> Get-Package Json.net  Id                             Version              Description/Release Notes                         --                             -------              -------------------------                         Newtonsoft.Json                4.5.1                Json.NET is a popular high-performance... PM> Install-Package Newtonsoft.Json Successfully installed 'Newtonsoft.Json 4.5.11'. Successfully added 'Newtonsoft.Json 4.5.11' to LessonProject. 

Документация
По этой ссылке находится документация. Мы начнем с простого преобразования объекта в json-формат и обратно. Создадим LessonProject.Console и сделаем его проектом по-умолчанию. Добавим тип User:

public class User     {         public string Id { get; set; }          public string Name { get; set; }          public string FirstName { get; set; }          public string MiddleName { get; set; }          public string LastName { get; set; }          public string UserName { get; set; }          public string Gender { get; set; }          public string Email { get; set; }     } 

Создадим объект и преобразуем в json:

var user = new User()             {                 Id = "404",                 Email = "chernikov@gmail.com",                 UserName = "rollinx",                 Name = "Andrey",                 FirstName = "Andrey",                 MiddleName = "Alexandrovich",                 LastName = "Chernikov",                 Gender = "M"             };              var jsonUser = JsonConvert.SerializeObject(user);             System.Console.Write(jsonUser);              System.Console.ReadLine();  

Результат:

{"Id":"404","Name":"Andrey","FirstName":"Andrey","MiddleName":"Alexandrovich","LastName":"Chernikov","UserName":"rollinx","Gender":"M","Email":"chernikov@gmail.com"} 

Попробуем обратное:

  var jsonUserSource = "{\"Id\":\"405\",\"Name\":\"Andrey\",\"FirstName\":\"Andrey\",\"MiddleName\":\"Alexandrovich\",\"LastName\":\"Chernikov\",\"UserName\":\"rollinx\",\"Gender\":\"M\",\"Email\":\"chernikov@gmail.com\"}";              var user2 = JsonConvert.DeserializeObject<User>(jsonUserSource); 

И получаем результат:

Т.е. работает в обоих направлениях. Но немного усложним. Например, зададим Gender через перечисляемый тип Male и Female, и в json должно передаваться именно Male и Female. А Id – это числовое значение:

public class User     {         public enum GenderEnum         {             Male,             Female         }          public int Id { get; set; }          public string Name { get; set; }          public string FirstName { get; set; }          public string MiddleName { get; set; }          public string LastName { get; set; }          public string UserName { get; set; }          public GenderEnum Gender { get; set; }          public string Email { get; set; }     } 

Пробуем первую часть:

var user = new User()             {                 Id = 404,                 Email = "chernikov@gmail.com",                 UserName = "rollinx",                 Name = "Andrey",                 FirstName = "Andrey",                 MiddleName = "Alexandrovich",                 LastName = "Chernikov",                 Gender = User.GenderEnum.Male             };              var jsonUser = JsonConvert.SerializeObject(user); 

Результат:

{"Id":404,"Name":"Andrey","FirstName":"Andrey","MiddleName":"Alexandrovich","LastName":"Chernikov","UserName":"rollinx","Gender":0,"Email":"chernikov@gmail.com"} 

Добавим:

[JsonConverter(typeof(StringEnumConverter))]         public enum GenderEnum          {             Male,             Female         } 

Результат:

{"Id":404,"Name":"Andrey","FirstName":"Andrey","MiddleName":"Alexandrovich","LastName":"Chernikov","UserName":"rollinx","Gender":"Male","Email":"chernikov@gmail.com"} 

Уже лучше. Обратно проверяем – всё ок. Изучим другие атрибуты, задающие тонкие правила настройки. Например, в json-формате будут имена в венгерской записи типа first_name:

[JsonObject]     public class User     {         [JsonConverter(typeof(StringEnumConverter))]         public enum GenderEnum          {             Male,             Female         }          [JsonProperty("id")]         public int Id { get; set; }          [JsonProperty("name")]         public string Name { get; set; }          [JsonProperty("first_name")]         public string FirstName { get; set; }          [JsonProperty("middle_name")]         public string MiddleName { get; set; }          [JsonProperty("last_name")]         public string LastName { get; set; }          [JsonProperty("user_name")]         public string UserName { get; set; }          [JsonProperty("gender")]         public GenderEnum Gender { get; set; }          [JsonProperty("email")]         public string Email { get; set; }     }  

Результат:

{"id":404,"name":"Andrey","first_name":"Andrey","middle_name":"Alexandrovich","last_name":"Chernikov","user_name":"rollinx","gender":"Male","email":"chernikov@gmail.com"} 

Для описания списка добавим класс Photo:

[JsonObject]     public class Photo     {         [JsonProperty("id")]         public int Id { get; set; }          [JsonProperty("name")]         public string Name { get; set; }     } 

И в User добавим:

[JsonProperty("photo_album")] public List<Photo> PhotoAlbum { get; set; } 

Результат:

{"id":404,"name":"Andrey","first_name":"Andrey","middle_name":"Alexandrovich","last_name":"Chernikov","user_name":"rollinx","gender":"Male","email":"chernikov@gmail.com","photo_album":[{"id":1,"name":"Я с инстаграммом"},{"id":2,"name":"Я на фоне заниженного таза"}]} 

Всё просто и предсказуемо.
Разберем сложный случай, например, когда для Gender нам надо описывать не Male\Female, а M\F. Для этого создаем класс по разбору GenderEnumConverter:

public class GenderEnumConverter : JsonConverter     {         public override object ReadJson(JsonReader reader,              Type objectType,             object existingValue,              JsonSerializer serializer)         {             var value = reader.Value.ToString();             if (string.Compare(value, "M", true) == 0)             {                 return User.GenderEnum.Male;             }             if (string.Compare(value, "F", true) == 0)             {                 return User.GenderEnum.Female;             }             return  User.GenderEnum.Male;         }          public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)         {             var obj = (User.GenderEnum)value;              // Write associative array field name             writer.WriteValue(value.ToString().Substring(0,1));         }          public override bool CanConvert(Type objectType)         {             return false;         }     } 

И устанавливаем этот конвертер для обработки

[JsonConverter(typeof(GenderEnumConverter))]         public enum GenderEnum          {             Male,             Female         } 

Вообще, конвертеры могут быть бесконечно сложными для разбора json-данных. Можно обрабатывать нетипичные записи дат, сложные структуры данных, формировать сериализацию своих классов.

Работа с facebook

Всё это понятно и скучно, так что метнемся работать с facebook API. Развлечений всем! Для начала заведем проект LessonProject.FacebookAPI. Добавим туда Json.NET и свяжем с основным проектом.

На facebook надо завести ApplicationID по адресу:
https://developers.facebook.com/apps

Создаем, получаем:

Нам интересно будет AppID и AppSecret.
Добавляем эти данные в Config/FacebookSetting.cs (уже знаем, как это делается):

 public class FacebookSetting : ConfigurationSection     {         [ConfigurationProperty("AppID", IsRequired = true)]         public string AppID         {             get             {                 return this["AppID"] as string;             }             set             {                 this["AppID"] = value;             }         }          [ConfigurationProperty("AppSecret", IsRequired = true)]         public string AppSecret         {             get             {                 return this["AppSecret"] as string;             }             set             {                 this["AppSecret"] = value;             }              }  } 

Общение с фейсбуком происходит так:

  • Попросим пользователя авторизоваться, так мы узнаем, какие права у нас есть
  • Ответом этого будет токен доступа, по нему мы будем получать информацию
  • Получаем информацию про самого пользователя

Создадим интерфейс, который будет реализовывать наш FacebookSetting (чтобы была обратная совместимость) (LessonProject.FacebookAPI/IFbAppConfig.cs):

public interface IFbAppConfig     {         string AppID { get; }          string AppSecret { get; }     } 

Добавляем в FacebookSetting (/Global/Config/FacebookSetting.cs):

public class FacebookSetting : ConfigurationSection, IFbAppConfig 

Используя наш AppID, мы идем по строке типа:

https://www.facebook.com/dialog/oauth?client_id=136398216534301&redirect_uri=http%3A%2F%2Flocalhost%3A54484%2FFacebook%2FToken&scope=email  

И это выглядит так:

Если мы нажимаем «Перейти к приложению» — то нас переправляют на страницу
http://localhost:54484/Facebook/Token с параметром code, по которому мы можем получить токен (который действует некоторое время). Выполняем такой запрос:

https://graph.facebook.com/oauth/access_token?client_id=136398216534301&redirect_uri=http://localhost:54484/Facebook/Token&client_secret=e6de78fd40596f00e225dce861b34a1a&code=AQAScKUYKGpzwijzT3Y3SHjNOd4Q5nsyrYPdJaPhX-88r-wBOuMrdimL8h82bGv3HAh7TL6oJyZ0gNgiB8BcCeH8G_Zj7h6hlft_BFbOfIJIZJB9nKW6Q4iR3a0VVImxM0QYJas3eVg4qtYNkqUcWbgXDSK2JENcuomUX38haxFUFdKXrVjL1acNZSocESsx6nfx_FyF_QlbwnUO5cwogrLp 

На что получаем ответ:

access_token=AAAB8Da8ZBmR0BAMCOx5293ZArYvFu5oRkmZCrwZAbvpWZB3ZCLBeiooslyYPZBVwHjxSpe3KzJ4VLFPIxwwf0D6TIEiM5ApzU8EMoDpOxE4uAZDZD&expires=5183977 

Нам нужен этот access_token, сохраняем его, и с помощью него мы запрашиваем данные по ссылке:

https://graph.facebook.com/me?access_token=AAAB8Da8ZBmR0BAImiTO9QwuUXbgHPLZBQWmAyZBUkjR2A37aVNs4vaqaFmt6h1ZBvurUpvN95EXddy5d6J1ldZA2jWTxSd3eZBHlYMzKwdxgZDZD 

На что он нам отвечает:

{"id":"708770020","name":"Andrey Chernikov","first_name":"Andrey","last_name":"Chernikov","link":"http:\/\/www.facebook.com\/chernikov1","username":"chernikov1","gender":"male","email":"chernikov\u0040gmail.com","timezone":2,"locale":"ru_RU","verified":true,"updated_time":"2013-03-06T15:01:28+0000"} 

И вот это мы приведем к классу FbUserInfo (LessonProject.FacebookAPI/FbUserInfo.cs):

[JsonObject]     public class FbUserInfo     {         [JsonProperty("id")]         public string Id { get; set; }          [JsonProperty("name")]         public string Name { get; set; }          [JsonProperty("first_name")]         public string FirstName { get; set; }          [JsonProperty("last_name")]         public string LastName { get; set; }          [JsonProperty("link")]         public string Link { get; set; }          [JsonProperty("username")]         public string UserName { get; set; }          [JsonProperty("gender")]         public string Gender { get; set; }          [JsonProperty("email")]         public string Email { get; set; }          [JsonProperty("locale")]         public string Locale { get; set; }          [JsonProperty("timezone")]         public double? Timezone { get; set; }          [JsonProperty("verified")]         public bool? Verified { get; set; }          [JsonProperty("updated_time")]         public DateTime? updatedTime { get; set; }      } 

Всю описанную выше работу заключаем в FbProvider.cs (LessonProject.FacebookAPI/Provider.cs):

public class FbProvider     {         private static string AuthorizeUri = "https://graph.facebook.com/oauth/authorize?client_id={0}&redirect_uri={1}&scope=email";         private static string GetAccessTokenUri = "https://graph.facebook.com/oauth/access_token?client_id={0}&redirect_uri={1}&client_secret={2}&code={3}";         private static string GetUserInfoUri = "https://graph.facebook.com/me?access_token={0}";          private static string GraphUri = "https://graph.facebook.com/{0}";          public IFbAppConfig Config { get; set; }          public string AccessToken { get; set; }          public string Authorize(string redirectTo)         {             return string.Format(AuthorizeUri, Config.AppID, redirectTo);         }          public bool GetAccessToken(string code, string redirectTo)         {             var request = string.Format(GetAccessTokenUri, Config.AppID, redirectTo, Config.AppSecret, code);             WebClient webClient = new WebClient();             string response = webClient.DownloadString(request);             try             {                 var pairResponse = response.Split('&');                 AccessToken = pairResponse[0].Split('=')[1];                 return true;             }             catch (Exception ex)             {                 return false;             }         }          public JObject GetUserInfo()         {             var request = string.Format(GetUserInfoUri, AccessToken);             WebClient webClient = new WebClient();              string response = webClient.DownloadString(request);             return JObject.Parse(response);         }     }  

Где

  • Authorize – это формирование ссылки для запроса прав.
  • GetAccessToken – это запрос по получению временного токена.
  • GetUserInfo – это запрос по получению данных пользователя.

Обратите внимание, как мы используем WebClient.DownloadString – и оп-па, мы получили нужные данные из недр интернета.

Едем дальше. Создадим контроллер FacebookController (/Areas/Default/Controllers/FacebookController.cs):

public class FacebookController : DefaultController     {         private FbProvider fbProvider;          protected override void Initialize(System.Web.Routing.RequestContext requestContext)         {             fbProvider = new FbProvider();             fbProvider.Config = Config.FacebookSetting;             base.Initialize(requestContext);         }          public ActionResult Index()         {             return Redirect(fbProvider.Authorize("http://" + HostName + "/Facebook/Token"));         }          public ActionResult Token()         {             if (Request.Params.AllKeys.Contains("code"))             {                 var code = Request.Params["code"];                 if (fbProvider.GetAccessToken(code, "http://" + HostName + "/Facebook/Token"))                 {                     var jObj = fbProvider.GetUserInfo();                     var fbUserInfo = JsonConvert.DeserializeObject<FbUserInfo>(jObj.ToString());                      return View(fbUserInfo);                 }                              }             return View("CantInitialize");         }      }  

В Initialize передаем в FbProvider AppIDи AppSecret. После захода – делаем редирект на facebook с запросом прав у пользователя (окошко разрешения). Если пользователь уже нам это когда-то разрешил, чтобы не спрашивать по 100 раз, facebook нас переправит на страницу /Facebook/Token. Если код для получения токена не удалось получить – возвращаем View CantInitialize (/Areas/Default/Views/Facebook/CantInitialize.cshtml):

@{     ViewBag.Title = "CantInitialize";     Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml"; }  <h2>CantInitialize</h2>  <h3> Ну нет - так нет</h3> 

Иначе, когда всё хорошо, то получаем наш токен (он сохраняется в fbProvider) и запрашиваем данные о пользователе. Получив – преобразовываем в объект класса FbUserInfo и выводим во View (/Areas/Default/Views/Facebook/Token.cshtml):

@model LessonProject.FacebookAPI.FbUserInfo  @{     ViewBag.Title = "Token";     Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml"; }  <h2>Данные</h2> <p> Вот что я про тебя знаю:</p>  <dl class="dl-horizontal">     <dt>ID</dt>     <dd>@Model.Id</dd>          <dt>FirstName</dt>     <dd>@Model.FirstName</dd>     <dt>LastName</dt>     <dd>@Model.LastName</dd>     <dt>Link</dt>     <dd>@Model.Link</dd> </dl> 
Клиентский код/Серверный код (Access-Control-Allow-Origin)

Рассмотрим еще ситуацию, когда всё это взаимодействие заключено в js-файлах, мы выполняем только ajax-запросы. Изменим код метода Token. Получаем данные пользователя не серверным кодом от facebook, а передаем во View токен (/Areas/Default/Controllers/FacebookController.cs:Token):

public ActionResult Token()         {             if (Request.Params.AllKeys.Contains("code"))             {                 var code = Request.Params["code"];                 if (fbProvider.GetAccessToken(code, "http://" + HostName + "/Facebook/Token"))                 {                   /*   var jObj = fbProvider.GetUserInfo();                     var fbUserInfo = JsonConvert.DeserializeObject<FbUserInfo>(jObj.ToString());                     */                     ViewBag.Token = fbProvider.AccessToken;                     return View();                 }                              }             return View("CantInitialize");         }  

Изменим Token.cshtml (/Areas/Default/Views/Facebook/Token.cshtml):

@{     ViewBag.Title = "Token";     Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml"; } @section scripts {     @Scripts.Render("~/Scripts/default/facebook-token.js") }  @Html.Hidden("Token", ViewBag.Token as string) <h2>Данные</h2> <p> Вот что я про тебя знаю:</p>  <dl class="dl-horizontal">     <dt>ID</dt>     <dd id="ID"></dd>          <dt>FirstName</dt>     <dd id="FirstName"></dd>     <dt>LastName</dt>     <dd id="LastName"></dd>     <dt>Link</dt>     <dd id="Link"></dd> </dl> 

Добавляем facebook-token.js (/Scripts/default/facebook-token.js):

function FacebookToken() {     _this = this;      this.ajaxGetUserInfo = "https://graph.facebook.com/me?access_token=";      this.init = function () {         var token = $("#Token").val();         $.ajax({             type: "GET",             dataType: 'json',             url: _this.ajaxGetUserInfo + token,             success: function (data)             {                 $("#ID").text(data.id);                 $("#FirstName").text(data.first_name);                 $("#LastName").text(data.last_name);                 $("#Link").text(data.link);             }         })     } }  var facebookToken = null;  $().ready(function () {     facebookToken = new FacebookToken();     facebookToken.init(); }); 


Запускаем, проверяем. Всё отлично. Но обратим внимание на такой параметр в http-ответе:

Access-control-allow-origin – это параметр, который, будучи установлен, позволяет делать ajax-запросы из браузера к сайту, размещенному на другом домене.
Т.е. если мы обращаемся по $.ajax() из браузера и в ответе этого заголовка нет, то выдается ошибка:
Origin http://localhost:8080 is not allowed by Access-Control-Allow-Origin
Для этого создадим атрибут, который будет добавлять этот заголовок, если мы захотим организовать обращение к нашему сайту с других сайтов (/Attribute/AllowCrossSiteJson.cs):

public class AllowCrossSiteJsonAttribute : ActionFilterAttribute     {         public override void OnActionExecuting(ActionExecutingContext filterContext)         {             filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Origin", "*");             base.OnActionExecuting(filterContext);         }     } 

Добавим использование. Напимер, метод-action OK, который всегда будет возвращать { “result”: “OK”} (/Areas/Default/Controllers/HomeController.cs):

[AllowCrossSiteJson]         public ActionResult OK()         {             return Json(new { result = "OK" }, JsonRequestBehavior.AllowGet);         } 

На этом всё по Json и работе с facebook. Можете потренироваться и поработать с авторизацией и взаимодействием с vk api. Документация тут: http://vk.com/developers.php.

Все исходники находятся по адресу https://bitbucket.org/chernikov/lessons

ссылка на оригинал статьи http://habrahabr.ru/post/176087/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *