How to fix System.ObjectDisposedException while creating user

0

I need to create a user telegram in IDENTITY. But when using the method _userManager.CreateAsync(appUser), I get an error.

Before that, I used IDENTITY only in the controller, from the data coming in the form. Now, I need to create a user from the data that comes from telegrams.

My user object:

public class AppUser : IdentityUser
{
    public int TelegramId { get; set; }
    public bool IsBot { get; set; }
    public string TelegramFirstName { get; set; }
    public string TelegramLangCode { get; set; }
}

Context:

public class AppIdentityContext : IdentityDbContext<AppUser>
{
    public AppIdentityContext(DbContextOptions<AppIdentityContext> options)
        : base(options) {}
}

AccountService:

public class Account : IAccount
{
    private readonly UserManager<AppUser> _userManager;

    public Account(UserManager<AppUser> userManager)
        => _userManager = userManager;

    public async Task<bool> TryRegisterUserAsync(User user)
    {
       // User - telegram user object
        var appUser = new AppUser
        {
            IsBot = user.IsBot,
            TelegramId = user.Id,
            UserName = user.Username,
            TelegramFirstName = user.FirstName,
            TelegramLangCode = user.LanguageCode
        };

        var createResult = await _userManager.CreateAsync(appUser);

        return createResult;
    }
}

And connecting everything in Startup.cs

...
services.AddDbContext<AppIdentityContext>(optios => optios.UseSqlServer(Configuration.GetConnectionString("UserIdentityConnection")));
services.AddIdentity<AppUser, IdentityRole>().AddEntityFrameworkStores<AppIdentityContext>();
services.AddTransient<IAccount, Account>();
...

I expect the user to be added to the database and return true from the method async Task<bool> TryRegisterUserAsync(User user). But in the end, I get an error at run time.

System.ObjectDisposedException
  HResult=0x80131622
  Message=Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
  ObjectDisposed_ObjectName_Name
  Source=Microsoft.EntityFrameworkCore

Calling Code:

var createResult = await _userManager.CreateAsync(appUser);

Updated: Messages from the Telegram server come to my controller

[Route("bot/[controller]")]
    [ApiController]
    public class UpdatesController : ControllerBase
    {
        private readonly IBot _bot;
        private readonly IUpdatesRouter _updateRoute;

        public UpdatesController(IBot bot, IUpdatesRouter updateRoute)
        {
            _bot = bot;
            _updateRoute = updateRoute;
        }

        [HttpPost]
        public async void Post([FromBody] Update update)
        {
            if (_updateRoute.IsText(update))
            {
                await _bot.HandleTextCommandAsync(update.Message);
            }

            //if (_updateRoute.IsCallback(update))
            //{
            //    await bot.HandleCallbackAsync(update.CallbackQuery);
            //}
        }
    }

The bot code is as follows:

public class Bot : IBot
    {
        private readonly ITelegramBotClient _botClient;
        private readonly IOptions<BotConfig> _options;
        private readonly IAccount _account;

        public Bot(IOptions<BotConfig> options, 
                    ITextCommands txtCommands,
                    IAccount account)
        {
            _options = options;
            _account = account;

            _botClient = new TelegramBotClient(_options.Value.Token);
            SetWebhookAsync().GetAwaiter().GetResult();
        }

        private async Task SetWebhookAsync() =>
            await _botClient.SetWebhookAsync(
                string.Format(_options.Value.WebhookHost,
                               _options.Value.WebhookUrl));

        public async Task HandleTextCommandAsync(Message message)
        {
            bool regResult = await _account.TryRegisterUserAsync(message.From);
            // to do
        }
    }
c#
asp.net-core
asp.net-identity
asked on Stack Overflow Aug 30, 2019 by Роман Тимохов • edited Aug 31, 2019 by Роман Тимохов

1 Answer

2

Controller Action should return a Task derived result and not async void. This will cause the action to be fire and forget, which will cause the db context to be disposed of before the controller or its dependencies are finished with it.

[Route("bot/[controller]")]
[ApiController]
public class UpdatesController : ControllerBase {
    private readonly IBot _bot;
    private readonly IUpdatesRouter _updateRoute;

    public UpdatesController(IBot bot, IUpdatesRouter updateRoute) {
        _bot = bot;
        _updateRoute = updateRoute;
    }

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] Update update) {
        if (_updateRoute.IsText(update)) {
            await _bot.HandleTextCommandAsync(update.Message);
        }
        return Ok();
    }
}

I would also advise against making blocking calls on async function in the constructor, which can lead to deadlocks.

Hold on to the task and await it as needed when appropriate.

public class Bot : IBot {
    private readonly ITelegramBotClient _botClient;
    private readonly BotConfig botConfig;
    private readonly IAccount _account;
    private Task setWebhookTask;

    public Bot(IOptions<BotConfig> options, ITextCommands xtCommands, IAccount account) {
        botConfig = options.Value;
        _account = account;

        _botClient = new TelegramBotClient(botConfig.Token);
        setWebhookTask = SetWebhookAsync();
    }

    private Task SetWebhookAsync() =>
        _botClient.SetWebhookAsync(
            string.Format(botConfig.WebhookHost, botConfig.WebhookUrl)
        );

    public async Task HandleTextCommandAsync(Message message) {
        await setWebhookTask;
        bool regResult = await _account.TryRegisterUserAsync(message.From);
        // to do
    }
}
answered on Stack Overflow Aug 31, 2019 by Nkosi • edited Aug 31, 2019 by Nkosi

User contributions licensed under CC BY-SA 3.0