Search code examples
asp.netasp.net-coreblazorasp.net-identityblazor-server-side

ASP.NET Core RoleManager not creating new role


I am working on a blazor server app, and right now trying to implement Role creation. I have a blazor page with a form. User enters the form, submits it, and a new role is supposed to be created. The issue arises when using Role manager to create the role.

When using RoleManager.CreateAsync(newRole), it throws an error as follows:

Microsoft.AspNetCore.Identity.RoleManager[0]
      Role (null) validation failed: InvalidRoleName.
fail: TestWebsite.Pages.Admin.Users[0]
      Role name '' is invalid.
fail: TestWebsite.Pages.Admin.Users[0]
      InvalidRoleName

When calling the CreateAsync method, I am passing the method a new ApplicationRole(roleModel.Name!). I know roleModel.Name is not empty or null, if I log it right before the method call, it logs the correct value. ApplicationRole is just a class that extends IdentityRole without overriding anything.

Even if I hard code the role name, like var result = await roleManager.CreateAsync(new ApplicationRole("Administrator"));, it still throws the same error. The problem seems to be occurring in the execution of CreateAsync().

I have been at this for a few hours now, and as a beginner to .NET Core or Blazor, I am completely stumped as to why this occurs. I could be missing something very obvious tho. My guesses are wrong DI or implementation of Identity services.

Here is how I inject the RoleManager in my Blazor page:

@inject RoleManager<ApplicationRole> roleManager

Here is the code from the blazor page where I call CreateAsync:

@code {

    private bool _viewingUsers = true;

    private IEnumerable<ApplicationUser>? AllUsers;
    private IEnumerable<ApplicationRole>? AllRoles;

    private bool _loadingUsers = true;
    private bool _loadingRoles = true;

    protected override async Task OnAfterRenderAsync(bool firstRender) {
        if (!firstRender) return;

        LoadAllUsers();
        LoadAllRoles();
    }

    private async void LoadAllUsers()
    {
        try 
        {
            AllUsers = await UserStore.GetAllUsersAsync();
        } 
        catch (Exception e) 
        {
            Logger.LogError(e.Message);
            Logger.LogError(e.StackTrace);
            AllUsers = null;
        }
        _loadingUsers = false;
        StateHasChanged();
    }

    private async void LoadAllRoles()
    {
        try 
        {
            AllRoles = await RoleStore.GetAllRolesAsync();
        } 
        catch (Exception e) 
        {
            Logger.LogError(e.Message);
            Logger.LogError(e.StackTrace);
            AllRoles = null;
        }
        _loadingRoles = false;
        StateHasChanged();
    }



    // ===== NEW ROLE CREATION =====

    private bool _creatingRole = false;

    private RoleModel roleModel = new();

    private EditContext? editContext;

    protected override void OnInitialized() {
        editContext = new(roleModel);
        editContext.EnableDataAnnotationsValidation();
    }

    private async Task HandleValidSubmit() {
        if (editContext != null && editContext.Validate()) {

            Logger.LogInformation(roleModel.Name);

            ArgumentNullException.ThrowIfNull(roleModel.Name);

            var result = await roleManager.CreateAsync(new ApplicationRole("Administrator"));

            if (result.Succeeded)
            {
                _creatingRole = false;
                _loadingRoles = true;
                LoadAllRoles();
            } 
            else
            {
                foreach (var error in result.Errors)
                {
                    Logger.LogError(error.Description);
                    Logger.LogError(error.Code);
                }
            }

        }
    }

    public class RoleModel
    {
        [Required(ErrorMessage = "Role name is required")]
        [StringLength(256, MinimumLength = 3, ErrorMessage = "Role name must be 3 to 256 characters long")]
        public string? Name { get; set; }
    }

}

And finally, the Program.cs file. IDapperUserStore and IDapperUserStore are interfaces that extend the IUserStore and IRoleStore accordingly (for adding a GetAllAsync() method which is not in the default I...Store interfaces). DapperUserStore and DapperUserStore implement said interfaces and all the necessary methods.

using TestWebsite.Data.Identity;
using TestWebsite.Models;

using Microsoft.AspNetCore.Identity;
using System.Data;
using System.Data.SqlClient;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();

builder.Services.AddScoped<IDapperUserStore, DapperUserStore>();
builder.Services.AddScoped<IDapperRoleStore, DapperRoleStore>();

builder.Services
    .AddIdentity<ApplicationUser, ApplicationRole>()
    .AddUserStore<DapperUserStore>()
    .AddRoleStore<DapperRoleStore>()
    .AddDefaultTokenProviders();

builder.Services.AddTransient<IDbConnection>(
    sp => new SqlConnection(builder.Configuration.GetConnectionString("KristeraLocal"))
);

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();


In summary, calling RoleManager.CreateAsync() method and passing it a new Role raises an error that role name '' is invalid - it thinks it's empty, although the role object being passed has a valid role name that is neither null nor empty. Something to cause this seems to happen in the execution of the CreateAsync() method. Does anyone have an idea as to what could cause this?


Solution

  • We need to inject IServiceProvider in your page or controller, and you also have registed service builder.Services.AddScoped<IDapperRoleStore, DapperRoleStore>();. But the method can't been excuted.

    If we want to use it, we can use it like below.

    var roleStore = _provider.GetService<IRoleStore<IdentityRole>>();
    

    My Test Code

    using System;
    using System.Linq.Expressions;
    using System.Threading;
    using Microsoft.AspNetCore.Identity;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace IdentityTest.Pages;
    
    public class CreateRoleModel : PageModel
    {
        private readonly ILogger<CreateRoleModel> _logger;
        //private readonly RoleManager<IdentityRole> _roleManager;
        public IServiceProvider _provider;
    
        public CreateRoleModel(ILogger<CreateRoleModel> logger,  IServiceProvider provider) //RoleManager<IdentityRole> roleManager,
        {
            _logger = logger;
            //_roleManager = roleManager;
            _provider= provider;
        }
    
        public IActionResult OnGet()
        {
            _logger.LogInformation("OnGetAsync()");
            return Page();
        }
    
        public async Task<IActionResult> OnPostAsync()
        {
            _logger.LogInformation("OnPostAsync()");
            
            var roleStore = _provider.GetService<IRoleStore<IdentityRole>>();
    
            //create the roles and seed them to the database 
            var result = await roleStore.CreateAsync(new IdentityRole("Administrator"), CancellationToken.None);
            if (result.Succeeded)
            {
                _logger.LogInformation("YAY!");
            }
            else
            {
                foreach (IdentityError error in result.Errors)
                {
                    _logger.LogError(error.Description);
                }
            }
    
            return Page();
        }
    }