GetCustomAttribute returns null in runtime on .NET5 - c#

I have a code that loads an assembly and categorize data by attributes.
I'm trying to get the actual CustomAttribute object.
During debug on net framework, the code returns a value.
Running the code on .Net5 returns null
Edit
After some digging in documentation:
.NET Framework version 2.0 provides a new load context, the reflection-only context, which can be used to examine code that cannot be loaded for execution.
https://learn.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/accessing-custom-attributes
Edit #2
The reason is that i'm trying to get the object during runtime, and as written in the documentaion quote above, the attribute as well as any other runtime reflection-only context is cannot be loaded, and the method returns null.
Is there any other solution other than using system.reflection to get the an actual attribute object on runtime?
The dictionary initialization
var ass = Assembly.LoadFile(Path.GetFullPath("MyInterface.dll"));
var asss = Assembly.GetExecutingAssembly();
var apiInterfaces = ass.DefinedTypes.Where(x => x.IsInterface && x.CustomAttributes != null && x.CustomAttributes.Any(z => z.AttributeType.FullName != null && z.AttributeType.FullName.Equals(typeof(ApiInterfaceDescriptorAttribute).FullName)));
The Attribute:
[AttributeUsage(AttributeTargets.Interface)]
public class ApiInterfaceDescriptorAttribute : Attribute
{
public string ApiUsageName { get; }
public ApiInterfaceDescriptorAttribute(string apiUsageName)
{
ApiUsageName = apiUsageName;
}
}
Sample interface
[ApiInterfaceDescriptor("powercontrol")]
public interface IMyInterface
{
[ApiMethodDescriptor(ApiExposureLevel.Basic, "a-method...")]
void SomeMethod();
}
The trial to get the attribute
public class ApiDetector
{
private Dictionary<Type, List<MethodInfo>> _apiDictionary = new Dictionary<Type, List<MethodInfo>>();
public void LoadApiElements()
{
var apiKeyDesriptor = key.GetCustomAttribute(typeof(ApiInterfaceDescriptorAttribute)) as ApiInterfaceDescriptorAttribute;
_apiDetector.Add(new ApiDataObjectDescriptor { Name = key.Name, ApiUsageName = apiKeyDesriptor?.ApiUsageName, Type = key });
}
}
when i ran this code locally i get the instance:
Running the code on .Net5 remote machine returns null:
Any help will be welcome.
Thanks ahead!

The root cause is the call to Assembly.LoadFile. This method always loads the specified assembly into its own AssemblyLoadContext. (More about assembly load context).
This means that you have the assembly loaded into the LoadFile load context (one copy) and I have a direct reference to it from your main code, which will load it into the default load context (second copy). While the assembly file is exactly the same, for the runtime these two copies are distinct assemblies and all types from the two will be treated as different types.
changing the assembly load method to:
Assembly.GetAssembly(typeof(MyType));
have resolved the problem.
thanks to vitek-karas from Microsoft support team.

Related

SourceGenerator: Attribute ConstructorArguments is empty

I am writing a source generator but am struggling to get the value of an argument passed to the constructor of my attribute.
I inject the following into the compilation:
namespace Richiban.Cmdr
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CmdrMethod : System.Attribute
{
private readonly string _alias;
public CmdrMethod(string alias)
{
_alias = alias;
}
}
}
And then in my sample application I have the following:
public static class InnerContainerClass
{
[CmdrMethod("test")]
public static void AnotherMethod(Data data)
{
Console.WriteLine($"In {nameof(AnotherMethod)}, {new { data }}");
}
}
This compiles without errors or warnings and I am successfully able to find all methods that have been decorated with my CmdrMethod attribute, but I am unable to get the value passed to the attribute because, for some reason, the ConstructorArguments property of the attribute is empty:
private static ImmutableArray<string> GetAttributeArguments(
IMethodSymbol methodSymbol,
string attributeName)
{
var attr = methodSymbol
.GetAttributes()
.Single(a => a.AttributeClass?.Name == attributeName);
var arguments = attr.ConstructorArguments;
if (methodSymbol.Name == "AnotherMethod")
Debugger.Launch();
return arguments.Select(a => a.ToString()).ToImmutableArray();
}
Have I misunderstood this API? What am I doing wrong?
The Compilation is immutable. When you add the source code for your attribute, you still have a Compilation object that don't know anything about the attribute. This causes the AttributeClass to be an ErrorType as #jason-malinowski mentioned. This is very known for this kind of source generators, and the solution is just simple. Create a new Compilation with the symbol you injected, then get a SemanticModel from the new Compilation:
// You should already have something similar to the following two lines.
SourceText attributeSourceText = SourceText.From("CmdrMethod source code here", Encoding.UTF8);
context.AddSource("CmdrMethod.g.cs" /* or whatever name you chose*/, attributeSourceText);
// This is the fix.
ParseOptions options = ((CSharpCompilation)context.Compilation).SyntaxTrees[0].Options;
SyntaxTree attributeTree = CSharpSyntaxTree.ParseText(atttributeSourceText, (CSharpParseOptions)options);
Compilation newCompilation = context.Compilation.AddSyntaxTrees(attributeTree);
// Get the semantic model from 'newCompilation'. It should have the information you need.
UPDATE: The experience became better starting from Microsoft.CodeAnalysis 3.9 packages, you can now add the attribute in Initialize instead of Execute:
public void Initialize(GeneratorInitializationContext context)
{
// Register the attribute source
context.RegisterForPostInitialization((i) => i.AddSource("CmdrMethod.g.cs", attributeText));
// .....
}
Then, you can directly work with the compilation you get in Execute.
See AutoNotify sample.
The AttributeClass property is an ErrorType, which means that the compiler didn't actually know what that type was. We fill in a fake "error" type when we had a name for the type but otherwise didn't know what it was; this makes some downstream handling easier for certain features. In any case, figure out where that's coming from. You commented that "This compiles without errors or warnings" but whatever environment your Roslyn code is running in, you likely have a compilation that will give errors.

What's the deal with Unit Tests and sharing static state?

All,
I am using Unit test framework to write my tests in .net. I wrote some unit tests that all access a configuration object (via AppConfig.Current) that is internally a class static.
In the startup I initialize this configuration object and use it immediately in the following line like this:
[AssemblyInitialize]
public static void AssemblyInitialize(TestContext context)
{
var appCfg = AppConfig.Current;
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "conf/app.settings.xml");
appCfg.Load(path);
var _ = AccountingContext.Current; // <= AccountingContext references the configuration object via "AppConfig.Current" to read some configuration values and initialize the accounting context but I receive a different instance (ONLY in release mode) ....
}
After loading configuration, next line uses this configuration object (AccountingContext.Current) to initialize itself from the configuration object.
The issue is, the tests run fine in Debug mode but in Release mode when I reference the configuration object I get a different object (I know this for a fact because the configuration is not loaded).
So what is the deal with using static reference in unit tests in release mode???
UPDATE: SELF ANSWERED
The issued lied in the "AccountingContext" class.
This class is also a static and it initialized itself like this:
public static AccountingContext Current { get; set; } = new AccountingContext();
public AccountingContext()
{
CompanyAccessContext = _CreateCompanyAccessContext();
}
Changing this intialization to using a static ctor resolved this issue and now everything works:
public static AccountingContext Current { get; set; }
static AccountingContext()
{
Current = new AccountingContext();
}
public AccountingContext()
{
CompanyAccessContext = _CreateCompanyAccessContext();
}
Looks like optimizations were made which changed the actual flow of initialization of AccountingContext. That's a bit unwanted, not sure if this is what everyone expects.
I also found this good resource:
Static member variable not being initialized in Release - Compiler/clr bug?
Cause of this issue was not using static ctor which guarantees order of initialization. For more details, see updated comments above.

How can I implement ISerializable in .NET 4+ without violating inheritance security rules?

Background: Noda Time contains many
serializable structs. While I dislike binary serialization, we
received many requests to support it, back in the 1.x timeline.
We support it by implementing the ISerializable interface.
We've received a recent issue
report of Noda
Time 2.x failing within .NET
Fiddle. The same code using Noda
Time 1.x works fine. The exception thrown is this:
Inheritance security rules violated while overriding member:
'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo,
System.Runtime.Serialization.StreamingContext)'. Security
accessibility of the overriding method must match the security
accessibility of the method being overriden.
I've narrowed this down to the framework that's targeted: 1.x
targets .NET 3.5 (client profile); 2.x targets .NET 4.5. They have
big differences in terms of support PCL vs .NET Core and the
project file structure, but it looks like this is irrelevant.
I've managed to reproduce this in a local project, but I haven't
found a solution to it.
Steps to reproduce in VS2017:
Create a new solution
Create a new classic Windows console application targeting .NET
4.5.1. I called it "CodeRunner".
In the project properties, go to Signing and sign the assembly with
a new key. Untick the password requirement, and use any key file name.
Paste the following code to replace Program.cs. This is an
abbreviated version of the code in this Microsoft
sample.
I've kept all the paths the same, so if you want to go back to the
fuller code, you shouldn't need to change anything else.
Code:
using System;
using System.Security;
using System.Security.Permissions;
class Sandboxer : MarshalByRefObject
{
static void Main()
{
var adSetup = new AppDomainSetup();
adSetup.ApplicationBase = System.IO.Path.GetFullPath(#"..\..\..\UntrustedCode\bin\Debug");
var permSet = new PermissionSet(PermissionState.None);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
var fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<System.Security.Policy.StrongName>();
var newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
var handle = Activator.CreateInstanceFrom(
newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
typeof(Sandboxer).FullName
);
Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();
newDomainInstance.ExecuteUntrustedCode("UntrustedCode", "UntrustedCode.UntrustedClass", "IsFibonacci", new object[] { 45 });
}
public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)
{
var target = System.Reflection.Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
target.Invoke(null, parameters);
}
}
Create another project called "UntrustedCode". This should be a
Classic Desktop Class Library project.
Sign the assembly; you can use a new key or the same one as for
CodeRunner. (This is partially to mimic the Noda Time situation,
and partly to keep Code Analysis happy.)
Paste the following code in Class1.cs (overwriting what's there):
Code:
using System;
using System.Runtime.Serialization;
using System.Security;
using System.Security.Permissions;
// [assembly: AllowPartiallyTrustedCallers]
namespace UntrustedCode
{
public class UntrustedClass
{
// Method named oddly (given the content) in order to allow MSDN
// sample to run unchanged.
public static bool IsFibonacci(int number)
{
Console.WriteLine(new CustomStruct());
return true;
}
}
[Serializable]
public struct CustomStruct : ISerializable
{
private CustomStruct(SerializationInfo info, StreamingContext context) { }
//[SecuritySafeCritical]
//[SecurityCritical]
//[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
throw new NotImplementedException();
}
}
}
Running the CodeRunner project gives the following exception (reformatted for readability):
Unhandled Exception: System.Reflection.TargetInvocationException:
Exception has been thrown by the target of an invocation.
--->
System.TypeLoadException:
Inheritance security rules violated while overriding member:
'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData(...).
Security accessibility of the overriding method must match the security
accessibility of the method being overriden.
The commented-out attributes show things I've tried:
SecurityPermission is recommended by two different MS articles (first,
second), although
interestingly they do different things around explicit/implicit interface implementation
SecurityCritical is what Noda Time currently has, and is what this question's answer suggests
SecuritySafeCritical is somewhat suggested by Code Analysis rule messages
Without any attributes, Code Analysis rules are happy - with either SecurityPermission or SecurityCritical
present, the rules tell you to remove the attributes - unless you do have AllowPartiallyTrustedCallers. Following the suggestions in either case doesn't help.
Noda Time has AllowPartiallyTrustedCallers applied to it; the example here doesn't work either with or without the attribute applied.
The code runs without an exception if I add [assembly: SecurityRules(SecurityRuleSet.Level1)] to the UntrustedCode assembly (and uncomment the AllowPartiallyTrustedCallers attribute), but I believe that's a poor solution to the problem that could hamper other code.
I fully admit to being pretty lost when it comes to this sort of
security aspect of .NET. So what can I do to target .NET 4.5 and
yet allow my types to implement ISerializable and still be used in
environments such as .NET Fiddle?
(While I'm targeting .NET 4.5, I believe it's the .NET 4.0 security policy changes that caused the issue, hence the tag.)
According to the MSDN, in .NET 4.0 basically you should not use ISerializable for partially trusted code, and instead you should use ISafeSerializationData
Quoting from https://learn.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization
Important
In versions previous to .NET Framework 4.0, serialization of custom user data in a partially trusted assembly was accomplished using the GetObjectData. Starting with version 4.0, that method is marked with the SecurityCriticalAttribute attribute which prevents execution in partially trusted assemblies. To work around this condition, implement the ISafeSerializationData interface.
So probably not what you wanted to hear if you need it, but I don't think there's any way around it while keeping using ISerializable (other than going back to Level1 security, which you said you don't want to).
PS: the ISafeSerializationData docs state that it is just for exceptions, but it doesn't seem all that specific, you may want to give it a shot... I basically can't test it with your sample code (other than removing ISerializable works, but you knew that already)... you'll have to see if ISafeSerializationData suits you enough.
PS2: the SecurityCritical attribute doesn't work because it's ignored when the assembly is loaded in partial trust mode (on Level2 security). You can see it on your sample code, if you debug the target variable in ExecuteUntrustedCode right before invoking it, it'll have IsSecurityTransparent to true and IsSecurityCritical to false even if you mark the method with the SecurityCritical attribute)
The accepted answer is so convincing that I almost believed this wasn't a bug. But after doing some experiments now I can say that Level2 security is a complete mess; at least, something is really fishy.
A couple of days ago I bumped into the same issue with my libraries. I quickly created a unit test; however, I could not reproduce the problem I experienced in .NET Fiddle, while the very same code "successfully" threw the exception in a console app. In the end I found two weird ways to overcome the issue.
TL;DR: It turns out that if you use an internal type of the used library in your consumer project, then the partially trusted code works as expected: it is able to instantiate an ISerializable implementation (and a security critical code cannot be called directly, but see below). Or, which is even more ridiculous, you can try to create the sandbox again if it didn't work for the first time...
But let's see some code.
ClassLibrary.dll:
Let's separate two cases: one for a regular class with security critical content and one ISerializable implementation:
public class CriticalClass
{
public void SafeCode() { }
[SecurityCritical]
public void CriticalCode() { }
[SecuritySafeCritical]
public void SafeEntryForCriticalCode() => CriticalCode();
}
[Serializable]
public class SerializableCriticalClass : CriticalClass, ISerializable
{
public SerializableCriticalClass() { }
private SerializableCriticalClass(SerializationInfo info, StreamingContext context) { }
[SecurityCritical]
public void GetObjectData(SerializationInfo info, StreamingContext context) { }
}
One way to overcome the issue is to use an internal type from the consumer assembly. Any type will do it; now I define an attribute:
[AttributeUsage(AttributeTargets.All)]
internal class InternalTypeReferenceAttribute : Attribute
{
public InternalTypeReferenceAttribute() { }
}
And the relevant attributes applied to the assembly:
[assembly: InternalsVisibleTo("UnitTest, PublicKey=<your public key>")]
[assembly: AllowPartiallyTrustedCallers]
[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)]
Sign the assembly, apply the key to the InternalsVisibleTo attribute and prepare for test project:
UnitTest.dll (uses NUnit and ClassLibrary):
To use the internal trick the test assembly should be signed as well. Assembly attributes:
// Just to make the tests security transparent by default. This helps to test the full trust behavior.
[assembly: AllowPartiallyTrustedCallers]
// !!! Comment this line out and the partial trust test cases may fail for the fist time !!!
[assembly: InternalTypeReference]
Note: The attribute can be applied anywhere. In my case it was on a method in a random test class took me a couple of days to find.
Note 2: If you run all test methods together it can happen that the tests will pass.
The skeleton of the test class:
[TestFixture]
public class SecurityCriticalAccessTest
{
private partial class Sandbox : MarshalByRefObject
{
}
private static AppDomain CreateSandboxDomain(params IPermission[] permissions)
{
var evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
var permissionSet = GetPermissionSet(permissions);
var setup = new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
};
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var strongNames = new List<StrongName>();
foreach (Assembly asm in assemblies)
{
AssemblyName asmName = asm.GetName();
strongNames.Add(new StrongName(new StrongNamePublicKeyBlob(asmName.GetPublicKey()), asmName.Name, asmName.Version));
}
return AppDomain.CreateDomain("SandboxDomain", evidence, setup, permissionSet, strongNames.ToArray());
}
private static PermissionSet GetPermissionSet(IPermission[] permissions)
{
var evidence = new Evidence();
evidence.AddHostEvidence(new Zone(SecurityZone.Internet));
var result = SecurityManager.GetStandardSandbox(evidence);
foreach (var permission in permissions)
result.AddPermission(permission);
return result;
}
}
And let's see the test cases one by one
Case 1: ISerializable implementation
The same issue as in the question. The test passes if
InternalTypeReferenceAttribute is applied
sandbox is tried to be created multiple times (see the code)
or, if all the test cases are executed at once and this is not the first one
Otherwise, there comes the totally inappropriate Inheritance security rules violated while overriding member... exception when you instantiate SerializableCriticalClass.
[Test]
[SecuritySafeCritical] // for Activator.CreateInstance
public void SerializableCriticalClass_PartialTrustAccess()
{
var domain = CreateSandboxDomain(
new SecurityPermission(SecurityPermissionFlag.SerializationFormatter), // BinaryFormatter
new ReflectionPermission(ReflectionPermissionFlag.MemberAccess)); // Assert.IsFalse
var handle = Activator.CreateInstance(domain, Assembly.GetExecutingAssembly().FullName, typeof(Sandbox).FullName);
var sandbox = (Sandbox)handle.Unwrap();
try
{
sandbox.TestSerializableCriticalClass();
return;
}
catch (Exception e)
{
// without [InternalTypeReference] it may fail for the first time
Console.WriteLine($"1st try failed: {e.Message}");
}
domain = CreateSandboxDomain(
new SecurityPermission(SecurityPermissionFlag.SerializationFormatter), // BinaryFormatter
new ReflectionPermission(ReflectionPermissionFlag.MemberAccess)); // Assert.IsFalse
handle = Activator.CreateInstance(domain, Assembly.GetExecutingAssembly().FullName, typeof(Sandbox).FullName);
sandbox = (Sandbox)handle.Unwrap();
sandbox.TestSerializableCriticalClass();
Assert.Inconclusive("Meh... succeeded only for the 2nd try");
}
private partial class Sandbox
{
public void TestSerializableCriticalClass()
{
Assert.IsFalse(AppDomain.CurrentDomain.IsFullyTrusted);
// ISerializable implementer can be created.
// !!! May fail for the first try if the test does not use any internal type of the library. !!!
var critical = new SerializableCriticalClass();
// Critical method can be called via a safe method
critical.SafeEntryForCriticalCode();
// Critical method cannot be called directly by a transparent method
Assert.Throws<MethodAccessException>(() => critical.CriticalCode());
Assert.Throws<MethodAccessException>(() => critical.GetObjectData(null, new StreamingContext()));
// BinaryFormatter calls the critical method via a safe route (SerializationFormatter permission is required, though)
new BinaryFormatter().Serialize(new MemoryStream(), critical);
}
}
Case 2: Regular class with security critical members
The test passes under the same conditions as the first one. However, the issue is completely different here: a partially trusted code may access a security critical member directly.
[Test]
[SecuritySafeCritical] // for Activator.CreateInstance
public void CriticalClass_PartialTrustAccess()
{
var domain = CreateSandboxDomain(
new ReflectionPermission(ReflectionPermissionFlag.MemberAccess), // Assert.IsFalse
new EnvironmentPermission(PermissionState.Unrestricted)); // Assert.Throws (if fails)
var handle = Activator.CreateInstance(domain, Assembly.GetExecutingAssembly().FullName, typeof(Sandbox).FullName);
var sandbox = (Sandbox)handle.Unwrap();
try
{
sandbox.TestCriticalClass();
return;
}
catch (Exception e)
{
// without [InternalTypeReference] it may fail for the first time
Console.WriteLine($"1st try failed: {e.Message}");
}
domain = CreateSandboxDomain(
new ReflectionPermission(ReflectionPermissionFlag.MemberAccess)); // Assert.IsFalse
handle = Activator.CreateInstance(domain, Assembly.GetExecutingAssembly().FullName, typeof(Sandbox).FullName);
sandbox = (Sandbox)handle.Unwrap();
sandbox.TestCriticalClass();
Assert.Inconclusive("Meh... succeeded only for the 2nd try");
}
private partial class Sandbox
{
public void TestCriticalClass()
{
Assert.IsFalse(AppDomain.CurrentDomain.IsFullyTrusted);
// A type containing critical methods can be created
var critical = new CriticalClass();
// Critical method can be called via a safe method
critical.SafeEntryForCriticalCode();
// Critical method cannot be called directly by a transparent method
// !!! May fail for the first time if the test does not use any internal type of the library. !!!
// !!! Meaning, a partially trusted code has more right than a fully trusted one and is !!!
// !!! able to call security critical method directly. !!!
Assert.Throws<MethodAccessException>(() => critical.CriticalCode());
}
}
Case 3-4: Full trust versions of case 1-2
For the sake of completeness here are the same cases as the ones above executed in a fully trusted domain. If you remove [assembly: AllowPartiallyTrustedCallers] the tests fail because then you can access critical code directly (as the methods are not transparent by default anymore).
[Test]
public void CriticalClass_FullTrustAccess()
{
Assert.IsTrue(AppDomain.CurrentDomain.IsFullyTrusted);
// A type containing critical methods can be created
var critical = new CriticalClass();
// Critical method cannot be called directly by a transparent method
Assert.Throws<MethodAccessException>(() => critical.CriticalCode());
// Critical method can be called via a safe method
critical.SafeEntryForCriticalCode();
}
[Test]
public void SerializableCriticalClass_FullTrustAccess()
{
Assert.IsTrue(AppDomain.CurrentDomain.IsFullyTrusted);
// ISerializable implementer can be created
var critical = new SerializableCriticalClass();
// Critical method cannot be called directly by a transparent method (see also AllowPartiallyTrustedCallersAttribute)
Assert.Throws<MethodAccessException>(() => critical.CriticalCode());
Assert.Throws<MethodAccessException>(() => critical.GetObjectData(null, default(StreamingContext)));
// Critical method can be called via a safe method
critical.SafeEntryForCriticalCode();
// BinaryFormatter calls the critical method via a safe route
new BinaryFormatter().Serialize(new MemoryStream(), critical);
}
Epilogue:
Of course, this will not solve your problem with .NET Fiddle. But now I would be very surprised if it wasn't a bug in the framework.
The biggest question to me now is the quoted part in the accepted answer. How did they come out with this nonsense? The ISafeSerializationData is clearly not a solution for anything: it is used exclusively by the base Exception class and if you subscribe the SerializeObjectState event (why isn't that an overridable method?), then the state will also be consumed by the Exception.GetObjectData in the end.
The AllowPartiallyTrustedCallers/SecurityCritical/SecuritySafeCritical triumvirate of attributes were designed for exactly the usage shown above. It seems total nonsense to me that a partially trusted code cannot even instantiate a type regardless of the attempt using its security critical members. But it is an even bigger nonsense (a security hole actually) that a partially trusted code may access a security critical method directly (see case 2) whereas this is forbidden for transparent methods even from a fully trusted domain.
So if your consumer project is a test or another well-known assembly, then the internal trick can be used perfectly. For .NET Fiddle and other real-life sandboxed environments the only solution is reverting back to SecurityRuleSet.Level1 until this is fixed by Microsoft.
Update: A Developer Community ticket has been created for the issue.
According to the MSDN see:
How to Fix Violations?
To fix a violation of this rule, make the GetObjectData method visible and overridable and make sure all instance fields are included in the serialization process or explicitly marked with the NonSerializedAttribute attribute.
The following example fixes the two previous violations by providing an overrideable implementation of ISerializable.GetObjectData on the Book class and by providing an implementation of ISerializable.GetObjectData on the Library class.
using System;
using System.Security.Permissions;
using System.Runtime.Serialization;
namespace Samples2
{
[Serializable]
public class Book : ISerializable
{
private readonly string _Title;
public Book(string title)
{
if (title == null)
throw new ArgumentNullException("title");
_Title = title;
}
protected Book(SerializationInfo info, StreamingContext context)
{
if (info == null)
throw new ArgumentNullException("info");
_Title = info.GetString("Title");
}
public string Title
{
get { return _Title; }
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Title", _Title);
}
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
throw new ArgumentNullException("info");
GetObjectData(info, context);
}
}
[Serializable]
public class LibraryBook : Book
{
private readonly DateTime _CheckedOut;
public LibraryBook(string title, DateTime checkedOut)
: base(title)
{
_CheckedOut = checkedOut;
}
protected LibraryBook(SerializationInfo info, StreamingContext context)
: base(info, context)
{
_CheckedOut = info.GetDateTime("CheckedOut");
}
public DateTime CheckedOut
{
get { return _CheckedOut; }
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
protected override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("CheckedOut", _CheckedOut);
}
}
}

How to dynamically load assemblies in dotnet core

I'm building a web application, where I would like separate concerns, i.e. having abstractions and implementations in different projects.
To achieve this, I've tried to implement a composition root concept, where all implementation must have an instance of ICompositionRootComposer to register services, types etc.
public interface ICompositionRootComposer
{
void Compose(ICompositionConfigurator configurator);
}
In projects that are referred directly in the build hierarchy, implementations of ICompositionRootComposer are called, and services are registered correct in the underlying IoC container.
The problem arises when I'm trying to register services in a project, where I've set up a post build task that copies the built dll to the web project's debug folder:
cp -R $(TargetDir)"assembly and symbol name"* $(SolutionDir)src/"webproject path"/bin/Debug/netcoreapp1.1
I'm loading the assembly with: (Inspiration: How to load assemblies located in a folder in .net core console app)
internal class AssemblyLoader : AssemblyLoadContext
{
private string folderPath;
internal AssemblyLoader(string folderPath)
{
this.folderPath = Path.GetDirectoryName(folderPath);
}
internal Assembly Load(string filePath)
{
FileInfo fileInfo = new FileInfo(filePath);
AssemblyName assemblyName = new AssemblyName(fileInfo.Name.Replace(fileInfo.Extension, string.Empty));
return this.Load(assemblyName);
}
protected override Assembly Load(AssemblyName assemblyName)
{
var dependencyContext = DependencyContext.Default;
var ressource = dependencyContext.CompileLibraries.FirstOrDefault(r => r.Name.Contains(assemblyName.Name));
if(ressource != null)
{
return Assembly.Load(new AssemblyName(ressource.Name));
}
var fileInfo = this.LoadFileInfo(assemblyName.Name);
if(File.Exists(fileInfo.FullName))
{
Assembly assembly = null;
if(this.TryGetAssemblyFromAssemblyName(assemblyName, out assembly))
{
return assembly;
}
return this.LoadFromAssemblyPath(fileInfo.FullName);
}
return Assembly.Load(assemblyName);
}
private FileInfo LoadFileInfo(string assemblyName)
{
string fullPath = Path.Combine(this.folderPath, $"{assemblyName}.dll");
return new FileInfo(fullPath);
}
private bool TryGetAssemblyFromAssemblyName(AssemblyName assemblyName, out Assembly assembly)
{
try
{
assembly = Default.LoadFromAssemblyName(assemblyName);
return true;
}
catch
{
assembly = null;
return false;
}
}
}
With this I'm able to load the assembly and call the projects ICompositionRootComposer implementation.
But the problem is that it doesn't seem to recognize any of my types.
When calling my configurator with
configurator.RegisterTransiantService<IFoo, Foo>();
it should register IFoo and Foo in the IoC.
But when debugging I'm not able to get info of the types, i.e via typeof(Foo) in the debug console in Visual Studio Code.
Necromancing.
You can create a wrapper class for the old Assembly.LoadFile to do that.
This has the added benefit that you can stay backward-compatible with dotnet-none-core by applying search-and-replace changes in old code-bases.
namespace System.Reflection
{
public class Assembly2
{
public static System.Reflection.Assembly LoadFile(string path)
{
System.Reflection.Assembly assembly = null;
#if NET_CORE
// Requires nuget - System.Runtime.Loader
assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(path);
#else
assembly = System.Reflection. Assembly.LoadFile(path);
#endif
return assembly;
}
}
}
You'll need to add System.Runtime.Loader via NuGet.
I found a solution to my problem.
It turned out, that I had the property PreserveCompilationContext set to true, and that's why the debugger wouldn't register my manually copied assembly.
When I removed the property from the web project csproj file, everything worked.
Are you aware that ASP.NET Core has it's own, built-in Dependency Injection mechanism? It can be easily switched to other IoC container for your needs, I don't think that you need to reinvent it.
What you need to do here is use a reflection to make a generic method and call after that, something like this:
public void ConfigureServices(IServiceCollection services)
{
var myAssembly = LoadAssembly();
// find first interface
var firstInterfaceType = myAssembly.DefinedTypes.FirstOrDefault(t => t.IsInterface).GetType();
// find it's implementation
var firstInterfaceImplementationType = myAssembly.DefinedTypes.Where(t => t.ImplementedInterfaces.Contains(firstInterfaceType)).GetType();
// get general method info
MethodInfo method = typeof(IServiceCollection).GetMethod("AddTransient");
// provide types to generic method
MethodInfo generic = method.MakeGenericMethod(firstInterfaceType, firstInterfaceImplementationType);
// register your types
generic.Invoke(services, null);
}

Why are my plugin-types only being registered partially in this case?

I am trying to implement a plugin architecture for our WPF program following the proposed implementation here. I want my plugins to reside in a separate folder from the main program folder. I have gotten it to only partially work. Here is the code:
The plan is for each plugin to provide its own StructureMap registry to override the default StructureMap regstry.
The plugin I am currently working on has the following registry and as you can see, I am overriding the registry for the plugin-type IPrintProgramExecutor to intercept and use AutomationController instead. And it works as expected:
public class PluginRegistry : Registry
{
public PluginRegistry()
{
this.ForConcreteType<AutomationController>()
.Configure
.Ctor<IPrintProgramExecutor>().Is(c=> c.GetInstance<PrintProgramExecutor>())
.Singleton();
this.For<IAutomationController>().Use(c => c.GetInstance<AutomationController>()).Singleton();
this.For<IPrintProgramExecutor>().Use(c => c.GetInstance<IAutomationController>()).Singleton();
//this.ForConcreteType<AutomationPlugin>()
// .Configure
// .Singleton();
this.For<IPluginBase>().Use<AutomationPlugin>();
}
}
AutomationPlugin currently is this stub:
public class AutomationPlugin : IPluginBase
{
public ViewModelBase ViewModel {
get { return viewModel; }
private set { viewModel = value; }
}
public ResourceDictionary View { get; }
private ViewModelBase viewModel { get; set; }
private ResourceDictionary viewDictionary = new ResourceDictionary();
public AutomationPlugin()
{
// do something meaningfull!
}
}
with IPluginBase:
public interface IPluginBase
{
ViewModelBase ViewModel { get; }
ResourceDictionary View { get; }
}
The class to add the registry is this, where pluginPath is the path to the extension folder:
public class PluginRegistryAdder : Registry
{
public PluginRegistryAdder(string pluginPath)
{
Scan( x =>
{
x.AssembliesFromPath(pluginPath);
x.LookForRegistries();
});
}
}
The class to actually tie in the plugin registry using the code above is this:
public static class ExtensionManager
{
public static void RegisterPluginsInDic(string pluginPath, IContainer container)
{
var pluginRegistries = new PluginRegistryAdder(pluginPath);
container.Configure(_ => _.IncludeRegistry(pluginRegistries));
var whatIHave = container.WhatDoIHave(typeof(IPluginBase));
var plugins = container.Model.GetAllPossible<IPluginBase>(); // the IEnumerable plugins is empty although I am registering `AutomationPlugin` for it. Why?!
}
}
Now, as mentioned above, the interception for the plugin-type IPrintProgramExecutor works as expected. But for some reason container.Model.GetAllPossible<IPluginBase>() and container.WhatDoIHave(typeof(IPluginBase)) do not find any registered types for the plugin-type IPluginBase. I have tried calling these methods for IPrintProgramExecutor and surely enough they return the concrete type. I have been looking quite a while for the reason and cannot find it.
Any ideas why? Could it have to do with the fact, that I am calling container.Configure(...) twice and perhaps the fact that I already register something for IPringProgramExecutor the first time I call container.Configure(...)? Help is greatly appreciated!
Updates:
After switching to my laptop, the interception, that previously worked, does not work anymore. Furthermore, I am now getting an exception, that one of the assemblies I am trying to register in PluginRegistry is not found:
StructureMap.StructureMapException: Unable to create an instance for Registry type 'Extensions.Automation.PluginRegistry'. Please check the inner exception for details
---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.IO.FileNotFoundException: Could not load file or assembly 'Automation.Servers.Interfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
at Extensions.Automation.PluginRegistry..ctor()
...
All in all, it seems like a path-problem that I cannot figure out. I suspect, that at run-time the program attempts to load the DLLs in the Plugin-registry from the main path, but the corresponding DLLs/asseblies reside in the Extension-folder. How can I see from where StructureMap attempts to load the assembly Automation.Servers.Interfaces for debugging? Hope somebody can help me out. I am slowly loosing it.
You should be able to see where StructureMap is trying to probe for assemblies with the type scanning diagnostics: http://structuremap.github.io/diagnostics/type-scanning/
You do have the ability to specify the folder instead of relying on the AppDomain/AppContext.

Categories

Resources