В этом примере показано, как подключить универсальные приложения к учетным записям Facebook, Google и Microsoft, используя шаблон MVVM.
Введение
В этом примере показано, как подключить универсальные приложения к учетным записям Facebook, Google и Microsoft, используя шаблон MVVM.
Построение примера кода
Для работы вам понадобятся только RTM-версии Visual Studio 2012 или Visual Studio 2013, Windows 8 либо Windows 8.1.
Описание
Недавно была опубликована статья Проверка подлинности в приложениях WP 8.0 посредством учетных записей Facebook, Google и Microsoft (MVVM). У данного примера та же цель, но объектом в этом случае являются универсальные приложения. В обоих случаях предлагается использовать шаблон MVVM.
Прежде чем приступить к рассмотрению примера, обращаю ваше внимание на то, что SDK из первого примера можно использовать и в данном случае, но тогда вам не удастся с первой попытки найти все необходимые пакеты для целевых объектов (Windows 8.1 и Windows Phone 8.1 Runtime). Давайте рассмотрим этот вопрос подробнее.
Использовались следующие пакеты:
Пакеты, которые были проанализированы:
- Facebook SDK для Windows Phone: предусмотрен только пакет для приложений из Магазина Windows 8.1, но не для Windows Phone 8.1 Runtime. Использовать версию для Windows Phone 8.0 мы не можем, поскольку пространство имен этого пакета отсутствует в Windows Phone 8.1 Runtime.
- Клиенты Google API Auth и Google APIs OAuth2: есть пакет, совместимый с приложениями из Магазина Windows 8.1, но он несколько отличается от созданного ранее примера, поскольку в API появились некоторые изменения. Для Windows Phone 8.1 Runtime пакета нет.
- Пакет Live SDK: совместим с Windows Phone 8.1 Runtime и Windows 8.1.
Следующий шаг: попытка портировать пакет для Google из Магазина приложений Windows 8.1 в среду выполнения Windows Phone 8.1 Runtime и создать логику, поскольку у них много общего кода. Затем началась самая тяжелая работа.
После нескольких попыток код начал генерировать исключение NotImplementedException, это обусловлено тем, что класс WebAuthenticationBroker для этих целей работает несколько по-другому. Есть пример, в котором показаны эти отличия. Далее представлен исходный код, который мы также увидим в данном примере.
Воспользовавшись результатами этого краткого анализа, я решил использовать инструмент WebAuthenticationBroker для проверки подлинности через учетные записи Facebook и Google, а также инструмент Live SDK — для учетных записей Microsoft.
Давайте рассмотрим данный пример.
Примечание. В этом примере используется MVVM Light и Cimbalino Toolkit.
На веб-сайте каждого поставщика необходимо получить идентификатор приложения, идентификатор клиента и секретный ключ клиента.
Константы
Прежде чем начать, укажите в файле Constant идентификаторы клиентов, секретный ключ клиента и идентификатор приложения, иначе ваше приложение не будет работать.
/// <summary>
/// Строка констант, используемых в приложении.
/// </summary>
public class Constants
{
/// <summary>
/// URL-адрес обратного вызова для Google.
/// </summary>
#if !WINDOWS_PHONE_APP
public const string GoogleCallbackUrl = "urn:ietf:wg:oauth:2.0:oob";
#else
public const string GoogleCallbackUrl = "http://localhost";
#endif
/// <summary>
/// Идентификатор приложения Facebook.
/// </summary>
public const string FacebookAppId = "<app id>";
/// <summary>
/// Идентификатор клиента Google.
/// </summary>
public const string GoogleClientId = "<client id>";
/// <summary>
/// Секретный ключ клиента Google.
/// </summary>
public const string GoogleClientSecret = "<client secret";
/// <summary>
/// Маркер имени входа.
/// </summary>
public const string LoginToken = "LoginToken";
/// <summary>
/// Поставщик Facebook.
/// </summary>
public const string FacebookProvider = "facebook";
/// <summary>
/// Поставщик Google.
/// </summary>
public const string GoogleProvider = "google";
/// <summary>
/// Поставщик Microsoft.
/// </summary>
public const string MicrosoftProvider = "microsoft";
}
Идея, лежащая в основе этого примера, аналогична проверке подлинности в приложениях WP8.0 через учетные записи Facebook, Google и Microsoft (MVVM). Есть классы с одинаковыми именами и назначением. Как и в предыдущем, в данном примере создан класс SessionService, управляющий входом и выходом из системы с учетом значения поставщика. Это замечательно, потому что в LoginView мы настраиваем кнопки для той же команды и для каждой команды устанавливаем поставщика в CommandParameter. Благодаря этому LoginView и LoginViewModel стали более простыми и понятными.
Были созданы следующие классы:
- FacebookService — содержит весь необходимый код для проверки подлинности с помощью учетной записи Facebook.
- MicrosoftService — содержит весь необходимый код для проверки подлинности с помощью учетной записи Microsoft.
- GoogleService — содержит весь необходимый код для проверки подлинности с помощью учетной записи Google.
- SessionService — вызывает методы для входа или выхода из системы с использованием указанного поставщика.
Поток
Чтобы продемонстрировать различия в потоках для каждой платформы, было создано несколько схем.
- Поток для приложений из Магазина Windows 8.1 выглядит следующим образом:
Увеличить - Поток для Windows Phone 8.1 Runtime выглядит следующим образом: — с использованием учетной записи Microsoft:
Увеличить
• Поток для Windows Phone 8.1 Runtime выглядит следующим образом: — с использованием учетной записи Microsoft:
Увеличить
Примечание. Начинайте с LoginView и следуйте по синему потоку; когда он закончится, переходите к FacebookService/GoogleService и красному потоку.
Как видите, проверка подлинности в Windows Phone 8.1 — очень сложный процесс, даже по сравнению с таковой в Магазине приложений Windows 8.1. Кроме того, этот процесс нарушает шаблон MVVM.
Этот пример создан для универсального приложения, поэтому код можно найти в общем проекте (Shared Project) и настроить каждую из используемых целевых директив (#if #else #endif). Это несколько затрудняет понимание кода, но иметь только один код в одном месте очень удобно. Здесь можно использовать частичные методы и классы, поскольку в большинстве случаев методы для Windows Phone добавляются при добавлении директив.
FacebookService
/// <summary>
/// Определение класса FacebookService.
/// </summary>
public class FacebookService : IFacebookService
{
private readonly ILogManager _logManager;
/// <summary>
/// Инициализация нового экземпляра класса <see cref="FacebookService"/>.
/// </summary>
/// <param name="logManager">
/// Менеджер журнала.
/// </param>
public FacebookService(ILogManager logManager)
{
_logManager = logManager;
}
/// <summary>
/// The login sync.
/// </summary>
/// <returns>
/// Объект <see cref="Task"/>.
/// </returns>
public async Task<Session> LoginAsync()
{
const string FacebookCallbackUrl = "https://m.facebook.com/connect/login_success.html";
var facebookUrl = "https://www.facebook.com/dialog/oauth?client_id="
+ Uri.EscapeDataString(Constants.FacebookAppId) +
"&redirect_uri=" + Uri.EscapeDataString(FacebookCallbackUrl) +
"&scope=public_profile,email&display=popup&response_type=token";
var startUri = new Uri(facebookUrl);
var endUri = new Uri(FacebookCallbackUrl);
#if WINDOWS_PHONE_APP
WebAuthenticationBroker.AuthenticateAndContinue(startUri, endUri, null, WebAuthenticationOptions.None);
return null;
#else
var webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync
(WebAuthenticationOptions.None, startUri, endUri);
return GetSession(webAuthenticationResult);
#endif
}
private void GetKeyValues(string webAuthResultResponseData, out string accessToken, out string expiresIn)
{
string responseData = webAuthResultResponseData.Substring(webAuthResultResponseData.IndexOf
("access_token", StringComparison.Ordinal));
string[] keyValPairs = responseData.Split('&');
accessToken = null;
expiresIn = null;
for (int i = 0; i < keyValPairs.Length; i++)
{
string[] splits = keyValPairs[i].Split('=');
switch (splits[0])
{
case "access_token":
accessToken = splits[1];
break;
case "expires_in":
expiresIn = splits[1];
break;
}
}
}
/// <summary>
/// Эта функция извлекает маркер доступа access_token из ответа, полученного
/// от брокера проверки подлинности,
/// и с его помощью запрашивает информацию о пользователе через интерфейс Facebook Graph API.
/// </summary>
/// <param name="accessToken">
/// Маркер доступа.
/// </param>
/// <returns>
/// Объект <see cref="Task"/>.
/// </returns>
private async Task<UserInfo> GetFacebookUserNameAsync(string accessToken)
{
var httpClient = new HttpClient();
var response = await httpClient.GetStringAsync(new Uri("https://graph.facebook.com/me?access_token="
+ accessToken));
var value = JsonValue.Parse(response).GetObject();
var facebookUserName = value.GetNamedString("name");
return new UserInfo
{
Name = facebookUserName,
};
}
/// <summary>
/// Выход из системы для этого экземпляра.
/// </summary>
public async void Logout()
{
Exception exception = null;
try
{
}
catch (Exception ex)
{
exception = ex;
}
if (exception != null)
{
await _logManager.LogAsync(exception);
}
}
#if WINDOWS_PHONE_APP
public async Task<Session> Finalize(WebAuthenticationBrokerContinuationEventArgs args)
{
Exception exception = null;
try
{
var result = args.WebAuthenticationResult;
return GetSession(result);
}
catch (Exception e)
{
exception = e;
}
await _logManager.LogAsync(exception);
return null;
}
#endif
private Session GetSession(WebAuthenticationResult result)
{
if (result.ResponseStatus == WebAuthenticationStatus.Success)
{
string accessToken;
string expiresIn;
GetKeyValues(result.ResponseData, out accessToken, out expiresIn);
return new Session
{
AccessToken = accessToken,
ExpireDate = new DateTime(long.Parse(expiresIn)),
Provider = Constants.FacebookProvider
};
}
if (result.ResponseStatus == WebAuthenticationStatus.ErrorHttp)
{
throw new Exception("Ошибка http");
}
if (result.ResponseStatus == WebAuthenticationStatus.UserCancel)
{
throw new Exception("Отменено пользователем.");
}
return null;
}
}
В классе LoginAsync присутствуют директивы, определяющие код для каждой платформы. С первой попытки было сложно найти решение, в итоге использовали нулевое значение (для Windows Phone, конечно!). Этот класс не задействован в методе выхода из системы и может быть удален. Он оставлен только для того, чтобы обеспечить целостность шаблона во всех классах. Кроме того, в WebView сеанс не сохраняется; по крайней мере, можно войти в систему под другой учетной записью, когда элемент управления LoginView появится на экране снова.
GoogleService
public class GoogleService : IGoogleService
{
private readonly ILogManager _logManager;
/// <summary>
/// Initializes a new instance of the <see cref="GoogleService"/> class.
/// </summary>
/// <param name="logManager">
/// The log manager.
/// </param>
public GoogleService(ILogManager logManager)
{
_logManager = logManager;
}
/// <summary>
/// The login async.
/// </summary>
/// <returns>
/// The <see cref="Task"/> object.
/// </returns>
public async Task<Session> LoginAsync()
{
var googleUrl = new StringBuilder();
googleUrl.Append("https://accounts.google.com/o/oauth2/auth?client_id= This link is external to TechNet Wiki. Страница откроется в новом окне. ");
googleUrl.Append(Uri.EscapeDataString(Constants.GoogleClientId));
googleUrl.Append("&scope=openid%20email%20profile");
googleUrl.Append("&redirect_uri=");
googleUrl.Append(Uri.EscapeDataString(Constants.GoogleCallbackUrl));
googleUrl.Append("&state=foobar");
googleUrl.Append("&response_type=code");
var startUri = new Uri(googleUrl.ToString());
#if !WINDOWS_PHONE_APP
var endUri = new Uri("https://accounts.google.com/o/oauth2/approval?
Это внешняя ссылка на TechNet Wiki. Страница откроется в новом окне. ");
var webAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync
(WebAuthenticationOptions.UseTitle, startUri, endUri);
return await GetSession(webAuthenticationResult);
#else
WebAuthenticationBroker.AuthenticateAndContinue(startUri, new Uri(Constants.GoogleCallbackUrl), null,
WebAuthenticationOptions.None);
return null;
#endif
}
private string GetCode(string webAuthResultResponseData)
{
// Success code=4/izytpEU6PjuO5KKPNWSB4LK3FU1c
var split = webAuthResultResponseData.Split('&');
return split.FirstOrDefault(value => value.Contains("code"));
}
/// <summary>
/// The logout.
/// </summary>
public void Logout()
{
}
#if WINDOWS_PHONE_APP
public async Task<Session> Finalize(WebAuthenticationBrokerContinuationEventArgs args)
{
Exception exception = null;
try
{
return await GetSession(args.WebAuthenticationResult);
}
catch (Exception e)
{
exception = e;
}
await _logManager.LogAsync(exception);
return null;
}
#endif
private async Task<Session> GetSession(WebAuthenticationResult result)
{
if (result.ResponseStatus == WebAuthenticationStatus.Success)
{
var code = GetCode(result.ResponseData);
var serviceRequest = await GetToken(code);
return new Session
{
AccessToken = serviceRequest.access_token,
ExpireDate = new DateTime(long.Parse(serviceRequest.expires_in)),
Id = serviceRequest.id_token,
Provider = Constants.GoogleProvider
};
}
if (result.ResponseStatus == WebAuthenticationStatus.ErrorHttp)
{
throw new Exception("Error http");
}
if (result.ResponseStatus == WebAuthenticationStatus.UserCancel)
{
throw new Exception("User Canceled.");
}
return null;
}
private static async Task<ServiceResponse> GetToken(string code)
{
const string TokenUrl = "https://accounts.google.com/o/oauth2/token
This link is external to TechNet Wiki. Страница откроется в новом окне. ";
var body = new StringBuilder();
body.Append(code);
body.Append("&client_id=");
body.Append(Uri.EscapeDataString(Constants.GoogleClientId));
body.Append("&client_secret=");
body.Append(Uri.EscapeDataString(Constants.GoogleClientSecret));
body.Append("&redirect_uri=");
body.Append(Uri.EscapeDataString(Constants.GoogleCallbackUrl));
body.Append("&grant_type=authorization_code");
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(TokenUrl))
{
Content = new StringContent(body.ToString(), Encoding.UTF8, "application/x-www-form-urlencoded")
};
var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
var serviceTequest = JsonConvert.DeserializeObject<ServiceResponse>(content);
return serviceTequest;
}
}
Здесь нет ничего нового, все так же, как в FacebookService.
Примечание. Классы FacebookService и GoogleService похожи, но запрос и ответ отличаются, поскольку эти классы не являются присоединенными.
MicrosoftService
/// <summary>
/// Класс MicrosoftService.
/// </summary>
public class MicrosoftService : IMicrosoftService
{
private readonly ILogManager _logManager;
private LiveAuthClient _authClient;
private LiveConnectSession _liveSession;
/// <summary>
/// Defines the scopes the application needs.
/// </summary>
private List<string> _scopes;
/// <summary>
/// Initializes a new instance of the <see cref="MicrosoftService"/> class.
/// </summary>
/// <param name="logManager">
/// The log manager.
/// </param>
public MicrosoftService(ILogManager logManager)
{
_scopes = new List<string> { "wl.signin", "wl.basic", "wl.offline_access" };
_logManager = logManager;
}
/// <summary>
/// The login async.
/// </summary>
/// <returns>
/// The <see cref="Task"/> object.
/// </returns>
public async Task<Session> LoginAsync()
{
Exception exception = null;
try
{
_authClient = new LiveAuthClient();
var loginResult = await _authClient.InitializeAsync(_scopes);
var result = await _authClient.LoginAsync(_scopes);
if (result.Status == LiveConnectSessionStatus.Connected)
{
_liveSession = loginResult.Session;
var session = new Session
{
AccessToken = result.Session.AccessToken,
Provider = Constants.MicrosoftProvider,
};
return session;
}
}
catch (LiveAuthException ex)
{
throw new InvalidOperationException("Login canceled.", ex);
}
catch (Exception e)
{
exception = e;
}
await _logManager.LogAsync(exception);
return null;
}
/// <summary>
/// The get user info.
/// </summary>
/// <returns>
/// The <see cref="Task"/> object.
/// </returns>
public async Task<IDictionary<string, object>> GetUserInfo()
{
Exception exception = null;
try
{
var liveClient = new LiveConnectClient(_liveSession);
LiveOperationResult operationResult = await liveClient.GetAsync("me");
return operationResult.Result;
}
catch (LiveConnectException e)
{
exception = e;
}
await _logManager.LogAsync(exception);
return null;
}
/// <summary>
/// The logout.
/// </summary>
public async void Logout()
{
if (_authClient == null)
{
_authClient = new LiveAuthClient();
var loginResult = await _authClient.InitializeAsync(_scopes);
}
if (_authClient.CanLogout)
{
_authClient.Logout();
}
}
}
Этот класс также использовался в примере «Проверка подлинности в приложениях WP8.0 через учетные записи Facebook, Google и Microsoft (MVVM)», но для его нормальной работы нужно зарегистрировать приложение в Магазине.
После этого в проекте появится еще один файл:
Примечания.
- Этот файл из примера удаляется, поскольку у каждого разработчика есть свой.
- На устройствах, где пользователь вошел в свою учетную запись Microsoft, приложение выполняет вход в систему автоматически.
SessionService
/// <summary>
/// Сеанс службы.
/// </summary>
public class SessionService : ISessionService
{
private readonly IApplicationDataService _applicationSettings;
private readonly IFacebookService _facebookService;
private readonly IMicrosoftService _microsoftService;
private readonly IGoogleService _googleService;
private readonly ILogManager _logManager;
/// <summary>
/// Initializes a new instance of the <see cref="SessionService" /> class.
/// </summary>
/// <param name="applicationSettings">Настройки приложения.</param>
/// <param name="facebookService">Служба Facebook.</param>
/// <param name="microsoftService">Служба Microsoft.</param>
/// <param name="googleService">Служба Google.</param>
/// <param name="logManager">Диспетчер журнала.</param>
public SessionService(IApplicationDataService applicationSettings,
IFacebookService facebookService,
IMicrosoftService microsoftService,
IGoogleService googleService, ILogManager logManager)
{
_applicationSettings = applicationSettings;
_facebookService = facebookService;
_microsoftService = microsoftService;
_googleService = googleService;
_logManager = logManager;
}
/// <summary>
/// Получает сеанс.
/// </summary>
/// <returns>The session object.</returns>
public Session GetSession()
{
var expiryValue = DateTime.MinValue;
string expiryTicks = LoadEncryptedSettingValue("session_expiredate");
if (!string.IsNullOrWhiteSpace(expiryTicks))
{
long expiryTicksValue;
if (long.TryParse(expiryTicks, out expiryTicksValue))
{
expiryValue = new DateTime(expiryTicksValue);
}
}
var session = new Session
{
AccessToken = LoadEncryptedSettingValue("session_token"),
Id = LoadEncryptedSettingValue("session_id"),
ExpireDate = expiryValue,
Provider = LoadEncryptedSettingValue("session_provider")
};
_applicationSettings.LocalSettings[Constants.LoginToken] = true;
return session;
}
/// <summary>
/// The save session.
/// </summary>
/// <param name="session">
/// Сеанс.
/// </param>
private void Save(Session session)
{
SaveEncryptedSettingValue("session_token", session.AccessToken);
SaveEncryptedSettingValue("session_id", session.Id);
SaveEncryptedSettingValue("session_expiredate", session.ExpireDate.Ticks.ToString
(CultureInfo.InvariantCulture));
SaveEncryptedSettingValue("session_provider", session.Provider);
_applicationSettings.LocalSettings[Constants.LoginToken] = true;
}
/// <summary>
/// The clean session.
/// </summary>
private void CleanSession()
{
_applicationSettings.LocalSettings.Remove("session_token");
_applicationSettings.LocalSettings.Remove("session_id");
_applicationSettings.LocalSettings.Remove("session_expiredate");
_applicationSettings.LocalSettings.Remove("session_provider");
_applicationSettings.LocalSettings.Remove(Constants.LoginToken);
}
/// <summary>
/// The login async.
/// </summary>
/// <param name="provider">
/// Поставщик.
/// </param>
/// <returns>
/// The <see cref="Task"/> object.
/// </returns>
public async Task<bool?> LoginAsync(string provider)
{
Provider = provider;
Exception exception;
try
{
Session session = null;
switch (provider)
{
case Constants.FacebookProvider:
session = await _facebookService.LoginAsync();
break;
case Constants.MicrosoftProvider:
session = await _microsoftService.LoginAsync();
break;
case Constants.GoogleProvider:
session = await _googleService.LoginAsync();
break;
}
if (session == null)
{
return null;
}
Save(session);
return true;
}
catch (Exception ex)
{
exception = ex;
}
await _logManager.LogAsync(exception);
return false;
}
/// <summary>
/// Gets or sets the provider.
/// </summary>
/// <value>
/// The provider.
/// </value>
public string Provider { get; set; }
/// <summary>
/// The logout.
/// </summary>
public async void Logout()
{
Exception exception = null;
try
{
var session = GetSession();
switch (session.Provider)
{
case Constants.FacebookProvider:
_facebookService.Logout();
break;
case Constants.MicrosoftProvider:
_microsoftService.Logout();
break;
case Constants.GoogleProvider:
_googleService.Logout();
break;
}
CleanSession();
}
catch (Exception ex)
{
exception = ex;
}
if (exception != null)
{
await _logManager.LogAsync(exception);
}
}
WINDOWS_PHONE_APP
public async Task<bool> Finalize(WebAuthenticationBrokerContinuationEventArgs args)
{
Exception exception = null;
try
{
Session session = null;
switch (Provider)
{
case Constants.FacebookProvider:
session = await _facebookService.Finalize(args);
break;
case Constants.GoogleProvider:
session = await _googleService.Finalize(args);
break;
}
Save(session);
return true;
}
catch (Exception e)
{
exception = e;
}
await _logManager.LogAsync(exception);
return false;
}
if
/// <summary>
/// Loads an encrypted setting value for a given key.
/// </summary>
/// <param name="key">
/// The key to load.
/// </param>
/// <returns>
/// The value of the key.
/// </returns>
private string LoadEncryptedSettingValue(string key)
{
string value = null;
var protectedBytes = _applicationSettings.LocalSettings[key];
if (protectedBytes != null)
{
// todo use DataProtectionProvider
// byte[] valueBytes = ProtectedData.Unprotect(protectedBytes, null);
// value = Encoding.UTF8.GetString(valueBytes, 0, valueBytes.Length);
value = protectedBytes.ToString();
}
return value;
}
/// <summary>
/// Saves a setting value against a given key, encrypted.
/// </summary>
/// <param name="key">
/// The key to save against.
/// </param>
/// <param name="value">
/// The value to save against.
/// </param>
/// <exception cref="System.ArgumentOutOfRangeException">
/// The key or value provided is unexpected.
/// </exception>
private void SaveEncryptedSettingValue(string key, string value)
{
if (!string.IsNullOrWhiteSpace(key) && !string.IsNullOrWhiteSpace(value))
{
// todo use DataProtectionProvider
// byte[] valueBytes = Encoding.UTF8.GetBytes(value);
// var dataProtectionProvider = new DataProtectionProvider();
// // Encrypt the value by using the Protect() method.
// byte[] protectedBytes = await dataProtectionProvider.ProtectAsync(valueBytes);
_applicationSettings.LocalSettings[key] = value;
}
}
}
<code>
This class will return null if the session is null, we need to be
aware that there isn´t any error
and the session is returned or if it is null, the process will be
done by the Finalize method.
Here is the class diagram with all classes and interfaces created in this sample:
[[File:universal8.jpg]]
Now to build the User Interface and because we are
using the MVVM pattern, it created a LoginViewModel
to do the binding to the LoginView.
=== The LoginViewModel ===
<code csharp>
/// <summary>
/// The login view model.
/// </summary>
public class LoginViewModel : ViewModelBase
{
private readonly ILogManager _logManager;
private readonly IMessageBoxService _messageBox;
private readonly INetworkInformationService _networkInformationService;
private readonly INavigationService _navigationService;
private readonly ISessionService _sessionService;
private bool _inProgress;
/// <summary>
/// Initializes a new instance of the <see cref="LoginViewModel"/> class.
/// </summary>
/// <param name="navigationService">
/// The navigation service.
/// </param>
/// <param name="sessionService">
/// The session service.
/// </param>
/// <param name="messageBox">
/// The message box.
/// </param>
/// <param name="networkInformationService">
/// The network connection.
/// </param>
/// <param name="logManager">
/// The log manager.
/// </param>
public LoginViewModel(INavigationService navigationService,
ISessionService sessionService,
IMessageBoxService messageBox,
INetworkInformationService networkInformationService,
ILogManager logManager)
{
_navigationService = navigationService;
_sessionService = sessionService;
_messageBox = messageBox;
_networkInformationService = networkInformationService;
_logManager = logManager;
LoginCommand = new RelayCommand<string>(LoginAction);
}
/// <summary>
/// Gets the navigation service.
/// </summary>
/// <value>
/// The navigation service.
/// </value>
public INavigationService NavigationService
{
get { return _navigationService; }
}
/// <summary>
/// Gets or sets a value indicating whether in progress.
/// </summary>
/// <value>
/// The in progress.
/// </value>
public bool InProgress
{
get { return _inProgress; }
set { Set(() => InProgress, ref _inProgress, value); }
}
/// <summary>
/// Gets the facebook login command.
/// </summary>
/// <value>
/// The facebook login command.
/// </value>
public ICommand LoginCommand { get; private set; }
/// <summary>
/// Facebook's login action.
/// </summary>
/// <param name="provider">
/// Поставщик.
/// </param>
private async void LoginAction(string provider)
{
Exception exception = null;
bool isToShowMessage = false;
try
{
if (!_networkInformationService.IsNetworkAvailable)
{
await _messageBox.ShowAsync("There isn´t network connection.",
"Authentication Sample",
new List<string> { "Ok" });
return;
}
if (Constants.GoogleClientId.Contains("<") || Constants.GoogleClientSecret.Contains("<"))
{
await _messageBox.ShowAsync("Is missing the google client id and client secret. Search for Constant.cs file.",
"Authentication Sample",
new List<string> { "Ok" });
return;
}
if (Constants.FacebookAppId.Contains("<"))
{
await _messageBox.ShowAsync("Is missing the facebook client id.
Search for Constant.cs file.",
"Authentication Sample",
new List<string> { "Ok" });
return;
}
InProgress = true;
var auth = await _sessionService.LoginAsync(provider);
if (auth == null)
{
return;
}
if (!auth.Value)
{
await ShowMessage();
}
else
{
_navigationService.Navigate<MainView>();
InProgress = false;
}
InProgress = false;
}
catch (Exception ex)
{
InProgress = false;
exception = ex;
isToShowMessage = true;
}
if (isToShowMessage)
{
await _messageBox.ShowAsync("Application fails.",
"Authentication Sample",
new List<string> { "Ok" });
}
if (exception != null)
{
await _logManager.LogAsync(exception);
}
}
private async Task ShowMessage()
{
await _messageBox.ShowAsync("Wasn´t possible to complete the login.",
"Authentication Sample",
new List<string>
{
"Ok"
});
}
#if WINDOWS_PHONE_APP
public async void Finalize(WebAuthenticationBrokerContinuationEventArgs args)
{
var result = await _sessionService.Finalize(args);
if (!result)
{
await ShowMessage();
}
else
{
_navigationService.Navigate<MainView>();
InProgress = false;
}
}
#endif
}
Примечание. В LoginAction параметр Provider — это значение параметра CommandParameter, возвращаемого командой LoginCommand; эти настройки выполняются на странице входа в систему.
В завершение рассмотрим код страницы.
LoginPage.xaml
<Page
x:Class="AuthenticationSample.UniversalApps.Views.LoginView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="using:Cimbalino.Toolkit.Converters"
mc:Ignorable="d">
<Page.DataContext>
<Binding Mode="OneWay"
Path="LoginViewModel"
Source="{StaticResource Locator}" />
</Page.DataContext>
<Page.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Page.Resources>
<!-- LayoutRoot is the root grid where all page content is placed -->
<Grid x:Name="LayoutRoot" Background="LightGray">
<Grid.RowDefinitions>
<RowDefinition Height="{StaticResource HeaderHeigth}" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- TitlePanel contains the name of the application and page title -->
<StackPanel x:Name="TitlePanel"
Margin="{StaticResource HeaderMargin}"
VerticalAlignment="Center" Grid.Row="0">
<TextBlock FontSize="30"
Foreground="Black"
Text="Login"/>
</StackPanel>
<!-- ContentPanel - place additional content here -->
<Grid x:Name="ContentPanel"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="80" />
<RowDefinition Height="80" />
<RowDefinition Height="80" />
<RowDefinition Height="80" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
FontSize="20"
Foreground="Black"
Text="Use your account"/>
<Button Grid.Row="1" Width="300"
Margin="10"
Command="{Binding LoginCommand}"
CommandParameter="facebook"
Background="{StaticResource FacebookBlueBackgroundBrush}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="ï‚š"
VerticalAlignment="Center"
FontFamily="/Fonts/fontawesome-webfont.ttf#FontAwesome"
HorizontalAlignment="Left"/>
<TextBlock Margin="10,0,0,0" Text="Facebook"
HorizontalAlignment="Center"/>
</StackPanel>
</Button>
<Button Grid.Row="3"
Margin="10" Width="300"
Command="{Binding LoginCommand}"
CommandParameter="microsoft"
Background="{StaticResource MicrosoftBlueBackgroundBrush}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="ï…º"
VerticalAlignment="Center"
FontFamily="/Fonts/fontawesome-webfont.ttf#FontAwesome"
HorizontalAlignment="Left"/>
<TextBlock Margin="10,0,0,0" Text="Microsoft"
HorizontalAlignment="Center"/>
</StackPanel>
</Button>
<Button Grid.Row="2" Width="300"
Margin="10"
Command="{Binding LoginCommand}"
CommandParameter="google"
Background="{StaticResource GoogleRedBackgroundBrush}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text=""
VerticalAlignment="Center"
FontFamily="/Fonts/fontawesome-webfont.ttf#FontAwesome"
HorizontalAlignment="Left"/>
<TextBlock Margin="10,0,0,0" Text="Google"
HorizontalAlignment="Center"/>
</StackPanel>
</Button>
</Grid>
<Grid Visibility="{Binding InProgress, Converter={StaticResource BooleanToVisibilityConverter}}"
Grid.Row="0"
Grid.RowSpan="2">
<Rectangle
Fill="Black"
Opacity="0.75" />
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Auth..." />
<ProgressBar IsIndeterminate="True" IsEnabled="True" Margin="0,60,0,0"/>
</Grid>
</Grid>
</Page>
Чтобы можно было использовать одну и ту же страницу в приложениях для Windows Phone 8.1 Runtime и Магазина Windows 8.1, необходим стиль.
LoginView.xaml
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
#if WINDOWS_PHONE_APP
public sealed partial class LoginView : Page, IWebAuthenticationContinuable
#else
public sealed partial class LoginView : Page
#endif
{
/// <summary>
/// Initializes a new instance of the <see cref="LoginView"/> class.
/// </summary>
public LoginView()
{
InitializeComponent();
}
/// <summary>
/// The on navigated to.
/// </summary>
/// <param name="e">
/// The e.
/// </param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var viewModel = (LoginViewModel)DataContext;
viewModel.NavigationService.RemoveBackEntry();
base.OnNavigatedTo(e);
}
#if WINDOWS_PHONE_APP
/// <summary>
/// Continues the web authentication.
/// </summary>
/// <param name="args">The <see cref="Windows.ApplicationModel.Activation.
WebAuthenticationBrokerContinuationEventArgs"/>
instance containing the event data.</param>
public void ContinueWebAuthentication(Windows.ApplicationModel.Activation.
WebAuthenticationBrokerContinuationEventArgs args)
{
var viewModel = (LoginViewModel)DataContext;
viewModel.Finalize(args);
}
#endif
}
Заключение
В заключение хочу обратить ваше внимание на тот факт, что потоки для каждой платформы полностью отличаются, что определенным образом влияет на разработку и совместное использование кода. Возможно, по этой причине ни один пакет для Windows Phone 8.1 не использует проверку подлинности.
Исходный код
Исходный код можно скачать здесь.
Файлы исходного кода
- IFacebookService — интерфейс для FacebookService
- IGoogleService — интерфейс для GoogleService
- ILogManager — интерфейс для LogManager
- IMicrosoftService — интерфейс для MicrosoftService
- ISessionProvider — интерфейс для всех поставщиков (общие методы)
- ISessionService — для SessionService
- Session — класс для сохранения информации о сеансе (поставщик, маркер, срок действия)
- FacebookService — класс, содержащий всю логику для входа и выхода из системы с использованием поставщика Facebook
- GoogleService — класс, содержащий всю логику для входа и выхода из системы с использованием поставщика Google
- MicrosoftService — класс, содержащий всю логику для входа и выхода из системы с использованием поставщика Microsoft
- SessionService — класс для управления входом и выходом из системы (он будет использовать всех описанных выше поставщиков)
- LoginViewModel — класс для привязки к LoginView.xaml
- LoginView — класс, который выводит страницу для входа в систему
- MainViewModel — класс для привязки к MainView.xaml
- MainView — класс, который выводит страницу после успешного входа в систему
- ViewModelLocator — класс, содержащий статические ссылки на модель представления в приложении и выступающий в качестве точки входа для привязок
Построение примера кода
Запустите Visual Studio Express 2012 для Windows 8 и выберите пункт меню File > Open > Project/Solution (Файл > Открыть > Проект/Решение). Откройте папку, в которую распаковали архив с примером. Дважды щелкните по файлу решения Visual Studio Express 2012 для Windows 8 (.sln). Нажмите F7 или Build > Build Solution (Построение > Построение решения) для построения примера кода.
Примечание. Вы можете работать с Visual Studio 2013 в Windows 8.1.
Запуск примера
Для отладки и последующего запуска приложения нажмите F5 или Debug > Start Debugging (Отладка > Начать отладку). Для запуска приложения без отладки нажмите Ctrl+F5 или Debug > Start Debugging (Отладка > Запуск без отладки).
Выходные данные
Выходные данные для каждого целевого объекта:
Приложение из Магазина Windows 8.1
Увеличить
Приложение Windows Phone 8.1