Feb 28, 2008

How To Create a WCF Custom Code Analysis Rule for Visual Studio

As most people know Visual Studio incorporates the FxCop static code analysis tool so you can perform static code analysis and check that your application follows good coding standards and doesn't contain nasty little surprises like forgetting to dispose of IDisposable objects, etc.

Recently I was doing some work with WCF and wanted to check that all methods with an OperationContract attribute had a FaultContract specified as well.  Unfortunately this isn't a rule that's part of the default code analysis rules, so I needed to create a Custom Code Analysis rule.  Unfortunately most of the samples on custom FxCop rules all seem to be based around doing spell checking or some other such frivolous checks, so hopefully this is something a little meatier for you to get into.

NOTE: The following "how to" is done with Visual Studio 2008.  Specific variations for VS2005 are shown in italics.

Also, a lot of this information was gathered from those who've gone before me.  Namely Nicole Calinoiu, James Guerts, Jason Kresowaty and others.  Many thanks to them for doing the hard yards and figuring out how things fit together.

Step 0 - We Need Something to Analyse

First, let's create a WCF project so we've got something we can run the rules against.

Create a new WCF Service Library project (.NET 3.0) - by default it will be called WcfServiceLibrary1 and will have a default service contract as shown:

    // NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in App.config.
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);

[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);

// TODO: Add your service operations here
}

As you can see the methods marked as OperationContract do not have any FaultContract attributes specified.


We're going to detect this with our custom rule because we want to make sure that the faults are explicitly declared.


Step 1 - Create a Class Library Project


Create a new class library project - we'll call it MyFxCopRules.  It can be a normal .NET 2.0 project.


Step 2 - Add References and Create a custom rule base class


You'll need to add 2 references to the MyFxCopRules project.  One for FxCopSdk.dll and one for Microsoft.Cci.dll. Both DLL's can be found in %ProgramFiles%\Microsoft Visual Studio 9.0\Team Tools\Static Analysis Tools\FxCop\


fxcop2


For FxCop to determine which classes in an assembly represent custom rules it will look for classes that implement the IIntrospectionRule interface.  When working with managed code you'll also need to implement the IManagedCodeRule interface.   Fortunately there is an abstract base class that implements these interfaces already - the BaseIntrospectionRule class.


So let's make Class1.cs a base class for all the custom rule we want to define.


Add a reference to the Microsoft.FxCop.Sdk namespace, change the name of the class to MyBaseRule and inherit from BaseIntrospectionRule as follows:

using Microsoft.FxCop.Sdk;

namespace MyFxCopRules
{
public abstract class MyBaseRule : BaseIntrospectionRule
{
}
}


[VS2005] In the using directives add Microsoft.FxCop.Sdk.Introspection


When creating a rule we need to tell the Introspection engine what the name of the rule is, the rule set it belongs to and the assembly it is declared in.  We could do this once per rule, or we can do it by adding the following constructor to our base rule class

        protected MyBaseRule(string name)
: base(name, "MyFxCopRules.Rules", typeof(MyBaseRule).Assembly)
{
}

Step 3 - Add a Folder And A Rule


Now we're going to create our rule.  Let's start by adding a folder to put all future rules in and then create a new class for our custom rule.  Add a folder to the project called "Rules" and a class called "EnsureFaultContractsAreDeclared".  It's good practice to name classes according to the rule they are validating.


You should now have a project as follows:


fxcop3


Step 4 - Define The Rule


OK! Now to the meat of it.  Let's define the rule itself.


First, we need to add references to the Microsoft.FxCop.Sdk and Microsoft.Cci namespaces.  Next we need to make our class inherit from the base class we defined earlier and pass the name of our rule to the base class constructor so that FxCop knows what our rule is called.  It's easy enough to do and you code should look like this:

using Microsoft.FxCop.Sdk;

namespace MyFxCopRules.Rules
{
public class EnsureFaultContractsAreDeclared : MyBaseRule
{
public EnsureFaultContractsAreDeclared()
: base("EnsureFaultContractsAreDeclared")
{
}
}
}

[VS2005] In the using directives add Microsoft.FxCop.Sdk.Introspection and Microsoft.Cci


Of course, this rule is pretty much useless right now as it doesn't actually do anything.


What we need to do is implement a Check() method.  There are various Check() methods that the BaseIntrospectionRule class exposes and these methods are called by the introspection engine each time it visits a code element.  When the Check() method runs it should report back any problems it finds in a ProblemCollection.  Obviously if there are no items in the collection it means the rule evaluated successfully.


Because the rule we are writing is going to be checking that method level attributes are correctly defined we will implement the Check(Member member) method.

        public override ProblemCollection Check(Member member)
{
if (VerifyAttributes(member.Attributes))
{
this.Problems.Add(new Problem(this.GetResolution(member), member));
}
return this.Problems;
}

private static bool VerifyAttributes(AttributeNodeCollection attributes)
{
bool isOperationContract = false;
bool hasFaultContract = false;
foreach (AttributeNode attribute in attributes)
{
if (attribute.Type.FullName == "System.ServiceModel.OperationContractAttribute")
isOperationContract = true;
if (attribute.Type.FullName == "System.ServiceModel.FaultContractAttribute")
hasFaultContract = true;
}
if (isOperationContract)
return !hasFaultContract;
return false;
}

[VS2005] Use the type AttributeList instead of AttributeNodeCollection for the VerifyAttributes parameter


[VS2005] Use RuleUtilities.Format(member) instead of member in the GetResolution() call


So, what happens when FxCop evaluates this rule?


1. The Check() method gets called and the current Member (method or property) being checked is passed in.


2. We take the collection of Attributes that the member has associated with it and pass them to the VerifyAttributes helper method.


3. VerifyAttributes does a simple scan through the Attribute collection looking for any attributes of the appropriate type.  If an [OperationContract()] is found but we don't find a [FaultContract()] we return false to true to the Check method to indicate we have a problem.


4. Back up in the Check method we create a new Problem and add it to the ProblemCollection before returning control back to FxCop.


Step 5 - Define a Rules.XML file


We're not quite done yet.  If we were to take this code, compile it and then try to use it we would see a rule group appear in Visual Studio, but we wouldn't see any rules in the group.


This is because we haven't yet created an XML file that defines the rule behaviours when problems are found.  The code determines if a problem exists, the XML file determines what do show when a problem is found.


Go and add a Rules.XML file to the project and ensure that the Build Action is set to "Embedded Resource" as shown


fxcop4


Inside the file add the following XML

<?xml version="1.0" encoding="utf-8"?>
<
Rules FriendlyName="My Custom Rules">
<
Rule TypeName="EnsureFaultContractsAreDeclared" Category="MyRules.Usage" CheckId="MU0001">
<
Name>Supply Fault Contracts for all WCF Operations</Name>
<
FixCategories>Breaking</FixCategories>
<
MessageLevel Certainty="95">Warning</MessageLevel>
<
Resolution>WCF Operation Contracts need to have a fault contract defined using the [FaultContract()] attribute</Resolution>
<
Description>Fault Contracts need to be applied to all WCF services</Description>
<
Owner>Richard Banks</Owner>
<
Email></Email>
<
Url>http://richardsbraindump.blogspot.com</Url>
</
Rule>
</
Rules>

Hopefully the properties are all obvious enough.  If you have problems check that the TypeName doesn't have any spelling mistakes in it.


Step 6 - Deployment


Now compile the project and make sure everything is OK.  Then close Visual Studio 2008.


Copy your custom FxCop rules assembly from your output folder into the Visual Studio rules folder - typically C:\Program Files\Microsoft Visual Studio 9.0\Team Tools\Static Analysis Tools\FxCop\Rules


Restart Visual Studio and open the solution again.  If you now go to the properties page for the WcfServiceLibrary1 project you should see something like the following:


fxcop5


If you then run Code Analysis for the project (Build->Run Code Analysis...) you should see the following in the Error List window:


fxcop6


You're all done!  You  now have a working rule that does something other than check spelling :-)


You can double check that the rule works properly by adding [FaultContract(null)] to one of the operation contracts methods and seeing that when you re-run analysis that there is one less MU0001 warning.


P.S. If you're into FxCop then you'll probably want to add some rule suppressions to your rule classes as well as the code for the custom rule will violate a few of the standard FxCop rules (CA1014 & CA2210).

2 comments:

  1. Cheers on the info!

    ----------------------------------------------
    Fred Reckling
    Microsoft 2008 Joint Launch Team
    http://www.microsoft.com/2008jointlaunch/

    ReplyDelete
  2. Excellent!!
    But can we create variable naming rules such as
    1)Private variable name must start with Camel Case with Leading Underscore.
    2)Interface name must start the name with "I" and capitalize the letter following the "I".
    and so on....

    Please need help.

    ReplyDelete