Статьи

Создание клиента Twitter для Windows Phone — клиент OAuth и базовая инфраструктура

Twitter теперь официально интегрирован в ОС Windows Phone как часть обновления Mango. Тем не менее, всегда можно расширить существующий пользовательский интерфейс, и именно поэтому я собираюсь показать вам, как можно создать полноценный клиент Twitter для Windows Phone с нуля.

Первое, что вам нужно сделать, это зарегистрировать приложение Twitter. Хотя существуют открытые конечные точки Twitter (например, общедоступная временная шкала для конкретного пользователя), некоторые интерактивные возможности, такие как обновления статуса или загрузка изображений, требуют аутентификации и прямой связи с приложением, которое известно в Twitter. Чтобы зарегистрировать новое приложение, у вас уже должна быть активная учетная запись Twitter. Отправляйся сюда сейчас:

https://dev.twitter.com/apps

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

Нажмите « Создать новое приложение» и заполните поля для своего приложения.

В моем случае я назвал приложение MangoTree (вы видите, откуда я это взял, верно?). И прежде чем его спросят — да, я выпущу приложение в Marketplace, как только мы закончим с этим проектом. Вы в основном следите (читай: затенение) моих этапов разработки с этим проектом.

По умолчанию новые приложения создаются с правами только для чтения . Очевидно, что для клиента Twitter это недопустимо, если вы просто не создаете читателя. Чтобы изменить это, над головой настройки для вашего приложения и изменения доступа к чтению, записи и прямым сообщениям доступа . После того, как вы сохранили изменения, посмотрите, какая у вас есть информация, относящаяся к приложению (вернитесь на вкладку Details).

Настройки OAuth — самая важная часть здесь. Убедитесь, что Consumer Key и Consumer Secret доступны в вашем приложении, но недоступны для пользователя — вы будете использовать их только для внутреннего использования.

Здесь мы начинаем наше приложение для Windows Phone. Создайте новое стандартное приложение Windows Phone на C # в Visual Studio и установите целевую платформу на Windows Phone 7.1, потому что позже мы будем использовать некоторые возможности Mango.

Создайте новую папку под названием Twitter в вашем решении. Создайте новый класс с именем AuthConstants . Пометьте этот класс как статический и создайте две статические строковые константы — ConsumerKey и ConsumerSecret . Они будут использованы позже в приложении. Я отмечаю их как константы, потому что оба значения никогда не меняются.

 

namespace MangoTree.Twitter
{
    public static class AuthConstants
    {
        public const string ConsumerKey = "";
        public const string ConsumerSecret = "";
    }
}

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

Еще один класс, который необходимо создать, — это UrlConstants . Здесь я буду ссылаться на конечные точки URL, которые я собираюсь использовать в различных частях приложения. Еще раз — вы можете сохранить их в файле XML и затем проанализировать, но вы уже знаете причину, по которой я этого не делаю. Это также статический класс, и на данный момент он содержит две константы — RequestToken и AccessToken .

namespace MangoTree.Twitter
{
    public static class UrlConstants
    {
        // Auth
        public const string RequestToken = "https://api.twitter.com/oauth/request_token";
        public const string AccessToken = "https://api.twitter.com/oauth/authorize";
    }
}

Вы можете быть удивлены — почему я вместо этого не использую класс Uri? Просто. Потому что время от времени мне может понадобиться прочитать необработанную строку, и я предпочитаю работать с самим значением, а не извлекать его из экземпляра Uri.

Теперь пришло время попросить пользователя авторизовать приложение. Чтобы сделать это, я построил начальный экран авторизации, чтобы по нему было довольно легко перемещаться.

Структура XAML выглядит следующим образом:

<phone:PhoneApplicationPage 
    x:Class="MangoTree.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="False">

    <StackPanel x:Name="LayoutRoot" Background="Transparent">
        <TextBlock Text="Step 1:" Style="{StaticResource PhoneTextLargeStyle}" Margin="10,0,0,0"></TextBlock>
        <TextBlock Text="Acquire a PIN code to authorize MangoTree to be used with your account." TextWrapping="Wrap" Style="{StaticResource PhoneTextTitle3Style}" Margin="10,0,0,0"></TextBlock>
        <Button x:Name="btnAcquire" Content="Acquire PIN" Width="220" HorizontalAlignment="Left"></Button>

        <TextBlock Text="Step 2:" Style="{StaticResource PhoneTextLargeStyle}" Margin="10,30,0,0"></TextBlock>
        <TextBlock Text="Enter the previously acquired PIN code." TextWrapping="Wrap" Style="{StaticResource PhoneTextTitle3Style}" Margin="10,0,0,0"></TextBlock>
        <TextBox x:Name="txtPIN" Width="480" HorizontalAlignment="Left"></TextBox>

        <TextBlock Text="Step 3:" Style="{StaticResource PhoneTextLargeStyle}" Margin="10,30,0,0"></TextBlock>
        <TextBlock Text="Confirm your PIN code." TextWrapping="Wrap" Style="{StaticResource PhoneTextTitle3Style}" Margin="10,0,0,0"></TextBlock>
        <Button x:Name="btnConfirm" Content="Confirm PIN" Width="220" HorizontalAlignment="Left"></Button>
    </StackPanel>
</phone:PhoneApplicationPage>

Это довольно просто, но эффективно. Позже мы улучшим пользовательский интерфейс — теперь нам нужны основные функциональные возможности. 

Вам может быть интересно — что такое PIN-код и зачем он мне нужен? Учитывая текущий процесс аутентификации для Twitter, было бы намного проще, если бы я использовал ПИН-код для авторизации пользователя, чем использовать обратные вызовы, как это происходит с веб-приложениями. Фактически, это был бы единственный эффективный способ авторизации пользователя.

Когда пользователь нажимает кнопку «Получить ПИН-код», я запрашиваю токен приложения и перенаправляю пользователя на официальную страницу аутентификации Twitter, где пользователь сможет просмотреть запрашиваемые мной разрешения в контексте MangoTree и ввести свои учетные данные в случае он соглашается, что мое заявление будет связано с его учетной записью.

Давайте построим инфраструктуру связи. В вашей папке Twitter создайте класс с именем OAuthClient . Это будет центральная точка подключения для всего, что делается в этом приложении. Взглянем:

public class OAuthClient
{
    public void PerformRequest(Dictionary<string, string> parameters, string url, string consumerSecret, string token, byte type)
    {
        string OAuthHeader = OAuthClient.GetOAuthHeader(parameters, "POST", url, consumerSecret, token);

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
        request.Method = "POST";
        request.Headers["Authorization"] = OAuthHeader;

        request.BeginGetResponse(new AsyncCallback(GetToken), request);
    }

    public static void GetToken(IAsyncResult result)
    {

        HttpWebRequest request = (HttpWebRequest)result.AsyncState;
        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);

        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
        {
            string[] data = reader.ReadToEnd().Split(new char[] { '&' });
            int index = data[0].IndexOf("=");
            string token = data[0].Substring(index + 1, data[0].Length - index - 1);
            Debug.WriteLine("TOKEN OBTAINED");

            WebBrowserTask task = new WebBrowserTask();
            task.Uri = new Uri("http://api.twitter.com/oauth/authorize?oauth_token=" + token);
            task.Show();
        }
    }

    public static string GetOAuthHeader(Dictionary<string, string> parameters, string httpMethod, string url, string consumerSecret, string tokenSecret)
        {
            parameters = parameters.OrderBy(x => x.Key).ToDictionary(v => v.Key, v => v.Value);

            string concat = string.Empty;

            string OAuthHeader = "OAuth ";
            foreach (string k in parameters.Keys)
            {
                concat += k + "=" + parameters[k] + "&";
                OAuthHeader += k + "=" + "\"" + parameters[k] + "\", ";
            }

            concat = concat.Remove(concat.Length - 1, 1);
            concat = StringHelper.EncodeToUpper(concat);

            concat = httpMethod + "&" + StringHelper.EncodeToUpper(url) + "&" + concat;

            byte[] content = Encoding.UTF8.GetBytes(concat);

            HMACSHA1 hmac = new HMACSHA1(Encoding.UTF8.GetBytes(consumerSecret + "&" + tokenSecret));
            hmac.ComputeHash(content);

            string hash = Convert.ToBase64String(hmac.Hash);
            hash = hash.Replace("-", "");

            OAuthHeader += "oauth_signature=\"" + StringHelper.EncodeToUpper(hash) + "\"";

            return OAuthHeader;
        }
}

В данный момент здесь происходит три вещи. У меня есть метод PerformRequest , который инициализирует все запросы, проходящие через него. Обратите пристальное внимание на то, что я передаю этому методу — экземпляру Dictionary , в котором хранятся параметры, используемые для построения заголовков авторизации OAuth.

GetOAuthHeader — это основной метод построения для организации заголовков в правильной последовательности — я располагаю правильные ключи и значения в алфавитном порядке. Он также создает подпись (HMAC-SHA1) для объединенной, правильно упорядоченной строки со всеми параметрами. В контексте этого метода я использую StringHelper.EncodeToUpper — ссылку на метод, который выбирает кодирует части объединенных строк и преобразует закодированные буквы на основе букв в заглавные буквы. Это потому, что по какой-то причине процесс OAuth в Twitter выдает ошибку, когда эти символы не в верхнем регистре.

Метод EncodeToUpper должен быть создан в контексте статического класса StringHelper , который создается в новой папке Utility .

namespace MangoTree.Utility
{
    public static class StringHelper
    {
        public static string EncodeToUpper(string raw)
        {
            raw = HttpUtility.UrlEncode(raw);
            return Regex.Replace(raw, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper());
        }
    }
}

Теперь вернемся к OAuthClient . И последнее, но не менее важное: у меня есть асинхронный обратный вызов, который вызывается после получения токена. Обратите внимание, что я беру его, а затем перехожу к WebBrowserTask , который позже покажет официальную страницу авторизации Twitter.

Поздравляем — теперь у вас есть все необходимое и вы готовы продолжить активную деятельность в Твиттере.