0

I am working on a razor component library in which I would like to expose some components. However I would like to have the user of the library be able to set some global (generic) object that all other components can access. The type of the generic would be set on startup and should not change runtime.

Here is what I have so far.

//MyComponent.razor
@typeparam TGlobalState
@typeparam TScope

<p>Test</p>
@Default

@code {
    [CascadingParameter(Name = "Global")]
    public required TGlobalState Global { get; set; }

    [CascadingParameter(Name = "Scope")]
    public required TScope Scope { get; set; }

    public Context<TGlobalState, TScope> CurrentScope => new()
    {
        Global = Global,
        Scope = Scope
    };
    [EditorRequired, Parameter]
    public required RenderFragment<Context<TGlobalState, TScope>> Default { get; set; }
   
    [CascadingParameter(Name = "ComponentName")]
    public required string ComponentName { get; set; }
    [Parameter]
    public string Key { get; set; }

    public class Context<TGlobal, TLocal>
    {
        public required TGlobal Global { get; set; }
        public required TLocal Scope { get; set; }
    }
}


//ScopeProvider.razor
@attribute [CascadingTypeParameter(nameof(TScope))]
@typeparam TScope

<CascadingValue Value="@ComponentName" Name="ComponentName">
    <CascadingValue Value="@Scope" Name="Scope">
        @ChildContent
    </CascadingValue>
</CascadingValue>

@code {
    [Parameter]
    public required string ComponentName { get; set; } = "";

    [Parameter]
    public TScope? Scope { get; set; }

    [Parameter]
    public required RenderFragment ChildContent { get; set; }
}


//GlobalStateProvider.razor
@attribute [CascadingTypeParameter(nameof(TGlobalState))]
@typeparam TGlobalState

    <CascadingValue Value="@Global" Name="Global">
            @ChildContent
    </CascadingValue>

@code {
    [Parameter]
    public TGlobalState? Global { get; set; }

    [Parameter]
    public required RenderFragment ChildContent { get; set; }
}

And here is how I imagine it would be used:

//MainLayout.razor
@inherits LayoutComponentBase

<GlobalStateProvider Global="globalState">
<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

</GlobalStateProvider>
@code{

    GlobalState globalState = new();
}
//Index.razor
@page "/"


<PageTitle>Index</PageTitle>


<h1>Hello, world!</h1>

Welcome to your new app.


@*<GlobalStateProvider Global="@(new GlobalState())">*@ //If I uncomment this it works

    <ScopeProviderScope="@(new HomeState())">

        <MyComponent Key="SomeText">
            <Default>@context.Global.Something</Default>
        </MyComponent>
 
    </ScopeProvider>
@*</GlobalStateProvider >*@



@code{

    private HomeState state = new();

    private class HomeState
    {
        public string Name { get; set; }
    }
}

This issue is as following. The MyComponent can not infer TGlobalState from the GlobalStateProvider when it is in another razor file. I am also aware that this is stated to not work in the docs.

I was just wondering if there is any workaround to achieving a similar thing. the GlobalStateProvider could just as good be a service instead of a component and the service could be registered in the dependency injection (or something).

Thank you!

Dylan Snel
  • 671
  • 2
  • 7
  • 26
  • Use a service, and in the Readme for your library, instruct users to add a Singleton or Scoped reference in Program.cs. Direct Injection is so easy to use. – Bennyboy1973 Aug 02 '23 at 01:57
  • Thank you, how would i consume said service? – Dylan Snel Aug 02 '23 at 06:14
  • If your object contains configuration data then use Appsettings and Options - see https://learn.microsoft.com/en-us/dotnet/core/extensions/options and more general information - https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration – MrC aka Shaun Curtis Aug 02 '23 at 08:02
  • But how would i know what generic the user has set. – Dylan Snel Aug 02 '23 at 08:55
  • What, exactly, do you mean by the term "setting a generic." What are the possibilities? – Bennyboy1973 Aug 02 '23 at 11:46
  • There is no notion, under normal conditions, of "setting a generic type at runtime," since generics are a compile-time concept. There might be a way to do what you're describing using reflection, but it's not pretty. Also, you can just store your data type in a variable of type `Type` without involving generics. See [here](https://stackoverflow.com/q/2604743/102937) for some ideas. – Robert Harvey Aug 02 '23 at 12:29
  • @RobertHarvey I dont want to come across as rude, but i never asked to have a "generic set at runtime". I want the user of the package to specify the type by either registering it to dependency injection or like in my example. I just dont know how to infer the type later on – Dylan Snel Aug 02 '23 at 14:31
  • There is no notion of generic type inference at runtime. Generic type inference is a compile-time concept. – Robert Harvey Aug 02 '23 at 15:12
  • @RobertHarvey Im still perfectly fine with doing it at compile time. Like i mentioned before. the type will never change. Basically my question is if i rgister class A as A in the dependency injection ( or component) how van i make sure that when i want an in stance of type B it also instantiates as type B – Dylan Snel Aug 02 '23 at 15:54

0 Answers0