Generic argument constraints lead to design time error

Hi,
when trying to render a component with a generic type parameter with type constraints, Radzen Blazor Studio fails with "The page cannot render".

An example of such a component would be:

@typeparam T where T : class, ITestInterface
<div>
    <span>@typeof(T).Name</span>
</div>
public interface ITestInterface {}
public class TestClass : ITestInterface {}

Full exception message:

System.ArgumentException: GenericArguments[0], 'Radzen.Server.Arg0', on 'Test2.Components.Shared.TestComponent`1[T]' violates the constraint of type 'T'.
 ---> System.TypeLoadException: GenericArguments[0], 'Radzen.Server.Arg0', on 'Test2.Components.Shared.TestComponent`1[T]' violates the constraint of type parameter 'T'.
   at System.RuntimeTypeHandle.Instantiate(QCallTypeHandle handle, IntPtr* pInst, Int32 numGenericArgs, ObjectHandleOnStack type)
   at System.RuntimeType.MakeGenericType(Type[] instantiation)
   --- End of inner exception stack trace ---
   at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
   at System.RuntimeType.MakeGenericType(Type[] instantiation)
   at Radzen.Server.Reflector.MakeGenericType(Type type)
   at Radzen.Server.ProjectServer.Render(String fileName, String source, Boolean renderLayout)
   at Radzen.Server.ProgramController.Render(RenderRequest request)

The --analyze command flag provides a similar error:

Analyzing D:\Dev\Test2\Test2.sln ...
    Analyzing D:\Dev\Test2\Test2.csproj ...
        Rendering Components\Layout\MainLayout.razor ... OK.
        Rendering Components\Pages\Broken.razor ... FAIL.
            System.ArgumentException: GenericArguments[0], 'Radzen.Server.Arg0', on 'Test2.Components.Shared.TestComponent`1[T]' violates the constraint of type 'T'.
             ---> System.TypeLoadException: GenericArguments[0], 'Radzen.Server.Arg0', on 'Test2.Components.Shared.TestComponent`1[T]' violates the constraint of type parameter 'T'.
               at System.RuntimeTypeHandle.Instantiate(QCallTypeHandle handle, IntPtr* pInst, Int32 numGenericArgs, ObjectHandleOnStack type)
               at System.RuntimeType.MakeGenericType(Type[] instantiation)
               --- End of inner exception stack trace ---
               at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, RuntimeType[] genericArguments, Exception e)
               at System.RuntimeType.MakeGenericType(Type[] instantiation)
               at Radzen.Server.Reflector.MakeGenericType(Type type)
               at Radzen.Server.Reflector.GetType(String assemblyName, String typeName, Boolean makeGeneric)
               at Radzen.Server.Reflector.Instantiate(String assemblyName, String typeName)
               at Radzen.Server.Reflector.<>c__DisplayClass9_0.<GetInstance>b__0()
               at Radzen.Server.ConcurrentDictionaryExtensions.GetValue[T,U](ConcurrentDictionary`2 dictionary, T key, Func`1 factory)
               at Radzen.Server.Reflector.GetInstance(String assemblyName, String typeName)
               at Radzen.Server.Visitor.VisitComponent(ComponentIntermediateNode node)
               at Microsoft.AspNetCore.Razor.Language.Intermediate.ComponentIntermediateNode.Accept(IntermediateNodeVisitor visitor)
               at Radzen.Server.Visitor.Serialize(Func`2 infer, IntermediateNode node, String source, Stack`1 path, ISet`1 visited)
               at Radzen.Server.InjectMetadataFeature.Serialize(IntermediateNode parent, IntermediateNodeReference node, String source, IDictionary`2 json, ISet`1 visited)
               at Radzen.Server.InjectMetadataFeature.ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
               at Microsoft.AspNetCore.Razor.Language.IntermediateNodePassBase.Execute(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
               at Microsoft.AspNetCore.Razor.Language.DefaultRazorOptimizationPhase.ExecuteCore(RazorCodeDocument codeDocument)
               at Microsoft.AspNetCore.Razor.Language.RazorEnginePhaseBase.Execute(RazorCodeDocument codeDocument)
               at Microsoft.AspNetCore.Razor.Language.DefaultRazorEngine.Process(RazorCodeDocument document)
               at Microsoft.AspNetCore.Razor.Language.DefaultRazorProjectEngine.ProcessCore(RazorCodeDocument codeDocument)
               at Microsoft.AspNetCore.Razor.Language.RazorProjectEngine.Process(RazorProjectItem projectItem)
               at Radzen.Server.ProjectContext.ProcessFile(String fileName, String source, RazorProjectEngine projectEngine, Boolean designTime)
               at Radzen.Server.ProjectServer.ProcessFile(String fileName, String source, RazorProjectEngine projectEngine, Boolean designTime)
               at Radzen.Server.ProjectServer.ParseDocument(String fileName, String source, RazorProjectEngine projectEngine, Boolean designTime, Boolean prepare)
               at Radzen.Server.ProjectServer.Compile(String fileName, String source, RazorProjectEngine projectEngine)
               at Radzen.Server.ProjectServer.CompilePage(String fileName, String source, RazorProjectEngine engine)
               at Radzen.Server.ProjectServer.Crawl(String fileName, String source)
               at Radzen.Server.ProjectAnalyzer.AnalyzeFile(ProjectContext projectContext, SolutionFacade solutionFacade, String razorFile, Boolean parallel, Boolean all, CancellationToken token)
    Done.
Finished with errors.

I'd appreciate any help and can upload the sample project. Just adding the above component to any project should reproduce the error through.

Hi @vincentscode,

I see why this error happens but unfortunately I don't think we can easily address it. Perhaps using the C# preprocessor as a workaround would work.

Hi @korchev, thank you for the quick response!

Unfortunately the preprocessor directive is not an option, as the typed parameters are passed down through the application and I cannot simply strip all type safety from the application layer.

Looking at the error message, it appears that you always use a stand-in (Arg0, Arg1, ...) class / object. When rendering however the provided type is used to instantiate the instance (i.e. the preview renders TestClass).

Would it be possible to always use the actual value instead of trying to instantiate a stand-in class?
If not: Would it be possible to generate a stand-in type (or even use a random one from the application) which matches the signature?

It is unfortunate that this is not an open-source project, as I would love to contribute on this. :smile:
But I hope we can resolve this, as it is currently what keeps us from using this tool for our product.

Yes. During design time the actual generic argument is unknown so in order to instantiate a generic component Radzen Blazor Studio passes an argument of internal type. This internal type doesn't implement any interfaces thus the constraint is not satisfied.

It is unknown what the actual value is (when you open the generic component in design mode).

Probably. We may add that in a future release depending on the demand. However this would fail as well if no type that satisfies the constraint is found.

Unfortunately commercial products are rarely open source.

That is the unfortunate reality, Radzen Blazor is one of the rare exceptions. :smile:

It does fail when used in pages where the type parameter is supplied, so the parameter type should be inferable?

This should have worked. We've made some changes that will allow such usage from the next release. Opening the generic component itself (its .razor file) won't work still though.

That's great! I'll verify once I have the next release.

I've also tried using the preprocessor to define the type with and without type params depending on if RADZEN is set. Unfortunately this resulted in it complaining about having the parameter defined twice / having an ambiguous name.
Is this the correct syntax?

@{ #if RADZEN }
@typeparam T
@{ #else }
@typeparam T where T : ...
@{ #endif }

<div> ... </div>

@{ ... }