简体   繁体   中英

Blazor + MongoDb Identity: Value cannot be null. (Parameter name 'source')

Could you help me. I am trying to use Blazor with MongoDb Identity and always get Exception: Value cannot be null. (Parameter name 'source') when I call signInManager.SignInAsync(user, false);

MongoDB server version: 4.2.5

ASP.Net Core 3.1

NuGet packages: AspNetCore.Identity.Mongo: 6.7.0 MongoDB.Driver: 2.10.3

Presteps: 1. Create simple Blazor Server project without authentication. 2. Then I simply add AspNetCore.Identity.Mongo.

Startup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

        services.Configure<BookstoreDatabaseSettings>(Configuration.GetSection(nameof(BookstoreDatabaseSettings)));
        services.AddSingleton<IBookstoreDatabaseSettings>(sp =>
             sp.GetRequiredService<IOptions<BookstoreDatabaseSettings>>().Value);

        services
            .AddIdentityMongoDbProvider<ApplicationUser, ApplicationRole>(identityOptions =>
            {
                identityOptions.Password.RequiredLength = 1;
                identityOptions.Password.RequireLowercase = false;
                identityOptions.Password.RequireUppercase = false;
                identityOptions.Password.RequireNonAlphanumeric = false;
                identityOptions.Password.RequireDigit = false;
            }, mongoIdentityOptions =>
            {
                mongoIdentityOptions.ConnectionString = Configuration.GetConnectionString("MongoDbDatabase");
            })
            .AddDefaultTokenProviders();

        services.AddAuthentication(options =>
        {
            options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        });

        services.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddSingleton<WeatherForecastService>();

        services.AddScoped<BookService>();
        services.AddTransient<LoginService>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            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.UseCookiePolicy();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapBlazorHub();
            endpoints.MapFallbackToPage("/_Host");
        });
    }
}

ApplicationUser.cs

public class ApplicationUser : MongoUser
{
    public string Name { get; set; }
    public string LastName { get; set; }
    public string Gender { get; set; }
    public DateTime? Birthdate { get; set; }
    public string Country { get; set; }
    public string State { get; set; }
    public string City { get; set; }
}

ApplicationRole.cs

public class ApplicationRole : MongoRole
{
}

RegisterNewUserData.cs

public class RegisterUserData
{
    public string UserName { get; set; }
    public string Gender { get; set; }
    public string Password { get; set; }
    public string ConfirmedPassword { get; set; }
}

LoginService.cs

public class LoginService
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    private readonly IConfiguration _configuration;
    private readonly RoleManager<ApplicationRole> _roleManager;

    public LoginService(UserManager<ApplicationUser> userManager,
                                 SignInManager<ApplicationUser> signInManager,
                                 IConfiguration configuration,
                                 RoleManager<ApplicationRole> roleManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
        _configuration = configuration;
        _roleManager = roleManager;
    }

    public async Task<bool> LogIn(LoginUserData loginUser)
    {
        ApplicationUser user = await _userManager.FindByNameAsync(loginUser.UserName);

        if (user != null)
        {
            try
            {  
                var result2 = await _signInManager.PasswordSignInAsync(user.Email, loginUser.Password, loginUser.RememberMe, lockoutOnFailure: true);
                if (result2.Succeeded)
                {
                    return true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw;
            }
        }
        return false;
    }

    public async Task<bool> RegisterNewUser(RegisterUserData regUser)
    {
        string roleName = "Member";
        var user = new ApplicationUser
        {
            Name = regUser.UserName,
            UserName = regUser.UserName,
            Email = regUser.UserName,
            Gender = regUser.Gender,
            LastName = "",
            Country = "",
            State = "",
            City = ""
        };

        bool isRoleExists = await _roleManager.RoleExistsAsync(roleName);
        if (isRoleExists == false)
        {
            var role = new ApplicationRole();
            role.Name = roleName;
            await _roleManager.CreateAsync(role);
        }

        var result = await _userManager.CreateAsync(user, regUser.Password);
        if (result.Succeeded)
        {
            await _userManager.AddToRoleAsync(user, roleName);

            var claims = new List<Claim>
            {
                new Claim("user", user.UserName),
                new Claim("role", roleName)
            };
            result = await _userManager.AddClaimsAsync(user, claims);

            if (result.Succeeded)
            {
                try
                {
                    await _signInManager.SignInAsync(user, false);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    throw;
                }                    
                return true;
            }
        }
        return false;
    }
}

During registration process User, Roles and Claimes are created and added successfully. Here is data from MongoDb:

{
  "_id": {
    "$oid": "5e9df2819fecf83484901211"
  },
  "UserName": "M1@g.com",
  "NormalizedUserName": "M1@G.COM",
  "Email": "M1@g.com",
  "NormalizedEmail": "M1@G.COM",
  "EmailConfirmed": false,
  "PasswordHash": "AQAAAAEAACcQAAAAEGdbk1EC4niPacknWuMDpbc+YRZP5CmvH0IaUIslo5/vcHplpJO/iWBU/6opCYsErQ==",
  "SecurityStamp": "BOYJUYQFPMLMHJ6NFBHG64K4SC7WEF5W",
  "ConcurrencyStamp": "e9133395-1eed-4757-91a5-a5fc1d699f5d",
  "PhoneNumber": null,
  "PhoneNumberConfirmed": false,
  "TwoFactorEnabled": false,
  "LockoutEnd": null,
  "LockoutEnabled": true,
  "AccessFailedCount": 0,
  "AuthenticatorKey": null,
  "Roles": [
    "5e9df27c9fecf83484901210"
  ],
  "Claims": [
    {
      "_id": 0,
      "UserId": null,
      "ClaimType": "user",
      "ClaimValue": "M1@g.com"
    },
    {
      "_id": 0,
      "UserId": null,
      "ClaimType": "role",
      "ClaimValue": "Member"
    }
  ],
  "Logins": [],
  "Tokens": [],
  "RecoveryCodes": [],
  "Name": "M1@g.com",
  "LastName": "",
  "Gender": "Male",
  "Birthdate": null,
  "Country": "",
  "State": "",
  "City": ""
}

But when the process calls:

_signInManager.SignInAsync(user, false);

or:

await _signInManager.PasswordSignInAsync(user.Email, loginUser.Password, loginUser.RememberMe, lockoutOnFailure: true);

I got exception: Value cannot be null. (Parameter name 'source') . So, what I had missed?

Notes, I uses the same solution with the ASP.Net Core 2.1 + Razor Pages + MongoDb Identity and this works well.

I think I found the problem. At least for the login error. I don't know if we won't find another similar error later on other user management statements. But, basically, the error says the following:

System.ArgumentNullException: Value cannot be null. (Parameter 'source') at System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) at System.Linq.Enumerable.Select[TSource,TResult](IEnumerable 1 source, Func 2 selector) at AspNetCore.Identity.Mongo.Stores.RoleStore`1.GetClaimsAsync(TRole role, CancellationToken cancellationToken)

First I thought that it was related to the fact that the user I created didn't have any claims. So as your code up here I implemented it on the registration method and also on the admin user seeding. After having the claims on my user, I was still receiving the same error. So after some time a really stopped and tried to understand the error message.

If we read it from bottom to top, we first see that the method GetClaimAsync from the RoleStore is called. After a Select from System.Linq is called, and then we have the argument "source" which is the one that throws the nu,, argument exception.

Looking at the source code for this plugin, we can see that the GetClaimsAsync have this statement:

return dbRole.Claims.Select(e => new Claim(e.ClaimType, e.ClaimValue)).ToList();

dbRole.Claims refer to the Claims object inside the Roles collection. My collection didn't have the Claims object set, they were null.

So, in my Roles seeding method I filled the Claims object in each of the created roles. After that, I was able to login without the ArgumentNullException.

await roleManager.CreateAsync(new MongoRole
                        {
                            Name = role,
                            Claims = new List<IdentityRoleClaim<string>> {
                                new IdentityRoleClaim<string> {
                                    ClaimType = "role",
                                    ClaimValue = role
                                }
                            }
                        });

Now, I wonder if we won't get another problems with these Claims objects.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM