Open seppo498573908457 opened 1 year ago
I might add, the results are inconsistent across runtimes and the way these methods are called. I copied the IsAnonymous()
method and added it among my own code, then tested this:
bool isAnon1 = typeof(TModel).IsAnonymous();
bool isAnon2 = isAnon(typeof(TModel));
bool isAnon11 = model.GetType().IsAnonymous();
bool isAnon12 = isAnon(model.GetType());
isAnon12
is true, all else false. Inconsistencies like this are unbearable in production code.
Hi, may I ask you to post a sample code to reporoduce this problem, like you did in first message.
if you pass dynamic into generic method, it should be wrapped, see https://www.codeproject.com/Articles/5260233/Building-String-Razor-Template-Engine-with-Bare-Ha
IsAnonymous supposed to check the instance, not a type,
in your example it will be obj.GetType().GetType()
public static bool IsAnonymous(this object obj)
{
Type type = obj.GetType();
return Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), false)
&& type.IsGenericType && type.Name.Contains("AnonymousType")
&& (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$"))
&& type.Attributes.HasFlag(TypeAttributes.NotPublic);
}
Thanks for your reply. Here is some sample code from my testing project. I first copied the decompiled IsAnonymous() into my code and added some formatting:
public static bool IsAnon(object obj)
{
Type type = obj.GetType();
if (Attribute.IsDefined(type, typeof(CompilerGeneratedAttribute), inherit: false)
&& type.IsGenericType
&& type.Name.Contains("AnonymousType")
&& (type.Name.StartsWith("<>") || type.Name.StartsWith("VB$"))
)
{
return type.Attributes.HasFlag(TypeAttributes.AnsiClass);
}
return false;
}
The first statement is clear, it gets type of the obj. That's my bad, I didn't notice it at first. Then I run this much code:
dynamic model1 = new { Name = "Testi", Age = 3 };
var model2 = new { Name = "Testi", Age = 3 };
Person model3 = new Person() { Name = "Testi", Age = 3 };
bool isAnon11 = model1.IsAnonymous(); // throws
bool isAnon12 = IsAnon(model1);
bool isAnon21 = model2.IsAnonymous();
bool isAnon22 = IsAnon(model2);
bool isAnon31 = model3.IsAnonymous();
bool isAnon32 = IsAnon(model3);
But the line that says throws, throws a Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: '<>f__AnonymousType0<string,int>' does not contain a definition for 'IsAnonymous'. This is caused by the fact that you cannot call extension methods with objects that are defined with dynamic-keyword. That's also what lead me to thinking that the IsAnonymous is not supposed to be called with the object itself.
Now if I pass those models into a generic method where the generic parameter is for the model, IsAnonymous seems to work on each of the models and give correct results when called on the object, thanks for pointing it out. But it throws errors from template compile as it is in my first post. I have not previously learned that I need the AnonymousTypeWrapper and also I need to use a non-generic Template for compiling (engine.Compile<MyTemplate>(templateSource, optBuilder)
instead of engine.Compile<MyTemplate<T>>(templateSource, optBuilder)
, inherits RazorEngineTemplateBase
instead of RazorEngineTemplateBase<T>
).
With this info I would suggest two things. 1) better documentation about how to use dynamic and anonymous types. There are no /// XML comments in the code? 2) Maybe automate the use of AnonymousTypeWrapper?
In my testing I noticed that when I enter
dynamic
typed or a proper anonymous typed model into the RazorEngineCore through some generic methods, I got error:Error is thrown when calling Compile<>:
Line 35 in generated code:
Creation of model:
When debugging that model has a type named "<>f__AnonymousType1`2", it returns true for all the other checks in
IsAnonymous
:Attribute.IsDefined(type, typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), inherit: false)
type.IsGenericType
type.Name.Contains("AnonymousType")
(type.Name.StartsWith("<>") || type.Name.StartsWith("VB$"))
Except for the last
type.Attributes.HasFlag(TypeAttributes.NotPublic)
although it has propertiestype.IsPublic
= false andtype.IsNotPublic
= true.Using a strongly typed class does not throw errors. I'm running a dotnet6 console app that references a dotnet48 library for proof-of-concept purposes. Am I doing something wrong here or has the anonymous detection gone bad?
When I run it in a clean dotnet6 console app, I get slightly different errors:
Generated class definition:
Again using strongly typed type works fine. To me the problem looks like 2-fold, the old dotnet 4.8 works differently with Type.Attributes and when using dynamic model, the generated code does not reflect a dynamic type.
-EDIT- Just in case someone wonders the assembly references, I could not make it work by adding no assemblies, adding only needed assemblies or adding all possible assemblies. The error message is usually the same, but sometimes different. What's written above seemed like the most stable combination.