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)