This is a simple solution built as a starter for writing Fody addins.
See also Fody usage.
Install the BasicFodyAddin.Fody NuGet package and update the Fody NuGet package:
PM> Install-Package Fody
PM> Install-Package BasicFodyAddin.FodyThe Install-Package Fody is required since NuGet always defaults to the oldest, and most buggy, version of any dependency.
Add <BasicFodyAddin/> to FodyWeavers.xml
<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
<BasicFodyAddin/>
</Weavers>A project that contains all classes used for compile time metadata. Generally any usage and reference to this project is removed at compile time so it is not needed as part of application deployment.
This project is also used to produce the NuGet package. To achieve this the project consumes two NuGets:
- Fody with
PrivateAssets="None". This results in producing NuGet package having a dependency on Fody with allinclude="All"in the nuspec. Note that while this project consumes the Fody NuGet, weaving is not performed on this project. This is due to the FodyPackaging NuGet (see below) including<DisableFody>true</DisableFody>in the MSBuild pipeline. - FodyPackaging with
PrivateAssets="All". This results in a NuGet package being produced by this project, but no dependency on FodyPackaging in the resulting NuGet package.
The produced NuGet package will be named with .Fody suffix.
This project should also contain all appropriate NuGet metadata properties. Many of these properties have defaults in FodyPackaging, but can be overridden.
The resultant NuGet package will target the same frameworks that this project targets.
The resultant NuGet package will be created in a directory named nugets at the root of the solution.
The project that does the weaving.
This project has a NuGet dependency on FodyHelpers.
This project must target net46 for msbuild.exe support, and netstandard2.0 for dotnet build support.
It outputs a file named BasicFodyAddin.Fody. The '.Fody' suffix is necessary to be picked up by Fody at compile time.
ModuleWeaver.cs is where the target assembly is modified. Fody will pick up this type during its processing. Note that the class must be named as ModuleWeaver.
ModuleWeaver has a base class of BaseModuleWeaver which exists in the FodyHelpers NuGet.
public class ModuleWeaver: BaseModuleWeaver
{
public override void Execute()
{
var ns = GetNamespace();
var newType = new TypeDefinition(ns, "Hello", TypeAttributes.Public, TypeSystem.ObjectReference);
AddConstructor(newType);
AddHelloWorld(newType);
ModuleDefinition.Types.Add(newType);
LogInfo("Added type 'Hello' with method 'World'.");
}
public override IEnumerable<string> GetAssembliesForScanning()
{
yield return "netstandard";
yield return "mscorlib";
}
string GetNamespace()
{
var attributes = ModuleDefinition.Assembly.CustomAttributes;
var namespaceAttribute = attributes.FirstOrDefault(x => x.AttributeType.FullName == "NamespaceAttribute");
if (namespaceAttribute == null)
{
return null;
}
attributes.Remove(namespaceAttribute);
return (string) namespaceAttribute.ConstructorArguments.First().Value;
}
void AddConstructor(TypeDefinition newType)
{
var method = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, TypeSystem.VoidReference);
var objectConstructor = ModuleDefinition.ImportReference(TypeSystem.ObjectDefinition.GetConstructors().First());
var processor = method.Body.GetILProcessor();
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Call, objectConstructor);
processor.Emit(OpCodes.Ret);
newType.Methods.Add(method);
}
void AddHelloWorld(TypeDefinition newType)
{
var method = new MethodDefinition("World", MethodAttributes.Public, TypeSystem.StringReference);
var processor = method.Body.GetILProcessor();
processor.Emit(OpCodes.Ldstr, "Hello World");
processor.Emit(OpCodes.Ret);
newType.Methods.Add(method);
}
public override bool ShouldCleanReference => true;
}Called to perform the manipulation of the module. The current module can be accessed and manipulated via BaseModuleWeaver.ModuleDefinition.
public override void Execute()
{
var ns = GetNamespace();
var newType = new TypeDefinition(ns, "Hello", TypeAttributes.Public, TypeSystem.ObjectReference);
AddConstructor(newType);
AddHelloWorld(newType);
ModuleDefinition.Types.Add(newType);
LogInfo("Added type 'Hello' with method 'World'.");
}Called by Fody when it is building up a type cache for lookups. This method should return all possible assemblies that the weaver may require while resolving types. In this case BasicFodyAddin requires System.Object, so GetAssembliesForScanning returns netstandard and mscorlib. It is safe to return assembly names that are not used by the current target assembly as these will be ignored.
To use this type cache, a ModuleWeaver can call BaseModuleWeaver.FindType within Execute method.
public override IEnumerable<string> GetAssembliesForScanning()
{
yield return "netstandard";
yield return "mscorlib";
}When BasicFodyAddin.dll is referenced by a consuming project, it is only for the purposes configuring the weaving via attributes. As such, it is not required at runtime. With this in mind BaseModuleWeaver has an opt in feature to remove the reference, meaning the target weaved application does not need BasicFodyAddin.dll at runtime. This feature can be opted in to via the following code in ModuleWeaver:
public override bool ShouldCleanReference => true;BaseModuleWeaver has a number of other members for logging and extensibility:
https://github.com/Fody/Fody/blob/master/FodyHelpers/BaseModuleWeaver.cs
In this case a new type is being injected into the target assembly that looks like this.
public class Hello
{
public string World()
{
return "Hello World";
}
}See ModuleWeaver for more details.
A target assembly to process and then validate with unit tests.
Contains all tests for the weaver.
The project has a NuGet dependency on FodyHelpers .
It has a reference to the AssemblyToProcess project, so that AssemblyToProcess.dll is copied to the bin directory of the test project.
FodyHelpers contains a utility WeaverTestHelper for executing test runs on a target assembly using a ModuleWeaver.
A test can then be run as follows:
public class WeaverTests
{
static TestResult testResult;
static WeaverTests()
{
var weavingTask = new ModuleWeaver();
testResult = weavingTask.ExecuteTestRun("AssemblyToProcess.dll");
}
[Fact]
public void ValidateHelloWorldIsInjected()
{
var type = testResult.Assembly.GetType("TheNamespace.Hello");
var instance = (dynamic)Activator.CreateInstance(type);
Assert.Equal("Hello World", instance.World());
}
}By default ExecuteTestRun will perform a PeVerify on the resultant assembly
Lego designed by Timur Zima from The Noun Project