Google Analytics

Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Friday, July 14, 2017

Mountebank Config File Issue and a Lesson At My Expense

tl;dr: Back up your stuff, and you have to create mountebank config files in something other than VisualStudio.

Okay, the first lesson to dole out for the day is to back up your blog entry drafts as you write them. I am currently going back through some projects we have been working on cleaning up and reworking tests to get better coverage (and finding some issues along the way, which has made this a very worthwhile excercise). In trying to get some tests working locally, I discovered they rely on mountebank, an incredibly handy code package that acts as a test double for downstream services. Tests were failing because I hadn't reinstalled mountebank locally since I had a crash about six weeks ago. 

So, I got mountebank re-installed. Some of the tests needed to be re-worked so. In doing so I ran into an extremely frustrating issue where MB just won't accept a configuration file. I was working from home, so my wife was suddenly an audience for the flood of expletives I can unleash at times while doing this job. After her admonishment and composing myself I realized I had run into this exact smae issue before. More on this later. I remembered I had even written down the exact nature of the issue before. I set about tryiing to find just exactly what had happened to what I wrote.

I remember thinking that it would make for a good blog post. Looking at my published blog posts, however, turned up nothing. Had I just saved it locally and then never published it? The crash I mentioned before was actually an issue with our corporate encryption software. Basically every file that I had created locally had been encrypted by the software, which then suddenly stopped recognizing that I was who I said I was and wouldn't decrypt anything. This included config files for software I used every day, which all then stopped working. Anyway, long story short, I lost basically everything I had written and only saved locally.

At one point I had sent out some notes to my fellow teammates on using mountebank. Maybe I had included my write-up for them. One sent me back that email, which included a couple links on getting started with MB, along with a note saying "I was looking forward to reading your blog post on it."  Argh!

So here I am re-creating that post along with the lesson for all to save those blog posts as drafts online, or somewhere where they will eventually be lost to corporate encryption perdition.

I'm going to make this a series of posts, building incrementally, since there is a lot of depth to mountebank that can be explored. With that in mind, I want to wrap this initial post up with a rundown of the problem I ran into once again yesterday,

You can begin running the mountbank API simply with the command 'mb' on the command line and then making a series of posts to it to set up the services you intend to impersonate. I've found for automated testing purposes it is simpler to pass a configuration file as an .ejs file. 

mb --configfile "C:/path/to/my/config.ejs"

The .ejs extension allows you to use javaScript and javaScript variables directly in the file, something to explore later. The issue is when you create this .ejb file in VisualStudio, VS does something to add additional info to the file which causes MB to choke on the file. MB will throw an error that the JSON in the file is incorrect. You will then spend the next half hour looking for stray brackets or misplaced commas and then copying in the content of previously-created files that work just fine and pulling out your hair.

Then you will try opening the file in a text editor like Notepad++ and re-copying the contents in, to discover that doesn't work either. Then you'll check to confirm that the .ejs file is being copied to the correct location upon build. You have to delete the file and re-create it in something other than VS. Notepad++ works just fine. I'm not sure if VS Code works. I've discovered now that this is an issue with both VS 2015 and VS 2017 (since my rebuild of the computer now has the latest VS).

Hope this helps out some of you to save both time and hair. My next post will hopefully get one at least started using mountebank for testing purposes.

Tuesday, July 15, 2014

Configuring GhostDoc Pro, part 1 - File, Class, and Properties Headers

I started this post back in the beginning of 2013. I apparently tossed my exported rules(*), so am redoing them and thought I would revisit my own, hopefully useful, advice.

I swear, why is it so hard to find documentation on doing some of the simplest programming things? Why is it that these companies can create such a relatively useful product, and yet it is impossible to find any real sort of documentation on how to use them? I have multiple blog entries brewing on my frustrations using Infragistics Reporting in their NetAdvantage suite. Today though, I'm going to rail against SubMain's GhostDoc Pro.

GhostDoc, a VisualStudio extension for automatically generating XML documentation and code comments, is a total time saver when it is properly configured. Because of the government certification our products generally go through, our code has to be meticulously commented in very specific ways. At one point my rules were properly configured and I had spent about half a day configuring the templates as well as I could, so that all I had to do was go to Tools > GhostDoc > Document File and my code file would be properly documented including headers, properties and fields. Unfortunately when I chose to upgrade my current version of GhostDoc it apparently took my desire to "Upgrade" my current rules file and merely used that as a "suggestion", which it then ignored, and I ended up with the default GhostDoc Rules templates. *So my first suggestion to you is that as soon as you have the templates the way you want them, export them to avoid having go through the hassle of re-creating them.

Secondly, bookmark this blog post as reference to refer back to later. The extent of SubMain's online documentation for creating your own templates seems to be to simply rely on the community of GhostDoc users creating blog posts in their community pages (http://community.submain.com/) and on StackOverflow. If you go to the tutorials on SubMain's site you will notice there are no tutorials for GhostDoc. (I take it back. Since I first began writing this blog article way long ago, a simple tutorial has been added. It is a very quick overview of all of GhostDoc Pro's features.) The help file that is downloaded for GhostDoc Pro contains an Introduction to T4 Templates and a description of each of the different templates that are used.

Editing T4 Templates
To edit the GhostDoc Pro T4 Rules templates, in Visual Studio go to Tools > GhostDoc Pro > Options > Rules and select the template you want to update. The template will open into an editor that does provide some Intellisense and color-coding.

Adding Global Properties
Global properties, accessible through the GetGlobalProperty of the Context object, can be added in the GhostDoc Pro options window under Global Properties. Here you can enter the key-value pairs for commonly used properties throughout your templates.

File Header

By necessity, our standard file header looks like this:

// -----------------------------------------------------------------------------
// <copyright company="Dominion Voting Systems" file="ElectionDataService.cs">
//     Copyright (c) 2013 Dominion Voting Systems, Inc. All Rights Reserved.
//     Any distribution of source code by others is prohibited.
// </copyright>
// <summary>
//    This file contains the Election Data Service Class
// </summary>
// <revision date="1/11/2013" revisor="ben.rice">
//     File created.
// </revision>
// -----------------------------------------------------------------------------

In order to create this I am using the following template.

<#@ template language="C#" #>
// -----------------------------------------------------------------------------
// ">
//     Copyright (c) <#= Context.DestinationFileCreationDateTime.ToString("yyyy") #> <#= Context.GetGlobalProperty("CompanyName") #>. All Rights Reserved.
//     Any distribution of source code by others is prohibited.
//
//









//     This file contains the <#= System.IO.Path.GetFileNameWithoutExtension(Context.DestinationFile) #> class.
//
// " date="<#= Context.DestinationFileCreationDateTime.ToString("MM/dd/yyyy") #>">
//     File created.
//
// -----------------------------------------------------------------------------

You can see that the Context object is perhaps the most useful in creating these templates. It's use is based on the template in which it is used. In this case, for example, the Context.Exceptions would be empty since the context of its use here is the File and Exceptions enumerates only any exceptions thrown by a method or property. You can also see the use of Context to retrieve our Global Properties. Of course, this can be done in whatever template you are editing. Also note that I can use the standard .Net objects and methods to get the file name without the file extension, for example. 

I need to modify the rule yet to add the "File modified." revision note. For now I throw that on manually.

Class Header
Our standard class header:

///









///     Interaction logic for navigating views within the Ballot Viewer.
///
///
///     Class created.
///

For this I am basically using the default class template for GhostDoc Pro.

<#@ template language="C#" #>
<#  CodeElement codeElement = Context.CurrentCodeElement; #>
///









///<# GenerateSummaryText(); #>
///
<#  if(codeElement.HasTypeParameters) 
{
for(int i = 0; i < codeElement.TypeParameters.Length; i++) 

TypeParameter typeParameter = codeElement.TypeParameters[i]; 
#>
/// <# GenerateTypeParamText(typeParameter, i); #>
<# }

#>
<#= Context.GetNonGeneratedTags() #>
<# GenerateRemarksText(); #>

<#+
private void GenerateSummaryText()
{
if(Context.HasExistingTagText("summary"))
{
this.WriteLine(Context.GetExistingTagText("summary"));
}
else if(Context.CurrentCodeElement.IsSealed)
{
this.WriteLine("TODO: Add summary for Class " + Context.CurrentCodeElement.Name + ". This class cannot be inherited." + Context.ExecMacro("$(End)"));
}
else
{
this.WriteLine("TODO: Add summary for Class " + Context.CurrentCodeElement.Name + "." + Context.ExecMacro("$(End)"));
}
}
    
private void GenerateTypeParamText(TypeParameter typeParameter, int index)
{
if(Context.HasExistingTagText("typeparam", index)) 

this.Write(Context.GetExistingTagText("typeparam", index));

else 
{
string typeParameterName = typeParameter.Name;
if(typeParameterName != null)
{
if(typeParameterName.Length == 1)
{
this.Write("");
}
else
{
this.Write("The type of " + Context.ExecMacro(typeParameterName, "$(TheAndAll)") + ".");
}            
}          

}

private void GenerateRemarksText()
{
if(Context.HasExistingTagText("remarks"))
{ #>
/// <#= Context.GetExistingTagText("remarks") #>
<#+    }
else if (!string.IsNullOrEmpty(Context.GetGlobalProperty("DefaultBlankRemarksText")))

            // Should you require a default comment, set it in
// Options -> Global Properties -> DefaultBlankRemarksText
#>
/// <#= Context.GetGlobalProperty("DefaultBlankRemarksText") #>
<#+    }
}
#>

Notice that within the template I can call C# methods coded below. In this case we are generating type parameter elements for each type parameter if there are any. If a summary element or remarks element already exists in the header, that text is preserved, else some default text is inserted. Mine has a TODO reminder in the summary text to highlight that I need to change that.

I'm going to skip ahead now to the Properties rule

Our property header will look something like this:
///









///     Gets or sets the name of the election.
///
/// The name of the election.
///
///     Member created.
///
///
///     Changed to allow public setter.
///

I added to the default property generated in order to set the revision element.

<#@ template language="C#" #>
///









///<# GenerateSummaryText(); #>
///
<# GenerateValueText(); #>
<# if(Context.HasExceptions) 
{
foreach (System.Collections.Generic.KeyValuePair> pair in Context.ExceptionDictionary)
{
string exceptionText = Context.GetExistingExceptionTagText(pair.Key); #>
/// <#= ((exceptionText != null && exceptionText.Length > 0) ? exceptionText : Context.GetExceptionTagText(pair.Value)) #>
<# }
}  #>
<#= Context.GetNonGeneratedExceptionTags() #>
<#= Context.GetNonGeneratedTags() #>
<# GenerateRevisionTag(); #>
<# GenerateRemarksText(); #>

<#+
private void GenerateSummaryText()
{
CodeElement codeElement = Context.CurrentCodeElement;
   
if(Context.HasExistingTagText("summary"))
{
this.WriteLine(Context.GetExistingTagText("summary"));
}
else if(Context.HasInheritedTagText("summary"))
{
this.WriteLine(Context.GetInheritedTagText("summary"));
}
else if(IsBooleanProperty()) 
{
if(IsStateCheckBooleanProperty())
{
// state check boolean property
this.WriteLine(Context.ExecMacro("$(PropertyAccessText) a value indicating whether this instance $(PropertyName.Words.All)."));
}
else if(IsSingleWordBooleanProperty())
{
// single word boolean property
this.WriteLine(Context.ExecMacro("$(PropertyAccessText) a value indicating whether this $(DeclaringTypeName.ShortNameAsSee) is $(PropertyName.Words.First)."));
}
else
{
// boolean property
this.WriteLine(Context.ExecMacro("$(PropertyAccessText) a value indicating whether $(End)[$(PropertyName.Words.All)]."));
}
}
else if(Context.ContainsOfTheReordering(codeElement.Name)) 
{
this.WriteLine(Context.ExecMacro(codeElement.Name, "$(OfTheReordering)", Context.ExecMacro("$(PropertyAccessText)")));
}
else
{
this.WriteLine(Context.ExecMacro("$(PropertyAccessText) $(PropertyName.Words.TheAndAll)."));
}
}
        
private void GenerateValueText()
{
CodeElement codeElement = Context.CurrentCodeElement;
        
if(Context.HasExistingTagText("value"))

#>
/// <#= Context.GetExistingTagText("value") #>
<#+
}
else if(Context.HasInheritedTagText("value"))

#>
/// <#= Context.GetInheritedTagText("value") #>
<#+
}
else /*if(Context.CurrentCodeElement.HasSet) - commented for StypeCop compliance (SA1609) */
{
if(IsBooleanProperty()) 
{
.....
}
}

.... 
    
private bool IsBooleanProperty()
{
CodeElement codeElement = Context.CurrentCodeElement;  
return (bool)(codeElement.ReturnType != null && 
codeElement.ReturnType.FullName != null &&
codeElement.ReturnType.FullName.Length > 0 && 
(string.Compare( codeElement.ReturnType.FullName, "System.Boolean", true) == 0 ||
string.Compare( codeElement.ReturnType.FullName, "System.Nullable", true) == 0));
}  

private void GenerateRemarksText()
{
if(Context.HasInheritedTagText("remarks"))
{ #>
/// <#= Context.GetInheritedTagText("remarks") #>
<#+    }
else if(Context.HasExistingTagText("remarks"))
{ #>
/// <#= Context.GetExistingTagText("remarks") #>
<#+    }
else if (!string.IsNullOrEmpty(Context.GetGlobalProperty("DefaultBlankRemarksText")))

            // Should you require a default comment, set it in
// Options -> Global Properties -> DefaultBlankRemarksText
#>
/// <#= Context.GetGlobalProperty("DefaultBlankRemarksText") #>
<#+    }
}

private void GenerateRevisionTag()

if( Context.HasExistingTagText("summary")) {#>
/// <revision revisor="<#= Context.GetGlobalProperty("UserName") #>" date="<#= DateTime.Now.ToString("MM/dd/yyyy") #>">
///     TODO: Add Revision
/// </revision>
///
<#+
} else { #>
/// " date="<#= DateTime.Now.ToString("MM/dd/yyyy") #>">
///     Member created.
///
<#+ }
}
#>

Please note that in the interest of space, I omitted code that determines how to word the value element of a boolean property. In the custom "GenerateRevisionTag" method I did a check to see if the summary has already been written. If it hadn't been (prior to this GD call), I assume the property has just been created and add "Member created." to the revision text. Otherwise, I assume that the property already existed, but is being revised and add the reminder to myself to update what the revision was. Also note that the GenerateRevisionTag call is made after Context.GetNonGeneratedTags is written. This makes later revisions appear below previously written ones.