/// <summary> /// Starts an asynchronous operation if one is not already in progress /// </summary> /// <param name="operation">The operation to start</param> /// <exception cref="InvalidOperationException"> is returned if this method is called while /// another asynchronous operation is still being processed. /// </exception> private void StartOperation(AuthenticationOperation operation) { Debug.Assert(operation != null, "The operation cannot be null."); lock (this._syncLock) { if (this.IsBusy) { throw new InvalidOperationException(Resources.ApplicationServices_UserServiceIsBusy); } this.Operation = operation; } try { var task = operation.InvokeAsync(operation.CancellationToken); // Continue on same SynchronizationContext var scheduler = SynchronizationContext.Current != null?TaskScheduler.FromCurrentSynchronizationContext() : TaskScheduler.Default; task.ContinueWith(StartOperationComplete, operation, CancellationToken.None, TaskContinuationOptions.HideScheduler, scheduler); } catch (Exception) { this.Operation = null; throw; } this.RaisePropertyChanged(nameof(IsBusy)); this.RaisePropertyChanged(AuthenticationService.GetBusyPropertyName(operation)); }
/// <summary> /// Marks an operation in error as handled. /// </summary> /// <param name="ao">The operation in error.</param> private static void HandleOperationError(AuthenticationOperation ao) { if (ao.HasError) { ao.MarkErrorAsHandled(); } }
/// <summary> /// Returns the name of the "busy" property for the specified operation /// </summary> /// <param name="operation">The operation to get the property name for</param> /// <returns>The name of the "busy" property for the operation</returns> /// <seealso cref="IsLoggingIn"/> /// <seealso cref="IsLoggingOut"/> /// <seealso cref="IsLoadingUser"/> /// <seealso cref="IsSavingUser"/> private static string GetBusyPropertyName(AuthenticationOperation operation) { Debug.Assert(operation != null, "The operation cannot be null."); Type operationType = operation.GetType(); if (typeof(LoginOperation) == operationType) { return("IsLoggingIn"); } else if (typeof(LogoutOperation) == operationType) { return("IsLoggingOut"); } else if (typeof(LoadUserOperation) == operationType) { return("IsLoadingUser"); } else if (typeof(SaveUserOperation) == operationType) { return("IsSavingUser"); } else { Debug.Assert(false, "operationType parameter is invalid."); return(string.Empty); } }
/// <summary> /// Starts an asynchronous operation if one is not already in progress /// </summary> /// <param name="operation">The operation to start</param> /// <exception cref="InvalidOperationException"> is returned if this method is called while /// another asynchronous operation is still being processed. /// </exception> private void StartOperation(AuthenticationOperation operation) { Debug.Assert(operation != null, "The operation cannot be null."); lock (this._syncLock) { if (this.IsBusy) { throw new InvalidOperationException(Resources.ApplicationServices_UserServiceIsBusy); } this.Operation = operation; } try { operation.Start(); } catch (Exception) { this.Operation = null; throw; } this.RaisePropertyChanged("IsBusy"); this.RaisePropertyChanged(AuthenticationService.GetBusyPropertyName(this.Operation)); }
/// <summary> /// Returns the name of the "busy" property for the specified operation /// </summary> /// <param name="operation">The operation to get the property name for</param> /// <returns>The name of the "busy" property for the operation</returns> /// <seealso cref="IsLoggingIn"/> /// <seealso cref="IsLoggingOut"/> /// <seealso cref="IsLoadingUser"/> /// <seealso cref="IsSavingUser"/> private static string GetBusyPropertyName(AuthenticationOperation operation) { Debug.Assert(operation != null, "The operation cannot be null."); return(operation switch { LoginOperation _ => nameof(IsLoggingIn), LogoutOperation _ => nameof(IsLoggingOut), LoadUserOperation _ => nameof(IsLoadingUser), SaveUserOperation _ => nameof(IsSavingUser), _ => throw new NotImplementedException("unknown operation type"), });
/// <summary> /// This is run on the calling SynchronizationContext when an operation started /// by <see cref="StartOperation"/> completes /// </summary> private void StartOperationComplete(Task <AuthenticationResult> res, object state) { var operation = (AuthenticationOperation)state; AuthenticationResult endResult = null; bool raiseUserChanged = false; bool raiseLoggedIn = false; bool raiseLoggedOut = false; // Setting the operation to null indicates the service is no longer busy and // can process another operation this.Operation = null; try { if (res.IsCanceled) { operation.SetCancelled(); return; } try { endResult = res.GetAwaiter().GetResult(); } catch (Exception ex) { operation.SetError(ex); if (ex.IsFatal()) { throw; } return; } // If the operation completed successfully, update the user and // determine which events should be raised IPrincipal currentUser = this._user; if (endResult?.User != null && currentUser != endResult.User) { raiseLoggedIn = // anonymous -> authenticated currentUser == null || (!currentUser.Identity.IsAuthenticated && endResult.User.Identity.IsAuthenticated) // authenticated -> authenticated || (endResult.User.Identity.IsAuthenticated && currentUser.Identity.Name != endResult.User.Identity.Name); raiseLoggedOut = // authenticated -> anonymous currentUser != null && currentUser.Identity.IsAuthenticated && !endResult.User.Identity.IsAuthenticated; this._user = endResult.User; raiseUserChanged = true; } operation.Complete(endResult); } finally { // Raise notification events as appropriate if (raiseUserChanged) { this.RaisePropertyChanged(nameof(User)); } this.RaisePropertyChanged(nameof(IsBusy)); this.RaisePropertyChanged(AuthenticationService.GetBusyPropertyName(operation)); if (raiseLoggedIn) { this.LoggedIn?.Invoke(this, new AuthenticationEventArgs(endResult.User)); } if (raiseLoggedOut) { this.LoggedOut?.Invoke(this, new AuthenticationEventArgs(endResult.User)); } } }
/// <summary> /// Wraps the specified action so the <see cref="AuthenticationService"/> can complete /// processing of the operation before invoking the <paramref name="completeAction"/>. /// </summary> /// <typeparam name="T">The type of operation.</typeparam> /// <param name="completeAction">The action to invoke once the service finishes /// processing the operation. This parameter is optional. /// </param> /// <returns>An action that will complete processing of the operation before invoking /// the wrapped action. /// </returns> private Action <T> WrapCompleteAction <T>(Action <T> completeAction) where T : AuthenticationOperation { return(new Action <T>(ao => { bool raiseUserChanged = false; bool raiseLoggedIn = false; bool raiseLoggedOut = false; // If the operation completed successfully, update the user and // determine which events should be raised if (!ao.IsCanceled && !ao.HasError && (ao.User != null)) { if (this._user != ao.User) { raiseLoggedIn = // anonymous -> authenticated (this._user == null) || (!this._user.Identity.IsAuthenticated && ao.User.Identity.IsAuthenticated) || // authenticated -> authenticated (ao.User.Identity.IsAuthenticated && (this._user.Identity.Name != ao.User.Identity.Name)); raiseLoggedOut = // authenticated -> anonymous (this._user != null) && (this._user.Identity.IsAuthenticated && !ao.User.Identity.IsAuthenticated); this._user = ao.User; raiseUserChanged = true; } } // Setting the operation to null indicates the service is no longer busy and // can process another operation this.Operation = null; // Invoke the wrapped action if (completeAction != null) { try { completeAction.DynamicInvoke(ao); } catch (TargetInvocationException tie) { if (tie.InnerException != null) { throw tie.InnerException; } throw; } } // Raise notification events as appropriate if (raiseUserChanged) { this.RaisePropertyChanged("User"); } this.RaisePropertyChanged("IsBusy"); this.RaisePropertyChanged(AuthenticationService.GetBusyPropertyName(ao)); if (raiseLoggedIn) { this.OnLoggedIn(new AuthenticationEventArgs(ao.User)); } if (raiseLoggedOut) { this.OnLoggedOut(new AuthenticationEventArgs(ao.User)); } })); }