'How Can I Pass an Object Property to a Razor Component as a Parameter

I have a razor component library where I'm creating custom, reusable components. I have a "ContentItem" component that I would like to simply bind the property of an object in the component and then use reflection or some other method to discover the necessary information. As an example:

ContentItem.razor

<div>
    <div>@DisplayName</div>
    <div>@PropertyValue</div>
</div>

ContentItem.razor.cs

public partial class ContentItem
{
        /// <summary>
        /// The property that this component will bind to
        /// </summary>
        [Parameter]
        public **???** ObjectProperty{ get; set; }
 
        public string DisplayName;
        public string PropertyValue;
 
        protected override void OnParametersSet()
        {
          try
          {
            DisplayName = //reflection or some way to get the display attribute from the Object Property
            PropertyValue = //reflection or inspection of the ObjectProperty

            base.OnParametersSet();
          }
         catch (Exception ex)
         {
            throw new exception("Error", ex);
         }
       } 

Page in the client app

<div>
    <ContentItem ObjectProperty="@User.FirstName" />
</div>  

So basically all you would have to do when using the "ContentItem" component would be to pass the ObjectProperty and then the "ContentItem" component would perform some sort of reflection and/or inspection of that parameter to render the HTML as desired.



Solution 1:[1]

You will need to pass the type of class, property and the value separately to the component.

The type will be typeof(User) and the property name can be derived from nameof(User.FirstName) and the value will be whatever the User.FirstName value is being held as string or whatever.

The parameters in your ContentItem component will be like this:

[Parameter]
public Type ObjectType { get; set; }

[Parameter]
public string ObjectProperty { get; set; }

[Parameter]
public string ObjectValue { get; set; }

and can be called like this:

<ContentItem 
            ObjectType="@(typeof(User))" 
            ObjectProperty="@(nameof(User.FirstName))" 
            ObjectValue="@User.FirstName" />

So assume your class is like this:

public class User
{
    [DisplayName("First name")]
    public string FirstName { get; set; }
}

After that in the component use the below helper method to get the DisplayName:

public static string GetDisplayName(Type @type, string propertyName)
{
    var memberInfo = @type?.GetMember(propertyName)[0];

    var displayNameAttribute = memberInfo?.GetCustomAttribute<DisplayNameAttribute>();
    string displayName = displayNameAttribute?.DisplayName ?? "";

    return string.IsNullOrEmpty(displayName) ? propertyName : displayName;
}

Solution 2:[2]

I ended up solving this with Cascading Values / Parameters. I have a container component that exposes a Cascading Value named "BindObject".

<CascadingValue Name="BindObject" Value="@BindObject">
@if (BindObject != null)
{
    @ChildContent
}
else
{
    if (PlaceholderLines > 0)
    {
        <Placeholder DisplayLines="@PlaceholderLines"></Placeholder>
    }
}
</CascadingValue>

Then in my Content Item component I use a Cascading Parameter to get the object. I also expose a "BindProperty" parameter.

    /// <summary>
    /// When the ContentItem is placed inside of a ContentItems
    /// container then the BindObject will be passed as a Cascading
    /// Parameter.  This ContentItem will then use reflection to
    /// populate the Label and Value of this control.
    /// </summary>
    [CascadingParameter(Name = "BindObject")]
    public object BindObject { get; set; }

    /// <summary>
    /// If a BindProperty is specified then a generic placeholder
    /// will be used while the property is being loaded or evaluated.
    /// The BindProperty only works when the ContentItem is placed
    /// inside of a ContentItems control.
    /// </summary>
    [Parameter]
    public string BindProperty { get; set; }

Now I can specify the bound object one time in the Content Items container component and then place individual Content Item components inside of it. I specify the "BindProperty" and then use reflection on the model.

        <ContentItems @ref="requestItems" BindObject="@jsonObject">
            <ContentItem BindProperty="@nameof(jsonObject.UserId)" />
            <ContentItem BindProperty="@nameof(jsonObject.FirstName)" />
            <ContentItem BindProperty="@nameof(jsonObject.LastName)" />
            <ContentItem BindProperty="@nameof(jsonObject.Email)" />
            <ContentItem BindProperty="@nameof(jsonObject.RegionId)" />
            <ContentItem BindProperty="@nameof(jsonObject.Password)" />
            <ContentItem BindProperty="@nameof(jsonObject.Id)" />
        </ContentItems>

I use data annotations to specify the Display Name, requirements, Icon, etc. The Content Item component uses reflection to consume this information so that it only needs to be defined in the model itself. I've also written some custom validation annotations to keep everything consistent.

    [Required]
    [DisplayName("First Name:")]
    [KzAlphaOnly(ErrorMessage = "Only letters are allowed here.")]
    [MaxLength(16, ErrorMessage = "First name must be 16 characters or less.")]
    [KzIcon(FaIcon = FaIcons.User)]
    public string FirstName { get; set; }

    [Required]
    [KzAlphaOnly(ErrorMessage = "Only letters are allowed here.")]
    [MaxLength(64, ErrorMessage = "Last Name name must be 64 characters or less.")]
    [KzIcon(FaIcon = FaIcons.User)]
    [DisplayName("Last Name:")]
    public string LastName { get; set; }

    [Required]
    [StringLength(256)]
    [DisplayName("Email:")]
    [KzEmail]
    [KzIcon(FaIcon = FaIcons.EnvelopeSquare)]
    public string Email { get; set; }

Now I am able to display information from a model with minimal effort. This is useful for templates, action cards, etc.

enter image description here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Umair
Solution 2 Anzel