I have a ASP.NET boilerplate 7.4 MVC app using .NET framework 4.6 and that now I'm migrating to ASP.NET Core 8 (.NET8).
I have a problem in a MVC Controller when using AbpSession.Use(...)
. The problem is not present in the MVC5 app (same code)
This is the MVC Controller:
public class BookMgmController : AbpController {
...
[HttpPost]
public async Task<JsonResult> Pay(PayRequestVM input) {
...
using (AbpSession.Use(AbpSession.TenantId, myAuthorizedUserIdToAccessOrders)) {
// create the order and put it into a status of 'order waiting for payments
var order = await _orderAppService.CreateAsync(input); // <--- problem!
...
} // using...
}
}
And the OrderAppService
service:
[AbpAuthorize(PermissionNames.Pages_Orders)]
public class OrderAppService : ..., IOrderAppService {
...
public override async Task<BasicOrderDto> CreateAsync(CreateOrderDto input) {
...
}
...
}
}
When I try to invoke the CreateAsyc(...)
, ABP throws the following exception:
Cannot access a disposed object.
Object name: 'GPSoftware.Booking.Authorization.Users.UserManager'
As said, the problem is not present in .NET framework and disappears if I remove the AbpAuthorize
attribute assigned to the OrderAppService
.
Here below, the stack trace:
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'GPSoftware.Booking.Authorization.Users.UserManager'.
at Microsoft.AspNetCore.Identity.UserManager`1.FindByIdAsync(String userId)
at Abp.Authorization.Users.AbpUserManager`2.<>c__DisplayClass91_0.<<GetUserPermissionCacheItemAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
at Abp.Runtime.Caching.TypedCacheWrapper`2.<>c__DisplayClass21_0.<<GetAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
at Abp.Runtime.Caching.AbpCacheBase`2.GetAsync(TKey key, Func`2 factory)
at Abp.Runtime.Caching.TypedCacheWrapper`2.GetAsync(TKey key, Func`2 factory)
at Abp.Authorization.Users.AbpUserManager`2.GetUserPermissionCacheItemAsync(Int64 userId)
at Abp.Authorization.Users.AbpUserManager`2.IsGrantedAsync(Int64 userId, Permission permission)
at Abp.Authorization.Users.AbpUserManager`2.IsGrantedAsync(Int64 userId, String permissionName)
at Abp.Authorization.PermissionChecker`2.IsGrantedAsync(Int64 userId, String permissionName)
at Abp.Authorization.PermissionChecker`2.IsGrantedAsync(String permissionName)
at Abp.Authorization.PermissionCheckerExtensions.IsGrantedAsync(IPermissionChecker permissionChecker, Boolean requiresAll, String[] permissionNames)
at Abp.Authorization.PermissionCheckerExtensions.AuthorizeAsync(IPermissionChecker permissionChecker, Boolean requireAll, String[] permissionNames)
at Abp.Authorization.AuthorizationHelper.AuthorizeAsync(IEnumerable`1 authorizeAttributes)
at Abp.Authorization.AuthorizationHelper.CheckPermissionsAsync(MethodInfo methodInfo, Type type)
at Abp.Authorization.AuthorizationHelper.AuthorizeAsync(MethodInfo methodInfo, Type type)
at Abp.Authorization.AuthorizationInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation)
at Abp.Auditing.AuditingInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation)
at Abp.Auditing.AuditingInterceptor.InternalInterceptAsynchronous[TResult](IInvocation invocation)
at GPSoftware.Booking.Web.Controllers.BookMgmController.DoPay(PayShoppingCartRequestVM input) in C:\WA\GPsoftware\PortamiInPista.NET\aspnet-core\src\web\Booking.Web.Mvc\Controllers\BookMgmController.cs:line 165
at GPSoftware.Booking.Web.Controllers.BookMgmController.Pay(PayShoppingCartRequestVM input) in C:\WA\GPsoftware\PortamiInPista.NET\aspnet-core\src\web\Booking.Web.Mvc\Controllers\BookMgmController.cs:line 131
Note that most of objects are injected into DI by the ABP framework and I checked they are not null at runtime (specifically, but not only, UserManager
and AbpSession
).
Error does not occur on a simpler very similar code starting from the ABP template.
By the way, I went deeper in debugging and I arrived to the source code of
Abp.Authorization.Users.AbpUserManager<TRole, TUser>
, ancestor of UserManager
. It has the following private method that fails when it invokes FindByIdAsync
(and later on GetRolesAsync
):
private async Task<UserPermissionCacheItem> GetUserPermissionCacheItemAsync(long userId) {
var cacheKey = userId + "@" + (GetCurrentTenantId() ?? 0);
return await _cacheManager.GetUserPermissionCache().GetAsync(cacheKey, async () => {
var user = await FindByIdAsync(userId.ToString()); // <= fails here
if (user == null) {
return null;
}
var newCacheItem = new UserPermissionCacheItem(userId);
foreach (var roleName in await GetRolesAsync(user)) { // <= fails here
newCacheItem.RoleIds.Add((await RoleManager.GetRoleByNameAsync(roleName)).Id);
}
foreach (var permissionInfo in await UserPermissionStore.GetPermissionsAsync(userId)) {
if (permissionInfo.IsGranted) {
newCacheItem.GrantedPermissions.Add(permissionInfo.Name);
} else {
newCacheItem.ProhibitedPermissions.Add(permissionInfo.Name);
}
}
return newCacheItem;
});
}
FindByIdAsync
is a method of Microsoft.AspNetCore.Identity.UserManager<TUser>
, still another ancestor. So I overrode it in my UserManager
by commenting out the line ThrowIfDisposed()
and it works!
public override Task<User> FindByIdAsync(string userId) {
//ThrowIfDisposed();
return Store.FindByIdAsync(userId, CancellationToken);
}
Now I can’t go any further because it's not so simple to override GetRolesAsync
too.
The questions are: Why all this? And why the error doesn't occur in a simpler very similat code I tested starting from the original template?
Any suggestion?
*** This is not a real answer. It’s just a workaround to overcome the problem ***
I overrode some methods in my UserManager
and RoleManager
to skip ThrowIfDisposed()
in some specific cases:
public class UserManager : AbpUserManager<Role, User> {
...
public override async Task<User> FindByIdAsync(string userId) {
try {
// try former with the ancestor method
return await base.FindByIdAsync(userId);
}
catch (ObjectDisposedException) when (Store != null) {
// ignore the exception if I'm able to call the Store method
return await Store.FindByIdAsync(userId, CancellationToken);
}
}
public override async Task<IList<string>> GetRolesAsync(User user) {
try {
// try former with the ancestor method
return await base.GetRolesAsync(user);
}
catch (ObjectDisposedException) when (Store != null) {
// ignore the exception if I'm able to call the Store method
Check.NotNull(user, nameof(user));
var userRoleStore = (Store as IUserRoleStore<User>);
if (userRoleStore != null) {
return await userRoleStore.GetRolesAsync(user, CancellationToken).ConfigureAwait(false);
} else {
throw;
}
}
}
...
}
and
public class RoleManager : AbpRoleManager<Role, User> {
...
public override async Task<Role> FindByNameAsync(string roleName) {
try {
// try former with the ancestor method
return await base.FindByNameAsync(roleName);
}
catch (ObjectDisposedException) when (Store != null) {
// ignore the exception if I'm able to call the Store method
Check.NotNullOrEmpty(roleName, nameof(roleName));
return await Store.FindByNameAsync(NormalizeKey(roleName), CancellationToken);
}
}
...
}