Example #1
0
        /// <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));
        }
Example #5
0
        /// <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"),
            });
Example #6
0
        /// <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));
                }
            }));
        }