How to set the culture in a dotnetcore xunit test - c#

I have the following unit test that I'm porting from a .Net Framework library to .Net core xunint test library. The project the unit test needs to be added to is
https://github.com/dotliquid/dotliquid
and is being added to the selected file as show here
The unit test I'm trying to add is
[Test]
public void ParsingWithCommaDecimalSeparatorShouldWork()
{
var ci = new CultureInfo(CultureInfo.CurrentCulture.Name)
{
NumberFormat =
{
NumberDecimalSeparator = ","
, NumberGroupSeparator = "."
}
};
Thread.CurrentThread.CurrentCulture = ci;
var t = Template.Parse("{{2.5}}");
var result = t.Render( new Hash(), CultureInfo.InvariantCulture );
Assert.AreEqual( result, "2.5" );
}
However the test fails to compile in dotnet core.
Severity Code Description Project File Line Suppression State
Error CS1061 'Thread' does not contain a definition for
'CurrentCulture' and no extension method 'CurrentCulture' accepting a
first argument of type 'Thread' could be found (are you missing a
using directive or an assembly
reference?) DotLiquid.Tests(net451) C:\Users\phelan\workspace\dotliquid\src\DotLiquid.Tests\OutputTests.cs 113 N/A
I need to have different unit tests with different cultures. I would like to create an XUnit theory where each instance passes in a different culture for the unit test to verify against. How is this done in .NetCore?

I looked at some of the dotnet source and I found this.
CultureInfo.DefaultThreadCurrentCulture = ci;
Basically it looks like you can set the default thread current culture from a static property of CultureInfo rather than from Thread.CurrentThread
poking around a bit more I found this
public CultureInfo CurrentCulture
{
get
{
Contract.Ensures(Contract.Result<CultureInfo>() != null);
return CultureInfo.CurrentCulture;
}
set
{
Contract.EndContractBlock();
// If you add more pre-conditions to this method, check to see if you also need to
// add them to CultureInfo.DefaultThreadCurrentCulture.set.
if (m_CurrentCulture == null && m_CurrentUICulture == null)
nativeInitCultureAccessors();
CultureInfo.CurrentCulture = value;
}
}
This is in Thread.cs. So you can set the CultureInfo.CurrentCulture property explicitly.
example:
CultureInfo.CurrentCulture = new CultureInfo("en-GB"); ;
Assert.Equal("£1,000.00", String.Format("{0:C}", 1000));
CultureInfo.CurrentCulture = new CultureInfo("en-US"); ;
Assert.Equal("$1,000.00", String.Format("{0:C}", 1000));
csproj file for unit test project:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework>
<IsPackable>false</IsPackable>
<ApplicationIcon />
<OutputType>Library</OutputType>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170425-07" />
<PackageReference Include="xunit" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
</ItemGroup>
</Project>

The solution is to set
CultureInfo.DefaultThreadCurrentCulture = ci;
and then spin up a new thread. This will set the current culture for the next thread. The final test case is.
[Test]
public void ParsingWithCommaDecimalSeparatorShouldWork()
{
var ci = new CultureInfo(CultureInfo.CurrentCulture.Name)
{
NumberFormat =
{
NumberDecimalSeparator = ","
, NumberGroupSeparator = "."
}
};
CultureInfo.DefaultThreadCurrentCulture = ci;
var result = "";
var thread = new Thread
( delegate()
{
Console.WriteLine(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);
Console.WriteLine(CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator);
var t = Template.Parse("{{2.5}}");
result = t.Render(new Hash(), CultureInfo.InvariantCulture);
} );
thread.Start();
thread.Join();
Assert.AreEqual(result, "2.5");
}
which is a bit messy but get the job done.

Related

Incremental source generator not called in production build context

Given a working source generator and a working test project for the generator.
Generator
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>
[...]
<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
</ItemGroup>
<ItemGroup Condition="!$(DefineConstants.Contains('NET5_0_OR_GREATER'))">
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
[...]
</Project>
namespace Rustic.DataEnumGen;
[Generator(LanguageNames.CSharp)]
[CLSCompliant(false)]
public class DataEnumGen : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(ctx => ctx.AddSource($"{GenInfo.DataEnumSymbol}.g.cs", SourceText.From(GenInfo.DataEnumSyntax, Encoding.UTF8)));
var enumDecls = context.SyntaxProvider.CreateSyntaxProvider(
static (s, _) => IsEnumDecl(s),
static (ctx, _) => CollectTreeInfo(ctx))
.Where(static m => m.HasValue)
.Select(static (m, _) => m!.Value);
var compilationEnumDeclUnion = context.CompilationProvider.Combine(enumDecls.Collect());
context.RegisterSourceOutput(compilationEnumDeclUnion, static (spc, source) => Generate(source.Right, spc));
}
private static bool IsEnumDecl(SyntaxNode node)
{
return node is EnumDeclarationSyntax;
}
private static GenInfo? CollectTreeInfo(GeneratorSyntaxContext context)
{
[...]
}
private static EnumDeclInfo CollectEnumDeclInfo(GeneratorSyntaxContext context, EnumMemberDeclarationSyntax memberDecl)
{
[...]
}
private static void Generate(ImmutableArray<GenInfo> members, SourceProductionContext context)
{
if (members.IsDefaultOrEmpty)
{
return;
}
foreach (var info in members.Distinct())
{
SrcBuilder text = new(2048);
GenInfo.Generate(text, in info);
context.AddSource($"{info.EnumName}Value.g.cs", SourceText.From(text.ToString(), Encoding.UTF8));
}
}
}
Test project
namespace Rustic.DataEnumGen.Tests;
[TestFixture]
public class GeneratorTests
{
private readonly StreamWriter _writer;
public GeneratorTests()
{
_writer = new StreamWriter($"GeneratorTests-{typeof(string).Assembly.ImageRuntimeVersion}.log", true);
_writer.AutoFlush = true;
Logger = new Logger(nameof(GeneratorTests), InternalTraceLevel.Debug, _writer);
}
~GeneratorTests()
{
_writer.Dispose();
}
internal Logger Logger { get; }
[Test]
public void SimpleGeneratorTest()
{
// Create the 'input' compilation that the generator will act on
Compilation inputCompilation = CreateCompilation(#"
using System;
using System.ComponentModel;
using Rustic;
namespace Rustic.DataEnumGen.Tests.TestAssembly
{
using static DummyValue;
public enum Dummy : byte
{
[Description(""The default value."")]
Default = 0,
[Rustic.DataEnum(typeof((int, int)))]
Minimum = 1,
[Rustic.DataEnum(typeof((long, long)))]
Maximum = 2,
}
public enum NoAttr
{
[Description(""This is a description."")]
This,
Is,
Sparta,
}
[Flags]
public enum NoFlags : byte
{
Flag = 1 << 0,
Enums = 1 << 1,
Are = 1 << 2,
Not = 1 << 3,
Supported = 1 << 4,
}
public static class Program
{
public static void Main()
{
DummyValue empty = default!;
DummyValue default = Default();
DummyValue min = Minimum((12, 43));
DummyValue min = Maximum((12, 43));
}
}
}
");
const int TEST_SOURCES_LEN = 1;
const int GEN_SOURCES_LEN = 3; // Attribute + Dummy + NoAttr
DataEnumGen generator = new();
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompilation, out var diagnostics);
[...Validation...]
}
private void Logging(Compilation comp, ImmutableArray<Diagnostic> diagnostics)
{
foreach (var diag in diagnostics)
{
Logger.Debug("Initial diagnostics {0}", diag.ToString());
}
foreach (var tree in comp.SyntaxTrees)
{
Logger.Debug("SyntaxTree\nName=\"{0}\",\nText=\"{1}\"", tree.FilePath, tree.ToString());
}
var d = comp.GetDiagnostics();
foreach (var diag in d)
{
Logger.Debug("Diagnostics {0}", diag.ToString());
}
}
private static Compilation CreateCompilation(string source)
=> CSharpCompilation.Create("compilation",
new[] { CSharpSyntaxTree.ParseText(source) },
new[]
{
MetadataReference.CreateFromFile(typeof(System.String).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.ComponentModel.DescriptionAttribute).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(ReadOnlySpan<char>).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Collections.Generic.List<char>).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Runtime.CompilerServices.MethodImplAttribute).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Runtime.Serialization.ISerializable).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(System.Runtime.InteropServices.StructLayoutAttribute).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(#"C:\Program Files (x86)\dotnet\shared\Microsoft.NETCore.App\6.0.1\System.Runtime.dll"),
},
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
}
The test runs without error, the types and methods are generated correctly.
But I absolutely hate writing tests in plain text, plus executing the tests like so doesnt yield test coverage or unit test cases, so I want to write a production test for the source generator. As per usual I create a .Run.Tests project and add the Rustic.DataEnumGen nuget project as an analyzer. Like so
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;net50;net60</TargetFrameworks>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AltCover" Version="8.2.835" />
<PackageReference Include="bogus" Version="33.0.2" />
<PackageReference Include="fluentassertions" Version="5.10.3" />
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Rustic.DataEnumGen" Version="0.5.0" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>
using System;
using System.ComponentModel;
using NUnit.Framework;
using Rustic;
namespace Rustic.DataEnumGen.Run.Tests
{
using static DummyValue;
public enum Dummy : byte
{
[Description("The default value.")]
Default = 0,
[DataEnum(typeof((int, int)))]
Minimum = 1,
[DataEnum(typeof((long, long)))]
Maximum = 2,
}
public enum NoAttr
{
[Description("This is a description.")]
This,
Is,
Sparta,
}
[Flags]
public enum NoFlags : byte
{
Flag = 1 << 0,
Enums = 1 << 1,
Are = 1 << 2,
Not = 1 << 3,
Supported = 1 << 4,
}
[TestFixture]
public static class DataEnumRunTests
{
[Test]
public static void TestFactory()
{
DummyValue empty = default!;
DummyValue default = Default();
DummyValue min = Minimum((12, 43));
DummyValue min = Maximum((12, 43));
}
[Test]
public static void TestImplicitEnumCast()
{
Dummy default = Default();
Dummy min = Minimum((12, 43));
Dummy min = Maximum((12, 43));
}
}
}
This is the exact same code as in the previous test, but wrapped in a TestFixture instead of a console application. So I build the project with the analyzer, so that the DataEnumAttribute generated and then add the code above. But the code does not compile, because the type DataEnum or DataEnumAttribute does not exist.
First I thought that I needed to (I) ReferenceOutputAssembly, but that didn't change anything either, then I tried combinations of removing OutputItemType="Analyzer" and hoping that would result in the analyzer being called; nothing helped.
I conclude, that in this example the imported source generator, the very same that works in the first test case with the plain text compilation, is not executed before building the project, because if that was the case then type that is always added by the generator would be available in the project, and I would see some Rusic.*.g.cs in the obj/ directory. That is not the case.
So maybe the generator is not packed in the nuget package? As you can see the analyzer is being packed. Maybe I need to IncludeBuildOutput aswell? Nope not working either.
Now my question is why that is the case? Is there some specific thing, some specific attribute, I need to pay attention to when importing IIncrementalGenerator into a project compared to ISourceGenerator, because using an ISourceGenerator in a project works in the exact same way?
Is there anything else I might try to get the incremental source generator to work, or should I just revert to using a regular source generator?
References to good articles help as well, because there is effectively no doc to be found. When working I referenced most of Andrew Lock`s source generator related articles, specifically this one.
I tested this mit net 6.0.101 and a build of 6.0.2xx from like a week ago.

C#: Unhandled exception. System.TypeLoadException: Could not load type 'System.Drawing.Color'

ANSWER for this question thanks to Jeremy C.:
There is no KeePass nuget package for the Net5.0 yet. Thats why there is that error message. Thanks Jeremy C. for the help and answers.
QUESTION:
Im getting this error after starting my solution.
Unhandled exception. System.TypeLoadException: Could not load type 'System.Drawing.Color' from assembly 'Splat, Version=3.0.0.0, Culture=neutral, PublicKeyToken=null'.
Already used google and tried to find a fix for it and also red all articles about similiar errors like "System.Drawing.Font" or "System.Drawing.Image". But theres nothing really helpful and nothing really informative about 'System.Drawing.Color'.
Ive got the code example and package from here:
github.com/wismna/ModernKeePassLib
This is my code:
.csproj
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ModernKeePassLib" Version="2.45.1" />
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
</ItemGroup>
</Project>
And:
using ModernKeePassLib;
using ModernKeePassLib.Interfaces;
using ModernKeePassLib.Keys;
using ModernKeePassLib.Serialization;
using System;
using System.Linq;
using System.Text;
namespace KeePasso
{
class Program
{
static void Main()
{
var dbpath = #"C:\Users\prusinma\Desktop\KeePassDatabase\Database.kdbx";
var keypath = #"C:\Users\prusinma\Desktop\KeePassDatabase\Database.key";
var masterpw = "1234abcd";
Console.WriteLine("init done");
byte[] DBPathBytes = Encoding.ASCII.GetBytes(dbpath);
byte[] KeyPathBytes = Encoding.ASCII.GetBytes(keypath);
var ioConnection = IOConnectionInfo.FromByteArray(DBPathBytes);
var compositeKey = new CompositeKey();
compositeKey.AddUserKey(new KcpPassword(masterpw)); // Password
compositeKey.AddUserKey(new KcpKeyFile(IOConnectionInfo.FromByteArray(KeyPathBytes))); // Keyfile
var db = new PwDatabase();
db.Open(ioConnection, compositeKey, new NullStatusLogger());
var kpdata = from entry in db.RootGroup.GetEntries(true)
select new
{
Group = entry.ParentGroup.Name,
Title = entry.Strings.ReadSafe("Title"),
Username = entry.Strings.ReadSafe("UserName"),
Password = entry.Strings.ReadSafe("Password"),
URL = entry.Strings.ReadSafe("URL"),
Notes = entry.Strings.ReadSafe("Notes")
};
db.Save(new NullStatusLogger());
var contents = db.IOConnectionInfo.Bytes;
string bitString = BitConverter.ToString(contents);
Console.WriteLine(bitString);
Console.WriteLine(kpdata.ToString());
}
}
}
Those classes were moved into their own nuget package. Add it to your project and you should be good to go: https://www.nuget.org/packages/System.Drawing.Common/
From the project directory at the command line:
dotnet add package System.Drawing.Common
Closer inspection reveals ModernKeepPass targets.netstandard1.2 and will not work with 5's System.Drawing nuget package without being upgraded to target the new framework.
https://github.com/wismna/ModernKeePassLib/blob/master/ModernKeePassLib/ModernKeePassLib.csproj
<PropertyGroup>
<TargetFramework>netstandard1.2</TargetFramework>

Edit and Continue doesn't work with Roslyn compiled class library

Background
I'm trying to get Edit and Continue to work with a class library I'm compiling at runtime using Roslyn. This is for adding modding support to a game I'm developing.
Breakdown of problem
I have one class library project (A) with source files (.cs)
I have another console application project (B) in another solution that does the following:
Compiles all of project A's source files
Emits a dll and pdb
Loads the emitted dll and pdb via an assembly context
Calls a static method defined within project B
My desire is to be able to attach a debugger to a running process of project B in an instance of VS with project A loaded and be able to break, edit project A's code, and continue with my changes being executed
Currently, I am only able to break and continue
Any edits lead to the following notification:
This source file has changed. It no longer matches the version of the file used to build the application being debugged.
Source
Project A: DebuggableClassLibrary.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
Project A: Test.cs
using System;
namespace DebuggableClassLibrary
{
public class Test
{
public static int Ct = 0;
public static void SayHello()
{
Ct++;
Console.WriteLine("Hello World");
}
}
}
Project B: DynamicLoading.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" />
</ItemGroup>
</Project>
Project B: Program.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
namespace DynamicLoading
{
class Program
{
static void Main(string[] args)
{
var references = new MetadataReference[]
{
MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location)
};
var files = Directory.GetFiles(#"C:\Users\mrbri\source\repos\DebuggableClassLibrary\DebuggableClassLibrary", "*.cs");
var assemblyName = "DebuggableClassLibrary.dll";
var debug = true;
var allowUnsafe = false;
var outputDirectory = #"C:\Users\mrbri\Documents\Test";
var preprocessorSymbols = debug ? new string[] { "DEBUG" } : new string[] { };
var parseOptions = new CSharpParseOptions(LanguageVersion.Latest, preprocessorSymbols: preprocessorSymbols);
var compilation = CSharpCompilation.Create(
assemblyName: assemblyName,
syntaxTrees: files.Select(f => SyntaxFactory.ParseSyntaxTree(File.ReadAllText(f), parseOptions, f, Encoding.UTF8)),
references: references,
options: new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default,
optimizationLevel: debug ? OptimizationLevel.Debug : OptimizationLevel.Release,
allowUnsafe: allowUnsafe
));
var pePath = Path.Combine(outputDirectory, assemblyName);
var pdbPath = Path.Combine(outputDirectory, Path.ChangeExtension(assemblyName, ".pdb"));
using (var peStream = new FileStream(pePath, FileMode.Create))
using (var pdbStream = new FileStream(pdbPath, FileMode.Create))
{
var results = compilation.Emit(
peStream: peStream,
pdbStream: pdbStream,
options: new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb)
);
}
var assemblyLoadContext = new SimpleUnloadableAssemblyLoadContext();
var assembly = assemblyLoadContext.LoadFromStream(File.OpenRead(pePath), File.OpenRead(pdbPath));
var type = assembly.GetTypes().First();
var method = type.GetMethod("SayHello");
while (true)
{
method.Invoke(null, null);
}
}
}
internal class SimpleUnloadableAssemblyLoadContext : AssemblyLoadContext
{
public SimpleUnloadableAssemblyLoadContext(): base(true) { }
protected override Assembly Load(AssemblyName assemblyName) => null;
}
}
Attempts at solutions and observations
Compiling project A manually through VS and loading the generated pdb and dll exactly as I do for the Roslyn compiled one does allow for Edit and Continue
Comparing project A's dlls generated via Roslyn and VS in JetBrains dotPeek did yield some interesting differences that stem from the compilation time generated .NETCoreApp,Version=v5.0.AssemblyAttributes.cs and DebuggableClassLibrary.AssemblyInfo.cs that I do not include when I compile in project B
Going through the trouble of compiling project A via a MSBuildWorkspace Project did not allow Edit and Continue, although did include .NETCoreApp,Version=v5.0.AssemblyAttributes.cs and DebuggableClassLibrary.AssemblyInfo.cs
Alternatives
I am open to Roslyn alternatives/wrappers that do have Edit and Continue support.
Edit and Continue does not support this scenario. The project for the library being edited needs to be loaded in VS (in the current solution) and the program needs to be launched with the debugger attached.

C# Localization - multiple resx files but only one working aside from default

I have a simple console application (.NET Framework 4.6.1) where I'm trying to use multiple resx files by changing cultureinfo. Here is my code:
`static void Main( string[] args )
{
//default
Console.WriteLine( Resource.Hello );
Thread.CurrentThread.CurrentCulture = new CultureInfo( "fr-FR" );
Thread.CurrentThread.CurrentUICulture = new CultureInfo( "fr-FR" );
//french
Console.WriteLine( Resource.Hello );
Thread.CurrentThread.CurrentCulture = new CultureInfo( "hu-HU" );
Thread.CurrentThread.CurrentUICulture = new CultureInfo( "hu-HU" );
//hungarian
Console.WriteLine( Resource.Hello );
string res = Resource.ResourceManager.GetString( "Hello", CultureInfo.GetCultureInfo( "hu-HU" ) );
Console.WriteLine( res );
Console.ReadKey();
}`
My default culture is en-US. I have a single line in the resx files with the key "Hello". For some reason with fr-FR cultureinfo it works fine but the others got the fallback value.
Related part of the project csproj file:
<ItemGroup>
<EmbeddedResource Include="Properties\Resource.hu-HU.resx">
<DependentUpon>Resource.resx</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resource.fr-FR.resx">
<DependentUpon>Resource.resx</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</ItemGroup>
I added all the resx files manually to my project and under Properties. Only the default has a designer.cs
Any ideas? Thanks for advance!

Can Json.Net be embedded into the executable?

I set the 'Embed Interop Types' property of the Netwonsoft.Json library to true and it returns an error:
Cannot embed interop types from assembly
'c:\path\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll'
because it is missing either the 'ImportedFromTypeLibAttribute' attribute or
the 'PrimaryInteropAssemblyAttribute' attribute
c:\path\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll
It looks like looking for missing references within the Newtonsoft.Json library, but I am not entirely certain. Is it possible for Json.Net to be embeded into the executable?
You didn't say which language you were using but here is how you'd do it for C#
First, turn off "Embed Interop Types"
Then, to the main executable project, unload and edit the .csproj file, and below the following line:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Add this XML to the project file, save, and load it back up.
<Target Name="AfterResolveReferences">
<ItemGroup>
<EmbeddedResource Include="#(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
<LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Target>
You’ll then add a new code file to the main project and add the following code to it (modified to fit how your application is named / structured, in a WPF application, a good place to put it would be App.xaml.cs):
[STAThread]
public static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
App.Main(); // Run WPF startup code.
}
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs e)
{
var thisAssembly = Assembly.GetExecutingAssembly();
// Get the Name of the AssemblyFile
var assemblyName = new AssemblyName(e.Name);
var dllName = assemblyName.Name + ".dll";
// Load from Embedded Resources - This function is not called if the Assembly is already
// in the same folder as the app.
var resources = thisAssembly.GetManifestResourceNames().Where(s => s.EndsWith(dllName));
if (resources.Any())
{
// 99% of cases will only have one matching item, but if you don't,
// you will have to change the logic to handle those cases.
var resourceName = resources.First();
using (var stream = thisAssembly.GetManifestResourceStream(resourceName))
{
if (stream == null) return null;
var block = new byte[stream.Length];
// Safely try to load the assembly.
try
{
stream.Read(block, 0, block.Length);
return Assembly.Load(block);
}
catch (IOException)
{
return null;
}
catch(BadImageFormatException)
{
return null;
}
}
}
// in the case the resource doesn't exist, return null.
return null;
}
Finally, make sure you update the target method for your main application to be the main method for the project you just added
Source: http://www.paulrohde.com/merging-a-wpf-application-into-a-single-exe/

Categories