{"id":783,"date":"2013-04-08T22:18:00","date_gmt":"2013-04-08T22:18:00","guid":{"rendered":"http:\/\/mooredynasty.net\/?p=783"},"modified":"2014-12-18T22:19:33","modified_gmt":"2014-12-18T22:19:33","slug":"creating-lookup-data-from-a-c-class","status":"publish","type":"post","link":"https:\/\/mooredynasty.net\/index.php\/2013\/04\/creating-lookup-data-from-a-c-class\/","title":{"rendered":"Creating Lookup Data From a C# Class"},"content":{"rendered":"<p>Hard-coding constant values in C# classes is a bad practice that we rarely indulge in here at TAMUS ESI.&#160; You will not see very many instances of code like:<\/p>\n<p>&#160;&#160;&#160; if (someObject.StatusCode == &quot;A&quot;)<\/p>\n<p> We prefer something more like:<\/p>\n<p>&#160;&#160;&#160; if (someObject.StatusCode == SomeProjectConstants.SomeFieldStatus.Active)<\/p>\n<p>where SomeFieldStatus is a class like this one in HRConnect 2:<\/p>\n<pre>    public static class BppLogFieldCodes&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>    {&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>        public const string FIT = &quot;FIT&quot;;<br \/>        public const string ACH = &quot;ACH&quot;;<br \/>        ...<br \/>    } <\/pre>\n<p>Nothing fancy about this, but creating classes\/constants like these takes only moments and helps reduce bugs and makes changing\/adding new codes easier in future releases.<\/p>\n<p>What isn&#8217;t so helpful about this class &#8211; or any other basic C# class &#8211; is that it doesn&#8217;t provide any metadata about the code values. For example, it would be very convenient if the &quot;friendly description&quot; for the FIT code were directly associated with the code property in the BppLogFieldCodes class.<\/p>\n<p>The usual technique for getting code\/description translation data into an ESI web application is to use a &quot;Lookups&quot; class that knows how to connect to a data source, read a database table (or tables), and create a list of key\/value pairs.&#160; The Lookups class will also normally provide caching of the resulting queries to improve performance.&#160; The Lookup data is then used to populate dropdowns, radio button lists, etc.&#160; Again, nothing fancy.<\/p>\n<p>Such Lookup classes are all well and good, but having cached lookup data does nothing to eliminate the BppLogFieldsCode class and others like it &#8211; the constants are still needed for comparisons, etc., as initially demonstrated.<\/p>\n<p>But what if we could create a class like this one that includes the lookup metadata in the class definition?<\/p>\n<pre>    public static class BppLogFieldCodes&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>    {&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>        [Description(&quot;Federal Income Tax&quot;)]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>        public const string FIT = &quot;FIT&quot;;<br \/><br \/>        [Description(&quot;Direct Deposit&quot;)]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>        public const string ACH = &quot;ACH&quot;;<br \/>        ...<br \/>    } <\/pre>\n<p>Thanks to the new AttributeInterrogator class in the So.Esi.Core assembly, it is now possible to annotate your class as shown, then write a simple, one-line method to get a list of lookup data for use in your app&#8217;s UI.&#160; For example: <\/p>\n<pre>&#160;&#160;&#160; public IList&lt;LookupDataDTO&gt; GetBppLogFieldSets()&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>    {&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>        return MapAttributeListToLookupData(&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>            AttributeInterrogator.GetTypeAttributes&lt;DescriptionAttribute&gt;(<br \/>                typeof(HRConnectWebConstants.BppLogFieldCodes)));&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>    }<\/pre>\n<p>The GetBppLogFieldSets method can be used in exactly the same way as methods in the &quot;Lookups&quot; data wrapper class are used now, but no database connection\/access is required.<\/p>\n<p>So how is this accomplished?&#160; <\/p>\n<p>The So.Esi.Core assembly contains a DescriptionAtttribute class that inherits from System.Attribute and accepts a description as a parameter to its constructor.&#160; That allows the [Description] attribute to be applied to a class and its properties.&#160; <\/p>\n<p>The Description attribute can also be applied to a C# enum and its fields.&#160; To make enums even more descriptive, a CodeDescription attribute was created to handle the &quot;three-way&quot; mapping that often takes place with an enumeration.&#160; <\/p>\n<p>In the three-way enum scenario, the three values in play are:<\/p>\n<ol>\n<li>The enum value itself <\/li>\n<li>The lookup code <\/li>\n<li>The friendly description <\/li>\n<\/ol>\n<p>For example, consider the following enum modeled on TrainTraq 3:<\/p>\n<pre>&#160;&#160;&#160; [Description(&quot;Role&quot;)]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>    public enum CodedRatingEnum<br \/>    {<br \/>        [CodeDescription(&quot;APP-EMPLOYEE&quot;, &quot;Employee&quot;)]<br \/>        Employee = 1,<br \/><br \/>        [CodeDescription(&quot;APP-DEPT-ADM&quot;, &quot;Department Admin&quot;)]<br \/>        Good = 2,<br \/>        ... <\/pre>\n<p>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; } <\/p>\n<p>The AttributeInterrogator class helps make this metadata convenient to consume.&#160; It also allows developers to create their own custom metadata attribute classes and apply them in their applications.<\/p>\n<p>A quick look at the current implementation of the GetPropertyAttributes method, which is used to scan a class for its metadata, demonstrates how it works beneath the covers:<\/p>\n<pre>&#160;&#160;&#160; public static IList&lt;AttributeInformation&lt;T&gt;&gt; GetPropertyAttributes&lt;T&gt;(object source) <br \/>        where T : System.Attribute<br \/>    {<br \/>        IList&lt;AttributeInformation&lt;T&gt;&gt; result = null;<br \/><br \/>        Type sourceType = source.GetType();<br \/>        MemberInfo[] members = sourceType.GetMembers();<br \/>        foreach (MemberInfo member in members)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>        {&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; if (member.MemberType == MemberTypes.Property)<br \/>            {<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; object[] attrs = member.GetCustomAttributes(typeof(T), false);<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; if (attrs != null &amp;&amp; attrs.Length &gt; 0)<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; {<br \/>                    if (result == null)<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; result = new List&lt;AttributeInformation&lt;T&gt;&gt;();<br \/><br \/>                    object value = sourceType.GetProperty(member.Name).GetValue(source, null);<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; result.Add(new AttributeInformation&lt;T&gt; <br \/>                    { <br \/>                        Name = member.Name,<br \/>                        Value = value,<br \/>                        Attributes = attrs[0] as T<br \/>                    });<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; }<br \/>            }<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; }&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; <br \/>        return result;<br \/>    }<\/pre>\n<p>AttributeInformation is a simple class in So.Esi.Core:<\/p>\n<pre>&#160;&#160;&#160; public class AttributeInformation&lt;T&gt; <br \/>        where T : class<br \/>    {<br \/>        public string Name { get; set; }<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; public object Value { get; set; }<br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160; public T Attributes { get; set; }<br \/>    }<\/pre>\n<p>It is notable only for the Attributes property, which is generic.&#160; This is what allows application developers to define their own custom attribute classes for use with the AttributeInterrogator.<\/p>\n<p>The AttributeInterrogator.GetPropertyAttributes method uses reflection to iterate over the specified class&#8217; properties.&#160; It then interrogates over each property to see if a custom attribute of the desired type T is defined.&#160; If so, the property name, value, and attribute information is captured in the result list and returned.&#160; <\/p>\n<p>Similar approaches are taken for interrogating class types and enums.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hard-coding constant values in C# classes is a bad practice that we rarely indulge in here at TAMUS ESI.&#160; You will not see very many instances of code like: &#160;&#160;&#160; if (someObject.StatusCode == &quot;A&quot;) We prefer something more like: &#160;&#160;&#160; if (someObject.StatusCode == SomeProjectConstants.SomeFieldStatus.Active) where SomeFieldStatus is a class like &hellip; <\/p>\n","protected":false},"author":6,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[15],"tags":[],"class_list":["post-783","post","type-post","status-publish","format-standard","hentry","category-development"],"_links":{"self":[{"href":"https:\/\/mooredynasty.net\/index.php\/wp-json\/wp\/v2\/posts\/783","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mooredynasty.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mooredynasty.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mooredynasty.net\/index.php\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/mooredynasty.net\/index.php\/wp-json\/wp\/v2\/comments?post=783"}],"version-history":[{"count":1,"href":"https:\/\/mooredynasty.net\/index.php\/wp-json\/wp\/v2\/posts\/783\/revisions"}],"predecessor-version":[{"id":784,"href":"https:\/\/mooredynasty.net\/index.php\/wp-json\/wp\/v2\/posts\/783\/revisions\/784"}],"wp:attachment":[{"href":"https:\/\/mooredynasty.net\/index.php\/wp-json\/wp\/v2\/media?parent=783"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mooredynasty.net\/index.php\/wp-json\/wp\/v2\/categories?post=783"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mooredynasty.net\/index.php\/wp-json\/wp\/v2\/tags?post=783"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}