During software development, sometimes we would like to do something with the assemblies like reading information such as description, copyright or comment from project to make custom documentation… I know that is very easy to read attributes from assemblies with help of Assembly but I have a small problem when I try to read comments from my project because they are not stored with the assembly. For reading metadata of assembly, we can simply create an instance of Assembly class and call its GetCustomAttributes function with appropriate type of arguments.
Assembly assembly = Assembly.GetExecutingAssembly(); var assemblyDescriptionAttribute = (AssemblyDescriptionAttribute)assembly.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false).FirstOrDefault(); Console.WriteLine(assemblyDescriptionAttribute.Description); var assemblyDescriptionCopyright = (AssemblyCopyrightAttribute)assembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false).FirstOrDefault(); Console.WriteLine(assemblyDescriptionCopyright.Copyright);
The code above does nothing than extracting the metadata about AssemblyDescription and AssemblyCopyright stored in AssemblyInfo.cs
[assembly: AssemblyTitle("Read Metadata from Assembly")] [assembly: AssemblyDescription("An example of ServusKevin")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Microsoft")] [assembly: AssemblyProduct("Read Metadata from Assembly")] [assembly: AssemblyCopyright("Copyright © ServusKevin 2011")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")]
The comments, however, are stored in separated XML file in Debug/Release folder as default when we turn on the option XML documentation file in project properties.
To read these comments, all we have to do is parsing this XML file and read exactly the comment of each class, method, property, field… from it. The XML file has a structure like this one
<?xml version="1.0"?> <doc> <assembly> <name>Read Metadata from Assembly</name> </assembly> <members> <member name="T:Read_Metadata_from_Assembly.Program"> <summary> Entry class of program </summary> </member> <member name="M:Read_Metadata_from_Assembly.Program.Main(System.String[])"> <summary> Main method where program starts </summary> <param name="args"></param> </member> <member name="F:Read_Metadata_from_Assembly.XMLEngine.MetaDataDoc.Assembly"> <summary> Property to store info of assembly </summary> </member> <member name="P:Read_Metadata_from_Assembly.XMLEngine.MetaDataAssembly.Name"> <summary> Name of current assembly </summary> </member> </members> </doc>
The abbreviation T, M, P, F stands for Type, Method, Property and Field. In combination with name of namespace and member, they represent each class, method with arguments, property and fields with their custom comment-objects such as summary, param, returns, etc… To parse this XML, it is not easier than using Linq2Xml. First I create a Value Object (VO) to contain attributes of each member
/// <summary> /// Main class for mapping xml to object /// </summary> public class MetaDataDoc { /// <summary> /// Property to store info of assembly /// </summary> public MetaDataAssembly Assembly { get; set; } /// <summary> /// Property to store info of all methods /// </summary> public List<MetaDataMember> Members { get; set; } /// <summary> /// Constructor to load XElement to Object /// </summary> /// <param name="xElement"></param> public MetaDataDoc(XElement xElement) { Assembly = new MetaDataAssembly(); Assembly.Name = xElement.Element("assembly").Element("name").Value; Members = new List<MetaDataMember>(); foreach (XElement element in xElement.Element("members").Elements("member")) { MetaDataMember member = new MetaDataMember(); member.Name = (element.Attribute("name") != null) ? element.Attribute("name").Value.Trim() : ""; member.Summary = (element.Element("summary") != null) ? element.Element("summary").Value.Trim() : ""; member.Remarks = (element.Element("remarks") != null) ? element.Element("remarks").Value.Trim() : ""; Members.Add(member); } } }
The VO MetaDataDoc maps exactly the structure of XML file, one node for current assembly and list of his members. The assembly node will be mapped to Assembly whose type of MetaDataAssembly and the member nodes will be mapped to List
/// <summary> /// Contains metadata of member /// </summary> public class MetaDataMember { /// <summary> /// Name of member /// </summary> public string Name { get; set; } /// <summary> /// Summary of member /// </summary> public string Summary { get; set; } /// <summary> /// Remarks of member /// </summary> public string Remarks { get; set; } }
If you want to read more attributes from members, then extend this class with more properties and set the new properties in constructor of MetaDataDoc class. The XML file is nothing more than our database,we are now done with preparing value object, then start to map XML to these objects with help of Data Access Object (DAO) classes.
/// <summary> /// Default constructor uses current assembly as default /// </summary> public DocsFromXML() : this(Assembly.GetEntryAssembly().GetName().Name + ".xml") { } /// <summary> /// Constructor allows to load specific XML file through filename /// </summary> /// <param name="fileName"></param> public DocsFromXML(string fileName) { XDocument doc = XDocument.Load(fileName); metadataDoc = (from d in doc.Descendants("doc") select new MetaDataDoc(d)).FirstOrDefault(); }
I load .xml file of entry assembly as default, you can of course change it as the way you want. The .xml file will be then loaded in XDocument object, and ‘doc’ node and all his descendants will be mapped to MetaDataDoc object. It is really very simple code and we can now create, remove, update or delete any node and write it back to .xml file. However in this blog post I just read out the attributes. Now, create a DummyClass with some dummy methods, properties and fields and try to read out the comment
/// <summary> /// A dummy class for testing /// </summary> /// <remarks> /// This is remark of class dummy /// </remarks> internal class DummyClass { /// <summary> /// This is just a dummy field to demonstrate how to read summary of field /// </summary> public int DummyField = 0; /// <summary> /// This is just a Dummymethod to demonstrate how to read summary of method /// </summary> public void DummyMethod() { } /// <summary> /// This is another Dummymethod which receives an argument and gives result back /// </summary> /// <param name="dummyInput"></param> /// <returns></returns> public int DummyMethod(int dummyInput) { return dummyInput; } /// <summary> /// This is another Dummymethod which receives two arguments and gives result back /// </summary> /// <param name="dummyInput"></param> /// <param name="dummyInput2"></param> /// <returns></returns> public string DummyMethod(int dummyInput, string dummyInput2) { return dummyInput.ToString() + dummyInput2; } /// <summary> /// A dummy property with type of date time /// </summary> public DateTime DummyProperty { get; set; } /// <summary> /// A dummy private property /// </summary> private int DummyPrivateProperty { get; set; } }
The DummyClass exports a lot of attributes information into XML file. The DAO class should provide functions to read all kinds of attributes such as attributes of method or member. The code using DAO class looks generally like this
DocsFromXML engine = new DocsFromXML(); MetaDataMember member = engine.FromType(typeof(DummyClass)); Console.WriteLine(member.Summary); Console.WriteLine(member.Remarks); member = engine.FromMethod(typeof(DummyClass).GetMethod("DummyMethod", new Type[] { })); Console.WriteLine(member.Summary); member = engine.FromMethod(typeof(DummyClass).GetMethod("DummyMethod", new Type[] { typeof(int) })); Console.WriteLine(member.Summary); member = engine.FromMethod(typeof(DummyClass).GetMethod("DummyMethod", new Type[] { typeof(int), typeof(string) })); Console.WriteLine(member.Summary); member = engine.FromMember(typeof(DummyClass).GetProperty("DummyProperty")); Console.WriteLine(member.Summary); member = engine.FromMember(typeof(DummyClass).GetField("DummyField")); Console.WriteLine(member.Summary); member = engine.FromMember(typeof(DummyClass).GetProperty("DummyPrivateProperty", BindingFlags.NonPublic | BindingFlags.Instance)); Console.WriteLine(member.Summary); Console.ReadLine();
Then I go back to DAO class and implement these functions. Property and Field will be handled in same function FromMember(). The FromMethod() and FromMember() again call a private function FromName() which make a search through database and give result as MetaDataMember back. When he finds nothing, he gives the default member back with notification member not found.
/// <summary> /// Search with Method /// </summary> /// <param name="methodInfo"></param> /// <returns></returns> public MetaDataMember FromMethod(MethodInfo methodInfo) { if (methodInfo != null) { string parametersString = ""; if (methodInfo.GetParameters().Length > 0) methodInfo.GetParameters().ToList().ForEach(x => { if (parametersString.Length > 0) parametersString += ","; parametersString += x.ParameterType.FullName; }); if (parametersString.Length > 0) return FromName(methodInfo.DeclaringType, methodInfo.MemberType, methodInfo.Name + "(" + parametersString + ")"); else return FromName(methodInfo.DeclaringType, methodInfo.MemberType, methodInfo.Name); } else { return defaultMember; } } /// <summary> /// Search with Property or Field /// </summary> /// <param name="memberInfo"></param> /// <returns></returns> public MetaDataMember FromMember(MemberInfo memberInfo) { return FromName(memberInfo.DeclaringType, memberInfo.MemberType, memberInfo.Name); } private MetaDataMember FromName(Type type, MemberTypes memberTypes, string name) { string fullName = memberTypes.ToString().Substring(0, 1) + ":" + type.FullName + ((name == "") ? "" : ("." + name)); MetaDataMember result = metadataDoc.Members.Where(x => x.Name == fullName).FirstOrDefault(); return (result != null) ? result : defaultMember; }
So we reach now the end of this blog post. We can integrate this DocsFromXML class to any project or to a standalone tool to read all comments our projects and make a custom documentation or anything you want. DocsFromXML demonstrates again how the technique of Linq2Xml works, simply and efficiently. You can download the source code here “Read MetaData Description, Copyright, Comment from Assembly“