Asp.Net Core 3.1 LinqToDB.Identity UserManager CreateAsync Error - asp.net-core

I have implemented LinqToDB.Identity into my project. I have been trying to create a user by using .Net Identity UserManager, but I am getting an error. I have also implemented LinqToDB.Identity optimizations, such as AddLinqToDBStores and IdentityConnectionFactory.
As I have mentioned about it, I am getting an error like this when I try to create an user.
{"Method not found: 'Int32 LinqToDB.DataExtensions.Insert(LinqToDB.IDataContext, System.__Canon, System.String, System.String, System.String)'."}
Here is my AddLinqToDBStores options and configurations.
public static void AddDevPlatformAuthentication(this IServiceCollection services, IConfiguration configuration)
{
services.AddIdentity<AppUser, LinqToDB.Identity.IdentityRole<int>>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 4;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.User.RequireUniqueEmail = true;
//TODO
//options.User.RequireUniqueEmail = true;
//options.SignIn.RequireConfirmedEmail = true;
//options.Lockout.MaxFailedAccessAttempts = 5;
//options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(3);
}).AddLinqToDBStores<int, AppUserClaim, AppUserRole, AppUserLogin, AppUserToken, AppRoleClaim>(new
IdentityConnectionFactory(new SqlServerDataProvider(ProviderName.SqlServer, SqlServerVersion.v2017), "SqlServerIdentity", DataSettingsManager.LoadSettings().ConnectionString))
.AddDefaultTokenProviders();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
// Uncomment the following lines to enable logging in with third party login providers
JwtTokenDefinitions.LoadFromConfiguration(configuration);
services.ConfigureJwtAuthentication();
services.ConfigureJwtAuthorization();
}
Here is my IdentityConnectionFactory class that has inherited from IConnectionFactory interface.
public class IdentityConnectionFactory : IConnectionFactory
{
private static readonly Dictionary<string, HashSet<string>> _tables = new Dictionary<string, HashSet<string>>();
private readonly string _configuration;
private readonly string _connectionString;
private readonly string _key;
private readonly IDataProvider _provider;
public IdentityConnectionFactory(IDataProvider provider, string configuration, string connectionString)
{
_provider = provider;
Configuration.Linq.AllowMultipleQuery = true;
//DataConnection.AddConfiguration(configuration, connectionString, provider);
_configuration = configuration;
_connectionString = connectionString;
_key = _configuration + "$$" + _connectionString;
}
public IDataContext GetContext()
{
return new DataContext(_provider, _connectionString);
}
public DataConnection GetConnection()
{
var db = new DataConnection(_provider, _connectionString);
db.AddMappingSchema(AdditionalSchema);
return db;
}
protected MappingSchema AdditionalSchema
{
get
{
if (!(Singleton<MappingSchema>.Instance is null))
return Singleton<MappingSchema>.Instance;
Singleton<MappingSchema>.Instance =
new MappingSchema(_provider.Name) { MetadataReader = new FluentMigratorMetadataReader() };
return Singleton<MappingSchema>.Instance;
}
}
There are so many code blocks that I can not paste here. I would be very happy if someone could help.
If you would like to see the project, you can check here;
https://github.com/dogaanismail/DevPlatform

This problem has been solved by adding LinqToDB.Identity class library into the solution. I have created an issue on Github. You can check from this link.
https://github.com/linq2db/linq2db/issues/2400
I have uninstalled LinqToDB.Identity that is a nuget package. Instead of using LinqToDB.Identity nuget package, it is better to use LinqToDB.Identity class library. In addition, I can debug this class library. It is really useful!
You can obtain LinqToDB.Identity class library with this link
https://github.com/linq2db/LinqToDB.Identity/tree/master/src/LinqToDB.Identity
or if you would, you can check my project that is called DevPlatform.
https://github.com/dogaanismail/DevPlatform
In addition to all of these, I have added an IdentityAttribute for my AppUser primary key. I did not create an user without this attribute.
public class AppUser : IdentityUser<int>, IEntity
{
[Required, Identity]
[Key]
public override int Id { get => base.Id; set => base.Id = value; }
public DateTime CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
public int? CreatedBy { get; set; }
public int? ModifiedBy { get; set; }
public int? StatusId { get; set; }
}
As a result of creating a class library and adding LinqToDB.Identity classes, such as IdentityUser, DefaultConnectionFactory, IConcurrency etc. I created an user successfully by using UserManager.

Related

Blazor : How to read appsetting.json from a class in .NET 6?

The following is working for me, but not sure this is the right way to do use DI in .NET6 blazor.
I have the following class
public class Authentication
{
private IConfiguration _configuration;
private AppState _appState;
public Authentication(IConfiguration Configuration, AppState appState)
{
_configuration = Configuration;
_appState = appState; ;
}
public async Task<AccessToken?> getAccessToken()
{
var tokenServer = _configuration.GetValue<string>("tokenUrl");
var clientID = _configuration.GetValue<string>("ABC:ClientID");
var clientSecret = _configuration.GetValue<string>("ABC:ClientSecret");
var grantType = _configuration.GetValue<string>("ABC:GrantType");
AccessToken? accessToken = null;
.............
............
return accessToken;
}
}
in my code behind of razor page
namespace XXXXXXXXXXX.Pages
{
public partial class Index
{
[Inject]
public ILogger<Index> _Logger { get; set; }
[Inject]
public IConfiguration Configuration { get; set; }
[Inject]
public AppState _appState { get; set; }
**Authentication auth;**
protected override void OnInitialized()
{
**auth = new Authentication(Configuration, _appState);**
base.OnInitialized();
}
private async Task HandleValidSubmit()
{
_Logger.LogInformation("HandleValidSubmit called");
auth.getAccessToken();
// Process the valid form
}
}
}
My Question is I was Expecting the DI to do its magic and Insert the Dependency in my class.
but to get this working i had to write
auth = new Authentication(Configuration, _appState);
I was expecting to instantiate
using auth = new Authentication() , but this one throws compiler error.

How to get AppSetting values in startup by using services in asp.net core?

I want to get value of appsetting inside StartUp and also using services for saving them.
I create a static IServiceCollection method for AddTransient my custom service.
I define a readonly variable for keep the appsetting values. My problem is that, this service creates new instance for readonly variable, for all calling.how can I prevent this?
and I have a question that other extensions like AddOpenIdConnect, how to work with their configs, I mean how to save and use them?
this is startup:
public void ConfigureServices(IServiceCollection services){
...
services.AddMyIntegration(conf =>
{
conf.ConnectionString = Configuration.GetConnectionString("Integration");
conf.AgentApiAddress = Configuration["AgentApiAddress"];
});
}
....
public static class MyExtension
{
public static IServiceCollection AddMyIntegration(this IServiceCollection services, Action<MyConstantsProvider> myConstantsProvider)
{
services.AddTransient((t) =>
{
return new MyService(myConstantsProvider);
});
return services;
}
}
this is my service:
public class MyService
{
public readonly MyConstantsProvider Provider;
public MyService(Action<MyConstantsProvider> configure)
{
Provider = new MyConstantsProvider();
configure(Provider);
}
}
public class MyConstantsProvider
{
public string ConnectionString { get; set; }
public string AgentApiAddress { get; set; }
}
Update my question:
Finally I fixed my issue by add MyConstantsProvider as singletone instead of MyService so this creates new instance of variable at the first time in extension class:
public static class MyExtension
{
public static IServiceCollection AddMyIntegration(this IServiceCollection services, Action<MyConstantsProvider> myConstantsProvider)
{
var provider = new MyConstantsProvider();
myConstantsProvider(provider);
services.AddSingleton(provider);
services.AddTransient<MyService>();
return services;
}
}
this is MyService class:
public class MyService
{
public readonly MyConstantsProvider Provider;
public MyService(MyConstantsProvider provider)
{
Provider = provider;
}
}
I wonder why we make it so complicated ? I just saw we're trying to read appsettings later in the application somewhere, and for this, the framework have default implementation to back us up.
Our app settings might look like
{
"Catalog": {
"ConnectionString": "SomeConnection",
"AgentApiAddress": "http://somewhere.dev"
}
}
Then our class could be
public class MySetting
{
public string ConnectionString { get; set; }
public string AgentApiAddress{ get; set; }
}
Config register it in startup (or somewhere we like in .net 6)
services.Configure<MySetting>(configuration.GetSection("Catalog"));
Retrive it later in the app via DI
public class SomeService
{
private readonly MySetting _setting;
public SomeService(IOptions<MySetting> config)
{
_setting = config.Value;
}
}
For setting that can be change dynamically, take a look at IOptionsMonitor
Or that might be some special case that I miss ?

How can I pass the authenticated User Id to the class library project in asp.net core via DI?

I have a NLayers application:
asp.net core mvc
asp.net web api
and some of my class libraries:
DataLayer
DomainClasses
Models
Services
here is my BaseService in ServicesLayer:
public abstract partial class BaseService
{
protected BaseService(AppDbContext dbContext
, UserManager<MyApplicationUser> userManager
, int authenticatedUserId)
{
DbContext = dbContext;
AuthenticatedUserId = authenticatedUserId;
MyUserManager = userManager;
Init();
}
public AppDbContext DbContext { get; }
protected UserManager<MyApplicationUser> MyUserManager;
public string AuthenticatedUserId { get; }
protected virtual void Init()
{
//
}
...
}
and one of my child service classes:
public class BookService :BaseService
{
public BookService(AppDbContext dbContext
, UserManager<MyApplicationUser> userManager
, int authenticatedUserId)
:base(dbContext,userManager, authenticatedUserId)
{
}
}
I want to access the authenticated user id (from Asp net core) in my services (class library). How can I pass it via DI or something else?
Updated based on #Frank's suggestion:
public class CommonServicesContainer
{
public AppDbContext DbContext { get; set; }
public AppUserManager UserManager { get; set; }
public int AuthenticatedUserId{ get; set; }
public CommonServicesContainer(AppDbContext appDbContext, AppUserManager userManager, string authenticatedUserId)
{
DbContext = dbContext;
UserManager = userManager;
AuthenticatedUserId = autheticatedUserId;
}
}
my startup:
services.AddScoped<AppDbContext>();
services.AddScoped<AppUserManager>();
services.AddScoped(x =>
{
var authenticatedUserId = x.GetRequiredService<IHttpContextAccessor>().HttpContext.User.Identity.Name;
return new CommonServicesContainer(x.GetRequiredService<AppDbContext>()
, x.GetRequiredService<AppUserManager>()
, authenticatedUserId);
});
AccountController :
private readonly CommonServicesContainer _commonServicesContainer;
public AccountController(CommonServicesContainer commonServicesContainer)
{
_commonServicesContainer = commonServicesContainer;
// ...
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
// ...
if(ModelState.IsValid)
{
var isValid = await _usersService.AreCredentialsValidAsync(model.Username, model.Password);
if(isValid)
{
var foundUser = await _usersService.GetByUserNameAsync(model.Username);
await HttpContext.SignInAsync(
foundUser.SubjectId,
foundUser.UserName);
//_commonServicesContainer.AuthenticatedUserId = foundUser.Id;
// ...
}
// ...
}
You can do that by register a AuthenticatedUser type as a AddScoped.
class AuthenticatedUser {
public int? UserId {get;set;}
public bool IsAuthenticated => int.HasValue;
}
in Startup.cs of your AspNetCore project:
public IServiceProvider ConfigureServices(IServiceCollection services) {
...
services.AddScoped<AuthenticatedUser>();
...
}
Somewhere you do the authentication, you get the AuthenticatedUser and set the UserId.
Since AuthenticatedUser is added as scoped it acts as global (same instance) for the particular httprequest scope. So all .GetService<AuthenticatedUser> / .GetRequiredService<AuthenticatedUser> will have the same instance - within the same scope.
Each http-request has it is own scope, and thereby also their own AuthenticatedUser.
When the user is Authenticated, using AspNetCore Identity, you can find the AspNetUsers Id by:
if( httpContext.User.Identity.IsAuthenticated ) {
var userIdClaim = httpContext.User.Claims.SingleOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier");
var aspNetUsersId = userIdClaim?.Value ?? 0;
}
This can be done as Middleware, then setting AuthenticatedUser.UserId.

DataContext.Users returns empty result in Razor pages

Hi I have a problem accessing Users table from a Razor page in ASP.NET Core
I Created an AppDataContext class that extends IdentityDbContext<Models.User, Models.UserRole, string>
I can use it with other controller classes and services without problems. But when I start working on razor pages the dataContext.Users always return empty enumerable. Other DbSets still working properly, only the Users not work.
This also happens when I try to access data from other services like UserManager.Users or SigniInManager.UserManager.Users
Here's some part of my files
AppDataContext
public class AppDataContext : IdentityDbContext<Models.User, Models.UserRole, string>
{
// Other DbSet's
public AppDataContext(DbContextOptions<AppDataContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Other entities building
MapIdentityTables(builder);
}
private void MapIdentityTables(ModelBuilder builder)
{
builder.Entity<Models.User>().ToTable("Users");
builder.Entity<IdentityUser>().ToTable("Users");
builder.Entity<Models.UserRole>().ToTable("UserRoles");
builder.Entity<IdentityRole>().ToTable("UserRoles");
builder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims");
builder.Entity<IdentityUserRole<string>>().ToTable("UserUserRoles");
builder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins");
builder.Entity<IdentityRoleClaim<string>>().ToTable("UserRoleClaims");
builder.Entity<IdentityUserToken<string>>().ToTable("UserTokens");
}
LoginPage.cshtml.cs
public class LoginModel : PageModel
{
private readonly Identity.AppUserManager userManager;
private readonly SignInManager<Models.User> signInManager;
private readonly Data.AppDataContext dataContext;
public LoginModel(Identity.AppUserManager userManager, SignInManager<Models.User> signInManager, Data.AppDataContext dataContext)
{
this.userManager = userManager;
this.signInManager = signInManager;
this.dataContext = dataContext;
}
public IList<Model.User> Users { get; private set; }
public void OnGet()
{
Users = userManager.Users.ToList(); // Empty
Users = signInManager.UserManager.Users.ToList(); // Empty
Users = dataContext.Users.ToList(); // Empty
}
}
User class
public class User : Microsoft.AspNetCore.Identity.IdentityUser
{
public ICollection<UserDevice> Devices { get; set; }
public IList<UserPassword> Passwords { get; set; }
}
Have I done anything wrong or am I missing something?
UPDATE
The problem is gone somehow now after I gave up and do something else. But it's not a solution since the original problem still there if I did the same.
What I have done was to revert all my changes and added AddSecondIdentity from this SO answer. Created StaffUser : IdentityUser and StaffUserManager<StaffUser, UserRole> (same UserRole as the original UserManager) to handle those new IdentityUser objects.
Then I just use StaffUserManager and SignInManager<StaffUser> instead of AppUserManager and SignInManager<User> in Login.cshtml.cs
public LoginModel(StaffUserManager userManager, SignInManager<Models.StaffUser> signInManager, Data.AppDataContext context)
{
var users = context.Users.ToList() // 1 user
}
Which now confuses me further. But I don't have time for this now. I think it has something to do with the Discriminator part of the database since the returned user is the one with StaffUser discriminator value but there are some others without the discriminator that are not returned.
Make sure you add services.AddIdentity
services
.AddIdentity<User, ApplicationRole>(options =>
{
options.Password.RequireDigit = false;
options.Password.RequiredLength = 4;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
//lock out attempt
options.Lockout.AllowedForNewUsers = true;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 3;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
If this doesn't work please provide me your source code so I can take a look on it. Perhaps your github.

Change injected object at runtime

I want to have multiples implementation of the IUserRepository each implementation will work with a database type either MongoDB or any SQL database. To do this I have ITenant interface that have a connection string and other tenant configuration. The tenant is been injected into IUserRepository either MongoDB or any SQLDB implementation. What I need to know is how properly change the injected repository to choose the database base on the tenant.
Interfaces
public interface IUserRepository
{
string Login(string username, string password);
string Logoff(Guid id);
}
public class User
{
public Guid Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public interface ITenant
{
string CompanyName { get; }
string ConnectionString { get; }
string DataBaseName { get; }
string EncriptionKey { get; }
}
Is important to know that the tenant id is been pass to an API via header request
StartUp.cs
// set inject httpcontet to the tenant implemantion
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
// inject tenant
services.AddTransient<ITenant, Tenant>();
// inject mongo repository but I want this to be programmatically
services.AddTransient<IUserRepository, UserMongoRepository>();
Sample Mongo Implementation
public class UserMongoRepository : IUserRepository
{
protected ITenant Tenant
public UserMongoRepository(ITenant tenant) :
base(tenant)
{
this.Tenant = tenant;
}
public string Login(string username, string password)
{
var query = new QueryBuilder<User>().Where(x => x.Username == username);
var client = new MongoClient(this.Tenant.ConnectionString);var server = client.GetServer();
var database = client.GetServer().GetDatabase(this.Tenant.DataBaseName);
var user = database.GetCollection<User>.FindAs<User>(query).AsQueryable().FirstOrDefault();
if (user == null)
throw new Exception("invalid username or password");
if (user.Password != password)
throw new Exception("invalid username or password");
return "Sample Token";
}
public string Logoff(Guid id)
{
throw new NotImplementedException();
}
}
Tenant
public class Tenant : ITenant
{
protected IHttpContextAccessor Accesor;
protected IConfiguration Configuration;
public Tenant(IHttpContextAccessor accesor, IDBConfiguration config)
{
this.Accesor = accesor;
this.Configuration = new Configuration().AddEnvironmentVariables();
if (!config.IsConfigure)
config.ConfigureDataBase();
}
private string _CompanyName;
public string CompanyName
{
get
{
if (string.IsNullOrWhiteSpace(_CompanyName))
{
_CompanyName = this.Accesor.Value.Request.Headers["Company"];
if (string.IsNullOrWhiteSpace(_CompanyName))
throw new Exception("Invalid Company");
}
return _CompanyName;
}
}
private string _ConnectionString;
public string ConnectionString
{
get
{
if (string.IsNullOrWhiteSpace(_ConnectionString))
{
_ConnectionString = this.Configuration.Get(this.CompanyName + "_" + "ConnectionString");
if (string.IsNullOrWhiteSpace(_ConnectionString))
throw new Exception("Invalid ConnectionString Setup");
}
return _ConnectionString;
}
}
private string _EncriptionKey;
public string EncriptionKey
{
get
{
if (string.IsNullOrWhiteSpace(_EncriptionKey))
{
_EncriptionKey = this.Configuration.Get(this.CompanyName + "_" + "EncriptionKey");
if (string.IsNullOrWhiteSpace(_EncriptionKey))
throw new Exception("Invalid Company Setup");
}
return _EncriptionKey;
}
}
private string _DataBaseName;
public string DataBaseName
{
get
{
if (string.IsNullOrWhiteSpace(_DataBaseName))
{
_DataBaseName = this.Configuration.Get(this.CompanyName + "_" + "DataBaseName");
if (string.IsNullOrWhiteSpace(_DataBaseName))
throw new Exception("Invalid Company Setup");
}
return _DataBaseName;
}
}
}
Controller
public class UsersController : Controller
{
protected IUserRepository DataService;
public UsersController(IUserRepository dataService)
{
this.DataService = dataService;
}
// the controller implematation
}
You should define a proxy implementation for IUserRepository and hide the actual implementations behind this proxy and at runtime decide which repository to forward the call to. For instance:
public class UserRepositoryDispatcher : IUserRepository
{
private readonly Func<bool> selector;
private readonly IUserRepository trueRepository;
private readonly IUserRepository falseRepository;
public UserRepositoryDispatcher(Func<bool> selector,
IUserRepository trueRepository, IUserRepository falseRepository) {
this.selector = selector;
this.trueRepository = trueRepository;
this.falseRepository = falseRepository;
}
public string Login(string username, string password) {
return this.CurrentRepository.Login(username, password);
}
public string Logoff(Guid id) {
return this.CurrentRepository.Logoff(id);
}
private IRepository CurrentRepository {
get { return selector() ? this.trueRepository : this.falseRepository;
}
}
Using this proxy class you can easily create a runtime predicate that decides which repository to use. For instance:
services.AddTransient<IUserRepository>(c =>
new UserRepositoryDispatcher(
() => c.GetRequiredService<ITenant>().DataBaseName.Contains("Mongo"),
trueRepository: c.GetRequiredService<UserMongoRepository>()
falseRepository: c.GetRequiredService<UserSqlRepository>()));
You can try injecting a factory rather than the actual repository. The factory will be responsible for building the correct repository based on the current user identity.
It might require a little more boiler plate code but it can achieve what you want. A little bit of inheritance might even make the controller code simpler.