I have a .NET assembly that is built by me but would like to be able rewrite the .DLL with some minor but arbitrary attribute change file at runtime. Specifically I would like to be able to change a property of an attribute of a class so that I can customize the binary depending on the situation.
To illustrate, I want to achieve the effect of editing the assembly being generated from the code
[SomeAttribute("Name")]
public class MyClass{
...
such that the new assembly is functionally the same as
[SomeAttribute("Custom Name")]
public class MyClass{
...
And this "Custom Name" could be anything (determined at runtime). Is this possible to do at runtime?
The reason why the actual .DLL needs to be modified is because it will get loaded up by a seperate process which cannot determine the runtime information (I do not control this process).
Experimentation so far has shown that it seems to work if the new "Custom Name" is the same length as the original, but not otherwise (even if you edit the preceding byte that specifies the length; presumably there are offsets stored in the file somewhere).
EDIT: Forgot to mention, solution needs to be under the .NET 2 framework as well.
Unclear what you really want to do (XY problem?)
Still, if you want to modify an assembly, you normally use Mono.Cecil that self-describes as: you can load existing managed assemblies, browse all the contained types, modify them on the fly and save back to the disk the modified assembly. .
Note that an attribute can contain extra data on top of the data that is passed as a parameter:
public class MyAttribute : Attribute
{
public MyAttribute(string str)
{
argument = str;
}
private string argument;
public string Argument { get; }
public string AssemblyName
{
get
{
return Assembly.GetEntryAssembly().FullName;
}
}
}
[MyAttribute("Hello")]
class Program
{
static void Main(string[] args)
{
var attr = typeof(Program).GetCustomAttribute<MyAttribute>();
Console.WriteLine(attr.Argument);
Console.WriteLine(attr.AssemblyName);
}
}
Using the extremely helpful suggestion from #xanatos I have made this solution:
Under .NET 2, you can install package Mono.Cecil 0.9.6.1.
The code then is as follows:
AssemblyDefinition assbDef = AssemblyDefinition.ReadAssembly("x.dll");
TypeDefinition type = assbDef.MainModule.GetType("NameSpace.MyClass").Resolve();
foreach (CustomAttribute attr in type.CustomAttributes)
{
TypeReference argTypeRef = null;
int? index = null;
for (int i = 0; i < attr.ConstructorArguments.Count; i++)
{
CustomAttributeArgument arg = attr.ConstructorArguments[i];
string stringValue = arg.Value as string;
if (stringValue == "Name")
{
argTypeRef = arg.Type;
index = i;
}
}
if (index != null)
{
attr.ConstructorArguments[(int)index] = new CustomAttributeArgument(argTypeRef, newName);
}
}
assbDef.Write("y.dll");
Which will search an assembly for any attribute arguments with value "Name" and replace their value with newName.
Rather than modifying the DLL, you can add attributes at run-time using the TypeDescriptor class; e.g.
TypeDescriptor.AddAttributes(typeof(MyClass), new SomeAttribute("Custom Name"));
The only caveat with this approach is that code which relies purely on reflection will not be able to read the added attribute(s) - you must use TypeDescriptor.GetAttributes(). However, most of the built-in .NET Framework methods that operate on attributes are aware of metadata added at run-time.
https://msdn.microsoft.com/en-us/library/system.componentmodel.typedescriptor_methods(v=vs.110).aspx
Related
I'm fairly rusty when it comes to C#. I've been poking my nose around internet trying to find a solution to my question without success.
I created a test project using MSTest. Some tests use files, that I added to my project test under the folder TestData, and they are copied when executing the test by using the attribute DeploymentItem.
Example: [DeploymentItem(#"TestData\test.txt")]
This copies test.txt at the execution folder and it works. However, when I want to use this file in the test, I then have to work on "test.txt" instead of #"TestData\test.txt". Thus, if I want to factorize my code, I have to have two variables:
const string testFileName = "test.txt";
const string testFilePath = #"TestData\test.txt";
and then use them as
[DeploymentItem(testFilePath)]
public void TestFunction()
{
[...]testFileName[...]
}
Ideally, I want instead to write:
[DeploymentItem(testFilePath)]
public void TestFunction()
{
[...]testFilePath[...]
}
This way I would only need one variable.
It would work if I use the second argument of DeploymentItem as such:
const string testFilesFolder = "TestData";
const string testFilePath = #"TestData\test.txt";
[DeploymentItem(testFilePath, testFilesFolder)]
public void TestFunction()
{
[...]testFilePath[...]
}
However, that forces me and everyone to think about passing the second argument every time we use DeploymentItem. But it has the merit of working.
Here are the different things I tried to do to address the issue:
Inheriting from DeploymentItem to simply add my own constructor: DeploymentItem is sealed so this is not possible.
Creating my own attribute, by copying the code of DeploymentItem. The file is not copied at all:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
class DeployFileAttribute : Attribute
{
public DeployFileAttribute(string path)
{
Path = path;
OutputDirectory = System.IO.Path.GetDirectoryName(path);
}
public string Path { get; }
public string OutputDirectory { get; }
}
[DeployFile(testFilePath)] // testFilePath is not copied at all, even though the constructor is correctly executed.
Creating a method that would return the attribute. It does not seem like it is possible to use the result of a method as an attribute:
public static DeploymentItemAttribute DeployFile(string path)
{
return new DeploymentItemAttribute(path, System.IO.Path.GetDirectoryName(path));
} // No compilation error
[DeployFile(testFilePath)] // DeployFileAttribute type does not exist
Creating something like a C++ style using statement or C style macro, I can't seem to find a syntax that works
using DeployFile(string toto) = DeploymentItemAttribute(toto, System.IO.Path.GetDirectoryName(path)); // Syntax is wrong, could not find one that works
Any hindsight would be welcome!
From my point of view, there are only two possibilities:
You use DeploymentItem in the way it was created by Microsoft.
[DeploymentItem(testFilePath, testFilesFolder)] as you manshioned in your post
You can combine source path:
const string testFileName = "test.txt";
[DeploymentItem(#"TestData\" + testFileName)]
public void TestFunction()
{
[...]testFileName[...]
}
In this case, you'll have just one variable :)
You can write your own extension for MSTest and create an attribute you need. But this is not the easy way. As key words for this approach, you could google for TestExtensionExecution, ITestMethodInvoker and TestClassExtensionAttribute
On the other hand, this is very understandable, why DeploymentItem is implemented as it is. Do not forget, that the source folder can be an absolute path as well. So assume, that you have the following attribute [DeploymentItem(#"S:\Shared\TestFiles\AAA\BBB\test.txt")] What should be the destination folder? But even with relative paths: [DeploymentItem(#"..\..\..\TestFiles\AAA\BBB\test.txt")] - can say the name of the destination folder in this case?
I'm creating a generic DLL that creates documents from models and can be used either for winForms or for webForms. It's composed of a main class that I instantiate with certain parameters.
I'd like my DLL to be able to lookup in the resource files without being tied down to 1 technology.
So to say, I know how to access my resource files (*.resx) in a WebForm :
HttpContext.GetGlobalResourceObject("Global", "myLabel")
I have a few restrictions :
I don't want to transfer the HttpContext to the DLL as it will tie it to the application
I don't want to rename the resource files from *.resx to *.resource because they are used in the application
I don't want to pass all the labels over to the DLL because then my models won't be modifiable as I need
I'd like to place a marker in my document models that is like this <%resource(Global,myLabel)%>
I've been looking at passing the class a resource object using ResourceManager but it never gets hold of my *.resx files.
Does anyone know how to acheive the final goal? Either passing a resource object to the class either picking up the resource object from inside the class.
Ok, I found how to do this.
In my DLL, I overloaded the constructor allowing to give it a ResourceManagerobject. So here is what I have :
For the model file :
\paragraph
[
Style = "Normal"
]
{
<%resource(lblMontant)%> : <%montant%>
}
For the DLL using the ResourceManager :
public Reporter(String inputModel, String outputPdf, Dictionary<String, IParameter> parameters, ResourceManager resman)
{
// Assigne parameters to globals
_sourceFile = inputModel;
_destinationFile = outputPdf;
_parameters = parameters;
_rm = resman;
Worker();
}
private String parseResource(String val)
{
MatchCollection _matches = _resourceMatcher.Matches(val);
foreach (Match _match in _matches)
{
String _item = _match.Groups["item"].Value;
val = val.Replace(_match.Groups[0].Value, String.Format("{0}", _rm.GetObject(_item)));
}
return val;
}
For the caller, we use Resources.global that is considered a class :
ResourceManager _rm = new ResourceManager(typeof(Resources.global));
Reporter _cl = new Reporter(modelFilePath, outputFilePath, _params, _rm);
If this is useful to others and some need more details, don't hesitate to ask ;)
Actual situation is, that I added to the MvxViewTypeResolver class the "Fragment"-Case, so it does look like this:
#region Copyright
// <copyright file="MvxViewTypeResolver.cs" company="Cirrious">
// (c) Copyright Cirrious. http://www.cirrious.com
// This source is subject to the Microsoft Public License (Ms-PL)
// Please see license.txt on http://opensource.org/licenses/ms-pl.html
// All other rights reserved.
// </copyright>
//
// Project Lead - Stuart Lodge, Cirrious. http://www.cirrious.com
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.Views;
using Cirrious.MvvmCross.Binding.Android.Interfaces.Binders;
namespace Cirrious.MvvmCross.Binding.Android.Binders
{
public class MvxViewTypeResolver : IMvxViewTypeResolver
{
private Dictionary<string, Type> _cache = new Dictionary<string, Type>();
public IDictionary<string, string> ViewNamespaceAbbreviations { get; set; }
#region IMvxViewTypeResolver Members
public virtual Type Resolve(string tagName)
{
Type toReturn;
if (_cache.TryGetValue(tagName, out toReturn))
return toReturn;
var unabbreviatedTagName = UnabbreviateTagName(tagName);
var longLowerCaseName = GetLookupName(unabbreviatedTagName);
var viewType = typeof(View);
#warning AppDomain.CurrentDomain.GetAssemblies is only the loaded assemblies - so we might miss controls if not already loaded
var query = from assembly in AppDomain.CurrentDomain.GetAssemblies()
from type in assembly.GetTypes()
where viewType.IsAssignableFrom(type)
where (type.FullName ?? "-").ToLowerInvariant() == longLowerCaseName
select type;
toReturn = query.FirstOrDefault();
_cache[tagName] = toReturn;
return toReturn;
}
private string UnabbreviateTagName(string tagName)
{
var filteredTagName = tagName;
if (ViewNamespaceAbbreviations != null)
{
var split = tagName.Split(new char[] {'.'}, 2, StringSplitOptions.RemoveEmptyEntries);
if (split.Length == 2)
{
var abbreviate = split[0];
string fullName;
if (ViewNamespaceAbbreviations.TryGetValue(abbreviate, out fullName))
{
filteredTagName = fullName + "." + split[1];
}
}
}
return filteredTagName;
}
#endregion
protected string GetLookupName(string tagName)
{
var nameBuilder = new StringBuilder();
switch (tagName)
{
case "View":
case "ViewGroup":
nameBuilder.Append("android.view.");
break;
case "fragment":
nameBuilder.Append("android.app.");
break;
default:
if (!IsFullyQualified(tagName))
nameBuilder.Append("android.widget.");
break;
}
nameBuilder.Append(tagName);
return nameBuilder.ToString().ToLowerInvariant();
}
private static bool IsFullyQualified(string tagName)
{
return tagName.Contains(".");
}
}
}
Now it is submitting the correct longLowerCaseTagName (android.app.fragment) but in the query it isn't able to resolve the type.
My suggestion is, that the fragment-control isn't loaded when the type should be resolved. Maybe there is an other way to get the type resolved?
Also if I add a custom type (giving the tag Mvx.MyCustomType in the axml) it doesn't get resolved. Do I have to add something in the MvxBindingAttributes.xml in this case?
Thanks for the help!
First an explanation of the code:
The custom XML inflater factory used by the MvvmCross Binder tries to load Views in a very similar way to the standard 2.x Android XML inflater.
The default code for the view type resolution is indeed in: https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross.Binding/Android/Binders/MvxViewTypeResolver.cs
If your xml contains a name such as <MyCompany.MyProject.MyViews.MyFirstView /> then the view type resolver:
first checks for abbreviations and expands these into full namespaces - by default the only known abbreviation is Mvx. which is expanded to: Cirrious.MvvmCross.Binding.Android.Views.. If you want to add more abbrevations then override ViewNamespaceAbbreviations in https://github.com/slodge/MvvmCross/blob/master/Cirrious/Cirrious.MvvmCross.Binding/Android/MvxBaseAndroidBindingSetup.cs
then checks to see if the unabbreviated name is a non-namespaced name. If it is, then it assumes that the class is the Android namespace and prepends it with android.view. or android.widget.
then converts the fully namespaced name to all lowercase as a case-insensitive lookup key
uses that lowercase key to search all Types which derive from View in all loaded assemblies.
caches the result (whether its null or not) in order to speed up subsequent inflations.
All of this behaviour was designed to match the default Android xml view inflation code in http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.6_r1/android/view/LayoutInflater.java#LayoutInflater.createViewFromTag%28java.lang.String%2Candroid.util.AttributeSet%29
With that explanation out of the way - here's an answer to your questions:
MvvmCross does not yet currently contain any Fragment support. The official MonoDroid fragment support itself was only released last week, and I've not yet had anybody request fragments - Android "fragmentation" seems to have kept most people back on Activity and Dialog based code.
Briefly ;ooking at the documentation, fragment isn't an Android View - it looks like Fragment inherits directly from Java.Lang.Object - see http://developer.android.com/reference/android/app/Fragment.html
Because of this, there's no way that the MvvmCross ViewTypeResolver will currently work with fragments.
I would suggest that if you need both mvvmcross and fragments today, then your best bet is to replace the default resolver (using IoC) with your own resolver - but I can't offer much advice on this as I haven't yet fully read and understood the droid docs on http://developer.android.com/guide/topics/fundamentals/fragments.html
From my experience in creating the current inflation code, then I think you will find the source essential reading when you do this - e.g. see : http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/view/LayoutInflater.java#LayoutInflater.createViewFromTag%28android.view.View%2Cjava.lang.String%2Candroid.util.AttributeSet%29
I can't give you any information on when official mvvmcross fragment support will be available - it's not something that is currently scheduled.
Custom views are supported, but will not normally live in the Mvx. abbreviated namespace.
They are much more likely to live in your UI application namespace, or in some shared library.
To see a custom view in action, see the PullToRefresh example in the tutorial - https://github.com/slodge/MvvmCross/blob/master/Sample%20-%20Tutorial/Tutorial/Tutorial.UI.Droid/Resources/Layout/Page_PullToRefreshView.axml
I am trying to reproduce something that System.Xml.Serialization already does, but for a different source of data.
For now task is limited to deserialization only.
I.e. given defined source of data that I know how to read. Write a library that takes a random type, learns about it fields/properties via reflection, then generates and compiles "reader" class that can take data source and an instance of that random type and writes from data source into the object's fields/properties.
here is a simplified extract from my ReflectionHelper class
public class ReflectionHelper
{
public abstract class FieldReader<T>
{
public abstract void Fill(T entity, XDataReader reader);
}
public static FieldReader<T> GetFieldReader<T>()
{
Type t = typeof(T);
string className = GetCSharpName(t);
string readerClassName = Regex.Replace(className, #"\W+", "_") + "_FieldReader";
string source = GetFieldReaderCode(t.Namespace, className, readerClassName, fields);
CompilerParameters prms = new CompilerParameters();
prms.GenerateInMemory = true;
prms.ReferencedAssemblies.Add("System.Data.dll");
prms.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().GetModules(false)[0].FullyQualifiedName);
prms.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);
CompilerResults compiled = new CSharpCodeProvider().CompileAssemblyFromSource(prms, new string[] {source});
if (compiled.Errors.Count > 0)
{
StringWriter w = new StringWriter();
w.WriteLine("Error(s) compiling {0}:", readerClassName);
foreach (CompilerError e in compiled.Errors)
w.WriteLine("{0}: {1}", e.Line, e.ErrorText);
w.WriteLine();
w.WriteLine("Generated code:");
w.WriteLine(source);
throw new Exception(w.GetStringBuilder().ToString());
}
return (FieldReader<T>)compiled.CompiledAssembly.CreateInstance(readerClassName);
}
private static string GetFieldReaderCode(string ns, string className, string readerClassName, IEnumerable<EntityField> fields)
{
StringWriter w = new StringWriter();
// write out field setters here
return #"
using System;
using System.Data;
namespace " + ns + #".Generated
{
public class " + readerClassName + #" : ReflectionHelper.FieldReader<" + className + #">
{
public void Fill(" + className + #" e, XDataReader reader)
{
" + w.GetStringBuilder().ToString() + #"
}
}
}
";
}
}
and the calling code:
class Program
{
static void Main(string[] args)
{
ReflectionHelper.GetFieldReader<Foo>();
Console.ReadKey(true);
}
private class Foo
{
public string Field1 = null;
public int? Field2 = null;
}
}
The dynamic compilation of course fails because Foo class is not visible outside of Program class. But! The .NET XML deserializer somehow works around that - and the question is: How?
After an hour of digging System.Xml.Serialization via Reflector I came to accept that I lack some kind of basic knowledge here and not really sure what am I looking for...
Also it is entirely possible that I am reinventing a wheel and/or digging in a wrong direction, in which case please do speak up!
You don’t need to create a dynamic assembly and dynamically compile code in order to deserialise an object. XmlSerializer does not do that either — it uses the Reflection API, in particular it uses the following simple concepts:
Retrieving the set of fields from any type
Reflection provides the GetFields() method for this purpose:
foreach (var field in myType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
// ...
I’m including the BindingFlags parameter here to ensure that it will include non-public fields, because otherwise it will return only public ones by default.
Setting the value of a field in any type
Reflection provides the function SetValue() for this purpose. You call this on a FieldInfo instance (which is returned from GetFields() above) and give it the instance in which you want to change the value of that field, and the value to set it to:
field.SetValue(myObject, myValue);
This is basically equivalent to myObject.Field = myValue;, except of course that the field is identified at runtime instead of compile-time.
Putting it all together
Here is a simple example. Notice you need to extend this further to work with more complex types such as arrays, for example.
public static T Deserialize<T>(XDataReader dataReader) where T : new()
{
return (T) deserialize(typeof(T), dataReader);
}
private static object deserialize(Type t, XDataReader dataReader)
{
// Handle the basic, built-in types
if (t == typeof(string))
return dataReader.ReadString();
// etc. for int and all the basic types
// Looks like the type t is not built-in, so assume it’s a class.
// Create an instance of the class
object result = Activator.CreateInstance(t);
// Iterate through the fields and recursively deserialize each
foreach (var field in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
field.SetValue(result, deserialize(field.FieldType, dataReader));
return result;
}
Notice I had to make some assumptions about XDataReader, most notably that it can just read a string like that. I’m sure you’ll be able to change it so that it works with your particular reader class.
Once you’ve extended this to support all the types you need (including int? in your example class), you can deserialize an object by calling:
Foo myFoo = Deserialize<Foo>(myDataReader);
and you can do this even when Foo is a private type as it is in your example.
If I try to use sgen.exe (the standalone XML serialization assembly compiler), I get the following error message:
Warning: Ignoring 'TestApp.Program'.
- TestApp.Program is inaccessible due to its protection level. Only public types can be processed.
Warning: Ignoring 'TestApp.Program+Foo'.
- TestApp.Program+Foo is inaccessible due to its protection level. Only public types can be processed.
Assembly 'c:\...\TestApp\bin\debug\TestApp.exe' does not contain any types that can be serialized using XmlSerializer.
Calling new XmlSerializer(typeof(Foo)) in your example code results in:
System.InvalidOperationException: TestApp.Program+Foo is inaccessible due to its protection level. Only public types can be processed.
So what gave you the idea that XmlSerializer can handle this?
However, remember that at runtime, there are no such restrictions. Trusted code using reflection is free to ignore access modifiers. This is what .NET binary serialization is doing.
For example, if you generate IL code at runtime using DynamicMethod, then you can pass skipVisibility = true to avoid any checks for visibility of fields/classes.
I've been working a bit on this. I'm not sure if it will help but, anyway I think it could be the way. Recently I worked with Serialization and DeSerealization of a class I had to send over the network. As there were two different programs (the client and the server), at first I implemented the class in both sources and then used serialization. It failed as the .Net told me it had not the same ID (I'm not sure but it was some sort of assembly id).
Well, after googling a bit I found that it was because the serialized class was on different assemblies, so the solution was to put that class in a independent library and then compile both client and server with that library. I've used the same idea with your code, so I put both Foo class and FieldReader class in a independent library, let's say:
namespace FooLibrary
{
public class Foo
{
public string Field1 = null;
public int? Field2 = null;
}
public abstract class FieldReader<T>
{
public abstract void Fill(T entity, IDataReader reader);
}
}
compile it and add it to the other source (using FooLibrary;)
this is the code I've used. It's not exactly the same as yours, as I don't have the code for GetCSharpName (I used t.Name instead) and XDataReader, so I used IDataReader (just for the compiler to accept the code and compile it) and also change EntityField for object
public class ReflectionHelper
{
public static FieldReader<T> GetFieldReader<T>()
{
Type t = typeof(T);
string className = t.Name;
string readerClassName = Regex.Replace(className, #"\W+", "_") + "_FieldReader";
object[] fields = new object[10];
string source = GetFieldReaderCode(t.Namespace, className, readerClassName, fields);
CompilerParameters prms = new CompilerParameters();
prms.GenerateInMemory = true;
prms.ReferencedAssemblies.Add("System.Data.dll");
prms.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().GetModules(false)[0].FullyQualifiedName);
prms.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);
prms.ReferencedAssemblies.Add("FooLibrary1.dll");
CompilerResults compiled = new CSharpCodeProvider().CompileAssemblyFromSource(prms, new string[] { source });
if (compiled.Errors.Count > 0)
{
StringWriter w = new StringWriter();
w.WriteLine("Error(s) compiling {0}:", readerClassName);
foreach (CompilerError e in compiled.Errors)
w.WriteLine("{0}: {1}", e.Line, e.ErrorText);
w.WriteLine();
w.WriteLine("Generated code:");
w.WriteLine(source);
throw new Exception(w.GetStringBuilder().ToString());
}
return (FieldReader<T>)compiled.CompiledAssembly.CreateInstance(readerClassName);
}
private static string GetFieldReaderCode(string ns, string className, string readerClassName, IEnumerable<object> fields)
{
StringWriter w = new StringWriter();
// write out field setters here
return #"
using System;
using System.Data;
namespace " + ns + ".Generated
{
public class " + readerClassName + #" : FieldReader<" + className + #">
{
public override void Fill(" + className + #" e, IDataReader reader)
" + w.GetStringBuilder().ToString() +
}
}";
}
}
by the way, I found a tiny mistake, you should use new or override with the Fill method, as it is abstract.
Well, I must admit that GetFieldReader returns null, but at least the compiler compiles it.
Hope that this will help you or at least it guides you to the good answer
regards
I need to access the assembly of my project in C#.
I can see the GUID in the 'Assembly Information' dialog in under project properties, and at the moment I have just copied it to a const in the code. The GUID will never change, so this is not that bad of a solution, but it would be nice to access it directly. Is there a way to do this?
Try the following code. The value you are looking for is stored on a GuidAttribute instance attached to the Assembly
using System.Runtime.InteropServices;
static void Main(string[] args)
{
var assembly = typeof(Program).Assembly;
var attribute = (GuidAttribute)assembly.GetCustomAttributes(typeof(GuidAttribute),true)[0];
var id = attribute.Value;
Console.WriteLine(id);
}
Or, just as easy:
string assyGuid = Assembly.GetExecutingAssembly().GetCustomAttribute<GuidAttribute>().Value.ToUpper();
It works for me...
Another way is to use Marshal.GetTypeLibGuidForAssembly.
According to MSDN:
When assemblies are exported to type libraries, the type library is assigned a LIBID. You can set the LIBID explicitly by applying the System.Runtime.InteropServices.GuidAttribute at the assembly level, or it can be generated automatically. The Tlbimp.exe (Type Library Importer) tool calculates a LIBID value based on the identity of the assembly. GetTypeLibGuid returns the LIBID that is associated with the GuidAttribute, if the attribute is applied. Otherwise, GetTypeLibGuidForAssembly returns the calculated value. Alternatively, you can use the GetTypeLibGuid method to extract the actual LIBID from an existing type library.
You should be able to read the GUID attribute of the assembly via reflection. This will get the GUID for the current assembly:
Assembly asm = Assembly.GetExecutingAssembly();
object[] attribs = asm.GetCustomAttributes(typeof(GuidAttribute), true);
var guidAttr = (GuidAttribute) attribs[0];
Console.WriteLine(guidAttr.Value);
You can replace the GuidAttribute with other attributes as well, if you want to read things like AssemblyTitle, AssemblyVersion, etc.
You can also load another assembly (Assembly.LoadFrom and all) instead of getting the current assembly - if you need to read these attributes of external assemblies (for example, when loading a plugin).
For an out-of-the-box working example, this is what I ended up using based on the previous answers.
using System.Reflection;
using System.Runtime.InteropServices;
label1.Text = "GUID: " + ((GuidAttribute)Attribute.GetCustomAttribute(Assembly.GetExecutingAssembly(), typeof(GuidAttribute), false)).Value.ToUpper();
Alternatively, this way allows you to use it from a static class:
/// <summary>
/// public GUID property for use in static class </summary>
/// <returns>
/// Returns the application GUID or "" if unable to get it. </returns>
static public string AssemblyGuid
{
get
{
object[] attributes = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(GuidAttribute), false);
if (attributes.Length == 0) { return String.Empty; }
return ((System.Runtime.InteropServices.GuidAttribute)attributes[0]).Value.ToUpper();
}
}
There wasn't any luck here with the other answers, but I managed to work it out with this nice one-liner:
((GuidAttribute)(AppDomain.CurrentDomain.DomainManager.EntryAssembly).GetCustomAttributes(typeof(GuidAttribute), true)[0]).Value
To get the appID you could use the following line of code:
var applicationId = ((GuidAttribute)typeof(Program).Assembly.GetCustomAttributes(typeof(GuidAttribute), true)[0]).Value;
For this you need to include the System.Runtime.InteropServices;
Use:
string AssemblyID = Assembly.GetEntryAssembly().GetCustomAttribute<GuidAttribute>().Value;
Or in VB.NET:
Dim AssemblyID As String = Assembly.GetEntryAssembly.GetCustomAttribute(Of GuidAttribute).Value