Oktober 2015

Band 30, Nummer 10

Codeanalyse – Erstellen und Bereitstellen von Bibliotheken mithilfe der integrierten Roslyn-Codeanalyse in NuGet

Von Alessandro Del Del | Oktober 2015 | Code herunterladen: C#VB

Die Microsoft .NET-Compilerplattform (auch als Codebasis "Roslyn" bezeichnet) bietet Open-Source-Compiler für C# und Visual Basic, die u. a. umfangreiche Codeanalyse-APIs zur Verfügung stellen, die in den Visual Studio 2015-Code-Editor integriert sind. Mithilfe der .NET-Compilerplattform können Sie benutzerdefinierte, domänenspezifische Codeanalyzer und Umgestaltungen schreiben, sodass Visual Studio Codefehler bei der Eingabe erkennen und Warnungen und Fehlermeldungen ausgeben kann. Ein großer Vorteil der .NET-Compilerplattform ist, dass Sie Codeanalyzer mit Ihren APIs bündeln können. Wenn Sie beispielsweise Bibliotheken oder wiederverwendbare Benutzersteuerelemente entwickeln, können Sie zusammen mit Ihren Bibliotheken Analyzer ausliefern und Entwicklern eine bessere Programmiererfahrung bieten.

In diesem Artikel erläutere ich, wie Bibliotheken und Analyzer für die Onlinebereitstellung in NuGet-Paketen gebündelt werden, und zeige, wie Sie für Ihre APIs die integrierte Roslyn-Codeanalyse bereitstellen können. Hierfür ist zumindest Grundlagenwissen zu den Konzepten der .NET-Compilerplattform und zum Schreiben eines Codeanalyzers erforderlich. Diese Themen wurden von Alex Turner in bisherigen MSDN Magazine-Artikeln behandelt: "C# und Visual Basic: Verwenden von Roslyn zum Erstellen eines Live-Codeanalysemoduls für Ihre API” (msdn.microsoft.com/magazine/dn879356) und "C# –Hinzufügen einer Codefehlerbehebung zu Ihrem Roslyn-Analysemodul" (msdn.microsoft.com/magazine/dn904670), die Sie unbedingt lesen sollten, bevor Sie hier fortfahren.

Vorbereiten einer Beispielbibliothek zum Simulieren benutzerdefinierter APIs

Als Erstes benötigen Sie eine Klassenbibliothek, die eine benutzerdefinierte API simuliert. Die Beispielbibliothek für diesen Artikel macht eine einfache öffentliche Methode verfügbar, die allgemeine Informationen aus einem RSS-Feed abruft und eine Auflistung von Feedelementen zurückgibt. Erstellen Sie in Visual Studio 2015 in C# oder Visual Basic eine neue portierbare Klassenbibliothek mit dem Namen "FeedLibrary", und stellen Sie als Zielplattform mindestens Windows 8.1, Windows Phone 8.1 und Microsoft .NET Framework 4.5.1 sicher. Bei diesen Zielplattformen kann die Bibliothek auch das "Async/Await"-Muster ohne weitere Vorbedingungen nutzen.

Benennen Sie die generierte Datei "Class1.vb" oder "Class1.cs" in "FeedItem.vb/.cs" um. Den C#-Code für diese Klasse sehen Sie in Abbildung 1, den Visual Basic-Code in Abbildung 2.

Abbildung 1: Implementieren einer Klasse zum Abrufen allgemeiner Elemente aus einem 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("http://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;
      }
    }
  }
}

Abbildung 2: Implementieren einer Klasse zum Abrufen allgemeiner Elemente aus einem RSS-Feed in Visual Basic

Imports System.Net.Http
Imports <xmlns:dc="http://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

Der Code ist sehr einfach: Er lädt die Fremdinhalte aus der angegebenen URL des RSS-Feeds herunter, erstellt pro Feedelement eine Instanz der "Feed­Item"-Klasse und gibt schließlich eine neue Auflistung von Elementen zurück. Um die Bibliothek zu nutzen, rufen Sie einfach die statische "ParseFeedAsyncAsync"-Methode für C# wie folgt auf:

// Replace the argument with a valid URL
var items = await FeedItem.ParseFeedAsyncAsync("http://sampleurl.com/rss");

Für Visual Basic sieht sie so aus:

'Replace the argument with a valid URL
Dim items = Await FeedItem.ParseFeedAsyncAsync("http://sampleurl.com/rss")

Dieser Aufruf gibt ein "IEnumerable<FeedItem>" zurück, das Sie anschließend entsprechend Ihren Anforderungen nutzen können. Wählen Sie die Releasekonfiguration aus, und erstellen Sie das Projekt. An dieser Stelle generiert Visual Studio 2015 die Bibliothek "FeedLibrary.dll", die später verwendet wird.

Schreiben eines Roslyn-Analyzers

Im nächsten Schritt wird ein Roslyn-Analyzer erstellt, der domänenspezifische Live-Analyzeregeln für die benutzerdefinierten APIs bietet. Der Analyzer erkennt mithilfe der "Uri.IsWellFormedUriString"-Methode, ob die als Argument der "ParseFeedAsyncAsync"-Methode angegebene URL wohlgeformt ist. Falls nicht, gibt der Analyzer während Ihrer Eingabe eine Warnung aus. Es gibt freilich zahlreiche Möglichkeiten zum Erkennen, ob eine URL ungültig ist, doch aus Gründen der Einfachheit nutze ich diese. Darüber hinaus bietet der Analyzer Live-Analysewarnungen, jedoch keine Codekorrekturen, die Sie jedoch zur Übung vornehmen können. Führen Sie nun die folgenden Schritte aus:

  1. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den Namen der Projektmappe, und wählen Sie dann "Hinzufügen | Neues Projekt" aus.
  2. Wählen Sie in der Projektvorlagenliste im Knoten "Erweiterbarkeit" den Eintrag "Analyzer with Code Fix (NuGet + VSIX)" aus. Der Knoten "Erweiterbarkeit" wird nicht standardmäßig angezeigt. Sie müssen zunächst das SDK für die .NET-Compilerplattform herunterladen, um die Projektvorlagen vom Typ "Analyzer with Code Fix" abzurufen. Durchsuchen Sie im Dialogfeld "Neues Projekt" die Analyzer, bis Sie die Projektvorlage zum Herunterladen dieses SDK finden.
  3. Nennen Sie den neuen Analyzer "FeedLibraryAnalyzer", und klicken Sie auf "OK".
  4. Sobald das neue Projekt bereit ist, entfernen Sie die Datei "CodeFix­Provider.cs (oder .vb)".

Die erste Aufgabe in der Datei "DiagnosticAnalyzer.cs (oder .vb)" ist das Bereitstellen von Zeichenfolgen, die den Analyzer in der Programmierumgebung identifizieren. Um die Implementierung des Analyzers einfacher zu halten, verwende ich im aktuellen Beispiel herkömmliche Zeichenfolgen anstelle des "LocalizableString"-Objekts und von Ressourcendateien unter der Annahme, dass das Analysemodul nicht lokalisiert wird. Schreiben Sie "DiagnosticId", "Title", "Message", "Description" und "Category" für C# wie folgt neu:

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";

Und für Visual Basic wie folgt:

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"

Ändern Sie nicht den Schweregrad der Diagnose, der standardmäßig "Warnung" ist, was eine geeignete Wahl für das aktuelle Beispiel ist. Nun ist es Zeit, sich auf die Analyselogik zu konzentrieren. Der Analyzer muss prüfen, ob der Code die "ParseFeedAsync"-Methode aufruft. Falls ja, prüft der Analyzer, ob die angegebene URL wohlgeformt ist. Mit Syntax Visualizer können Sie in Abbildung 3 erkennen, wie ein Aufruf der "ParseFeedAsync"-Methode durch einen "Invocation­Expression"-Ausdruck dargestellt wird, der dem Objekttyp "InvocationExpressionSyntax" zugeordnet ist.

Syntax Visualizer hilft beim Auffinden der ordnungsgemäßen Syntaxknotendarstellung
Abbildung 3: Syntax Visualizer hilft beim Auffinden der ordnungsgemäßen Syntaxknotendarstellung

Der Analyzer konzentriert sich also ausschließlich auf den Objekttyp "InvocationExpressionSyntax". Sobald einer gefunden wird, konvertiert es den dazugehörigen Ausdruck in ein Objekt vom Typ "MemberAccessExpressionSyntax", das Informationen zu einem Methodenaufruf enthält. Wenn die Konvertierung Erfolg hat, prüft der Analyzer, ob die Methode "ParseFeedAsync" ist. Anschließend ruft er das erste Argument ab und wendet eine Live-Analyse auf dessen Wert an. Dazu dient die neue Methode "AnalyzeMethod", die auf "SyntaxNode"-Ebene zum Einsatz kommt und in Abbildung 4 für C# und Abbildung 5 für Visual Basic dargestellt ist.

Abbildung 4: Erkennen von Problemen beim "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);
  }
}

Abbildung 5: Erkennen von Problemen beim "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

An dieser Stelle müssen Sie die "Initialize"-Methode so bearbeiten, dass die neu hinzugefügte "AnalyzeMethodInvocation"-Methode aufgerufen wird (siehe Abbildung 6 für C# und Abbildung 7 für Visual Basic).

Abbildung 6: Bearbeiten der "Initialize"-Methode 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);
  });
}

Abbildung 7: Bearbeiten der "Initialize"-Methode 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

Beachten Sie, wie der Code zunächst prüft, ob ein Verweis auf die Bibliothek im Projekt vorhanden ist, indem die "Compilation.GetTypeMetadataName"-Methode aufgerufen wird. Deren Argument ist der Name des Typs, der im aktuellen Kontext vorhanden sein muss, um sicherzustellen, dass ein Verweis hinzugefügt wurde. Wenn dieser Aufruf NULL zurückgibt, bedeutet dies, dass der Typ nicht vorhanden ist und demzufolge der Bibliothek kein Verweis hinzugefügt wurde. Deshalb muss eine Codeanalyseaktion registriert werden, wodurch sich die Leistung des Analyzers verbessert. Wenn Sie nun F5 drücken, um den Analyzer in der experimentellen Instanz von Visual Studio 2015 zu testen und ein neues Projekt mit einem Verweis auf die "FeedLibrary"-Bibliothek zu erstellen, können Sie erkennen, dass immer dann ordnungsgemäß eine Warnung erfolgt, wenn Sie eine ungültige URL angeben (siehe Abbildung 8).

Der Analyzer gibt eine Warnung aus, wenn die URL nicht wohlgeformt ist
Abbildung 8: Der Analyzer gibt eine Warnung aus, wenn die URL nicht wohlgeformt ist

Bislang haben Sie APIs und dazugehörige domänenspezifische Codeanalyseregeln erstellt. Nun wollen wir uns anschauen, wie beide zu einem einzelnen NuGet-Paket gebündelt werden.

Erstellen eines NuGet-Pakets mit APIs und Analyzern

Die "MSBuild"-Regeln für die Projektvorlage "Analyzer with Code Fix" automatisieren die Erstellung eines NuGet-Pakets, das den kompilierten Analyzer enthält und das Sie mit anderen Entwicklern gemeinsam nutzen können, indem Sie es in einem NuGet-Repository veröffentlichen. In der Praxis erstellt Visual Studio 2015, immer wenn Sie F5 drücken oder das Analyzerprojekt erstellen, die DLL-Datei des Analyzers ("Feed­LibraryAnalyzer.dll" im aktuellen Beispiel) und ein verteilbares NuGet-Paket neu, das den Analyzer enthält.

Beim Buildprozess wird auch ein VSIX-Paket generiert, das Sie in der Visual Studio Gallery veröffentlichen können. Es dient auch zum Debuggen des Analyzers in der experimentellen Instanz von Visual Studio 2015, was aber in diesem Artikel nicht weiter behandelt wird.

Wenn Sie eine Bibliothek mit integrierter Roslyn-Analyse freigeben möchten, müssen Sie die Bibliothek dem NuGet-Paket hinzufügen, das Visual Studio 2015 beim Erstellen des Projektbuilds generiert. Doch zuvor möchte ich Ihnen etwas ausführlicher erläutern, wie ein NuGet-Paket für einen Analyzer erstellt wird. Ein NuGet-Paket ist eigentlich ein ZIP-Archiv mit der Erweiterung NUPKG. Deshalb können Sie Inhalt und Struktur eines NuGet-Pakets mit einem Tool zur Erstellung von ZIP-Archiven, wie z. B. den Windows Explorer-Tools für komprimierte Ordner, WinZip oder WinRar einfach untersuchen. Es folgt eine Übersicht über die wichtigsten Elemente in einem NuGet-Paket zum Bereitstellen von Analyzern:

  • .nuspec-Datei: Diese Datei enthält Paketmetadaten und für die Veröffentlichung benötigte Informationen zum Paket, beispielsweise u. a. Name, Version, Beschreibung, Autor und Lizenz-URL. Die ".nuspec"-Datei wird im NuGet-Paket basierend auf der Datei "Diagnostic.nuspec" gebündelt, die im Projektmappen-Explorer im Projekt des Analyzers angezeigt wird. "Diagnostic.nuspec" werden Sie in Kürze in Visual Studio 2015 bearbeiten.
  • Ordner "tools": Dieser Ordner enthält Windows PowerShell-Skripts, die von Visual Studio zum Installieren (Install.ps1) und Deinstallieren (Uninstall.ps1) eines Analyzers für ein bestimmtes Projekt verwendet werden.
  • Ordner "analyzers": Dieser Ordner enthält DLL-Dateien für den Analyzer, die in besonderen Unterordnern organisiert sind. Im Unterordner "dotnet" sind sprachunabhängige Analyzerbibliotheken enthalten. Analyzer für C# befinden sich im Unterordner "dotnet\cs", während Analyzer für Visual Basic im Unterordner "dotnet\vb" enthalten sind. Wichtig ist der Hinweis, dass "dotnet" das NuGet-Profil für .NET Core darstellt und Projekttypen wie universelle Windows-Apps und ASP.NET 5-Projekte unterstützt.

In einem NuGet-Paket können verschiedene weitere Elemente gebündelt werden. Ich konzentriere mich hier allerdings auf den Roslyn-Analyzer, weshalb nur die benötigten Elemente erläutert werden.

Bibliotheken, auf die aus einem Visual Studio-Projekt verwiesen werden kann, müssen im Ordner "lib" organisiert werden. Da Bibliotheken für verschiedene Plattformen vorgesehen sein können, z. B. für unterschiedliche Versionen von .NET Framework, der Windows-Runtime, von Windows Phone oder sogar eine portierbare Teilmenge (einschließlich Xamarin-Bibliotheken), muss der Ordner "lib" pro Zielplattform einen Unterordner enthalten, und jeder Unterordner muss eine Kopie der bereitzustellenden Bibliothek enthalten. Der Name des jeweiligen Unterordners muss dem Namen eines Profils entsprechen, das eine bestimmte Plattform darstellt. Wenn Sie beispielsweise über eine Bibliothek für .NET Framework 4.5.1 und Windows 8.1 verfügen, liegt die folgenden Struktur vor. Dabei ist "net451" der Profilname für .NET Framework 4.5.1 und "netcore451" der Profilname für die Windows-Runtime in Windows 8.1:

lib\net451\mylib.dll
lib\netcore451\mylib.dll

Erwähnenswert ist das Profil "uap10.0" für die universelle Windows-Plattform (UWP) zum Entwickeln von Windows 10-Apps. Die vollständige Liste unterstützter Profile steht in der NuGet-Dokumentation zur Verfügung. Die zuvor erstellte Beispielbibliothek ist für.NET Framework 4.5.1, Windows 8.1 und Windows Phone 8.1 vorgesehen. Der Profilname für diese Art von Ziel ist "portable-net451+netcore451+wpa81" und muss zum Benennen des Unterordners verwendet werden, der die Bibliothek im NuGet-Paket enthält. Es ist nicht erforderlich, den Unterordner manuell zu erstellen und die Bibliothek zu kopieren. Sie müssen lediglich in Visual Studio die NuGet-Paketmetadaten (die Datei "Diagnostic.nuspec") bearbeiten. Abbildung 9 zeigt aktualisierte Metadaten mit ordnungsgemäßen Informationen für die Veröffentlichung ("id", "title", "author", "description", "license" usw.) und ein neues Dateielement im Knoten "files", das die Quelldatei und die Zielunterordner angibt.

Abbildung 9: Bearbeiten der Metadaten des NuGet-Pakets

<?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>http://opensource.org/licenses/MIT</licenseUrl>
    <!-- Removing these lines as they are not needed
    <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>
    <iconUrl>http://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>

Das "src"-Attribut gibt den Quellspeicherort der Bibliothek und "target" den Zielunterordner basierend auf dem ordnungsgemäßen Profilnamen an. In diesem Fall sucht Visual Studio im Ordner "lib" nach der Bibliothek "FeedLibrary.dll", die Sie im aktuellen Verzeichnis erstellen müssen. Dieser Ordner enthält das kompilierte Analysemodul und abhängig von der gewählten Buildkonfiguration den Ordner "Release" oder "Debug". Basierend auf dem aktuellen Beispiel müssen Sie den Ordner "lib" im Ordner "Release" erstellen und dann die Datei "FeedLibrary.dll" (die beim Kompilieren der Beispielbibliothek am Anfang erstellt wird) in den Ordner "lib" kopieren. Anschließend können Sie einfach Ihre Projektmappe neu erstellen, woraufhin Visual Studio ein aktualisiertes NuGet-Paket mit der Bibliothek und dem Roslyn-Analyzer generiert.

Wichtig ist der Hinweis, dass Visual Studio 2015 beim Neuerstellen des Projekts die Versionsnummern von Build und Revision des NuGet-Pakets automatisch aktualisiert, und zwar ungeachtet der Nummern, die im Tag "version" angegeben sind. Der aktualisierte Inhalt des NuGet-Pakets kann einfach untersucht werden, indem das aktualisierte NuGet-Paket mit der höchsten Version in einem ZIP-Archivtool geöffnet wird. Denken Sie daran: Bei jeder Änderung des Werts des Elements "id" (siehe Abbildung 9) generiert Visual Studio 2015 basierend auf der neuen ID ein NuGet-Paket mit einem anderen Namen. In diesem Fall führt das Ändern des Werts von "id" mit "RSSFeedLibrary" zu einem NuGet-Paket mit dem Namen "RSSFeedLibrary.1.0.xxx.yyy.NuPkg". Dabei ist "xxx" die Buildversionsnummer und "yyy" die Revisionsversionsnummer, die beide automatisch zur Buildzeit bereitgestellt werden. An dieser Stelle haben Sie das erste Ziel erreicht, denn Sie haben benutzerdefinierte APIs mit integrierter Roslyn-Analyse in einem NuGet-Paket gebündelt.

Alternativ können Sie zwei getrennte Pakete erstellen (und veröffentlichen), nämlich eines für den Analyzer und eines für die Bibliothek. Dann können Sie ein drittes leeres NuGet-Paket erstellen, das die beiden zusammenführt, indem sie als Abhängigkeiten aufgelöst werden. Bei diesem Ansatz können Sie die Installation kleiner halten, indem Sie nur die APIs ohne den Analyzer verwenden. Allerdings müssen Sie mit den NuGet-Konventionen vertraut sein, um ein Paket von Grund auf manuell zu erstellen. Vor der Veröffentlichung des neu generierten Pakets im NuGet-Onlinerepository sollten Sie lokal testen.

Lokales Testen eines NuGet-Pakets

Visual Studio 2015 ermöglicht das Auswählen von NuGet-Paketen in lokalen Repositorys. Dies ist sehr nützlich, insbesondere in Situationen, in denen Sie Pakete im Cache speichern, die Sie häufig verwenden oder ggf. benötigen, wenn Sie offline arbeiten. Dies ist eine gute Lösung, wenn die Anzahl der Pakete im lokalen Ordner klein ist. Wenn Sie jedoch Hunderte von Paketen haben, ist die Verarbeitung zu komplex. Um das zuvor generierte NuGet-Paket zu testen, erstellen Sie auf dem Datenträger den neuen Ordner "LocalPackages", und kopieren Sie anschließend die neueste Version der Datei "RSSFeed­Library.1.0.xxx.yyy.nupkg" in diesen Ordner. Der nächste Schritt besteht darin, Visual Studio 2015 das Auswählen von Paketen im angegebenen lokalen Ordner zu ermöglichen (siehe Abbildung 10). Wählen Sie "Tools | Optionen | NuGet-Paket-Manager | Paketquellen" aus, und klicken Sie im Feld "Verfügbare Paketquellen" auf die Schaltfläche "Hinzufügen" (das grüne Plussymbol). Geben Sie an dieser Stelle in die Textfelder "Name" und "Quelle" zunächst "Lokale Pakete" und dann "C:\LokalePakete" ein. Klicken Sie schließlich auf "Aktualisieren", um die Liste der Paketquellen zu aktualisieren.

Hinzufügen eines lokalen Repositorys als NuGet-Paketquelle
Abbildung 10: Hinzufügen eines lokalen Repositorys als NuGet-Paketquelle

Nachdem Sie nun über ein lokales NuGet-Repository verfügen erstellen Sie in Visual Studio 2015 eine neue Konsolenanwendung zum Testen der Bibliothek sowie des Roslyn-Analyzers. Speichern Sie das Projekt, und wählen Sie dann "Projekt | NuGet-Pakete verwalten" aus. Wenn das Fenster "NuGet-Paket-Manager" angezeigt wird, wählen Sie im Kombinationsfeld "Paketquelle" die Quelle "Lokale Paketquelle" aus. An dieser Stelle zeigt der Paket-Manager eine Liste der im angegebenen Repository verfügbaren Pakete an, in diesem Fall nur das Beispielpaket.

Klicken Sie auf "Installieren". Wie bei NuGet-Onlinepaketen zeigt Visual Studio zusammenfassende Informationen für das ausgewählte Paket an und fordert Sie auf, die Lizenzvereinbarung zu akzeptieren. Nach Abschluss der Installation werden sowohl die Bibliothek als auch der Roslyn-Analyzer im Projektmappen-Explorer angezeigt (siehe Abbildung 11).

Sowohl die Bibliothek als auch der Roslyn-Analyzer wurden mithilfe des NuGet-Pakets installiert
Abbildung 11: Sowohl die Bibliothek als auch der Roslyn-Analyzer wurden mithilfe des NuGet-Pakets installiert

Wenn Sie nun Code schreiben, der die "FeedItem.ParseFeed­Async"-Methode zum Übergeben einer ungültigen URL als Argument nutzt, gibt das Live-Analysemodul wie erwartet eine Warnmeldung aus (siehe Abbildung 8 als Referenz).

Wenn Sie einen Analyzer installieren, den entweder Sie oder andere Entwickler erstellt haben, können Sie sich im Projektmappen-Explorer die Details aller Regeln ansehen, indem Sie "Verweise", "Analyzer" und dann den Namen des Analyzers erweitern. In diesem Fall können Sie den Namen von "FeedLibraryAnalyzer" erweitern und die RSS-URL-Analyseregel anzeigen (siehe Abbildung 11). Wenn Sie auf eine Regel klicken, werden im Fenster "Eigenschaften" detaillierte Informationen angezeigt, z. B. der Standard- und geltende Schweregrad (sofern standardmäßig aktiviert) und die vollständige Regelbeschreibung. Darüber hinaus können Sie den Regelsatz-Editor zum Anzeigen aller für ein Projekt geltenden Regeln, zum Anzeigen oder Ändern des Schweregrads einer Regel sowie zum Aktivieren und Deaktivieren von Analysemodulen und Regeln verwenden. Zum Öffnen des Regelsatz-Editors doppelklicken Sie im Projektmappen-Explorer auf "Eigenschaften". Wählen Sie dann im Fenster "Eigenschaften" des Projekts die Registerkarte "Codeanalyse" aus. Klicken Sie anschließend auf "Öffnen", und lassen Sie den Standardregelsatz unverändert.

Wie Sie in Abbildung 11 erkennen können, erfolgt das Deaktivieren/Aktivieren einer Regel über das Deaktivieren/Aktivieren des Kontrollkästchens neben dem Regelcode. Sie können den Standardschweregrad durch Klicken auf den schwarzen Pfeil nach unten rechts neben dem aktuellen Schweregrad ändern (dies kann auch durch Klicken mit der rechten Maustaste auf die Regel im Projektmappen-Explorer und anschließendes Auswählen von "Regelsatz-Schweregrad einstellen" im Kontextmenü erfolgen). Wenn Sie mit Ihren lokalen Tests zufrieden sind, können Sie das NuGet-Paket online veröffentlichen.

Ein Paket online testen

In NuGet erwarten Entwickler professionelle Pakete hoher Qualität. Deshalb müssen Sie vor dem Veröffentlichen eines Pakets im NuGet-Onlinekatalog Ihre Lösung mithilfe eines Onlinediensts testen, der das Erstellen privater NuGet-Repositorys und -Feeds ermöglicht, und zum offiziellen NuGet-Repository wechseln, sobald Ihr Paket stabil ist. Hierfür eignet sich besonders MyGet (myget.org), ein Onlinedienst, der das Erstellen persönlicher und geschäftlicher NuGet-Feeds sowie von VSIX-, NPM- und Bower-Feeds erlaubt. MyGet bietet für die meisten Features, die zum Veröffentlichen und Nutzen von NuGet-Paketen erforderlich sind, einen kostenlosen Tarif. Dieser Tarif lässt nicht das Erstellen privater Feeds zu (wozu Sie einen kostenpflichtigen Tarif wählen müssen), eignet sich aber besonders gut für Tests, wenn Ihre Pakete mit einem Onlinerepository wie erwartet funktionieren. Wenn Sie sich registrieren, haben Sie die Option, einen NuGet-Feed zu erstellen. Mein öffentlicher Feed auf MyGet ist beispielsweise unter myget.org/F/­aledelsole/api/v2 verfügbar. Eine Erläuterung der Funktionsweise von MyGet würde den Rahmen dieses Artikels sprengen, doch in der Dokumentation ist die Konfiguration eines NuGet-Feeds vollständig beschrieben. Nachdem Sie einen Feed erstellt und Ihr Paket veröffentlicht haben, müssen Sie Visual Studio 2015 ermöglichen, NuGet-Pakete aus dem MyGet-Feed auszuwählen. Hierfür können Sie die im vorhergehenden Abschnitt beschriebenen Schritte ausführen und Abbildung 10 als Referenz zum Angeben der URL Ihres MyGet-Feeds nutzen. Zum Herunterladen und Testen eines Pakets in Visual Studio müssen Sie weiterhin die Schritte im vorherigen Abschnitt ausführen, dabei allerdings den MyGet-Feed als Quelle im Fenster "NuGet-Paket-Manager" auswählen.

Veröffentlichen eines Pakets im NuGet-Onlinekatalog

Um Pakete im NuGet-Onlinerepository zu veröffentlichen, müssen Sie zu nuget.org wechseln und sich mit einem Konto anmelden. Wenn Sie noch kein Konto haben, klicken Sie rechts oben auf der Seite auf den Link "Register/Sign In". Sie können sich entweder mit einem Microsoft-Konto (empfohlen) oder mit aus Benutzername und Kennwort bestehenden Anmeldeinformationen registrieren. Nachdem Sie sich registriert haben, klicken Sie auf "Upload Package". Als Erstes werden Sie aufgefordert, das NuGet-Paket anzugeben, das Sie hochladen möchten. Klicken Sie hierfür auf "Browse", wählen Sie auf dem Datenträger die neueste Version von "RSSFeed­Library.1.0.xxx.yyy.nupkg" aus, und klicken Sie auf "Upload".

Nun werden Sie aufgefordert, die Metadateninformationen zum Paket anzugeben. Hier haben Sie die Möglichkeit, die Details des Pakets zu überprüfen, ehe Sie es in den Katalog hochladen (siehe Abbildung 12). Wenn Sie bereit sind, klicken Sie auf "Submit". An dieser Stelle werden das Paket mit Ihren APIs und dem integrierten Roslyn-Analyzer im NuGet-Katalog veröffentlicht. Es dauert meist 15-20 Minuten, bis das Paket im Fenster "NuGet-Paket-Manager" in Visual Studio 2015 verfügbar ist.

Überprüfen der Paketdetails vor der Veröffentlichung
Abbildung 12: Überprüfen der Paketdetails vor der Veröffentlichung

Sobald das Paket im Katalog aufgeführt wird, können Sie es aus dem NuGet-Onlinerepository in Ihrem Projekt installieren (siehe Abbildung 13). Nach der Installation verwenden Sie die Bibliothek gemäß den Angaben im vorherigen Abschnitt mit der integrierten domänenspezifischen Live-Codeanalyse auf Basis der .NET-Compilerplattform.

Das NuGet-Paket steht im Onlinerepository öffentlich zur Verfügung
Abbildung 13: Das NuGet-Paket steht im Onlinerepository öffentlich zur Verfügung

Paketaktualisierungen

Wie bei allen NuGet-Paketen können Sie Ihre Bibliotheken verbessern, indem Sie eine aktualisierte Paketversion in NuGet hochladen. Zum Erstellen eines aktualisierten Pakets müssen Sie lediglich Ihre Projektmappe neu erstellen, woraufhin Visual Studio 2015 die Paketversionsnummer automatisch aktualisiert. Erstellen Sie anschließend das Projekt neu, und wiederholen Sie die zuvor beschriebenen Schritte, um das NuGet-Paket online zu veröffentlichen. NuGet verwaltet eine Liste verfügbarer Versionen für jedes Paket und erlaubt Entwicklern das Wählen der benötigten Version.

Zusammenfassung

Einer der größten Vorteile der .NET-Compilerplattform ist, dass Sie für Ihre APIs domänenspezifische Codeanalyseregeln erstellen können. In diesem Artikel habe ich zunächst das Erstellen einer einfachen Bibliothek und dann eines Roslyn-Analyzers erklärt, der für die Elemente der Bibliothek spezifische Codeprobleme bei der Eingabe erkennt. Als Nächstes habe ich gezeigt, wie die Bibliothek und der Analyzer in einem NuGet-Paket gebündelt werden, was das Hauptanliegen dieses Artikels ist. Auf diese Weise erhalten Entwickler, die das NuGet-Paket herunterladen, die APIs mit integrierter Roslyn-Live-Analyse. Danach habe ich erörtert, wie das NuGet-Paket lokal getestet werden kann, ehe es online veröffentlicht wird. Dieser Schritt ist sehr wichtig, da Sie die Möglichkeit zum Überprüfen haben, ob das Paar aus Bibliothek und Analyzer ordnungsgemäß funktioniert, ehe Sie es öffentlich verfügbar machen. Doch der Spaß fängt erst an, wenn andere Entwickler die Früchte Ihrer Arbeit nutzen können. Deshalb habe ich im letzten Abschnitt des Artikels gezeigt, wie das Paket im NuGet-Onlinerepository veröffentlicht wird und wie es später in Visual Studio in Ihren Projekten installiert werden kann. Die Bereitstellung von Roslyn-Analyzern zusammen mit Ihren Bibliotheken steigert definitiv den Nutzen Ihrer Lösungen und ermöglicht anderen Entwicklern eine bessere Programmiererfahrung.


Alessandro Del Soleist seit 2008 ein Microsoft MVP. Er wurde bereits fünfmal mit diesem Titel ausgezeichnet und ist Autor zahlreicher Bücher, E-Books, Schulungsvideos und Artikel zur .NET-Entwicklung mit Visual Studio. Sie können ihm auf Twitter @progalex folgen.

Unser Dank gilt den folgenden technischen Experten für die Durchsicht dieses Artikels: Srivatsn Narayanan und Manish Vasani
Srivatsn Narayanan ist bei Microsoft seit acht Jahren im Bereich Programmiersprachen (IronPython, C#, VB.NET) aktiv. Er hat an den Compilern und Sprachdiensten für diese Sprachen mitgewirkt und war vor Kurzem in leitender Funktion an der Entwicklung des Roslyn-Frameworks für die Codeanalyse und -korrektur tätig.

Manish Vasani ist ein Softwareentwickler, der bei Microsoft seit acht Jahren in verschiedenen Teams in der Visual Studio-Gruppe arbeitet. Er gehört derzeit zum Managed Languages Analysis Services-Team, in dem er für die APIs des Roslyn-Analyzers, dessen Ausführungsinfrastruktur und Integration in Visual Studio zuständig ist.