Вопрос из двух частей: При запуске My Xamarin.Forms app.cs переходит на страницу входа в систему (ContentPage) с помощью кнопки. Я нажимаю кнопку и успешно захожу в систему с помощью этого обработчика событий в коде приложения:
async void OnLoginButtonClicked(object sender, EventArgs e)
{
try
{
bool authenticated = await App.AuthenticationProvider.LoginAsync();
if (authenticated)
{
Application.Current.MainPage = new PapMobLandingPage();
}
else
{
await DisplayAlert("Authentication", "Authentication", "OK");
}
}
catch (MsalException ex)
{
if (ex.ErrorCode == "authentication_canceled")
{
await DisplayAlert("Authentication", "Authentication was cancelled by the user.", "OK");
}
else
{
await DisplayAlert("An error has occurred", "Exception message: " + ex.Message, "OK");
}
}
catch (Exception ex)
{
await DisplayAlert("Authentication", "Authentication failed in a big way. Exception: " + ex.Message, "OK");
}
}
}
Затем я перенаправляюсь на страницу 2 (PapMobLandingPage), на которой также есть кнопка. Я нажимаю эту кнопку PapMobLandingPage и перенаправляюсь на страницу TabbedPage с подробной информацией о зарегистрированном пользователе, летящем прямо из моей базы данных SQL Azure, без проблем. Все отлично до сих пор! Вот обработчик события:
public async void GoToTabbedPage(object sender, EventArgs args)
{
await Xamarin.Forms.Application.Current.MainPage.Navigation.PushModalAsync(new BotInTabbedPage());
}
Вопрос 1. В коде BotInTabbedPage (TabbedPage) нет кода OnAppearing (), который проверяет, вошел ли пользователь в систему ... не происходит получение токенов при инициализации TabbedPage, так как приложение узнает что я зашел на эту страницу ??
Я посмотрел на похожий вопрос от @Creepin и, используя ссылку, не мог понять ответ на его первоначальный вопрос о том, нужно ли вам аутентифицировать каждую страницу в отдельности. Ссылка здесь:
использование AcquireTokenSilentAsync
Причина, по которой я задаю вопрос 1, заключается в том, что приведенная выше страница с вкладками - это один из шести вариантов, которые пользователь получает при входе в систему, поэтому мне потребовалась страница панели мониторинга (на основе MVVM) с шестью плитками. Когда я вставляю это между Page 2 (PapMobLandingPage) и BotInTabbedPage (TabbedPage) и нажимаю на плитку TabbedPage, команда tap, связанная с плиткой, переводит меня в BotInTabbedPage (TabbedPage) ... НО ...
НЕТ ДАННЫХ КЛИЕНТА! Я ЗАПИСАЛСЯ!
Итак, подведем итог:
Страница входа -> PapMobLandingPage -> BotInTabbedPage = Остается аутентифицированным.
Страница входа -> PapMobLandingPage -> Панель инструментов -> BotInTabbedPage = Отключить аутентификацию.
Если я использую «ванильный» ContentPage с кнопкой:
Страница входа -> PapMobLandingPage -> ContentPage -> BotInTabbedPage = Проверка подлинности остается!
Итак, второй вопрос: кто-нибудь знает, почему?
Когда я говорю, что панель мониторинга основана на MVVM, я имею в виду, что она имеет ContentPage XAML, код .cs позади, со связываниями значений с моделью представления, которая также имеет шаблон модели представления и базу шаблона. Это код:
Панель инструментов XAML:
<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:PapWine;assembly=PapWine"
xmlns:artina="clr-namespace:UXDivers.Artina.Shared;assembly=UXDivers.Artina.Shared"
x:Class="PapWine.DashboardTaskMultipleTilesPage"
BackgroundColor="Black"
Title="{ artina:Translate PageTitleDashboardTaskMultipleTiles }">
<ContentPage.Resources>
<ResourceDictionary>
<artina:BoolMemberTemplateSelector
x:Key="Selector"
MemberName="IsNotification">
<artina:BoolMemberTemplateSelector.TrueDataTemplate>
<DataTemplate>
<local:DashboardAppNotificationItemTemplate
WidthRequest="145"
HeightRequest="145" />
</DataTemplate>
</artina:BoolMemberTemplateSelector.TrueDataTemplate>
<artina:BoolMemberTemplateSelector.FalseDataTemplate>
<DataTemplate>
<local:TaskTilesItemTemplate
ShowBackgroundImage="true"
ShowBackgroundColor="true"
ShowiconColoredCircleBackground="false"
TextColor="{ DynamicResource DashboardIconColor }"
WidthRequest="145"
HeightRequest="145"
/>
</DataTemplate>
</artina:BoolMemberTemplateSelector.FalseDataTemplate>
</artina:BoolMemberTemplateSelector>
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView
Orientation="Both">
<artina:GridOptionsView
WidthRequest="320"
Margin="0"
Padding="10"
ColumnSpacing="10"
RowSpacing="10"
ColumnCount="2"
ItemsSource="{Binding DashboardTaskMultipleTilesList}"
ItemTemplate="{StaticResource Selector}"
/>
</ScrollView>
</ContentPage>
Панель инструментов XAML.cs
using Microsoft.Identity.Client;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;
namespace PapWine
{
public partial class DashboardTaskMultipleTilesPage : ContentPage
{
public DashboardTaskMultipleTilesPage()
{
InitializeComponent();
NavigationPage.SetHasNavigationBar(this, true);
BindingContext = new DashboardTaskMultipleTilesViewModel();
}
protected override async void OnAppearing()
{
base.OnAppearing();
PublicClientApplication PCA = new PublicClientApplication(Constants.ClientID, Constants.Authority);
IEnumerable<IAccount> accounts = await PCA.GetAccountsAsync();
AuthenticationResult authenticationResult = await PCA.AcquireTokenSilentAsync(Constants.Scopes, GetAccountByPolicy(accounts, Constants.PolicySignUpSignIn), Constants.Authority, false);
JObject user = ParseIdToken(authenticationResult.IdToken);
var currentuseroid = user["oid"]?.ToString();
}
private IAccount GetAccountByPolicy(IEnumerable<IAccount> accounts, string policy)
{
foreach (var account in accounts)
{
string userIdentifier = account.HomeAccountId.ObjectId.Split('.')[0];
if (userIdentifier.EndsWith(policy.ToLower())) return account;
}
return null;
}
JObject ParseIdToken(string idToken)
{
// Get the piece with actual user info
idToken = idToken.Split('.')[1];
idToken = Base64UrlDecode(idToken);
return JObject.Parse(idToken);
}
string Base64UrlDecode(string str)
{
str = str.Replace('-', '+').Replace('_', '/');
str = str.PadRight(str.Length + (4 - str.Length % 4) % 4, '=');
var byteArray = Convert.FromBase64String(str);
var decoded = Encoding.UTF8.GetString(byteArray, 0, byteArray.Count());
return decoded;
}
}
}
Панель управления Модель:
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace PapWine
{
public class DashboardTaskMultipleTilesViewModel : ObservableObject
{
private List<DashboardTaskMultipleTileItem> _dashboardTaskMultipleTilesList;
public DashboardTaskMultipleTilesViewModel()
: base(listenCultureChanges: true)
{
LoadData();
}
public List<DashboardTaskMultipleTileItem> DashboardTaskMultipleTilesList
{
get { return _dashboardTaskMultipleTilesList; }
set { SetProperty(ref _dashboardTaskMultipleTilesList, value); }
}
protected override void OnCultureChanged(CultureInfo culture)
{
LoadData();
}
public async Task LogMeOut()
{
bool loggedOut = await App.AuthenticationProvider.LogoutAsync();
if (loggedOut)
{
Application.Current.MainPage = new LandingPagePreLogin();
}
}
private void LoadData()
{
DashboardTaskMultipleTilesList = new List<DashboardTaskMultipleTileItem>
{
//1 line
new DashboardTaskMultipleTileItem
{
Title = "Log Out",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeFont.Lock,
IconColour = "White"
},
new DashboardTaskMultipleTileItem
{
Title = "User Settings",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeFont.Gear,
IconColour = "White"
},
//2 line
new DashboardTaskMultipleTileItem
{
Title = "User Info",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeFont.User,
Badge = 12,
IconColour = "White"
},
new DashboardTaskMultipleTileItem
{
Title = "Papillon Shop",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeWeb511Font.store,
Badge = 2,
IconColour = "White"
},
//3 line
new DashboardTaskMultipleTileItem
{
Title = "Check Bottles In",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeFont.Book,
IconColour = "White"
},
new DashboardTaskMultipleTileItem
{
Title = "Lay Bottles Down",
Body = "",
Avatar = "",
BackgroundColor = "transparent",
ShowBackgroundColor = false,
IsNotification = false,
BackgroundImage = "Tiles/DarkBlackTile.jpg",
Icon = FontAwesomeFont.Bed,
Badge = 2,
IconColour = "White"
},
};
}
}
public class DashboardTaskMultipleTileItem
{
public string Title { get; set; }
public string Body { get; set; }
public string Avatar { get; set; }
public string BackgroundColor { get; set; }
public string BackgroundImage { get; set; }
public bool ShowBackgroundColor { get; set; }
public bool IsNotification { get; set; }
public string Icon { get; set; }
public int Badge { get; set; }
public string NavigPage { get; set; }
private Xamarin.Forms.Command _tapCommand;
public Xamarin.Forms.Command TapCommand
{
get
{
if (_tapCommand == null)
{
switch (this.Title) {
case "Log Out":
App.AuthenticationProvider.LogoutAsync();
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage.Navigation.PushModalAsync(new LogoutSuccessPage()));
break;
case "User Settings":
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new UserSettingsPage()));
break;
case "User Info":
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new UserProfilePage()));
break;
case "Papillon Shop":
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new ProductFamilyMultipleTilesPage()));
break;
case "Check Bottles In":
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage = new SignBottlesIn());
break;
case "Check Bottles Out":
_tapCommand = new Xamarin.Forms.Command(() =>
Xamarin.Forms.Application.Current.MainPage = new SignBottlesIn());
break;
}
}
return _tapCommand;
}
}
}
}
Шаблон элементов плиток:
using Xamarin.Forms;
namespace PapWine
{
public partial class TaskTilesItemTemplate : TaskTilesItemTemplateBase
{
public TaskTilesItemTemplate()
{
InitializeComponent();
}
private async void OnTileTapped(object sender, ItemTappedEventArgs e)
{
if (e.Item == null)
return;
var content = e.Item as DashboardTaskMultipleTileItem;
//await Navigation.PushAsync(new LayBottlesDown()); //pass content if you want to pass the clicked item object to another page
}
}
}
Шаблон элемента плиток База:
using System;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace PapWine
{
public class TaskTilesItemTemplateBase : ContentView
{
public uint animationDuration = 250;
public bool _processingTag = false;
public static readonly BindableProperty ShowBackgroundImageProperty =
BindableProperty.Create(
nameof(ShowBackgroundImage),
typeof(bool),
typeof(TaskTilesItemTemplate),
true,
defaultBindingMode: BindingMode.OneWay
);
public bool ShowBackgroundImage
{
get { return (bool)GetValue(ShowBackgroundImageProperty); }
set { SetValue(ShowBackgroundImageProperty, value); }
}
public static readonly BindableProperty ShowBackgroundColorProperty =
BindableProperty.Create (
nameof( ShowBackgroundColor ),
typeof ( bool ),
typeof ( TaskTilesItemTemplate ),
false,
defaultBindingMode : BindingMode.OneWay
);
public bool ShowBackgroundColor {
get { return ( bool )GetValue( ShowBackgroundColorProperty ); }
set { SetValue ( ShowBackgroundColorProperty, value ); }
}
public static readonly BindableProperty ShowiconColoredCircleBackgroundProperty =
BindableProperty.Create (
nameof( ShowiconColoredCircleBackground ),
typeof ( bool ),
typeof (TaskTilesItemTemplate),
true,
defaultBindingMode : BindingMode.OneWay
);
public bool ShowiconColoredCircleBackground {
get { return ( bool )GetValue( ShowiconColoredCircleBackgroundProperty ); }
set { SetValue ( ShowiconColoredCircleBackgroundProperty, value ); }
}
public static readonly BindableProperty TextColorProperty =
BindableProperty.Create (
nameof( TextColor ),
typeof ( Color ),
typeof (TaskTilesItemTemplate),
defaultValue : Color.White,
defaultBindingMode : BindingMode.OneWay
);
public Color TextColor {
get { return ( Color )GetValue( TextColorProperty ); }
set { SetValue ( TextColorProperty, value ); }
}
//added start
public Color IconColour
{
get { return (Color)GetValue(TextColorProperty); }
set { SetValue(TextColorProperty, value); }
}
//added end
public async void OnWidgetTapped(object sender, EventArgs e)
{
if (_processingTag)
{
return;
}
_processingTag = true;
try{
await AnimateItem (this, animationDuration );
await SamplesListFromCategoryPage.NavigateToCategory ((SampleCategory)BindingContext, Navigation);
}finally{
_processingTag = false;
}
}
private async Task AnimateItem(View uiElement, uint duration ){
var originalOpacity = uiElement.Opacity;
await uiElement.FadeTo(.5, duration/2, Easing.CubicIn);
await uiElement.FadeTo(originalOpacity, duration/2, Easing.CubicIn);
}
}
}
LoginAsync выглядит так:
public async Task<bool> LoginAsync(bool useSilent = false)
{
bool success = false;
//AuthenticationResult authResult = null;
try
{
AuthenticationResult authenticationResult;
if (useSilent)
{
authenticationResult = await ADB2CClient.AcquireTokenSilentAsync(
Constants.Scopes,
GetAccountByPolicy(await ADB2CClient.GetAccountsAsync(), Constants.PolicySignUpSignIn),
Constants.Authority,
false);
UpdateUserInfo(authenticationResult);
}
else
{
authenticationResult = await ADB2CClient.AcquireTokenAsync(
Constants.Scopes,
GetAccountByPolicy(await ADB2CClient.GetAccountsAsync(), Constants.PolicySignUpSignIn),
App.UiParent);
}
if (User == null)
{
var payload = new JObject();
if (authenticationResult != null && !string.IsNullOrWhiteSpace(authenticationResult.IdToken))
{
payload["access_token"] = authenticationResult.IdToken;
}
User = await TodoItemManager.DefaultManager.CurrentClient.LoginAsync(
MobileServiceAuthenticationProvider.WindowsAzureActiveDirectory,
payload);
success = true;
}
}