This commit is contained in:
Brandon Minnick 2019-08-29 14:19:48 -07:00
Родитель a40d649f38
Коммит 8ace522b1a
19 изменённых файлов: 502 добавлений и 96 удалений

2
src/XamarinAzureChallenge.sln Executable file → Normal file
Просмотреть файл

@ -7,7 +7,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamarinAzureChallenge.Andro
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamarinAzureChallenge.iOS", "XamarinAzureChallenge\XamarinAzureChallenge.iOS\XamarinAzureChallenge.iOS.csproj", "{2C81F630-FC66-4BEC-A800-56A0C743F3D3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XamarinAzureChallenge", "XamarinAzureChallenge\XamarinAzureChallenge\XamarinAzureChallenge.csproj", "{906E148B-01D5-4106-9C5B-404A8B655069}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamarinAzureChallenge", "XamarinAzureChallenge\XamarinAzureChallenge\XamarinAzureChallenge.csproj", "{906E148B-01D5-4106-9C5B-404A8B655069}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XamarinAzureChallenge.Functions", "XamarinAzureChallenge\XamarinAzureChallenge.Functions\XamarinAzureChallenge.Functions.csproj", "{2A44CE99-0A65-4AE7-897F-D2EA679A42B7}"
EndProject

Просмотреть файл

@ -1,11 +1,15 @@
using Android.App;
using System;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Runtime;
using XamarinAzureChallenge.Pages;
namespace XamarinAzureChallenge.Droid
{
[Activity(Label = "XamarinAzureChallenge", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[Activity(Label = "XamarinAzureChallenge", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ScreenOrientation = ScreenOrientation.Portrait)]
[IntentFilter(new string[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable }, DataSchemes = new[] { "xamarinazurechallenge" })]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
@ -25,6 +29,29 @@ namespace XamarinAzureChallenge.Droid
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
if (Intent?.Data is Android.Net.Uri callbackUri)
ExecuteCallbackUri(callbackUri);
}
async void ExecuteCallbackUri(Android.Net.Uri callbackUri)
{
if (Xamarin.Forms.Application.Current.MainPage is Xamarin.Forms.NavigationPage navigationPage)
{
navigationPage.Pushed += HandlePushed;
await navigationPage.PushAsync(new UserDataPage());
async void HandlePushed(object sender, Xamarin.Forms.NavigationEventArgs e)
{
if (e.Page is UserDataPage)
{
navigationPage.Pushed -= HandlePushed;
await AzureAuthenticationService.AuthorizeSession(new Uri(callbackUri.ToString()));
}
}
}
}
}
}

Просмотреть файл

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.XamarinAzureChallenge">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.xamarin.XamarinAzureChallenge">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:label="XamarinAzureChallenge.Android"></application>

Просмотреть файл

@ -17,6 +17,7 @@
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
@ -33,7 +34,7 @@
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>pdbonly</DebugType>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>

Просмотреть файл

@ -23,7 +23,7 @@ namespace Microsoft.XamarinAzureChallenge.AZF
private static HttpClient Client => clientHolder.Value;
[FunctionName(nameof(SubmitChallengeFunction))]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post")][FromBody] User user, ILogger log, ExecutionContext context)
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = nameof(SubmitChallengeFunction) + "/{azureSubscriptionId}")][FromBody] User user, ILogger log, ExecutionContext context, string azureSubscriptionId)
{
log.LogInformation("HTTP Triggered");

Просмотреть файл

@ -0,0 +1,11 @@
using System;
namespace XamarinAzureChallenge.Shared
{
public class AzureAuthenticationCompletedEventArgs : EventArgs
{
public AzureAuthenticationCompletedEventArgs(bool isAuthenticationSuccessful) =>
IsAuthenticationSuccessful = isAuthenticationSuccessful;
public bool IsAuthenticationSuccessful { get; }
}
}

Просмотреть файл

@ -0,0 +1,7 @@
namespace XamarinAzureChallenge.ViewModels
{
public class AzureClientIdModel
{
public string ClientId { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,32 @@
using System;
using Newtonsoft.Json;
namespace XamarinAzureChallenge.Shared
{
public class AzureToken
{
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public long ExpiresIn { get; set; }
[JsonProperty("expires_on")]
public long ExpiresOn { get; set; }
[JsonProperty("resource")]
public Uri Resource { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
[JsonProperty("scope")]
public string Scope { get; set; }
[JsonProperty("id_token")]
public string IdToken { get; set; }
}
}

Просмотреть файл

@ -10,6 +10,9 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Models\User.cs" Condition=" '$(EnableDefaultCompileItems)' == 'true' " />
<Compile Include="$(MSBuildThisFileDirectory)Models\AzureToken.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Models\AzureClientIdModel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Models\AzureAuthenticationCompletedEventArgs.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)Models\" />

Просмотреть файл

@ -1,5 +1,10 @@
using Foundation;
using System;
using System.Threading.Tasks;
using Foundation;
using SafariServices;
using UIKit;
using Xamarin.Forms;
using XamarinAzureChallenge.ViewModels;
namespace XamarinAzureChallenge.iOS
{
@ -13,5 +18,53 @@ namespace XamarinAzureChallenge.iOS
return base.FinishedLaunching(uiApplication, launchOptions);
}
public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options)
{
var callbackUri = new Uri(url.AbsoluteString);
HandleCallbackUri(callbackUri);
return true;
}
async void HandleCallbackUri(Uri callbackUri)
{
await CloseSFSafariViewController();
await AzureAuthenticationService.AuthorizeSession(callbackUri);
}
async Task CloseSFSafariViewController()
{
while (await GetVisibleViewController() is SFSafariViewController sfSafariViewController)
{
await Device.InvokeOnMainThreadAsync(async () =>
{
await sfSafariViewController.DismissViewControllerAsync(true);
sfSafariViewController.Dispose();
sfSafariViewController = null;
});
}
}
Task<UIViewController> GetVisibleViewController()
{
return Device.InvokeOnMainThreadAsync(() =>
{
var rootController = UIApplication.SharedApplication.KeyWindow.RootViewController;
switch (rootController.PresentedViewController)
{
case UINavigationController navigationController:
return navigationController.TopViewController;
case UITabBarController tabBarController:
return tabBarController.SelectedViewController;
case null:
return rootController;
default:
return rootController.PresentedViewController;
}
});
}
}
}

Просмотреть файл

@ -2,41 +2,50 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>MinimumOSVersion</key>
<string>8.0</string>
<key>CFBundleDisplayName</key>
<string>XamarinAzureChallenge</string>
<key>CFBundleIdentifier</key>
<string>com.companyname.XamarinAzureChallenge</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>CFBundleName</key>
<string>XamarinAzureChallenge</string>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIAppFonts</key>
<array>
<string>roboto.regular.ttf</string>
</array>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>MinimumOSVersion</key>
<string>11.0</string>
<key>CFBundleDisplayName</key>
<string>XamarinAzureChallenge</string>
<key>CFBundleIdentifier</key>
<string>com.xamarin.XamarinAzureChallenge</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>CFBundleName</key>
<string>XamarinAzureChallenge</string>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIAppFonts</key>
<array>
<string>roboto.regular.ttf</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.xamarin.XamarinAzureChallenge</string>
<key>CFBundleURLSchemes</key>
<array>
<string>xamarinazurechallenge</string>
</array>
</dict>
</array>
</dict>
</plist>

Просмотреть файл

@ -13,12 +13,13 @@
<IPhoneResourcePrefix>Resources</IPhoneResourcePrefix>
<AssemblyName>XamarinAzureChallenge.iOS</AssemblyName>
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\iPhoneSimulator\Debug</OutputPath>
<DefineConstants>DEBUG</DefineConstants>
@ -43,7 +44,7 @@
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<DebugType>portable</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\iPhone\Debug</OutputPath>
<DefineConstants>DEBUG</DefineConstants>

Просмотреть файл

@ -4,7 +4,7 @@ using XamarinAzureChallenge.ViewModels;
namespace XamarinAzureChallenge.Pages
{
public partial class ResultPage : BaseContentPage<ResultViewModel>
{
{
public ResultPage(HttpStatusCode statusCode)
{
InitializeComponent();

Просмотреть файл

@ -50,7 +50,7 @@
<Setter Property="FontSize"
Value="12" />
<Setter Property="TextColor"
Value="Gray" />
Value="Black" />
<Setter Property="Margin"
Value="4,0,0,0" />
</Style>
@ -64,6 +64,8 @@
Value="Black" />
<Setter Property="BackgroundColor"
Value="White" />
<Setter Property="PlaceholderColor"
Value="Gray" />
</Style>
<Style x:Key="HyperLinkStyle"
TargetType="Label">
@ -121,6 +123,7 @@
Grid.ColumnSpan="2"
Placeholder="Enter your name"
Style="{StaticResource EntryStyle}"
ReturnType="Next"
Text="{Binding User.Name}" />
<Label Grid.Row="4"
@ -131,6 +134,7 @@
Grid.ColumnSpan="2"
Keyboard="Email"
Placeholder="Enter your email"
ReturnType="Next"
Style="{StaticResource EntryStyle}"
Text="{Binding User.Email}" />
@ -183,7 +187,8 @@
</Label.GestureRecognizers>
</Label>
<Button Grid.Row="14"
<Button x:Name="SubmitButton"
Grid.Row="14"
Grid.ColumnSpan="2"
Padding="10"
Command="{Binding SubmitCommand}"

Просмотреть файл

@ -3,11 +3,19 @@
namespace XamarinAzureChallenge.Pages
{
public partial class UserDataPage : BaseContentPage<UserDataViewModel>
{
public UserDataPage ()
{
InitializeComponent ();
{
public UserDataPage()
{
InitializeComponent();
}
}
BindingContext = new UserDataViewModel();
}
protected override void OnAppearing()
{
ViewModel.IsBusy = false;
base.OnAppearing();
}
}
}

Просмотреть файл

@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Newtonsoft.Json;
using Xamarin.Essentials;
using Xamarin.Forms;
using XamarinAzureChallenge.Shared;
using XamarinAzureChallenge.ViewModels;
namespace XamarinAzureChallenge
{
public static class AzureAuthenticationService
{
const string _oauthTokenKey = "OAuthToken";
private static readonly Lazy<HttpClient> clientHolder = new Lazy<HttpClient>();
private static readonly Lazy<JsonSerializer> serializer = new Lazy<JsonSerializer>();
private static string _sessionAuthenticationId;
public static event EventHandler AuthorizeSessionStarted;
public static event EventHandler<AzureAuthenticationCompletedEventArgs> AuthenticationCompleted;
private static HttpClient Client => clientHolder.Value;
private static JsonSerializer Serializer => serializer.Value;
public static async Task AuthorizeSession(Uri callbackUri)
{
OnAuthorizeSessionStarted();
var code = HttpUtility.ParseQueryString(callbackUri.Query).Get("code");
var state = HttpUtility.ParseQueryString(callbackUri.Query).Get("state");
var errorDescription = HttpUtility.ParseQueryString(callbackUri.Query).Get("error_description");
if (string.IsNullOrEmpty(code))
errorDescription = "Invalid Authorization Code";
if (state != _sessionAuthenticationId)
errorDescription = "Invalid SessionId";
if (string.IsNullOrEmpty(errorDescription))
{
_sessionAuthenticationId = string.Empty;
var clientId = await GetAzureClientId();
var content = new Dictionary<string,string>
{
{ "grant_type", "authorization_code" },
{ "client_id", clientId },
{ "code", code },
{ "redirect_uri", $"{nameof(XamarinAzureChallenge).ToLower()}://auth" }
};
using (var response = await Client.PostAsync("https://login.microsoftonline.com/common/oauth2/token", new FormUrlEncodedContent(content)))
{
var responseContent = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
var azureToken = JsonConvert.DeserializeObject<AzureToken>(responseContent);
await SaveAzureToken(azureToken);
OnAuthenticationCompleted(true);
}
else
{
await Application.Current.MainPage.DisplayAlert("Azure Authentication Unsuccessful", responseContent, "Ok");
OnAuthenticationCompleted(false);
}
}
}
else
{
await Application.Current.MainPage.DisplayAlert("Azure Authentication Unsuccessful", errorDescription, "Ok");
OnAuthenticationCompleted(false);
}
}
public static async Task<AzureToken> GetAzureToken()
{
var serializedToken = await SecureStorage.GetAsync(_oauthTokenKey).ConfigureAwait(false);
try
{
var token = JsonConvert.DeserializeObject<AzureToken>(serializedToken);
if (token is null)
return new AzureToken();
return token;
}
catch (ArgumentNullException)
{
return new AzureToken();
}
catch (JsonReaderException)
{
return new AzureToken();
}
}
public static async Task OpenAuthenticationPage()
{
var azureClientId = await GetAzureClientId();
_sessionAuthenticationId = Guid.NewGuid().ToString();
var azureLoginUrl = $"https://login.microsoftonline.com/common/oauth2/authorize?client_id={azureClientId}&response_type=code&state={_sessionAuthenticationId}";
await Device.InvokeOnMainThreadAsync(() => Browser.OpenAsync(azureLoginUrl));
}
static async Task<string> GetAzureClientId()
{
using (var stream = await Client.GetStreamAsync("https://xamarinazurechallenge-private.azurewebsites.net/api/GetClientId"))
using (var streamReader = new StreamReader(stream))
using (var json = new JsonTextReader(streamReader))
{
var azureClientIdModel = Serializer.Deserialize<AzureClientIdModel>(json);
return azureClientIdModel.ClientId;
}
}
private static async Task SaveAzureToken(AzureToken token)
{
if (token is null)
throw new ArgumentNullException(nameof(token));
if (token.AccessToken is null)
throw new ArgumentNullException(nameof(token.AccessToken));
var serializedToken = JsonConvert.SerializeObject(token);
await SecureStorage.SetAsync(_oauthTokenKey, serializedToken);
}
private static void OnAuthorizeSessionStarted() => AuthorizeSessionStarted?.Invoke(null, EventArgs.Empty);
private static void OnAuthenticationCompleted(bool isAuthenticationSuccessful) =>
AuthenticationCompleted?.Invoke(null, new AzureAuthenticationCompletedEventArgs(isAuthenticationSuccessful));
}
}

Просмотреть файл

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
@ -10,13 +11,15 @@ namespace XamarinAzureChallenge.ViewModels
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetAndRaisePropertyChanged<TRef>(ref TRef field, TRef value, [CallerMemberName] string propertyName = "")
protected void SetAndRaisePropertyChanged<T>(ref T field, T value, Action onChanged = null, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<TRef>.Default.Equals(field, value))
if (EqualityComparer<T>.Default.Equals(field, value))
return;
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
onChanged?.Invoke();
}
protected Task NavigateToPage(Page page) => Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushAsync(page));

Просмотреть файл

@ -1,18 +1,20 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Newtonsoft.Json;
using Xamarin.Essentials;
using Xamarin.Forms;
using XamarinAzureChallenge.Pages;
using XamarinAzureChallenge.Shared;
using XamarinAzureChallenge.Shared.Models;
namespace XamarinAzureChallenge.ViewModels
{
public class UserDataViewModel : BaseViewModel
{
#error Missing Azure Function Endpoint Url. Replace "Enter Your Function API Url Here" with your Azure Function Endopint Url
//#error Missing Azure Function Endpoint Url. Replace "Enter Your Function API Url Here" with your Azure Function Endopint Url
private const string endpoint = "Enter Your Function API Url Here";
private readonly Lazy<HttpClient> clientHolder = new Lazy<HttpClient>();
@ -21,18 +23,21 @@ namespace XamarinAzureChallenge.ViewModels
public UserDataViewModel()
{
User = new User();
SubmitCommand = new Command(async () => await SubmitCommmandExecute(User));
User = GetSavedUser();
SubmitCommand = new Command(async () => await SubmitCommmandExecute(User), () => !IsBusy);
PrivacyStatementCommand = new Command(async () => await PrivacyStatementCommandExecute());
AzureAuthenticationService.AuthorizeSessionStarted += HandleAuthorizeSessionStarted;
}
public ICommand SubmitCommand { get; }
public ICommand PrivacyStatementCommand { get; }
public Command SubmitCommand { get; }
public Command PrivacyStatementCommand { get; }
public bool IsBusy
{
get => isBusy;
set => SetAndRaisePropertyChanged(ref isBusy, value);
set => SetAndRaisePropertyChanged(ref isBusy, value, () => Device.BeginInvokeOnMainThread(SubmitCommand.ChangeCanExecute));
}
public User User
@ -45,62 +50,154 @@ namespace XamarinAzureChallenge.ViewModels
private async Task SubmitCommmandExecute(User submittedUser)
{
if (IsBusy)
return;
var areFieldsValid = await AreFieldsValid(submittedUser.Name, submittedUser.Email, submittedUser.Phone, submittedUser.IsTermsOfServiceAccepted);
IsBusy = true;
try
if (areFieldsValid)
{
var areFieldsValid = await AreFieldsValid(submittedUser.Name, submittedUser.Email, submittedUser.Phone, submittedUser.IsTermsOfServiceAccepted);
IsBusy = true;
if (areFieldsValid)
{
var serializedUser = JsonConvert.SerializeObject(User);
SaveUser(User);
var content = new StringContent(serializedUser, Encoding.UTF8, "application/json");
var result = await Client.PostAsync(endpoint, content);
await NavigateToPage(new ResultPage(result.StatusCode));
}
}
catch
{
await NavigateToPage(new ResultPage(default));
}
finally
{
IsBusy = false;
await AzureAuthenticationService.OpenAuthenticationPage();
}
}
private async Task<bool> AreFieldsValid(string name, string email, string phone, bool isTermsOfServiceAccepted)
private void HandleAuthorizeSessionStarted(object sender, EventArgs e)
{
var result = false;
//Ensure the SubmitButton remains disabled until Authorization has completed
IsBusy = true;
// Listen for AuthenticationCompleted event
AzureAuthenticationService.AuthenticationCompleted += HandleAuthenticationCompleted;
// Always unsubscribe events to avoid memory leaks
AzureAuthenticationService.AuthorizeSessionStarted -= HandleAuthorizeSessionStarted;
}
private async void HandleAuthenticationCompleted(object sender, AzureAuthenticationCompletedEventArgs e)
{
// Always unsubscribe events to avoid memory leaks
AzureAuthenticationService.AuthenticationCompleted -= HandleAuthenticationCompleted;
if (e.IsAuthenticationSuccessful)
{
var azureToken = await AzureAuthenticationService.GetAzureToken();
var subscriptionId = await GetAzureSubscriptionId(azureToken);
await SubmitUserInfo(User, subscriptionId);
}
IsBusy = false;
}
private async Task<string> GetAzureSubscriptionId(AzureToken azureToken)
{
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(azureToken.TokenType, azureToken.AccessToken);
using (var response = await Client.GetAsync("https://management.azure.com/subscriptions?api-version=2016-06-01"))
{
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
return responseContent;
}
else
{
await Application.Current.MainPage.DisplayAlert("Get Azure Subscription Id Failed", "", "Ok");
return string.Empty;
}
}
}
private async Task SubmitUserInfo(User submittedUser, string azureSubscriptionId)
{
try
{
var serializedUser = JsonConvert.SerializeObject(submittedUser);
using (var content = new StringContent(serializedUser, Encoding.UTF8, "application/json"))
using (var response = await Client.PostAsync($"{endpoint}/{azureSubscriptionId}", content))
{
if (response.IsSuccessStatusCode)
{
await NavigateToPage(new ResultPage(response.StatusCode));
}
else
{
var responseContent = await response.Content.ReadAsStringAsync();
await Application.Current.MainPage.DisplayAlert("Submission Unsuccessful", responseContent, "Ok");
}
}
}
catch (Exception ex)
{
await Application.Current.MainPage.DisplayAlert("Submission Unsuccessful", ex.Message, "Ok");
}
}
private async Task<bool> AreFieldsValid(string name, string email, string phone, bool isTermsOfServiceAccepted, bool shouldDisplayAlert = true)
{
var areFieldsValid = false;
var errorMessage = "";
if (string.IsNullOrWhiteSpace(name))
{
await DisplayInvalidFieldAlert("Name cannot be blank");
errorMessage = "Name cannot be blank";
}
else if (string.IsNullOrWhiteSpace(email))
{
await DisplayInvalidFieldAlert("Email cannot be blank");
errorMessage = "Email cannot be blank";
}
else if (string.IsNullOrWhiteSpace(phone))
{
await DisplayInvalidFieldAlert("Phone cannot be blank");
errorMessage = "Phone cannot be blank";
}
else if (!isTermsOfServiceAccepted)
{
await DisplayInvalidFieldAlert("Terms of Service Not Accepted");
errorMessage = "Terms of Service Not Accepted";
}
else
{
result = true;
areFieldsValid = true;
}
return result;
if (!areFieldsValid && shouldDisplayAlert)
await DisplayInvalidFieldAlert(errorMessage);
return areFieldsValid;
}
private User GetSavedUser()
{
var serializedUser = Preferences.Get(nameof(User), string.Empty);
try
{
var token = JsonConvert.DeserializeObject<User>(serializedUser);
if (token is null)
return new User();
return token;
}
catch (ArgumentNullException)
{
return new User();
}
catch (JsonReaderException)
{
return new User();
}
}
private void SaveUser(User currentUser)
{
if (currentUser is null)
throw new ArgumentNullException(nameof(currentUser));
var serializedUser = JsonConvert.SerializeObject(currentUser);
Preferences.Set(nameof(User), serializedUser);
}
private Task PrivacyStatementCommandExecute() =>

Просмотреть файл

@ -3,9 +3,10 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ProduceReferenceAssembly>True</ProduceReferenceAssembly>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>pdbonly</DebugType>
<DebugType>portable</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
@ -16,6 +17,7 @@
<ItemGroup>
<Folder Include="Pages\" />
<Folder Include="ViewModels\" />
<Folder Include="Services\" />
</ItemGroup>
<Import Project="..\XamarinAzureChallenge.Shared\XamarinAzureChallenge.Shared.projitems" Label="Shared" Condition="Exists('..\XamarinAzureChallenge.Shared\XamarinAzureChallenge.Shared.projitems')" />
</Project>