ASP.NET Core 6.0 - Member Profile
This article describes the implementation of a MemberProfile, used to store and display a user's public properties. I will assume you have downloaded the ASP.NET Core 6.0 - Users With Comments Project.
Users With Comments Project and Article Series
The ASP.NET Core 6.0 - Users With Comments Project (UWCP) implements public member profiles and a moderated comment workflow from user submission to admin publication. I started with the ASP.NET Core 6.0 - Users With Device 2FA Project (UWD2FAP) which implements WebAuthn, also known as FIDO2, instead of authenticator apps. The latest version of the UWCP is published at Preview. KenHaggerty. Com. I encourage you to register and submit a comment. Details, screenshots, and related articles can be found at ASP.NET Core 6.0 - Users With Comments Project. The details page includes the version change log.
- ASP.NET Core 6.0 - Users With Comments
- ASP.NET Core 6.0 - API Implementation
- ASP.NET Core 6.0 - API Authorization
- ASP.NET Core 6.0 - Member Profile
- ASP.NET Core 6.0 - Profile Image Control
- ASP.NET Core 6.0 - Comments Workflow
- ASP.NET Core 6.0 - Filtered Comments
- ASP.NET Core 6.0 - Member Listings
- ASP.NET Core 6.0 - Lazy Load On Scroll
Visual Studio 2022 (VS 2022) is required to develop .NET 6 and ASP.NET Core 6.0 applications. .NET 6 and ASP.NET Core 6.0 are Long Term Support (LTS) releases and will be supported until November 08, 2024. .NET 5 is a Current release and will be supported until May 08, 2022. .NET 3.1 is a LTS release and will be supported until December 3, 2022. See .NET and .NET Core release lifecycle.
I wanted a clear separation of public and private profile information. I developed a MemberProfile entity to store DisplayedName, ImageString, and About properties.
public class MemberProfile { /// <summary> /// Creates a new instance of <see cref="MemberProfile"/>. /// </summary> public MemberProfile() { } /// <summary> /// <see cref="MemberProfile"/> constructor with parameters. /// </<summary> /// <param name="appUserId"></param> /// <param name="displayedName"></param> /// <param name="createdDate"></param> public MemberProfile(int appUserId, string displayedName, DateTimeOffset createdDate) { AppUserId = appUserId; DisplayedName = displayedName; CreatedDate = createdDate; } [Key] public int Id { get; set; } [Required] public int AppUserId { get; set; } [StringLength(32)] [Display(Name = "Displayed Name")] public string DisplayedName { get; set; } = string.Empty; public string? ImageString { get; set; } = string.Empty; [StringLength(1024)] public string? About { get; set; } = string.Empty; [Display(Name = "Displayed")] public bool ListingDisplayed { get; set; } [Display(Name = "Disabled")] public bool ListingDisabled { get; set; } [Display(Name = "Created")] public DateTimeOffset CreatedDate { get; set; } [Display(Name = "Updated")] public DateTimeOffset? UpdatedDate { get; set; } }
The MemberProfile implements a foreign key on AppUserId. The AppUser implements a navigational property on MemberProfile.
public class AppUser { ... public virtual MemberProfile? MemberProfile { get; set; } }
The account/ manage/ index page displays the MemberProfile properties with a Public header.
I implement a DisplayedName to protect the user's login name from being compromised. I developed a NotEqualAttribute to ensure the Displayed Name does not match the Login Name.
public class NotEqualAttribute : ValidationAttribute { private string OtherProperty { get; set; } public NotEqualAttribute(string otherProperty) { OtherProperty = otherProperty; } protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { // Get the other property values. var otherPropertyInfo = validationContext.ObjectType.GetRuntimeProperty(OtherProperty); if (otherPropertyInfo == null) return new ValidationResult("OtherPropertyInfo == null"); if (otherPropertyInfo.GetIndexParameters().Length > 0) throw new ArgumentException("IndexParameters not found"); object? otherPropertyValue = otherPropertyInfo.GetValue(validationContext.ObjectInstance, null); if (Equals(value, otherPropertyValue)) { var otherPropertyDisplayName = otherPropertyInfo .GetCustomAttributes(typeof(DisplayAttribute), false) .Cast<DisplayAttribute>().Single().Name ?? OtherProperty; return new ValidationResult(string.Format(ErrorMessageString, validationContext.DisplayName, otherPropertyDisplayName)); } return null; } }
New users enter the Displayed Name when they register.
The NotEqualAttribute is implemented on the RegisterUserInputModel.cs.
public class RegisterUserInputModel { [Required] [StringLength(32, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [Display(Name = "Login Name")] public string LoginName { get; set; } = string.Empty; [Required] [NotEqual("LoginName", ErrorMessage = "The {0} must not equal the {1}.")] [StringLength(32, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [Display(Name = "Displayed Name")] public string DisplayedName { get; set; } = string.Empty; [Required] [StringLength(320)] [EmailAddress] public string Email { get; set; } = string.Empty; [Required] [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W]).{8,}$", ErrorMessage = "The {0} does not meet requirements.")] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } = string.Empty; [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The Password and Confirm password do not match.")] public string ConfirmPassword { get; set; } = string.Empty; }
If the user attempts to submit a Displayed Name equal to the Login Name, the model error is displyed.
After a new AppUser is created a new MemberProfile is created with the AppUserId, DisplayedName, and CreatedDate.
if (!await _userService.AddAppUserAsync(appUser).ConfigureAwait(false)) throw new InvalidOperationException($"An error occurred adding an AppUser."); if (appUser.Id == 0) throw new InvalidOperationException($"An error occurred adding an AppUser (appUser.Id = 0)."); var memberProfile = new MemberProfile(appUser.Id, Input.DisplayedName, createdDate); if (!await _userService.AddMemberProfileAsync(memberProfile).ConfigureAwait(false)) throw new InvalidOperationException("An error occurred adding a MemberProfile.");
More than one user may have the same DisplayedName. Comments and the Member Listing use a Member Title composed of the DisplayedName and the CreatedDate to improve identity.
MemberTitle = $"{(appUser.MemberProfile == null ? "Not Found" : appUser.MemberProfile.DisplayedName)} - Member Since {string.Format(AppSettings.DateOnlyFormat, appUser.CreatedDate)}";
Comments(0)