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.

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.

Manage Account.
Manage Account Mobile.

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.

Register Page.
Register Page Mobile.

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.

Register With Error.
Register With Error Mobile.

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)}";
Ken Haggerty
Created 06/02/22
Updated 06/19/22 15:24 GMT

Log In or Reset Quota to read more.

Successfully completed. Thank you for contributing.
Contribute to enjoy content without advertisments.
Something went wrong. Please try again.

Comments(0)

Loading...
Loading...

Not accepting new comments.

Submit your comment. Comments are moderated.

User Image.
DisplayedName - Member Since ?