ASP.NET MVC Урок B. Json

в 18:53, , рубрики: .net, ASP, asp.net mvc, Facebook API, 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);

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

ASP.NET MVC Урок B. Json

Т.е. работает в обоих направлениях. Но немного усложним. Например, зададим 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 нам надо описывать не MaleFemale, а MF. Для этого создаем класс по разбору 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

ASP.NET MVC Урок B. Json

Создаем, получаем:
ASP.NET MVC Урок B. Json

Нам интересно будет 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 

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

ASP.NET MVC Урок B. Json

Если мы нажимаем «Перейти к приложению» — то нас переправляют на страницу
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":"chernikovu0040gmail.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-ответе:

ASP.NET MVC Урок B. Json

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

Автор: chernikov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js