Search code examples
c#backendaspnetboilerplate

App Service stops writing to DB table after throwing exception


Using AspNetBoilerplate. I'm being asked to create a way to provide a user to unsubscribe from a service prior password check which is where I'm stuck.

    [AbpAuthorize]
    public class UserCompaniesManagerAppService : InvoiceAppServiceBase, IApplicationService
    {
        public async Task CancelSubscription(CheckPasswordDto input)
        {
            using (CurrentUnitOfWork.SetTenantId(AbpSession.TenantId))
            
            {
                var user = await GetCurrentUserAsync();

                //verify pass
                if (await UserManager.CheckPasswordAsync(user, input.Password))
                {
                    await UserManager.ResetAccessFailedCountAsync(user);
                   
                    //TODO: unsubscribe
                }
                else
                {
                    //response from server when wrong password
                    await UserManager.AccessFailedAsync(user);

                    var count = await UserManager.GetAccessFailedCountAsync(user);
                    var maxAttempts = await SettingManager.GetSettingValueAsync<int>(
                        AbpZeroSettingNames.UserManagement.UserLockOut.MaxFailedAccessAttemptsBeforeLockout);
                   
                    throw new UserFriendlyException("tried " + count.ToString() + " left attempts " + (maxAttempts - count).ToString());
                }
            }
        }

I'm using "UserFriendlyException" which is what is suggested from docs. I was expecting to see the times wrong password was done and the times left for a correct password. Server response this way.

{
  "result": null,
  "targetUrl": null,
  "success": false,
  "error": {
    "code": 0,
    "message": "tried 1 left attempts 2",
    "details": null,
    "validationErrors": null
  },
  "unAuthorizedRequest": false,
  "__abp": true
}

Thing is, that if I keep executing it, it stays on those values. Also, the DB doesn't count the failed attempts. No data is written on AbpUsers table.

I've tried using, just in case, Task<string> and return the message that way and it works fine, the counters, the fields in AbpUsers and the lockdown. But I know this shouldn't be done this way but to provide an object as a response like throw.


Solution

  • ABP's Conventional Unit of Work is scoped around your App Service method CancelSubscription:

    if no exception is thrown, it commits the transaction at the end of it. If an exception is thrown, it rolls everything back.

    Begin a new unit of work uow that gets completed:

    using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
    {
        await UserManager.AccessFailedAsync(user);
        uow.Complete();
    }
    
    var count = await UserManager.GetAccessFailedCountAsync(user);
    var maxAttempts = await SettingManager.GetSettingValueAsync<int>(
        AbpZeroSettingNames.UserManagement.UserLockOut.MaxFailedAccessAttemptsBeforeLockout);
    
    //response from server when wrong password
    throw new UserFriendlyException("tried " + count.ToString() + " left attempts " + (maxAttempts - count).ToString());
    

    Alternatively, you can make AccessFailedAsync virtual and mark it with the UnitOfWork attribute:

    [UnitOfWork(TransactionScopeOption.RequiresNew)]
    public virtual async Task AccessFailedAsync(User user)
    {
        // ...
    }