Resx files and embedding into assembly - c#

I have a T4 template which generates a DbContext and a migration configuration. During runtime, I use that template to create an assembly, then use that assembly to generate a migration. However, when I want to do an update database, still programmatically. I get an error, however:
Could not find any resources appropriate for the specified culture or the neutral culture. Make sure "EFMigrations._11_01_30.resources" was correctly embedded or linked into assembly "AutomatedMigrations" at compile time, or that all the satellite assemblies required are loadable and fully signed.
The code for creating the assembly:
var configuration = (DbMigrationsConfiguration)icc.CompiledAssembly.CreateInstance("EFMigrations.Configuration");
File.WriteAllText(directory + scaffold.MigrationId + ".designer.cs", scaffold.DesignerCode);
File.WriteAllText(directory + scaffold.MigrationId + ".cs", scaffold.UserCode);
using (var writer = new ResXResourceWriter(directory + scaffold.MigrationId + ".resources"))
{
foreach (var resource in scaffold.Resources)
writer.AddResource(resource.Key, resource.Value);
}
var filesContents = Directory.GetFiles(directory).Where(x => x.EndsWith(".cs")).Select(File.ReadAllText).ToList();
var resources = Directory.GetFiles(directory).Where(x => x.EndsWith(".resources"));
compilerParams.EmbeddedResources.AddRange(resources.ToArray());
var assemblies = provider.CompileAssemblyFromSource(compilerParams, filesContents.ToArray());
configuration.MigrationsAssembly = assemblies.CompiledAssembly;
configuration.MigrationsNamespace = "EFMigrations";
var migrator = new DbMigrator(configuration);
migrator.Update();
The exception is thrown on the Update() line.
Update:
I've performed a small hack to resolve that issue (by naming my resource Namespace.Class.resources, however now I am getting an error:
Stream is not a valid resource file
Update 2:
I have resolved the issue by creating another T4 runtime template and placing the values from the resources directly into it. It's a hackish solution, but it works for my purposes. However I'm still annoyed by the behaviour of the resource file and why it doesn't like the generated resource file.

Just to mark this as answered - I have resolved my particular issue by creating another t4 generator which uses a sample of the designer code as base and fills in MigrationMetadataID and Target with the ones from scaffold resources like this:
var designerGenerator = new MigrationDesignerGenerator();
designerGenerator.Session = new Dictionary<string, object>();
designerGenerator.Session.Add("Target", scaffold.Resources["Target"]);
designerGenerator.Session.Add("MigrationId", scaffold.MigrationId);
designerGenerator.Initialize();
File.WriteAllText(directory + scaffold.MigrationId + ".Designer.cs", designerGenerator.TransformText());
I have excluded the ResourceManager field from the metadata generator, and used this as target instead:
string IMigrationMetadata.Target
{
get { return "<#= Target #>"; }
}
I have come no further to solving the Resx file issue, but I will forget this for now - as hackish as this feels, it works and it works well based on my tests.

Related

Dynamically compile a class in App_Code while pre-compiling the rest of the project/library

ASP.NET has specicial application folders like App_Code which:
Contains source code for shared classes and business objects (for example, ..cs, and .vb files) that you want to compile as part of your application. In a dynamically compiled Web site project, ASP.NET compiles the code in the App_Code folder on the initial request to your application. Items in this folder are then recompiled when any changes are detected.
Problem is, I'm building a web application, not a dynamically compiled web site. But I'd love to be able to store config values directly in C#, rather than serve via an XML and have to read in during Application_Start and store in HttpContext.Current.Application
So I have the following code in /App_Code/Globals.cs:
namespace AppName.Globals
{
public static class Messages
{
public const string CodeNotFound = "The entered code was not found";
}
}
Which could be anywhere within the application like this:
string msg = AppName.Globals.Messages.CodeNotFound;
The goal is to be able to store any literals in a configurable area that can be updated without recompiling the entire application.
I can use the .cs file by setting its build action to compile, but doing so strips out App_Code/Globals.cs from my output.
Q: Is there a way to identify some parts of a project that should dynamically compile while allowing the rest of the project to be precompiled?
If I set the build action to content - the .cs file will get copied to the bin folder and compiled at runtime. However, in that case, it's not available at design time.
If I set the build action to compile - I can access the objects the same as any other compiled class during design/runtime, but it'll get stripped out of the /App_Code folder when published. I can still place it in the output directory via Copy Always, but the already compiled classes seem to take priority so I can't push config changes without re-deploying the whole application.
Problem Overview
We need to overcome two different problems here:
The first is having a single file that can be compiled at build time and also re-compiled at runtime.
The second is resolving the two different versions of that class created by the solving the first problem so we can actually make use of them.
Problem 1 - Schrödinger's Compilation
The first problem is trying to get a class that is both compiled and not compiled. We need to compile it at design time so that other sections of code are aware it exists and can use its properties with strong typing. But normally, compiled code is stripped out of the output so there aren't multiple versions of the same class causing naming conflicts.
In any case, we need to compile the class initially, but there are two options to persist a re-compilable copy:
Add the file to App_Code, which is compiled at runtime by default, but set it's Build Action = Compile so it's available at design time as well.
Add a regular class file, which is compiled at design time by default, but set it to Copy to Output Directory = Copy Always, so there's a chance we can evaluate it at runtime as well.
Problem 2 - Self Imposed DLL Hell
At a bare minimum, this is a tricky task to charge to the compiler. Any code that consumes a class, must have a guarantee that it exists at compile time. Anything that is dynamically compiled, whether via App_Code or otherwise, will be part of an entirely different assembly. So producing an identical class is treated more like a picture of that class. The underlying type might be the same, but ce n'est une pipe.
We have two options: use an interface or crosswalk between assemblies:
If we use an interface, we can compile it with the initial build and any dynamic types can implement that same interface. This way we are safely relying on something that exists at compile time, and our created class can be safely swapped out as a backing property.
If we cast types across assemblies, it's important to note that any existing usages rely on the type that was originally compiled. So we'll need to grab the values from the dynamic type and apply those property values to the original type.
Existing Answers
Per evk, I like the idea of querying AppDomain.CurrentDomain.GetAssemblies() on startup to check for any new assemblies/classes. I'll admit that using an interface is probably an advisable way to unify precompiled/dynamically compiled classes, but I would ideally like to have a single file/class that can simply be re-read if it changes.
Per S.Deepika, I like the idea of dynamically compiling from a file, but don't want to have to move the values to a separate project.
Ruling out App_Code
App_Code does unlock the ability to build two versions of the same class, but it's actually hard to modify either one after publication as we'll see. Any .cs file located in ~/App_Code/ will be dynamically compiled when the application runs. So in Visual Studio, we can build the same class twice by adding it to App_Code and setting the Build Action to Compile.
Build Action and Copy Output:
When we debug locally, all .cs files will be built into the project assembly and the physical file in ~/App_Code will also be built as well.
We can identify both types like this:
// have to return as object (not T), because we have two different classes
public List<(Assembly asm, object instance, bool isDynamic)> FindLoadedTypes<T>()
{
var matches = from asm in AppDomain.CurrentDomain.GetAssemblies()
from type in asm.GetTypes()
where type.FullName == typeof(T).FullName
select (asm,
instance: Activator.CreateInstance(type),
isDynamic: asm.GetCustomAttribute<GeneratedCodeAttribute>() != null);
return matches.ToList();
}
var loadedTypes = FindLoadedTypes<Apple>();
Compiled and Dynamic Types:
This is really close to solving problem #1. We have access to both types every time the app runs. We can use the compiled version at design time and any changes to the file itself will automatically be recompiled by IIS into a version that we can access at runtime.
The problem is apparent however once we step out of debug mode and try to publish the project. This solution relies on IIS building the App_Code.xxxx assembly dynamically, and that relies on the .cs file being inside the root App_Code folder. However, when a .cs file is compiled, it is automatically stripped out of the published project, to avoid the exact scenario we're trying to create (and delicately manage). If the file was left in, it would produce two identical classes, which would create naming conflicts whenever either one was used.
We can try to force its hand by both compiling the file into the project's assembly and also copying the file into the output directory. But App_Code doesn't work any of it's magic inside of ~/bin/App_Code/. It'll only work at the root level ~/App_Code/
App_Code Compilation Source:
With every publish, we could manually cut and paste the generated App_Code folder from the bin and place it back at the root level, but that's precarious at best. Perhaps we could automate that into build events, but we'll try something else...
Solution
Compile + (Copy to Output and Manually Compile File)
Let's avoid the App_Code folder because it will add some unintended consequences.
Just create a new folder named Config and add a class that will store the values we want to be able to modify dynamically:
~/Config/AppleValues.cs:
public class Apple
{
public string StemColor { get; set; } = "Brown";
public string LeafColor { get; set; } = "Green";
public string BodyColor { get; set; } = "Red";
}
Again, we'll want to go to the file properties (F4) and set to compile AND copy to output. This will give us a second version of the file we can use later.
We'll consume this class by using it within a static class that exposes the values from anywhere. This helps separate concerns, especially between the need to dynamically compile and statically access.
~/Config/GlobalConfig.cs:
public static class Global
{
// static constructor
static Global()
{
// sub out static property value
// TODO magic happens here - read in file, compile, and assign new values
Apple = new Apple();
}
public static Apple Apple { get; set; }
}
And we can use it like this:
var x = Global.Apple.BodyColor;
What we'll attempt to do inside the static constructor, is seed Apple with the values from our dynamic class. This method will be called once every time the application is restarted, and any changes to the bin folder will automatically trigger recycling the app pool.
In short order, here's what we'll want to accomplish inside of the constructor:
string fileName = HostingEnvironment.MapPath("~/bin/Config/AppleValues.cs");
var dynamicAsm = Utilities.BuildFileIntoAssembly(fileName);
var dynamicApple = Utilities.GetTypeFromAssembly(dynamicAsm, typeof(Apple).FullName);
var precompApple = new Apple();
var updatedApple = Utilities.CopyProperties(dynamicApple, precompApple);
// set static property
Apple = updatedApple;
fileName - The File path might be specific to where you'd like to deploy this, but note that inside of a static method, you need to use HostingEnvironment.MapPath instead of Server.MapPath
BuildFileIntoAssembly - In terms of loading the assembly from a file, I've adapted the code from the docs on CSharpCodeProvider and this question on How to load a class from a .cs file. Also, rather than fight dependencies, I just gave the compiler access to every assembly that was currently in the App Domain, same as it would have gotten on the original compilation. There's probably a way to do that with less overhead, but it's a one time cost so who cares.
CopyProperties - To map the new properties onto the old object, I've adapted the method in this question on how to Apply properties values from one object to another of the same type automatically? which will use reflection to break down both objects and iterate over each property.
Utilities.cs
Here's the full source code for the Utility methods from above
public static class Utilities
{
/// <summary>
/// Build File Into Assembly
/// </summary>
/// <param name="sourceName"></param>
/// <returns>https://msdn.microsoft.com/en-us/library/microsoft.csharp.csharpcodeprovider.aspx</returns>
public static Assembly BuildFileIntoAssembly(String fileName)
{
if (!File.Exists(fileName))
throw new FileNotFoundException($"File '{fileName}' does not exist");
// Select the code provider based on the input file extension
FileInfo sourceFile = new FileInfo(fileName);
string providerName = sourceFile.Extension.ToUpper() == ".CS" ? "CSharp" :
sourceFile.Extension.ToUpper() == ".VB" ? "VisualBasic" : "";
if (providerName == "")
throw new ArgumentException("Source file must have a .cs or .vb extension");
CodeDomProvider provider = CodeDomProvider.CreateProvider(providerName);
CompilerParameters cp = new CompilerParameters();
// just add every currently loaded assembly:
// https://stackoverflow.com/a/1020547/1366033
var assemblies = from asm in AppDomain.CurrentDomain.GetAssemblies()
where !asm.IsDynamic
select asm.Location;
cp.ReferencedAssemblies.AddRange(assemblies.ToArray());
cp.GenerateExecutable = false; // Generate a class library
cp.GenerateInMemory = true; // Don't Save the assembly as a physical file.
cp.TreatWarningsAsErrors = false; // Set whether to treat all warnings as errors.
// Invoke compilation of the source file.
CompilerResults cr = provider.CompileAssemblyFromFile(cp, fileName);
if (cr.Errors.Count > 0)
throw new Exception("Errors compiling {0}. " +
string.Join(";", cr.Errors.Cast<CompilerError>().Select(x => x.ToString())));
return cr.CompiledAssembly;
}
// have to use FullName not full equality because different classes that look the same
public static object GetTypeFromAssembly(Assembly asm, String typeName)
{
var inst = from type in asm.GetTypes()
where type.FullName == typeName
select Activator.CreateInstance(type);
return inst.First();
}
/// <summary>
/// Extension for 'Object' that copies the properties to a destination object.
/// </summary>
/// <param name="source">The source</param>
/// <param name="target">The target</param>
/// <remarks>
/// https://stackoverflow.com/q/930433/1366033
/// </remarks>
public static T2 CopyProperties<T1, T2>(T1 source, T2 target)
{
// If any this null throw an exception
if (source == null || target == null)
throw new ArgumentNullException("Source or/and Destination Objects are null");
// Getting the Types of the objects
Type typeTar = target.GetType();
Type typeSrc = source.GetType();
// Collect all the valid properties to map
var results = from srcProp in typeSrc.GetProperties()
let targetProperty = typeTar.GetProperty(srcProp.Name)
where srcProp.CanRead
&& targetProperty != null
&& (targetProperty.GetSetMethod(true) != null && !targetProperty.GetSetMethod(true).IsPrivate)
&& (targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) == 0
&& targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType)
select (sourceProperty: srcProp, targetProperty: targetProperty);
//map the properties
foreach (var props in results)
{
props.targetProperty.SetValue(target, props.sourceProperty.GetValue(source, null), null);
}
return target;
}
}
But Why Tho?
Okay, so there are other more conventional ways to accomplish the same goal. Ideally, we'd shoot for Convention > Configuration. But this provides the absolute easiest, most flexible, strongly typed way to store config values I've ever seen.
Normally config values are read in via an XML in an equally odd process that relies on magic strings and weak typing. We have to call MapPath to get to the store of value and then do Object Relational Mapping from XML to C#. Instead here, we have the final type from the get go, and we can automate all of the ORM work between identical classes that just happen to be compiled against different assemblies.
In either case, the dream output of that process is to be able to write and consume C# directly. In this case, if I want to add an extra, fully configurable property, it's as easy as adding a property to the class. Done!
It will be available immediately and recompiled automatically if that value changes without needing to publish a new build of the app.
Dynamically Changing Class Demo:
Here's the full, working source code for the project:
Compiled Config - Github Source Code | Download Link
You can move configuration part to separate project, and create common interface like (IApplicationConfiguration.ReadConfiguration) to access it.
You can compile the code dynamically at run time like below, and you can access the configuration details using reflection.
public static Assembly CompileAssembly(string[] sourceFiles, string outputAssemblyPath)
{
var codeProvider = new CSharpCodeProvider();
var compilerParameters = new CompilerParameters
{
GenerateExecutable = false,
GenerateInMemory = false,
IncludeDebugInformation = true,
OutputAssembly = outputAssemblyPath
};
// Add CSharpSimpleScripting.exe as a reference to Scripts.dll to expose interfaces
compilerParameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location);
var result = codeProvider.CompileAssemblyFromFile(compilerParameters, sourceFiles); // Compile
return result.CompiledAssembly;
}
Let's see how dynamic compilation of files in App_Code works. When first request to your application arrives, asp.net will compile code files in that folder into assembly (if were not compiled before), and then load that assembly into current application domain of asp.net application. That's why you see your message in a watch - assembly was compiled and is available in current app domain. Because it was compiled dynamically, of course you have compile-time error when trying to reference it explicitly - this code is not yet compiled, and when it will be compiled - it might have completely different structure and message you reference might just not be there at all. So there is no way you can explicitly reference code from dynamic-generated assembly.
What options do you have then? For example, you can have an interface for your messages:
// this interface is located in your main application code,
// not in App_Code folder
public interface IMessages {
string CodeNotFound { get; }
}
Then, in your App_Code file - implement that interface:
// this is in App_Code folder,
// you can reference code from main application here,
// such as IMessages interface
public class Messages : IMessages {
public string CodeNotFound
{
get { return "The entered code was not found"; }
}
}
And then in main application - provide a proxy by searching current app domain for assembly with type that implements IMessage interface (only once, then cache it) and proxy all calls to that type:
public static class Messages {
// Lazy - search of app domain will be performed only on first call
private static readonly Lazy<IMessages> _messages = new Lazy<IMessages>(FindMessagesType, true);
private static IMessages FindMessagesType() {
// search all types in current app domain
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) {
foreach (var type in asm.GetTypes()) {
if (type.GetInterfaces().Any(c => c == typeof(IMessages))) {
return (IMessages) Activator.CreateInstance(type);
}
}
}
throw new Exception("No implementations of IMessages interface were found");
}
// proxy to found instance
public static string CodeNotFound => _messages.Value.CodeNotFound;
}
This will achieve your goal - now when you change code in App_Code Messages class, on next request asp.net will tear down current application domain (first waiting for all pending requests to finish), then create new app domain, recompile your Messages and load into that new app domain (note that this recreating of app domain always happen when you change something in App_Code, not just in this particular situation). So next request will already see new value of your message without you explicitly recompile anything.
Note that you obviously cannot add or remove messages (or change their names) without recompiling main application, because doing that will require changes to IMessages interface which belongs to main application code. If you try - asp.net will throw compilation failure error on next (and all subsequent) requests.
I'd personally avoid doing such things, but if you are fine with that - why not.

Access Resources from Externally-Created .resx File

My main Windows Forms (managed C++) project has a class that presents an image with tiles that can be shown or hidden to create a responsive diagram.
I created a separate utility app that helps me get all the images positioned correctly, etc. This app is written in C# and writes a .resx file containing the image data and positioning, using the following code snippet:
using(ResXResourceWriter resx = new ResXResourceWriter(sfd.FileName)) {
resx.AddResource("Size", canvas.Size);
List<int> IDs = canvas.IDs;
resx.AddResource("IDList", IDs);
resx.AddResource("BackgroundIndex", canvas.BackgroundIndex);
foreach(int id in IDs) {
String positionKey = String.Format("Position.id{0}", id);
String visibilityKey = String.Format("Visibility.id{0}", id);
String imageKey = String.Format("Image.id{0}", id);
resx.AddResource(imageKey, canvas.TileImage(id));
resx.AddResource(positionKey, canvas.TilePosition(id));
resx.AddResource(visibilityKey, canvas.TileVisible(id));
}
}
I can open the .resx file in a text editor and see that it is well-formed and contains the expected data.
Then I take that .resx file and add it to my main application's project. Now I can't figure out how to get at the resources inside it. The code I've tried is:
ResourceManager ^ image_rm = gcnew ResourceManager(
"resx_file_name_without_extension", GetType()->Assembly);
ResourceSet ^ image_rs = image_rm->GetResourceSet(
System::Globalization::CultureInfo::CurrentCulture, true, true);
At runtime, the second line (the GetResourceSet call) throws a System.Resources.MissingManifestResourceException with the following message text:
Resource load failure:
Could not find any resources appropriate for the specified culture or the
neutral culture. Make sure "resx_file_name_without_extension.resources" was
correctly embedded or linked into assembly "my_assembly" at compile time, or
that all the satellite assemblies required are loadable and fully signed.
I suspect my problem is...well I don't really know. Maybe I'm not using the right identifier in the ResourceManager constructor. I tried explicitly setting "Excluded From Build: No" and "Content: Yes" in the file's properties, but that had no effect.
Is it even possible to drop an externally-created .resx file into a project and get at the resources within? I definitely need it compiled in; I can't ship a product with dangling .resx files. I can always create a set of static data objects in .cpp files, but the .resx approach seemed more elegant...
Turns out a comment on this unanswered question was the secret sauce. Prepending the root namespace to the identifier made the ResourceManager happy:
ResourceManager ^ image_rm = gcnew ResourceManager(
"my_root_namespace.resx_file_name_without_extension", GetType()->Assembly);
ResourceSet ^ image_rs = image_rm->GetResourceSet(
System::Globalization::CultureInfo::CurrentCulture, true, true);
Just how or why the compiler decided to place the resources within that namespace is still a bit of a mystery to me, but that's trivia for another day.
The question to which I link involves a .resx created with VS within the project, and mine involves adding one created externally, so I think it's a different-enough situation to warrant a separate Q/A.

How to edit a resource assembly in C# .net application

Now, I am working on edit existing string/icon resource assembly which is code in C# in VS. I have search some method to edit the binary file directly, particularly, I use mono.cecil (http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/), almost all the localized resource files work fine. But only the English original resource file does not work. So I think I should give up this way, I hope to edit the file manually, either open source or original .net API is ok. Anyone have this kind experience to edit the resource file, please let me know:
BTW, the English project contains core logic code and references other dlls (OtherDLL.dll), this may cause exceptions when using mono.cecil, the code and exception is below.
AssemblyDefinition assemblyDefinition = AssemblyDefinition.ReadAssembly(resourceFileName);
// Some code
assemblyDefinition.Write(newFileName); // This will cause exception: "Failed to resolve assembly: 'OtherDLL, Version=10.1.1.1, Culture=neutral, PublicKeyToken=null'
The english dll is TestDll.dll under deployment directory, and other localized resource dlls are TestDll.resources.dll under localized directory, like /de/TestDll.resources.dll and /zh-CN/TestDll.resources.dll.
Now I need the method to achieve the target(edit and save resource part), so please help me to find a way to achieve the goal. Any comments will be appreciated.
Thanks in advance.
The problem is that you need to set the search directories for the assembly references in the metadata tables to be rewritten correctly:
var assemblyFile = #"c:\myassembly.dll";
var resolver = new DefaultAssemblyResolver();
// add directory of the source assembly for references
resolver.AddSearchDirectory(Path.GetDirectoryName(assemblyFile));
// add .NET runtime directories
var runtimeDir = RuntimeEnvironment.GetRuntimeDirectory();
foreach (var dir in Directory.GetDirectories(RuntimeEnvironment.GetRuntimeDirectory(), "*", SearchOption.AllDirectories))
{
resolver.AddSearchDirectory(dir);
}
var mod = AssemblyDefinition.ReadAssembly(assemblyFile, new ReaderParameters { AssemblyResolver = resolver }).MainModule;
mod.Write(assemblyFile + ".patched");
When you want to change resources with Mono.Cecil:
var strings = mod.Resources.SingleOrDefault(a => a.Name == "English.resources");
mod.Resources.Remove(strings);
using var ms = new MemoryStream();
using var rw = new ResourceWriter(ms);
rw.AddResource("GreetingText", "Hello World");
rw.Generate();
ms.Position = 0;
mod.Resources.Add(new EmbeddedResource("English.resources", ManifestResourceAttributes.Public, ms));
mod.Write(assemblyFile + ".patched");
Regards

How to create a Solution from single .cs file with Roslyn?

I am trying to create a Solution from a single source file and tested different solutions.
One of them is the following:
var info = ProjectInfo.Create(
projectId,
version: VersionStamp.Default,
name: "TestProject",
assemblyName: "TestProject.dll",
language: LanguageNames.CSharp);
using (var ws = new CustomWorkspace())
{
var project = ws.AddProject(info);
}
But when running this code, I just get an exception saying that "language is not supported".
Any hint about what is happening?
You need to make sure Microsoft.CodeAnalysis.Workspaces.CSharp.dll is copied alongside your project. We detect that it's there and load it to provide C# support.

Reading the Version number from a AssemblyInfo.cs file

I'm trying to extract the version number from a AssemblyInfo.cs file!
And I'm trying to use System.Reflection.Assembly.LoadFile(path); But while doing this I get a BadImageFormatException; "The module was expected to contain an assembly manifest. (Exception from HRESULT: 0x80131018)". So now I wounder, is that not a possible way to go about it? And should I use RegEx instead?
I have read many examples with GetExecutingAssembly() but I do want to get the version from an other project.
Clarification: I want to read the version info from the AssemblyInfo.cs file! And not from a compiled file. I'm trying to make a tool to update my version numbers before I make a new release.
You can get Assembly version without loading it as:
using System.Reflection;
using System.IO;
...
// Get assembly
AssemblyName currentAssembly = AssemblyName.GetAssemblyName(path);
Version assemblyVersion = currentAssembly.Version;
Edit:
If you want to read file then you can do it like this:
string path = #"d:\AssemblyInfo.cs";
if (File.Exists(path))
{
// Open the file to read from.
string[] readText = File.ReadAllLines(path);
var versionInfoLines = readText.Where(t => t.Contains("[assembly: AssemblyVersion"));
foreach (string item in versionInfoLines)
{
string version = item.Substring(item.IndexOf('(') + 2, item.LastIndexOf(')') - item.IndexOf('(') - 3);
//Console.WriteLine(Regex.Replace(version, #"\P{S}", string.Empty));
Console.WriteLine(version);
}
}
//Output
1.0.*
1.0.0.0
Hope this help...
You can specify the target assembly path in AssemblyName.GetAssemblyName
AssemblyName.GetAssemblyName("ProjectB.exe").Version
AssemblyInfo.cs file gets compiled to IL assembly.
If you load that assembly you can read the version with all the examples that you have already seen. Which is reading an embedded version information from a compiled assembly file, and it may be overwritten by compilation process to a value different from what is in AssemblyInfo.cs
However it sounds like what you want instead is to read a version number from AssemblyInfo.cs text file, without compiling it down.
If this is the case you really just have to use regex with a format appropriate for your project, or even come up with a convention that will keep it simple.
This could be as simple as
var versionMatch = Regex.Match(File.ReadAllText(filename), #"AssemblyVersion\s*\(\s*""([0-9\.\*]*?)""\s*\)");
if (versionMatch.Success)
{
Console.WriteLine(versionMatch.Groups[1].Value);
}
You would have to consider convention around what goes there, since 1.0.* is a valid version string that translates to timestamp values of form 1.0.nnn.mmm at compile time, and nnn and mmm part closely guessable but not precisely guessable.
It sounds like you're trying to load an assembly compiled for x86 in an x64 environment or vice-versa.
Ensure the assembly this code resides in is built for the same environment as the target and you can get it with the examples it sounds like you've read.
You can proceed with Assembly.GetName().Version where your assembly could be the type of your class
public class Test
{
public static void Main()
{
Console.WriteLine("Current assembly : " + typeof(Test).Assembly.GetName().Version);
}
}
For the test application I have working on, shows me below details using above code:

Categories

Resources