Статьи

Создание внутренних данных и служб для приложений Windows 8: OData — часть 2

В первой части этой статьи я показал, как создать базу данных SQL в Windows Azure , создать схему для добавления функциональности таблицы лидеров в игру, создать модель Entity Framework для базы данных, а затем создать и протестировать службу данных WCF сверху модели, которая предоставляет богатую модель взаимодействия в стиле REST с отличной поддержкой запросов через OData. Если вы еще не прочитали часть 1, вам следует это сделать, прежде чем продолжить .

И если вы хотите подписаться, но у вас еще нет учетной записи Windows Azure, вы можете подписаться на 90-дневную пробную версию , которая предоставит вам все необходимое.

Если вы хотите рассказать историю игр, которые я планирую использовать для демонстрации сервиса лидеров, ознакомьтесь с этим постом .

Во второй части я покажу вам:

  1. Как развернуть службу OData в Windows Azure
  2. Как я подключил свои игры для Windows 8 JavaScript, Space Cadet и Catapult Wars, к сервису лидеров.

Развертывание Сервиса

Теперь я мог бы начать интеграцию сервиса в наши игры, используя локальную версию сервиса, но так как сервис довольно прост, и я уверен, что он будет делать то, что я хочу, давайте продолжим и опубликуем сервис в Windows Azure. и используйте опубликованный URI для клиентского приложения.

Если вы никогда не публиковали облачную службу в Windows Azure или уже давно не используете инструменты Visual Studio для Azure, вы будете приятно удивлены ее легкостью. Все, что требуется, — это однократная настройка учетных данных для публикации, которую я начну, щелкнув правой кнопкой мыши мой облачный проект и выбрав Публикация…

AzurePublish1_thumb [2]

При первой публикации я должен сопоставить свой проект с подпиской Azure, на которую я хочу опубликовать, и, поскольку я еще не настроил подписку, я нажму ссылку «Войти, чтобы загрузить учетные данные». «чтобы установить это первым. Это приведет меня к следующей странице на сайте Windows Azure, на которой также будет предложено загрузить файл .publishsettings, содержащий сертификат, необходимый Visual Studio для процесса публикации:

AzurePublish2_thumb [2]

После того как я скачал файл (отметив, где я его сохранил), мне нужно вернуться в Visual Studio и нажать кнопку «Импортировать…», затем перейти к файлу .publishsettings, который я скачал на предыдущем шаге, и нажать кнопку Открыть кнопку. Это должно заполнить раскрывающийся список с новой информацией о подписке, чтобы я мог нажать Далее. Так как моя подписка еще не содержит облачных сервисов для публикации, мне предлагается указать имя сервиса и место, где я хочу опубликовать мой сервис, который я заполнил в диалоговом окне ниже:

AzurePublish3_thumb [2]

Как только я нажимаю кнопку ОК, меня приветствует страница в мастере публикации, которая позволяет мне указать настройки для моей службы, как показано ниже:

AzurePublish4_thumb [2]

Обратите внимание, что если у меня уже была служба в производственном процессе, я мог бы изменить среду на промежуточную, чтобы можно было протестировать службу перед ее продвижением в производство. Я также могу включить доступ к удаленному рабочему столу для своих ролей, если я хочу иметь больший контроль над конфигурацией службы или если мне нужно установить какие-либо дополнительные компоненты для моей службы. На вкладке «Дополнительно» есть дополнительные настройки, но они выходят за рамки этого поста. Для моих целей по умолчанию все будет в порядке, поэтому теперь я могу либо щелкнуть Далее, чтобы просмотреть сводку выбранных настроек публикации (и, при желании, сохранить их как профиль для будущего использования, что позволит быстрее выполнять развертывание в будущем). ) или просто нажмите кнопку «Опубликовать», чтобы опубликовать мой сервис.

Когда я нажимаю кнопку «Опубликовать», Visual Studio создает свой проект и начинает процесс развертывания, предоставляя состояние в окне «Журнал активности Windows Azure», как показано ниже:

AzurePublish5_thumb [2]

После того , как журнал сообщает Complete, то URL сайта выше будет заполняться с URL для веб — роли, где сроки службы, в виде HTTP: // имя_службы .cloudapp.net /. Я нажму на ссылку, чтобы проверить, активен ли сервис.

К сожалению, когда я впервые попробовал это, я столкнулся с общим экраном ошибок ASP.NET (он же желтый экран смерти). Если вы откроете этот экран, вам нужно будет отключить пользовательские ошибки в файле web.config и развернуть версию облачной службы в режиме отладки, чтобы просмотреть подробную информацию об ошибках. Оказывается, в моем случае у меня был конфликт между номером версии по умолчанию для службы WCF, настроенной в разметке файла .svc, и версией, загружаемой из каталога bin. После быстрого Бинг поиска, я нашел этот блог, который обсуждает процесс развертывания бина WCF Data Services и предлагает несколько различных обходных путей, одним из которых было просто удаление информации о версии из разметки, что я и сделал. После того, как я развернул новую версию, моя служба работала так же, как и при локальном запуске. Потрясающие!

Подключение клиентского приложения Leaderboard

Одна из целей этой серии постов в блоге и сопроводительных данных, которую я ставлю перед собой, — сделать код таблицы лидеров как можно менее навязчивым по отношению к основной игровой логике. С этой целью я планирую определить всю логику таблицы лидеров в одном файле JavaScript с функциональностью, предоставляемой клиентской игре, с использованием функциональности пространства имен WinJS, как в следующем примере кода:

     WinJS.Namespace.define("Leaderboard", {

         setPlayerName: setPlayerName,

         init: init,

         addWin: incrementWins,

         addLoss: incrementLosses,

         addTie: incrementTies,

         updateScore: updateHighScore,

         getTopTenScores: getTopTenScores,

         getTopTenWins: getTopTenWinLossTie,

        leaderboardList: leaderboardList

    });


Это позволяет мне предоставлять непротиворечивый API для функциональности таблицы лидеров, доступной через пространство имен Leaderboard, за которым следует желаемый член API. Поэтому для инициализации таблицы лидеров я бы назвал Leaderboard.init (args). Большим преимуществом здесь является то, что самой игре не нужно ничего знать о базовой реализации логики таблицы лидеров, и для меня довольно просто изменить внутреннюю логику каждой из функций, чтобы использовать другой серверный сервис без изменение кода в самой игре (именно это я и буду делать в следующих статьях об использовании веб-API ASP.NET и мобильных служб Windows Azure для предоставления аналогичных внутренних служб).

Еще одна полезная вещь: после того, как я ссылаюсь на файл JavaScript в основном HTML-файле игры (например, в Space Cadet Дейва Исбицкого, это будет default.html), Visual Studio автоматически обеспечит завершение оператора IntelliSense для моего API, включая аргументы, требуемые для данной функции, как показано ниже:

Кроме того, поиск IntelliSense отображает комментарий, который появляется в строке перед функцией для этого API, в качестве описания функции. Делает очень удобным добавление простой документации для вашего API.

Как видно из приведенного выше списка кода, API поддерживает два основных типа отслеживания оценки: высокая оценка и запись побед / поражений / ничьих. Для последнего у нас есть функции добавления выигрыша, проигрыша или ничьей к существующей записи, а для высокого результата у нас есть функция для обновления счета игрока (при условии, что он выше, чем существующий высокий результат для этого игрока). Затем, поскольку нам может потребоваться отобразить текущую информацию в таблице лидеров, у нас есть пара функций, которые возвращают список рекордов или записи побед / поражений / ничьих для текущей игры. Обе эти функции обновляют внутреннюю переменную с именем leaderboardList, которая предоставляется как член API с тем же именем.

Инициализация таблицы лидеров

Я начну с объявления большинства моих переменных наверх. Поскольку JavaScript использует нечто, называемое hoisting, которое обрабатывает объявления переменных в любом месте данной области видимости, как если бы они были объявлены в верхней части функции… НО, если строка, в которой объявлена ​​переменная, также включает в себя инициализацию, инициализация не поднимается, что может привести к в некоторых сложных ситуациях для отладки. Рекомендуется объявлять все переменные в верхней части области действия функции, в которой они используются:

     var leaderboardClient, xhrOptions, leaderboardList, scores, playerName, 

         gameName, currentPlayerScore, diag;

Затем следует функция инициализации, которую игра предоставляет с именами игры и игрока, и которая проверяет, существует ли хотя бы одна запись счета, и, если нет, инициализирует таблицу лидеров с пустой записью счета для этой комбинации игрока / игры. Обратите внимание, что в интересах простоты я не добавил никакого кода, чтобы гарантировать, что комбинация игра / игрок уникальна для всех возможных клиентов службы списка лидеров.

     // player and game are required for initialization

     function init(player, game) {

         return new WinJS.Promise(

             function (completed, error, progress) {

                 if (player) {

                     playerName = player;

                 }

                 else {

                     error("Player Name is required for initialization");

                }

     

                if (game) {

                    gameName = game;

                }

                else {

                    error("Game Name is required for initialization");

                }

    

                // replace with the URI for your custom service

                leaderboardClient = {

                    baseUri: "http://[YOUR SERVICE URI]/GameLeaderServiceOData.svc/Scores",

                    query: "?$filter=Game eq '" + gameName + "' and Player eq '" + playerName + "'"

                };

     

                // get score record for the current game/player

                xhrOptions = {

                    url: leaderboardClient.baseUri + leaderboardClient.query,

                    headers: {

                        "Accept": "application/json;"

                    }

                };

                WinJS.xhr(xhrOptions).done(function (results) {

                    var gameScore;

                    scores = JSON.parse(results.responseText);

                    if (!scores.value[0]) {

                        // create initial leaderboard entry

                        gameScore = {

                            Game: gameName, Player: playerName, Score1: 0, Wins: 0, Losses: 0, Ties: 0

                        };

                        xhrOptions = {

                            data: JSON.stringify(gameScore),

                            headers: {

                                "Content-type": "application/json; charset=utf-8"

                            },

                            type: "post",

                            url: leaderboardClient.baseUri

                        };

                        WinJS.xhr(xhrOptions).done(function (results) {

                            completed();

                        },

                        function (e) {

                            error(e);

                        });

                    }

                    else {

                        completed();

                    }

                },

                function (e) {

                    error(e);

                });

            });

    }

 

В этой функции я показываю небольшое изменение схемы обещаний. На этот раз я объявляю переменную ScorePromise, которая передается в результате вызова getCurrentPlayerScore. В строке 3 я затем вызываю .done, передавая функцию, которую я хочу вызвать как обычно. Этот код просто делает то, что происходит (getCurrentPlayerScore возвращает объект Promise) немного более явным, и может способствовать удобочитаемости моего кода. В строке 4 выше я проверяю, является ли новая оценка, переданная в функцию, выше, чем оценка, возвращаемая getCurrentPlayerScore через объект обещания, и, если это так, вызывает updateScoreRecord, передавая ему объект currentScore.

ПРИМЕЧАНИЕ. В приведенном выше коде вы можете заметить, что я проверяю «Score1», когда вы можете ожидать от схемы, показанной в части 1 этого поста, в которой есть столбец с именем «Score», что свойство должно быть « Гол». Ну, это произошло из-за того, как я назвал таблицу («Счета») и столбца («Оценка»), а также из-за того, что в процессе построения модели Entity Framework по умолчанию используется сингулярность имен объектов, поэтому сущность в модели, представляющей таблицу баллов, называется «Оценка». Поскольку у вас не может быть сущности с именем свойства, совпадающим с именем сущности, Entity Framework услужливо переименовал свойство «Score1», и службы данных WCF предоставляют такое же свойство для объекта Score.

Вот’
s код для updateScoreRecord:

     function updateScoreRecord(newScoreRecord) {

         xhrOptions = {

             data: JSON.stringify(newScoreRecord),

             headers: {

                 "Content-type": "application/json; charset=utf-8",

                 "X-HTTP-Method": "MERGE"

             },

             type: "post",

             url: leaderboardClient.baseUri + "(" + newScoreRecord.Id + ")"

        };

        WinJS.xhr(xhrOptions).done(function (results) {

            showMessage("Leaderboard Updated.");

        },

        function (e) {

            showMessage("Leaderboard could not be updated.");

        });

    }

Эта функция берет запись оценки, переданную из updateHighScore, подготавливает ее для отправки с использованием JSON.stringify, а затем делает результат свойством data объекта xhrOptions. Я также передаю заголовок Content-type, чтобы указать, что я отправляю данные JSON, а также заголовок X-HTTP-метода со значением «MERGE». Это говорит службе OData обновить указанную запись с любыми предоставленными свойствами, оставляя при этом все неопределенные значения без изменений. Подробнее об обновлении записей через OData вы можете прочитать здесь.

Обновление высокой оценки из игрового кода довольно просто, для обновления требуется только передать счет:

    Leaderboard.updateScore(score);

Это все, что нужно для подключения функциональности для хранения и обновления записей партитур для данной игры (в данном случае Space Cadet). Для Space Cadet и аналогичных игр, которые позволяют игроку изменять свое имя игрока, я также добавил функцию для обновления имени игрока, и есть функции для увеличения свойств Wins, Losses и Ties для игр, в которых счет сохраняется с использованием этих значений. вместо числового рекорда. А для Catapult Wars я могу использовать точно такую ​​же библиотеку JavaScript. Единственное необходимое изменение — вызвать Leaderboard.addWin или Leaderboard.addLoss, в зависимости от того, выиграл или проиграл игрок.

Добавление страницы лидеров

Конечно, наличие всей этой информации о таблице лидеров не принесет мне большой пользы, если я не буду время от времени показывать ее, поэтому, чтобы исправить это, я добавил в свой проект папку Leaderboard и добавил к ней новый элемент управления Page (который добавляет соответствующий набор файлов HTML, CSS и JavaScript) с именем Leaderboard.html. Вот как выглядит разметка для Space Cadet:

     <!DOCTYPE html>

     <html>

     <head>

         <meta charset="utf-8" />

         <title>Leaderboard</title>

      

         <!-- WinJS references -->

         <link href="//Microsoft.WinJS.1.0/css/ui-dark.css" rel="stylesheet" />

         <script src="//Microsoft.WinJS.1.0/js/base.js"></script>

        <script src="//Microsoft.WinJS.1.0/js/ui.js"></script>

     

        <link href="leaderboard.css" rel="stylesheet" />

        <script src="/js/leaderboardOData.js"></script>

        <script src="leaderboard.js"></script>

    </head>

    <body>

        <div id="myTemplate" data-win-control="WinJS.Binding.Template">

            <div>

                <div class="win-type-x-large" style="width: 400px;">

                    <span>Player: </span><em><span data-win-bind="textContent: Player"></span></em>

                </div>

                <div class="win-type-large">

                    <span>Score: </span><em><span data-win-bind="textContent: Score1"></span></em>

                </div>

            </div>

        </div>

        <div class="pagecontrol">

            <header aria-label="Header content" role="banner">

                <button id="backButton" class="win-backbutton" aria-label="Back" type="button"></button>

                <span id="title" class="win-type-xx-large pagetitle titlearea win-type-ellipsis">Leaderboard</span>

            </header>

            <section aria-label="Main content" role="main">

                <div id="leaderListView" data-win-control="WinJS.UI.ListView" 

                    data-win-options="{itemTemplate : select('#myTemplate'), selectionMode: 'none'}"></div>

            </section>

        </div>

    </body>

    </html>

Он просто устанавливает элемент управления WinJS.UI.ListView, который я буду использовать для привязки данных результатов оценки сервиса, с шаблоном, который отображает имя каждого игрока вместе с его счетом. Также есть кнопка «Назад», чтобы вернуть игрока в игру, как только он закончит просмотр списка лидеров.

Вот сопровождающий код JS:

     // For an introduction to the Page Control template, see the following documentation:

     // http://go.microsoft.com/fwlink/?LinkId=232511

     (function () {

         "use strict";

      

         var appdata = Windows.Storage.ApplicationData;

         var playerName = appdata.current.roamingSettings.values["playerName"]

         var gameName = "Space Cadet"

      

        WinJS.UI.Pages.define("/leaderboard/leaderboard.html", {

            // This function is called whenever a user navigates to this page. It

            // populates the page elements with the app's data.

            ready: function (element, options) {

                // TODO: Initialize the page here.

                document.getElementById("backButton").addEventListener("click", goBack, false);

                Leaderboard.init(playerName, gameName);

                Leaderboard.getTopTenScores().done(

                    function (leaderList) {

                        title.textContent = gameName +" Top 10";

                        leaderListView.winControl.itemDataSource = leaderList.dataSource;

                    }

                );

            },

     

            unload: function () {

                // TODO: Respond to navigations away from this page.

            },

     

            updateLayout: function (element, viewState, lastViewState) {

                /// <param name="element" domElement="true" />

     

                // TODO: Respond to changes in viewState.

            }

        });

     

        function goBack() {

            location.href = "/default.html";

        }

     

    })();

Обратите внимание, что для Space Cadet я использую поддержку настроек роуминга в Windows 8, что означает, что где бы я ни устанавливал игру, мое имя игрока будет следовать за мной, пока я вхожу с той же учетной записью Microsoft. В функции ready я сначала связываю событие click кнопки back с функцией goBack, которая просто перезагружает default.html. Затем я вызываю Leaderboard.init (чтобы убедиться, что таблица лидеров была правильно инициализирована для игры), а затем вызываю Leaderboard.getTopTenScores и предоставляю функцию, которая будет вызвана после завершения этой операции. В этой функции я установил в заголовке страницы списка лидеров имя игры плюс «Топ-10» и, наконец, установил itemDataSource элемента управления ListView в свойстве dataSource объекта WinJS.Binding.List, передаваемого в функцию из getTopTenScores.Добавьте немного CSS для форматирования, и вот результат:

Ничего особенного, я признаю, но работа сделана. Вот код для функции getTopTenScores:

     function getTopTenScores() {

         return new WinJS.Promise(

             function (completed, error, progress) {

                 // get top ten scores for the current game

                 xhrOptions = {

                     url: leaderboardClient.baseUri + "?$filter=Game eq '" + gameName + "'&$orderby=Score1 desc&$top=10",

                     headers: {

                         "Accept": "application/json;"

                     }

                };

                WinJS.xhr(xhrOptions).done(function (results) {

                    scores = JSON.parse(results.responseText);

                    if (!scores.value[0]) {

                        // throw exception...no score found

                        error("No score record found!");

                    }

                    else {

                        leaderboardList = new WinJS.Binding.List(scores.value);

                        completed(leaderboardList);

                    }

                },

                function (e) {

                    showMessage(e);

                });

            });

    }

Следует обратить внимание на использование параметров запроса $ orderby и $ top, которые помогают упорядочить данные так, как мне нужно для таблицы лидеров (вы также можете заметить, что у меня нет 10 полных записей в моем списке). базы данных еще нет, но если бы у меня было более 10 записей, этот запрос вернул бы только первые 10), и что на этот раз я не использую имя игрока в запросе, так как я действительно хочу получить оценки для всех игроков за это игра.

Я снова возвращаю объект WinJS.Promise, и если операция xhr прошла успешно, я создаю новый объект WinJS.Binding.List на основе полученных результатов и передаю его завершенной функции, которая позволяет Leaderboard.html. страница, чтобы связать его с элементом управления ListView на странице. Страница списка лидеров для Catapult Wars выглядит практически идентично, но в этом случае я вызываю Leaderboard.getTopTenWins и связываю свойство Wins в ListView:

Заворачивать

Итак, в этой паре постов вы увидели, как я создал новую базу данных SQL в Windows Azure, определил схему для данных, использовал Entity Framework для создания модели данных и обернул ее в службу данных WCF, предоставив мощный, но простой синтаксис запросов на основе URL, который я могу использовать для взаимодействия со своим сервисом. Вы также видели, как я создал единственную библиотеку JavaScript, которая использовала функциональность пространства имен WinJS, чтобы представить простой API, который может использоваться несколькими играми для взаимодействия со службой списка лидеров.

Как отмечается в обзорном посте для этой серии, у этого подхода к созданию внутренних сервисов есть как преимущества, так и недостатки:

Преимущества

Устаревшая платформа — SQL Server, Entity Framework и WCF Data Services были созданы для множества версий, и за это время у них было время для улучшения и роста. Зрелость также означает большую осведомленность для разработчиков .NET.

Параметры формата данных — могут возвращать данные в формате XML (ATOM) или JSON.

Настраиваемая — реализация настраиваемой логики может быть выполнена различными способами, включая атрибуты, настраиваемый код в службе данных WCF и представление хранимых процедур как операций службы.

Гибкость запросов — благодаря мощному синтаксису запросов OData, запрос и отправка данных легко осуществляются путем создания соответствующего URL-адреса и выполнения простых HTTP-запросов.

Недостатки

Сложность — как вы могли заметить из длины этого поста в блоге, есть довольно много шагов для его настройки и несколько слоев. Хотя это означает больше точек, в которых услуга может быть настроена, это также увеличивает общую сложность решения.

Нет клиентской библиотеки для приложений JavaScript — хотя в приложениях Магазина Windows на основе XAML есть прямая поддержка для работы со службами OData, официальной поддержки служб OData в приложениях JavaScript нет. Как вы уже видели, довольно легко создавать запросы xhr, которые обращаются к службе, но этот подход может показаться довольно чуждым всем, кто использует клиентскую библиотеку OData, многие из которых используют синтаксис в стиле LINQ для выполнения операций над службой, обработки Сервис больше похож на локальный объект, который скрывает основные сложности HTTP от разработчика.

Так это правильный подход для вашего приложения? Ответ, конечно же, «это зависит». Безусловно, вы можете создать надежный, масштабируемый и простой в использовании сервисный бэкэнд для своего приложения или игры в Магазине Windows, используя методы, которые я здесь продемонстрировал. Но есть и другие, более простые подходы, которые вы также можете рассмотреть.

Что дальше?

В следующей части этой серии я покажу другой подход к созданию внутренних сервисов, а также создам тот же сервис списка лидеров игр с использованием нового ASP.NET Web API.

Пока вы ждете, рассмотрите возможность подписки на приложение поколения. Существует множество отличных ресурсов для создания приложений для Windows 8 (а теперь и для Windows Phone 8). Это бесплатно, и вы контролируете, как часто отправляются обновления, поэтому нет веских оснований для этого. Войти Сейчас!