October 2015
Volume 30 Number 10
Code Analysis - Build and Deploy Libraries with Integrated Roslyn Code Analysis to NuGet
The Microsoft .NET Compiler Platform (also referred to as the “Roslyn” code base) offers open source C# and Visual Basic compilers that expose, among others, rich code analysis APIs you can leverage to build live analysis rules that integrate into the Visual Studio 2015 code editor. With the .NET Compiler Platform, you can write custom, domain-specific code analyzers and refactorings so Visual Studio can detect code issues as you type, reporting warnings and error messages. A big benefit of the .NET Compiler Platform is that you can bundle code analyzers with your APIs. For instance, if you build libraries or reusable user controls, you can ship analyzers together with your libraries and provide developers an improved coding experience.
In this article, I’ll explain how to bundle libraries and analyzers into NuGet packages for online deployment, showing how you can offer integrated Roslyn code analysis for your APIs. This requires you have at least a basic knowledge about .NET Compiler Platform concepts and about writing a code analyzer. These topics have been discussed in past MSDN Magazine articles by Alex Turner: “C# and Visual Basic: Use Roslyn to Write a Live Code Analyzer for Your API” (msdn.microsoft.com/magazine/dn879356) and “C#—Adding a Code Fix to Your Roslyn Analyzer” (msdn.microsoft.com/magazine/dn904670), which you’re strongly encouraged to read before going on here.
Preparing a Sample Library to Simulate Custom APIs
The first thing you need is a class library that simulates a custom API. The sample library for this article exposes a simple public method that retrieves common information from an RSS feed, returning a collection of feed items. In Visual Studio 2015, create a new Portable Class Library called FeedLibrary with either C# or Visual Basic and ensure that the minimum target is Windows 8.1, Windows Phone 8.1, and the Microsoft .NET Framework 4.5.1. With this target, the library can also take advantage of the Async/Await pattern with no additional requirements.
Rename the Class1.vb or Class1.cs generated file into FeedItem.vb/.cs. The C# code for this class is in Figure 1 and the Visual Basic code is in Figure 2.
Figure 1 Implementing a Class to Retrieve Common Items from an RSS Feed in C#
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace FeedLibrary
{
// Represent a single content in the RSS feed
public class FeedItem
{
// Properties representing information,
// which is common to any RSS feed
public string Title { get; set; }
public string Author { get; set; }
public string Description { get; set; }
public DateTimeOffset PubDate { get; set; }
public Uri Link { get; set; }
// Return a collection of FeedItem objects from a RSS feed
public static async Task<IEnumerable<FeedItem>> ParseFeedAsync(
string feedUrl)
{
var client = new HttpClient();
// Download the feed content as a string
var result = await client.GetStringAsync(new Uri(feedUrl));
// If no result, throw an exception
if (result == null)
{
throw new InvalidOperationException(
"The specified URL returned a null result");
}
else
{
// LINQ to XML: Convert the returned string into an XDocument object
var doc = XDocument.Parse(result);
var dc = XNamespace.Get("https://purl.org/dc/elements/1.1/");
// Execute a LINQ query over the XML document
// and return a collection of FeedItem objects
var query = (from entry in doc.Descendants("item")
select new FeedItem
{
Title = entry.Element("title").Value,
Link = new Uri(entry.Element("link").Value),
Author = entry.Element(dc + "creator").Value,
Description = entry.Element("description").Value,
PubDate = DateTimeOffset.Parse(
entry.Element("pubDate").Value,
System.Globalization.CultureInfo.InvariantCulture)
});
return query;
}
}
}
}
Figure 2 Implementing a Class to Retrieve Common Items from an RSS Feed in Visual Basic
Imports System.Net.Http
Imports <xmlns:dc="https://purl.org/dc/elements/1.1/">
'Represent a single content in the RSS feed
Public Class FeedItem
'Properties representing information
'which is common to any RSS feed
Public Property Title As String = String.Empty
Public Property Author As String = String.Empty
Public Property Description As String = String.Empty
Public Property PubDate As DateTimeOffset
Public Property Link As Uri
'Return a collection of FeedItem objects from a RSS feed
Public Shared Async Function ParseFeedAsync(feedUrl As String) As _
Task(Of IEnumerable(Of FeedItem))
Dim client As New HttpClient
'Download the feed content as a string
Dim result = Await client.GetStringAsync(New Uri(feedUrl, UriKind.Absolute))
'If no result, throw an exception
If result Is Nothing Then
Throw New InvalidOperationException(
"The specified URL returned a null result")
Else
'LINQ to XML: Convert the returned string
'into an XDocument object
Dim document = XDocument.Parse(result)
'Execute a LINQ query over the XML document
'and return a collection of FeedItem objects
Dim query = From item In document...<item>
Select New FeedItem With {
.Title = item.<title>.Value,
.Author = item.<dc:creator>.Value,
.Description = item.<description>.Value,
.PubDate = DateTimeOffset.Parse(item.<pubDate>.Value),
.Link = New Uri(item.<link>.Value)}
Return query
End If
End Function
End Class
The code is very simple: It downloads the syndicated content from the specified RSS feed’s URL, creates an instance of the FeedItem class per feed item, and it finally returns a new collection of items. To use the library, you simply invoke the static ParseFeedAsyncAsync method for C#, as follows:
// Replace the argument with a valid URL
var items = await FeedItem.ParseFeedAsyncAsync("https://sampleurl.com/rss");
And for Visual Basic it looks like this:
'Replace the argument with a valid URL
Dim items = Await FeedItem.ParseFeedAsyncAsync("https://sampleurl.com/rss")
This invocation returns an IEnumerable<FeedItem>, which you can then use according to your needs. Select the Release configuration and build the project; at this point, Visual Studio 2015 generates a library called FeedLibrary.dll, which will be used later.
Writing a Roslyn Analyzer
The next step is creating a Roslyn analyzer that provides domain-specific live analysis rules for the custom APIs. The analyzer will detect if the URL supplied as the ParseFeedAsyncAsync method’s argument is well-formed, using the Uri.IsWellFormedUriString method; if not, the analyzer will report a warning as you type. Of course, there are plenty of ways to detect if a URL is invalid, but I use this one for the sake of simplicity. In addition, for the same reasons, the analyzer will provide live analysis reporting warnings, but it will not offer any code fixes, which is left to you as an exercise. That said, follow these steps:
- In Solution Explorer, right-click the solution name, then select Add | New Project.
- In the Extensibility node of the project templates list, select Analyzer with Code Fix (NuGet + VSIX). Note that the Extensibility node doesn’t appear by default. You need to first download the .NET Compiler Platform SDK to fetch the Analyzer with Code Fix template projects. Search Analyzers in the New Project dialog and you’ll see the template project to download this SDK.
- Call the new analyzer FeedLibraryAnalyzer and click OK.
- When the new project is ready, remove the CodeFixProvider.cs (or .vb) file.
In the DiagnosticAnalyzer.cs (or .vb) file, the first thing to do is supply strings that identify the analyzer in the coding experience. In order to keep the implementation of the analyzer simpler, in the current example I use regular strings instead of the LocalizableString object and resource files, assuming the analyzer will not need to be localized. Rewrite DiagnosticId, Title, Message, Description and Category for C#, as follows:
public const string DiagnosticId = "RSS001";
internal static readonly string Title = "RSS URL analysis";
internal static readonly string MessageFormat = "URL is invalid";
internal static readonly string Description =
"Provides live analysis for the FeedLibrary APIs";
internal const string Category = "Syntax";
And for Visual Basic, as follows:
Public Const DiagnosticId = "RSS001"
Friend Shared ReadOnly Title As String = "RSS URL analysis"
Friend Shared ReadOnly MessageFormat As String = "URL is invalid"
Friend Shared ReadOnly Description As String =
"Provides live analysis for the FeedLibrary APIs"
Friend Const Category = "Syntax"
Don’t change the diagnostic severity, which is Warning by default and is a proper choice for the current example. Now it’s time to focus on the analysis logic. The analyzer must check whether the code is invoking a method called ParseFeedAsync. If so, the analyzer will then check if the supplied URL is well-formed. With the help of the Syntax Visualizer, you can see in Figure 3 how an invocation of the ParseFeedAsync method is represented by an InvocationExpression, mapped to an object of type InvocationExpressionSyntax.
Figure 3 The Syntax Visualizer Helps Find the Proper Syntax Node Representation
So the analyzer focuses only on objects of type InvocationExpressionSyntax; when it finds one, it converts the associated expression into an object of type MemberAccessExpressionSyntax, which contains information about a method call. If the conversion succeeds, the analyzer checks if the method is ParseFeedAsync, then it retrieves the first argument and performs live analysis on its value. This is accomplished by a new method, called AnalyzeMethod, that works at the SyntaxNode level and is represented in Figure 4 for C# and Figure 5 for Visual Basic.
Figure 4 Detecting Issues on the ParseFeedAsync Argument in C#
private static void AnalyzeMethodInvocation(SyntaxNodeAnalysisContext context)
{
// Convert the current syntax node into an InvocationExpressionSyntax,
// which represents a method call
var invocationExpr = (InvocationExpressionSyntax)context.Node;
// Convert the associated expression into a MemberAccessExpressionSyntax,
// which represents a method's information
// If the expression is not a MemberAccessExpressionSyntax, return
if (!(invocationExpr.Expression is MemberAccessExpressionSyntax))
{
return;
}
var memberAccessExpr = (MemberAccessExpressionSyntax)invocationExpr.Expression;
// If the method name is not ParseFeedAsync, return
if (memberAccessExpr?.Name.ToString() != "ParseFeedAsync") { return; }
// If the method name is ParseFeedAsync, check for the symbol
// info and see if the return type matches
var memberSymbol = context.SemanticModel.
GetSymbolInfo(memberAccessExpr).
Symbol as IMethodSymbol;
if (memberSymbol == null) { return; }
var result = memberSymbol.ToString();
if (memberSymbol?.ReturnType.ToString() !=
"System.Threading.Tasks.Task<
System.Collections.Generic.IEnumerable<FeedLibrary.FeedItem>>")
{
return;
}
// Check if the method call has the required argument number
var argumentList = invocationExpr.ArgumentList;
if (argumentList?.Arguments.Count != 1) {
return; }
// Convert the expression for the first method argument into
// a LiteralExpressionSyntax. If null, return
var urlLiteral = (LiteralExpressionSyntax)invocationExpr.ArgumentList.
Arguments[0].Expression;
if (urlLiteral == null) { return; }
// Convert the actual value for the method argument into string
// If null, return
var urlLiteralOpt = context.SemanticModel.GetConstantValue(urlLiteral);
var urlValue = (string)urlLiteralOpt.Value;
if (urlValue == null) { return; }
// If the URL is not well-formed, create a diagnostic
if (Uri.IsWellFormedUriString(urlValue, UriKind.Absolute) == false)
{
var diagn = Diagnostic.Create(Rule, urlLiteral.GetLocation(),
"The specified parameter Is Not a valid RSS feed");
context.ReportDiagnostic(diagn);
}
}
Figure 5 Detecting Issues on the ParseFeedAsync Argument in Visual Basic
Private Sub Shared AnalyzeMethodInvocation(context As SyntaxNodeAnalysisContext)
'Convert the current syntax node into an InvocationExpressionSyntax
'which represents a method call
Dim invocationExpr = CType(context.Node, InvocationExpressionSyntax)
'Convert the associated expression into a MemberAccessExpressionSyntax
'which represents a method's information
'If the expression Is Not a MemberAccessExpressionSyntax, return
If TypeOf invocationExpr.Expression IsNot MemberAccessExpressionSyntax Then Return
Dim memberAccessExpr = DirectCast(invocationExpr.Expression,
MemberAccessExpressionSyntax)
'If the method name Is Not ParseFeedAsync, return
If memberAccessExpr?.Name.ToString <> "ParseFeedAsync" Then Return
'If the method name is ParseFeedAsync, check for the symbol info
'and see if the return type matches
Dim memberSymbol = TryCast(context.SemanticModel.
GetSymbolInfo(memberAccessExpr).Symbol, IMethodSymbol)
If memberSymbol Is Nothing Then Return
Dim result = memberSymbol.ToString
If Not memberSymbol?.ReturnType.ToString =
"System.Threading.Tasks.Task(Of System.Collections.Generic.IEnumerable(
Of FeedLibrary.FeedItem))"
Then Return
'Check if the method call has the required argument number
Dim argumentList = invocationExpr.ArgumentList
If argumentList?.Arguments.Count <> 1 Then Return
'Convert the expression for the first method argument into
'a LiteralExpressionSyntax. If null, return
Dim urlLiteral =
DirectCast(invocationExpr.ArgumentList.Arguments(0).GetExpression,
LiteralExpressionSyntax)
If urlLiteral Is Nothing Then Return
'Convert the actual value for the method argument into string
'If null, return
Dim urlLiteralOpt = context.SemanticModel.GetConstantValue(urlLiteral)
Dim urlValue = DirectCast(urlLiteralOpt.Value, String)
If urlValue Is Nothing Then Return
'If the URL Is Not well-formed, create a diagnostic
If Uri.IsWellFormedUriString(urlValue, UriKind.Absolute) = False Then
Dim diagn = Diagnostic.Create(Rule, urlLiteral.GetLocation,
"The specified parameter Is Not a valid RSS feed")
context.ReportDiagnostic(diagn)
End If
End Sub
At this point, you need to edit the Initialize method to call the newly added AnalyzeMethodInvocation method, as shown in Figure 6 for C# and Figure 7 for Visual Basic.
Figure 6 Editing the Initialize Method in C#
public override void Initialize(AnalysisContext context)
{
// Register an action when compilation starts
context.
RegisterCompilationStartAction((CompilationStartAnalysisContext ctx) =>
{
// Detect if the type metadata
// exists in the compilation context
var myLibraryType =
ctx.Compilation.
GetTypeByMetadataName("FeedLibrary.FeedItem");
// If not, return
if (myLibraryType == null)
return;
// Register an action against an InvocationExpression
ctx.RegisterSyntaxNodeAction(AnalyzeMethodInvocation,
SyntaxKind.InvocationExpression);
});
}
Figure 7 Editing the Initialize Method in Visual Basic
Public Overrides Sub Initialize(context As AnalysisContext)
' Register an action when compilation starts
context.
RegisterCompilationStartAction
(Sub(ctx As CompilationStartAnalysisContext)
'Detect if the type metadata
'exists in the compilation context
Dim myLibraryType =
ctx.Compilation.
GetTypeByMetadataName("FeedLibrary.FeedItem")
'If not, return
'(no reference to the library)
If myLibraryType Is Nothing
Then Return
'Register an action against
'an InvocationExpression
ctx.RegisterSyntaxNodeAction(
AddressOf AnalyzeMethodInvocation,
SyntaxKind.InvocationExpression)
End Sub)
End Sub
Notice how the code first checks to see if a reference to the library exists in the project by invoking the Compilation.GetTypeMetadataName method, whose argument is the name of the type that must exist in the current context to make sure that a reference has been added. If this invocation returns null, it means that the type does not exist and, therefore, no reference has been added to the library. So, there’s no need to register a code analysis action, improving the analyzer’s performances. If you now press F5 to test the analyzer in the Experimental instance of Visual Studio 2015 and create a new project with a reference to the FeedLibrary library, you’ll be able to see how it correctly reports a warning every time you supply an invalid URL, as shown in Figure 8.
Figure 8 The Analyzer Reports a Warning If the URL Is Not Well-Formed
So far you’ve built APIs and related, domain-specific code analysis rules. Now it’s time to see how to bundle both into a single NuGet package.
Building a NuGet Package That Includes APIs and Analyzers
The MSBuild rules for the Analyzer with Code Fix project template automate the generation of a NuGet package that includes the compiled analyzer, which you can share with other developers by publishing it to a NuGet repository. In practice, every time you debug an analyzer by pressing F5 or when you build the analyzer project, Visual Studio 2015 rebuilds the analyzer .dll file (FeedLibraryAnalyzer.dll in the current example) and a redistributable NuGet package that contains the analyzer.
The build process also generates a VSIX package that you can publish to the Visual Studio Gallery and that’s also used to debug the analyzer inside the Experimental instance of Visual Studio 2015, but this is out of scope for this article and isn’t covered here.
If you want to share a library with integrated Roslyn analysis, you need to add the library to the NuGet package that Visual Studio 2015 generates when you build the project. Before doing this, you need to understand a bit more about how a NuGet package for an analyzer is made. Actually, a NuGet package is a .zip archive with .nupkg extension. Because of this, you can easily investigate the contents and structure of a NuGet package with a .zip archiver tool, such as the Windows Explorer compressed folder tools, WinZip or WinRar. Following is a summary of the most important items in a NuGet package designed to deploy analyzers:
- .nuspec file: This file contains package metadata and includes information required for publication, such as package name, version, description, author, license URL and more. The .nuspec file is bundled into the NuGet package based on the Diagnostic.nuspec file that you see in Solution Explorer, within the analyzer project. You’ll edit Diagnostic.nuspec in Visual Studio 2015 shortly.
- tools folder: This folder contains Windows PowerShell scripts used by Visual Studio to install (Install.ps1) and uninstall (Uninstall.ps1) an analyzer for a given project.
- analyzers folder: This folder contains analyzer .dll files organized into particular subfolders. Agnostic analyzer libraries (that is, targeting all languages) reside in a subfolder called dotnet. Analyzers targeting C# reside in a subfolder called dotnet\cs, whereas analyzers targeting Visual Basic reside in a folder called dotnet\vb. It’s worth mentioning that dotnet represents the NuGet profile for .NET Core, and supports projects types such as Universal Windows apps and ASP.NET 5 projects.
A number of additional items can be bundled into a NuGet package, but here I focus on a typical package generated for a Roslyn analyzer, so only the required items are discussed.
Any libraries that can be referenced from a Visual Studio project must be organized into a folder called lib. Because libraries can target different platforms, such as different versions of the .NET Framework, the Windows Runtime, different versions of Windows Phone, or even a portable subset (including Xamarin libraries), the lib folder must contain one subfolder per targeted platform, and each subfolder must contain a copy of the library to deploy. The name of each subfolder must match the name of a so-called profile representing a specific platform. For instance, if you have a library targeting the .NET Framework 4.5.1 and Windows 8.1, you’d have the following structure, where net451 is the profile name for the .NET Framework 4.5.1 and netcore451 is the profile name for the Windows Runtime in Windows 8.1:
lib\net451\mylib.dll
lib\netcore451\mylib.dll
It is worth mentioning the uap10.0 profile that targets the Universal Windows Platform (UWP) to build Windows 10 apps. The full list of supported profiles is available in the NuGet documentation.The sample library created previously is a portable library targeting .NET Framework 4.5.1, Windows 8.1 and Windows Phone 8.1. The profile name for this kind of target is portable-net451+netcore451+wpa81 and must be used to name the subfolder that will contain the library in the NuGet package. You don’t need to create the subfolder and copy the library manually; you simply need to edit the NuGet package metadata (the Diagnostic.nuspec file) inside Visual Studio. Figure 9 shows updated metadata with proper information for publishing (id, title, author, description, license and so on) and a new file element in the files node that specifies the source file and the target subfolder.
Figure 9 Editing the NuGet Package Metadata
<?xml version="1.0"?>
<package xmlns="https://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>RSSFeedLibrary</id>
<version>1.0.0.0</version>
<title>RSS Feed Library with Roslyn analysis</title>
<authors>Alessandro Del Sole</authors>
<owners>Alessandro Del Sole</owners>
<licenseUrl>https://opensource.org/licenses/MIT</licenseUrl>
<!-- Removing these lines as they are not needed
<projectUrl>https://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
<iconUrl>https://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>-->
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<description>Companion sample for the "Build and Deploy Libraries
with Integrated Roslyn Code Analysis to NuGet" article
on MSDN Magazine</description>
<releaseNotes>First release.</releaseNotes>
<copyright>Copyright 2015, Alessandro Del Sole</copyright>
<tags>RSS, analyzers, Roslyn</tags>
<frameworkAssemblies>
<frameworkAssembly assemblyName="System" targetFramework="" />
</frameworkAssemblies>
</metadata>
<files>
<file src="*.dll" target="analyzers\dotnet\cs"
exclude="**\Microsoft.CodeAnalysis.*;
**\System.Collections.Immutable.*;**\System.Reflection.Metadata.*;
**\System.Composition.*" />
<file src="tools\*.ps1" target="tools\" />
<file src="lib\FeedLibrary.dll" target="lib\portable-net451+netcore451+wpa81\"/>
</files>
</package>
The src attribute indicates the source location for the library, and target specifies the target subfolder, based on the proper profile name. In this case, Visual Studio will search for a library called FeedLibrary.dll inside a folder called lib, which you have to create in the current directory. The latter is the folder that contains the compiled analyzer, typically the Release or Debug folder, depending on the selected build configuration. Based on the current example, you’ll need to create a folder called lib inside the Release folder, then copy the FeedLibrary.dll (generated when compiling the sample library at the beginning) into the lib folder. Once you’ve done this, you can simply rebuild your solution and Visual Studio will generate an updated NuGet package containing both the library and the Roslyn analyzer.
It is worth noting that, when you rebuild the project, Visual Studio 2015 automatically updates the build and revision version numbers of the NuGet package, disregarding the numbers supplied in the version tag. The updated content of the NuGet package can be easily investigated by opening the updated, highest version of the NuGet package with a .zip archiver tool. Remember: Every time you change the id element’s value, as in Figure 9, Visual Studio 2015 generates a NuGet package with a different name, based on the new id. In this case, changing the id value with RSSFeedLibrary results in a NuGet package called RSSFeedLibrary.1.0.xxx.yyy.NuPkg, where xxx is the build version number and yyy is the revision version number, both automatically supplied at build time. At this point, you’ve achieved the first objective: packaging custom APIs with integrated Roslyn analysis into one NuGet package.
As an alternative, you could create (and publish) two separate packages—one for the analyzer and one for the library—and a third empty NuGet package that pulls them together by resolving them as dependencies. With this approach, you can decide to keep the installation size smaller by using the APIs only without the analyzer, though you’ll need to be familiar with NuGet conventions to manually create a package from scratch. Before publishing the newly generated package to the online NuGet repository, it’s a good idea to test it locally.
Testing a NuGet Package Locally
Visual Studio 2015 allows picking NuGet packages from local repositories. This is very useful, especially in situations in which you need to cache packages you use often or that you might need when working offline. Notice that this is a good solution when the number of packages in the local folder is small; if you had hundreds of packages, it would be much more complex to handle. In order to test the NuGet package generated previously, create a new folder on disk called LocalPackages, then copy the latest version of the RSSFeedLibrary.1.0.xxx.yyy.nupkg file into this folder. The next step is to enable Visual Studio 2015 to pick packages from the specified local folder, as shown in Figure 10; select Tools | Options | NuGet Package Manager | Package Sources and, in the Available package sources box, click the Add button (the addition symbol in green). At this point, in the Name and Source text boxes type Local packages and C:\LocalPackages, respectively. Finally, click Update to refresh the list of package sources.
Figure 10 Adding a Local Repository as a NuGet Package Source
Now that you have your local NuGet repository, in Visual Studio 2015 create a new console application to test the library plus Roslyn analysis package. Save the project, then select Project | Manage NuGet Packages. When the NuGet Package Manager window appears, in the Package source combo box select the Local packages source. At this point, the package manager will show a list of packages available inside the specified repository, in this case only the sample package.
Click Install. As with NuGet packages online, Visual Studio will show summary information for the selected package and will ask you to accept the license agreement. When the installation completes, you’ll be able to see both the library and the Roslyn analyzer in Solution Explorer, as shown in Figure 11.
Figure 11 Both the Library and the Roslyn Analyzer Have Been Installed Via the NuGet Package
If you simply write some code that uses the FeedItem.ParseFeedAsync method to pass an invalid URL as the argument, the live analysis engine will report a warning message, as expected (see Figure 8 for reference).
When you install an analyzer, no matter if it was produced by you or by other developers, you can look at details of each rule in Solution Explorer by expanding References, Analyzers, and then the name of the analyzer. In this case, you can expand the name of FeedLibraryAnalyzer and see the RSS URL analysis rule, as shown in Figure 11. When you click a rule, the Properties window shows detailed information such as the default and effective severity, if it’s enabled by default, and the full rule description. Additionally, you can use the Ruleset Editor to view all the rules applicable to a project and to view or change a rule’s severity, as well as disabling or enabling analyzers and rules. To open the Ruleset Editor, in Solution Explorer double-click Properties, then in the project’s Properties window select the Code Analysis tab, finally click Open, leaving unchanged the default rule set.
As you can see from Figure 11, disabling/enabling a rule can be done by deselecting/selecting the checkbox near the rule code; you can change the default severity by clicking the black down arrow on the right side of the current severity level (this can also be done by right-clicking the rule in Solution Explorer and then selecting Set Rule Set Severity from the context menu). When you’re satisfied with your local tests, you can move on to publishing the NuGet package online.
Testing a Package Online
On NuGet, developers expect to find high-quality, professional packages. For this reason, before you publish a package to the online NuGet gallery, you should test your work with the help of an online service that allows creating private NuGet repositories and feeds, and graduate to the official NuGet repository once your package is stable. A good choice is offered by MyGet (myget.org), an online service that allows creating personal and enterprise NuGet feeds, plus VSIX, npm and Bower feeds. MyGet offers a free plan with the most common features required to publish and consume NuGet packages; the free plan does not allow you to create private feeds (you need a paid plan for that), but it is a great choice to test if your packages work as expected from an online repository. When you register, you have an option to create a NuGet feed. For instance, my public feed on MyGet is available at myget.org/F/aledelsole/api/v2. Explaining how to work with MyGet is out of the scope of this article, but the documentation fully describes how to configure your NuGet feed. Once you’ve created a feed and published your package, you simply enable Visual Studio 2015 to pick NuGet packages from the MyGet feed. To accomplish this, you can follow the steps described in the previous section and take Figure 10 as a reference, supplying your MyGet feed’s URL. To download and test a package in Visual Studio, you will still follow the steps described in the previous section, obviously selecting the MyGet feed as the source in the NuGet Package Manager window.
Publishing a Package to the Online NuGet Gallery
In order to publish packages to the online NuGet repository, you need to open nuget.org and sign in with an account. If you don’t have an account yet, click the Register/Sign In hyperlink at the upper-right corner of the page. You can sign in either with a Microsoft Account (recommended) or with username/password credentials. Once you’ve registered, click Upload Package. The first thing you’ll be asked is to specify the NuGet package you want to upload, so click Browse, select the latest version of the RSSFeedLibrary.1.0.xxx.yyy.nupkg from disk, and then click Upload.
Now you’ll be prompted with the metadata information for the package. Here you have an option to review the package details before publishing it to the gallery, as shown in Figure 12. When ready, click Submit. At this point, the package containing both your APIs and the integrated Roslyn analyzer will be published to the NuGet gallery. Notice that it usually takes 15 to 20 minutes before you can see the package available in the NuGet Package Manager window in Visual Studio 2015.
Figure 12 Reviewing Package Details Before Publication
After the package is listed in the gallery, you’ll be able to install it into your project from the online NuGet repository, as shown in Figure 13. Once installed, you’ll use the library as explained in the previous section, with the integrated live, domain-specific code analysis powered by the .NET Compiler Platform.
Figure 13 The NuGet Package Is Available to the Public from the Online Repository
Package Updates
As for any NuGet packages, you can improve your libraries and push an updated package version to NuGet. To create an updated package, simply rebuild your solution and Visual Studio 2015 will automatically update the package version number. Then rebuild the project and repeat the steps previously described to publish the NuGet package online. NuGet will handle a list of available versions for each package and will allow developers to choose the version they need.
Wrapping Up
One of the biggest benefits offered by the .NET Compiler Platform is that you can create domain-specific code analysis rules for your APIs. In this article, I first showed how to create a sample library, then how to create a Roslyn analyzer that detects code issues while typing, specific for the library’s members. Next, I showed how to bundle both the library and the analyzer into one NuGet package, which is the core of the article. In this way, developers that download the NuGet package will get the APIs with integrated Roslyn live analysis. I then moved on to discuss how to test the NuGet package locally, before publishing online. This step is very important because you have an opportunity to verify that the library/analyzer pair works properly before you make it available to the public. But the fun is when other developers can use the result of your work, so in the last section of the article I showed how to publish the package to the online NuGet repository, and how it can be later installed into your projects from Visual Studio. Deploying Roslyn analyzers alongside your libraries definitely increases the value of your work, providing a better coding experience to other developers.
Alessandro Del Sole has been a Microsoft MVP since 2008. Awarded MVP of the Year five times, he has authored many books, eBooks, instructional videos, and articles about .NET development with Visual Studio. You can follow him on Twitter @progalex.
Thanks to the following technical experts for reviewing this article: Srivatsn Narayanan and Manish Vasani
Srivatsn Narayanan has worked in languages (IronPython, C#, VB.NET) in Microsoft for the last eight years. He has worked on the compilers and language services efforts for these languages and most recently has been leading the effort to develop the Roslyn analyzer\fixer framework.
Manish Vasani is a software engineer with eight years experience working with different teams in the Visual Studio group at Microsoft. He is currently on the Managed Languages Analysis Services team, where he owns the Roslyn analyzer APIs, analyzer execution infrastructure and its integration with Visual Studio.