I have multiple projects in my solution, all .NET Core 3.1. One of them is my core project (“A Project”) where I just have basic model classes with no methods or database access. For demonstration purposes, below are simplified versions of my Address.cs
and User.cs
files:
public class Address
{
public int Id {get;set;}
public string AddressText {get;set;}
public virtual User User {get;set;}
}
public class User
{
public int UserId {get;set;}
public int UserName {get;set;}
public ICollection<Address> {get;set;}
}
In another project (“B Project”), I will be building the actual functionality. This project already has ASP.NET Core Identity setup with an ApplicationUser
class, which derives from IdentityUser
and adds some custom properties.
This is where I run into my problem. The Address.User
property has to be set to an instance of ApplicationUser
, but ApplicationUser
lives in the B Project.
Obviously, I don't have ASP.NET Core Identity set up as a dependency within my A Project, so I can't move the ApplicationUser
class into that project. Further, I cannot assign the ApplicationUser
to the Address.User
property since ApplicationUser
doesn’t derive from User
.
Having done some research, I found a couple of different suggestions. One proposal is to use a separate project for the ASP.NET Core Identity components and then reference it alongside my A Project from within my B Project. Another soul suggested creating my own UserStore
.
I don't want my A Project to be dependent upon anything. But I don’t have enough experience to decide what option is preferable for my scenario.
I have definitely painted myself into this particular corner before!
There are a few strategies you can take to resolve this, including the two you listed. The approach I‘d recommend, however, is to use interfaces.
Instead of having a concrete User
class, you’ll instead have an IUser
interface which you’ll reference from the models in Project A. You’ll then apply the IUser
interface to your ApplicationUser
class. This will allow instances of ApplicationUser
to be assigned to your e.g. Address.User
property, despite the fact that Address
isn’t aware of ApplicationUser
.
In Project A, you’ll update your classes to something like the following:
public class Address
{
public int Id {get;set;}
public string AddressText {get;set;}
public virtual IUser User {get;set;}
}
public interface IUser
{
int UserId {get;set;}
int UserName {get;set;}
ICollection<Address> {get;set;}
}
Then, in Project B, you’ll apply the IUser
interface to your ApplicationUser
class and ensure it implements the required properties:
public class ApplicationUser: IdentityUser, IUser
{
…
public int UserId {get;set;}
public ICollection<Address> {get;set;}
}
Note: You don’t need to implement
UserName
, as that’s already been implemented onIdentityUser
. Though, of course, you can always override the property, should the need arise (e.g., to add validation attributes).
When you access e.g., your Address.User
property, you’ll only be able to access the members you defined on IUser
. If you need to access any additional members defined on either ApplicationUser
or IdentityUser
, you will first need to cast your IUser
reference to an ApplicationUser
; e.g.,
var user = address.User as ApplicationUser;
var emailConfirmed = user?.EmailConfirmed?? false;
Of course, if you know you’re going to need access to these members, you can just make sure they’re defined on your interface, and not have to worry about this.
There are a couple of considerations worth being aware of. These may not apply to you, but I want to include them for completeness.
As mentioned in my comment, if you’re using an O/RM to populate your models—such as Entity Framework (EF) Core—you may run into problems identifying a concrete implementation of your interface in a separate assembly. This can be done, but it definitely adds complexity which you may not want to contend with! If you’re manually constructing your object graph, though, this won’t be an issue.
The IdentityUser
is meant to represent the currently authenticated user, not a general user reference. For instance, in an e-commerce app, it doesn’t make sense to construct an IdentityUser
for references to the seller of each product. There is obviously overlap here, and using one data source to feed both is fine. But there are also properties—such as PasswordHash
or SecurityStamp
—which don’t make sense to populate on a general user model. You may eventually find these needs in conflict with one another.
In either of the above cases, you may find it easier to differentiate between your ApplicationUser
and your User
classes. That’s not what you asked for, but it’s worth considering. In that case, @RomanKalinchuk‘s approach makes more sense. Though, even then, you can still unify them by applying the same IUser
interface to each, thus ensuring they share a core set of properties.
It is only an opinion on how to solve this particular case
You have some domain classes like User, Address etc And all your application domain properties are there Like Age, Gender, FirstName, LastName, Email, ID, City, Street , those are populated by your application users, could be viewed on application personal area, used e.g. for delivery, notifications etc
And you have authorization part. There is IdentityUser
class (AspNetUsers table) with data about user credentials (UserName, hashed password, etc). The only usage for this data is to check which actions are available at the moment
Some actions or controllers could be marked by Authorize
attribute and those need user to be authentificated in your system.
So domain User
and authorization IdentityUser
classes could exist entirely separate from each other and this will satisfy your needs
But how should you get current domain user?
You could do it like this
public interface IUserService
{
User GetUserByEmail(string email);
}
[Authorize]
public class UserController : Controller
{
private readonly IUserService _userService;
public UserController(IUserService userService)
{
_userService = userService;
}
public User GetCurrentUser()
{
var userName = HttpContext.User.Identity.Name;
return _userService.GetUserByEmail(userName);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With