aspnetcore icon indicating copy to clipboard operation
aspnetcore copied to clipboard

Assigning an object with implicit operator (cast to string) to Blazor component's parameter causes InvalidCastException

Open Oblomoff opened this issue 6 years ago • 8 comments

Object

namespace BlazorBug
{
    public class Tag
    {
        public Tag(string value) => Value = value;

        public string Value { get; }

        /*
         * BUG!
         * 
         * This implicit operator causes InvalidCastException:
         * Unable to cast object of type 'System.String' to type 'BlazorBug.Tag'.
         * 
         * Comment the code below to avoid the exception.
         */
        public static implicit operator string(Tag tag) => tag.Value;
    }
}

Component

<h3>Tag Editor</h3>

<p>@Tag</p>

@code {
    [Parameter]
    public Tag Tag { get; set; }
}

Assignment

@page "/"

<TagEditor Tag="Tag" />

@code {
    public Tag Tag { get; } = new Tag("My Tag");
}

Exception


InvalidCastException: Unable to cast object of type 'System.String'
to type 'BlazorBug.Tag'.

InvalidOperationException: Unable to set property 'Tag' on object of type
'BlazorBug.Shared.TagEditor'. The error was: Unable to cast object of type
'System.String' to type 'BlazorBug.Tag'.

Sample project

BlazorBug.zip

Oblomoff avatar Dec 26 '19 16:12 Oblomoff

This issue just killed our localization framework... Any updates on this one?

TLabWest avatar Apr 15 '21 21:04 TLabWest

I just came across this bug, which is preventing me from binding non-primitive objects (like Value Objects) to components that are designed to only accept those types of objects. I see that this bug is about 2 years old already, so is it on any road map to get fixed in the near future?

GordonRudman avatar Jun 11 '21 19:06 GordonRudman

I've just wasted a morning tracking this down, any movement... updates? ...anything?

kieronlanning avatar Sep 09 '21 07:09 kieronlanning

I came across by another way. I was assigning a string value to a Parameter, which is a reference type (like Tag in example above). The compiler says it's fine, but it breaks when running it, since "SetParametersAsync" works internally with reflection.

public static implicit operator Tag(string? value) => new() { Value = value };

Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer: [12:30:33.8354]WARN ::RemoteRenderer.HandleException: Unhandled exception rendering component: Unable to set property 'Tag' on object of type 'TagComponent'. The error was: Unable to cast object of type 'System.String' to type 'Tag'. System.InvalidOperationException: Unable to set property 'Tag' on object of type 'TagComponent'. The error was: Unable to cast object of type 'System.String' to type 'Tag'.
 ---> System.InvalidCastException: Unable to cast object of type 'System.String' to type 'Tag'.
   at Microsoft.AspNetCore.Components.Reflection.PropertySetter.CallPropertySetter[TTarget,TValue](Action`2 setter, Object target, Object value)
   at Microsoft.AspNetCore.Components.Reflection.ComponentProperties.<SetProperties>g__SetProperty|2_0(Object target, PropertySetter writer, String parameterName, Object value)
   --- End of inner exception stack trace ---
   at Microsoft.AspNetCore.Components.Reflection.ComponentProperties.<SetProperties>g__SetProperty|2_0(Object target, PropertySetter writer, String parameterName, Object value)
   at Microsoft.AspNetCore.Components.Reflection.ComponentProperties.SetProperties(ParameterView& parameters, Object target)
   at Microsoft.AspNetCore.Components.ParameterView.SetParameterProperties(Object target)
   at Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync(ParameterView parameters)
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(DiffContext& diffContext, Int32 frameIndex)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(DiffContext& diffContext, Int32 frameIndex)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(DiffContext& diffContext, Int32 newFrameIndex)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)
   at Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange`1 oldTree, ArrayRange`1 newTree)
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()

it seems like it ignores implicit operators at all.

delasource avatar Dec 07 '21 11:12 delasource

I also stumbled on it. I had something similar to the original problem, but with feature flags instead of tags. Exception trace is not very helpful, so I spent more time on it than expected.

rmihael avatar Feb 11 '22 15:02 rmihael

Any update on this?

glenn-kallidus avatar Apr 11 '22 10:04 glenn-kallidus

Looks like https://github.com/dotnet/aspnetcore/issues/28949 should be able to solve this issue? It would be great if this gets fixed, would make a lot of things a lot easier to do.

Thanks for contacting us. We're moving this issue to the .NET 8 Planning milestone for future evaluation / consideration. Because it's not immediately obvious that this is a bug in our framework, we would like to keep this around to collect more feedback, which can later help us determine the impact of it. We will re-evaluate this issue, during our next planning meeting(s). If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

ghost avatar Sep 20 '22 17:09 ghost

This may be addressed if we do this: https://github.com/dotnet/aspnetcore/issues/29550

mkArtakMSFT avatar Oct 19 '22 17:10 mkArtakMSFT

This is currently unsupported because component parameters are assigned using reflection. This bypasses any implicit conversion operators defined on the parameter type. We are considering adding source generation for component parameter setters which would automatically address this.

MackinnonBuck avatar Dec 06 '22 18:12 MackinnonBuck

Thanks for your response @MackinnonBuck

It's nice that this is going to be addressed in some form.

The real reason this bug/feature-gap is a pain is that the error message is unhelpful and we end up spending hours trying to find why a smart enum or another class that worked everywhere else wouldnt work in a blazor component. Often times, the person writing the component is simply using a core DDD class written earlier, and might not necessarily be aware of the operator overload under the hood.

If source generation will automagically solve this such that we will not have to worry about this ever again -- excellent!

But if that is not the case, the minimum bar (in my humble opinion) to call this bug addressed would be to throw an unambiguous error at build time (using a Roslyn analyser for e.g.) that specifically states that the code wont work because (a) class X overloads the implicit conversion operator and that (b) this is not supported.

gldraphael avatar Dec 07 '22 19:12 gldraphael