Search code examples
c#.net-coreblazorrendertype-parameter

How to pass type parameter to Body() when render fragment?


I have a project with a UserBase and several applications with different user types which inherit from the userBase. I pretend to use the different users for all my razor views instead the UserBase. For that, I'm passing the type in the way below through the views:

@typeparam UserType where UserType: class, IUserBase

The problem I have is when I need to pass this type to the @Body which I want to render, in this case, in the MainLayout.razor:

    @inherits LayoutComponentBase
    @typeparam UserType where UserType : class, IUserBase
    
    <PageTitle>ApplicationName</PageTitle> 
    
    <AuthorizeView>
       <div class='layout-wrapper layout-content-navbar'>
          <div class='layout-container'>
             <NavigationMenu UserType="UserType" />
             <div class="layout-page">
                <div class="content-wrapper">
                   <div class='container-xxl flex-grow-1 container-p-y' id="HispaCoreBlazor_MainLayout_ContentWrapper">
                      @Body               
                   </div>
                   <div class="content-backdrop fade"></div>
                </div>
             </div>
          </div>
          <div class="layout-overlay layout-menu-toggle"></div>
          <div class="drag-target"></div>
       </div>
    </AuthorizeView>

I define the user type in the App.razor of my MyBlazorApp1 and here is where I pass the user type to MainLayout (Code above):

@using MyBlazorApp1.Base
<CascadingAuthenticationState>
   <Router AppAssembly="@typeof(App).Assembly"
      AdditionalAssemblies="new[] { typeof(MyBlazorApp1.App).Assembly }">
      <Found Context="routeData">
         <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MyBlazorApp1.Shared.MainLayout<UserTypeFromMyBlazorApp1>)" />
         <FocusOnNavigate RouteData="@routeData" Selector="h1" />
      </Found>
      <NotFound>
         <PageTitle>Not found</PageTitle>
         <LayoutView Layout="@typeof(MyBlazorApp1.Shared.MainLayout<UserTypeFromMyBlazorApp1>)">
            <p role="alert">Sorry, there's nothing at this address.</p>
         </LayoutView>
      </NotFound>
   </Router>
</CascadingAuthenticationState>

@Body should render this view:

@page "/IndexMyBlazorApp1"
@page "/"

@using MyBlazorApp1.Contracts
@using MyBlazorApp1.Base;
@using MyBlazorApp1.Shared;
@typeparam UserType where UserType : IUserBase, new()

@inherits IndexMyBlazorApp1<IUserBase>


<div class="row mb-4">
    ...
</div>

If I want to pass the type to a component, as in the case of NavigationMenu above, it is passed well. But here I get the error Cannot create an instance of MyBlazorApp1.Pages.Index.IndexMyBlazorApp1r`1[UserType] because Type.ContainsGenericParameters is true.

Really appreciate any help. Thank you in advance.


Solution

  • Revised answer based on OP Comments. Original answer removed as irrelevant based on those comments and revised question information.

    The problem is @Body is a RenderFragment and you can't pass types into it like you want.

    The simple solution is to bypass the RenderFragment problem by cascading the Type. Any sub component can then pickup the cascade and use it.

    Here's the revised Layout:

    @inherits LayoutComponentBase
    @typeparam TUserType where TUserType : class, IUserBase
    
    <CascadingValue TValue="Type" Name="UserType" Value="typeof(TUserType)" >
    
    <div class="page">
        <div class="sidebar">
            <NavMenu />
        </div>
    
        <main>
            <div class="top-row px-4">
                <a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
            </div>
    
            <article class="content px-4">
                @Body
            </article>
        </main>
    </div>
    
    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    
    </CascadingValue>
    

    Which could be simplified to this:

    @inherits LayoutComponentBase
    
    <CascadingValue TValue="Type" Name="UserType" Value="typeof(UserABase)">
      //...
    </CascadingValue>
    

    And a demo Home page:

    @page "/"
    
    <PageTitle>Home</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <div class="alert alert-info">@_name</div>
    
    @code {
        [CascadingParameter(Name = "UserType")] private Type UserType { get; set; } = default!;
    
        private string? _name;
    
        protected override void OnInitialized()
        {
            ArgumentNullException.ThrowIfNull(UserType);
            var instance = Activator.CreateInstance(UserType);
            if (instance is IUserBase userBase)
                _name = userBase.Name;
        }
    }