3

I'm creating a web application with user authentication.

I use the Microsoft.AspNetCore.Identity.EntityFrameworkCore with UserManager<IdentityUser> and a RoleManager<IdentityRole> for managing the users and their roles.

When I use the localDB feature in Visual Studio with MultipleActiveResultSets=true in the connection string, everything works fine.

When I want to use a MySQL Server installed on my Ubuntu 16.04 Server, I can login. But as soon as the RoleManager first tries to connect to the database, I get the following error:

MySqlException: There is already an open DataReader associated with this Connection which must be closed first

.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using tsv_core.Models;

    // For more information on enabling MVC for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860

    namespace tsv_core.Controllers
    {
        [Authorize(Roles = "Admins")]
        public class AdminController : Controller
        {
            UserManager<AppUser> userManager;
            private IUserValidator<AppUser> userValidator;
            private IPasswordValidator<AppUser> passwordValidator;
            private IPasswordHasher<AppUser> passwordHasher;
            private IRequestLogger _logger;

            public AdminController(UserManager<AppUser> usrMgr, IUserValidator<AppUser> userValid, IPasswordValidator<AppUser> passValid, IPasswordHasher<AppUser> passwordHash, IRequestLogger logger)
            {
                userManager = usrMgr;
                userValidator = userValid;
                passwordValidator = passValid;
                passwordHasher = passwordHash;
                _logger = logger;
            }

            public ViewResult Accounts() => View(userManager.Users);

            public ViewResult Create() => View();
            [HttpPost]
            public async Task<IActionResult> Create(CreateModel model)
            {
                if (ModelState.IsValid)
                {
                    AppUser user = new AppUser
                    {
                        UserName = model.Name,
                        Email = model.Email
                    };
                    IdentityResult result
                    = await userManager.CreateAsync(user, model.Password);
                    if (result.Succeeded)
                    {
                        return RedirectToAction("Accounts");
                    }
                    else
                    {
                        foreach (IdentityError error in result.Errors)
                        {
                            ModelState.AddModelError("", error.Description);
                        }
                    }
                }
                return View(model);
            }

            [HttpPost]
            public async Task<IActionResult> Delete(string id)
            {
                AppUser user = await userManager.FindByIdAsync(id);
                if (user != null)
                {
                    IdentityResult result = await userManager.DeleteAsync(user);
                    if (result.Succeeded)
                    {
                        return RedirectToAction("Index");
                    }
                    else
                    {
                        AddErrorsFromResult(result);
                    }
                }
                else
                {
                    ModelState.AddModelError("", "User Not Found");
                }
                return View("Index", userManager.Users);
            }

            public async Task<IActionResult> Edit(string id)
            {
                AppUser user = await userManager.FindByIdAsync(id);
                if (user != null)
                {
                    return View(user);
                }
                else
                {
                    return RedirectToAction("Index");
                }
            }
            [HttpPost]
            public async Task<IActionResult> Edit(string id, string email,
            string password)
            {
                AppUser user = await userManager.FindByIdAsync(id);
                if (user != null)
                {
                    user.Email = email;
                    IdentityResult validEmail
                    = await userValidator.ValidateAsync(userManager, 

user);
                        if (!validEmail.Succeeded)
                        {
                            AddErrorsFromResult(validEmail);
                        }
                        IdentityResult validPass = null;
                        if (!string.IsNullOrEmpty(password))
                        {
                            validPass = await passwordValidator.ValidateAsync(userManager,
                            user, password);
                            if (validPass.Succeeded)
                            {
                                user.PasswordHash = passwordHasher.HashPassword(user,
                                password);
                            }
                            else
                            {
                                AddErrorsFromResult(validPass);
                            }
                        }
                        if ((validEmail.Succeeded && validPass == null)
                        || (validEmail.Succeeded
                        && password != string.Empty && validPass.Succeeded))
                        {
                            IdentityResult result = await userManager.UpdateAsync(user);
                            if (result.Succeeded)
                            {
                                return RedirectToAction("Index");
                            }
                            else
                            {
                                AddErrorsFromResult(result);
                            }
                        }
                    }
                    else
                    {
                        ModelState.AddModelError("", "User Not Found");
                    }
                    return View(user);
                }

                private void AddErrorsFromResult(IdentityResult result)
                {
                    foreach (IdentityError error in result.Errors)
                    {
                        ModelState.AddModelError("", error.Description);
                    }
                }

                public ViewResult RequestLogging()
                {
                    return View(_logger.GetRequestRepository());
                }
            }
        }

.

    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using tsv_core.Models;
    using Microsoft.AspNetCore.Identity;

    namespace tsv_core.Controllers
    {
        [Authorize(Roles = "Admins")]
        public class AccountController : Controller
        {
            private UserManager<AppUser> userManager;
            private SignInManager<AppUser> signInManager;
            public AccountController(UserManager<AppUser> userMgr,
            SignInManager<AppUser> signinMgr)
            {
                userManager = userMgr;
                signInManager = signinMgr;
            }
            [AllowAnonymous]
            public IActionResult Login(string returnUrl)
            {
                ViewBag.returnUrl = returnUrl;
                return View();
            }
            [HttpPost]
            [AllowAnonymous]
            [ValidateAntiForgeryToken]
            public async Task<IActionResult> Login(LoginModel details,
            string returnUrl)
            {
                if (ModelState.IsValid)
                {
                    AppUser user = await userManager.FindByEmailAsync(details.Email);
                    if (user != null)
                    {
                        await signInManager.SignOutAsync();
                        Microsoft.AspNetCore.Identity.SignInResult result =
                        await signInManager.PasswordSignInAsync(
                        user, details.Password, false, false);
                        if (result.Succeeded)
                        {
                            return Redirect(returnUrl ?? "/");
                        }
                    }
                    ModelState.AddModelError(nameof(LoginModel.Email), "Invalid user or password");
                }
                return View(details);
            }

            public async Task<IActionResult> Logout()
            {
                await signInManager.SignOutAsync();
                return RedirectToAction("Index", "Home");
            }

            [AllowAnonymous]
            public IActionResult AccessDenied()
            {
                return View();
            }
        }
    }

.

using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc;
using System.Linq;
using tsv_core.Models;
using System.Collections.Generic;
using Microsoft.AspNetCore.Authorization;

namespace tsv_core.Controllers
{
    [Authorize(Roles = "Admins")]
    public class RoleAdminController : Controller
    {
        private RoleManager<IdentityRole> roleManager;
        private UserManager<AppUser> userManager;
        public RoleAdminController(RoleManager<IdentityRole> roleMgr, UserManager<AppUser> userMrg)
        {
            roleManager = roleMgr;
            userManager = userMrg;
        }
        public ViewResult Index() => View(roleManager.Roles);
        public IActionResult Create() => View();
        [HttpPost]
        public async Task<IActionResult> Create([Required]string name)
        {
            if (ModelState.IsValid)
            {
                IdentityResult result
                = await roleManager.CreateAsync(new IdentityRole(name));
                if (result.Succeeded)
                {
                    return RedirectToAction("Index");
                }
                else
                {
                    AddErrorsFromResult(result);
                }
            }
            return View(name);
        }
        [HttpPost]
        public async Task<IActionResult> Delete(string id)
        {
            IdentityRole role = await roleManager.FindByIdAsync(id);
            if (role != null)
            {
                IdentityResult result = await roleManager.DeleteAsync(role);
                if (result.Succeeded)
                {
                    return RedirectToAction("Index");
                }
                else
                {
                    AddErrorsFromResult(result);
                }
            }
            else
            {
                ModelState.AddModelError("", "No role found");
            }
            return View("Index", roleManager.Roles);
        }

        public async Task<IActionResult> Edit(string id)
        {
            IdentityRole role = await roleManager.FindByIdAsync(id);
            List<AppUser> members = new List<AppUser>();
            List<AppUser> nonMembers = new List<AppUser>();
            foreach (AppUser user in userManager.Users)
            {
                var list = await userManager.IsInRoleAsync(user, role.Name)
                ? members : nonMembers;
                list.Add(user);
            }
            return View(new RoleEditModel
            {
                Role = role,
                Members = members,
                NonMembers = nonMembers
            });
        }
        [HttpPost]
        public async Task<IActionResult> Edit(RoleModificationModel model)
        {
            IdentityResult result;
            if (ModelState.IsValid)
            {
                foreach (string userId in model.IdsToAdd ?? new string[] { })
                {
                    AppUser user = await userManager.FindByIdAsync(userId);
                    if (user != null)
                    {
                        result = await userManager.AddToRoleAsync(user,
                        model.RoleName);
                        if (!result.Succeeded)
                        {
                            AddErrorsFromResult(result);
                        }
                    }
                }
                foreach (string userId in model.IdsToDelete ?? new string[] { })
                {
                    AppUser user = await userManager.FindByIdAsync(userId);
                    if (user != null)
                    {
                        result = await userManager.RemoveFromRoleAsync(user,
                        model.RoleName);
                        if (!result.Succeeded)
                        {
                            AddErrorsFromResult(result);
                        }
                    }
                }
            }
            if (ModelState.IsValid)
            {
                return RedirectToAction(nameof(Index));
            }
            else
            {
                return await Edit(model.RoleId);
            }
        }

        private void AddErrorsFromResult(IdentityResult result)
        {
            foreach (IdentityError error in result.Errors)
            {
                ModelState.AddModelError("", error.Description);
            }
        }
    }
}

.

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Razor.TagHelpers;
using tsv_core.Models;

namespace tsv_core.Infrastructure
{
    [HtmlTargetElement("td", Attributes = "identity-role")]
    public class RoleUsersTagHelper : TagHelper
    {
        private UserManager<AppUser> userManager;
        private RoleManager<IdentityRole> roleManager;
        public RoleUsersTagHelper(UserManager<AppUser> usermgr,
        RoleManager<IdentityRole> rolemgr)
        {
            userManager = usermgr;
            roleManager = rolemgr;
        }
        [HtmlAttributeName("identity-role")]
        public string Role { get; set; }
        public override async Task ProcessAsync(TagHelperContext context,
        TagHelperOutput output)
        {
            IdentityRole role = await roleManager.FindByIdAsync(Role);;
            List<string> names = new List<string>();

                if (role != null)
                {
                    foreach (var user in userManager.Users)
                    {
                        if (user != null && await userManager.IsInRoleAsync(user, role.Name))
                        {
                            names.Add(user.UserName);
                        }
                    }
                }
                output.Content.SetContent(names.Count == 0 ?
                "No Users" : string.Join(", ", names));           
        }
    }
}

.

@model IEnumerable<IdentityRole>
<div class="bg-primary panel-body"><h4>Roles</h4></div>
<div class="text-danger" asp-validation-summary="ModelOnly"></div>
<table class="table table-condensed table-bordered table-bordered">
    <tr><th>ID</th><th>Name</th><th>Users</th><th></th></tr>
    @if (Model.Count() == 0)
    {
        <tr><td colspan="4" class="text-center">No Roles</td></tr>
    }
    else
    {
        foreach (var role in Model)
        {
            <tr>
                <td>@role.Id</td>
                <td>@role.Name</td>
                <td identity-role="@role.Id"></td>
                <td>
                    <form asp-action="Delete" asp-route-id="@role.Id" method="post">
                        <a class="btn btn-sm btn-primary" asp-action="Edit"
                           asp-route-id="@role.Id">Edit</a>
                        <button type="submit"
                                class="btn btn-sm btn-danger">
                            Delete
                        </button>
                    </form>
                </td>
            </tr>
        }
    }
</table>
    <a class="btn btn-primary" asp-action="Create">Create</a>

.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using tsv_core.Models;
using tsv_core.Infrastructure;
using MySQL.Data.EntityFrameworkCore.Extensions;

namespace tsv_core
{
    public class Startup
    {
        IConfigurationRoot Configuration;
        public Startup(IHostingEnvironment env)
        {
            Configuration = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json").Build();
        }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            //services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["Data:tsvIdentity:ConnectionString"]));
            services.AddDbContext<AppIdentityDbContext>(options => options.UseMySQL(Configuration["Data:tsvIdentity:ConnectionString"]));
            services.AddIdentity<AppUser, IdentityRole>(opts => opts.User.RequireUniqueEmail = true).AddEntityFrameworkStores<AppIdentityDbContext>();

            services.AddTransient<IRequestLogger, TestRequestLogger>();
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
                loggerFactory.AddConsole(Configuration.GetSection("Logging"));
                loggerFactory.AddDebug();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();
            app.UseIdentity();

            //This position of the LoggingMiddleware is important!! If it would be placed before "app.UseStaticFiles();" the request paths to the static files would be logged too.
            //If it would be placed behind app.UseMvc, it wouldn't log anything at all.            
            app.UseMiddleware<LoggingMiddleware>();

            app.UseMvcWithDefaultRoute();

            AppIdentityDbContext.CreateAdminAccount(app.ApplicationServices, Configuration).Wait();
        }
    }
}

These should be all relevant classes.

AppUser just inherits from IdentityUser and does not have any extra fields at the moment.

The first time the error appears hear:

ErrorExceptionPage

I have already searched for answers and came across topics like connection-pooling etc.

My problem is that I don't know exactly what EF Core already does for me, when it comes to pooling.

Do I need to Dispose the UserManager and the RoleManager after every use by using "using- blocks" or is there a completely different solution?

Jason
  • 3,330
  • 1
  • 33
  • 38
  • Can you show your startup class ? – AdrienTorris Dec 16 '16 at 15:20
  • added the startup class at the end of the code segment – Christoph Neuner Dec 16 '16 at 15:45
  • Does this answer your question? http://stackoverflow.com/questions/20693216/there-is-already-an-open-datareader-associated-with-this-connection-which-must-b – TrevorBrooks Dec 16 '16 at 16:47
  • Ah i think i got the problem now: in the TagHelper and the RoleAdmin class multiple Tasks are used that all want to read from the Database concurrently. So I somehow have to get these Tasks performing their accesses via the RoleManager and UserManager after another or get the information from the Databank once and then let all these Tasks use this information. Am I getting this right? If yes, what would be the best way doing this? – Christoph Neuner Dec 16 '16 at 21:53

0 Answers0