Leitfaden: Ausführen von .NET 5.0-Funktionen in Azure

Dieser Artikel ist eine Einführung in die Verwendung von C#, um isolierte .NET-Prozessfunktionen zu entwickeln, die prozessextern in Azure Functions ausgeführt werden. Durch die prozessexterne Ausführung können Sie den Funktionscode von der Azure Functions-Runtime entkoppeln. Sie ermöglicht zudem die Erstellung und Ausführung von Funktionen, die auf das aktuelle .NET 5.0-Release ausgelegt sind.

Erste Schritte Konzepte Beispiele

Wenn Sie .NET 5.0 nicht unterstützen müssen oder Ihre Funktionen prozessextern ausführen, sollten Sie stattdessen eine C#-Klassenbibliotheksfunktion entwickeln.

Gründe für isolierte .NET-Prozesse

Zuvor hat Azure Functions nur einen stark integrierten Modus für .NET-Funktionen unterstützt, die als Klassenbibliothek im selben Prozess wie der Host ausgeführt werden. Dieser Modus ermöglicht die enge Verzahnung zwischen dem Hostprozess und den Funktionen. Funktionen der .NET-Klassenbibliothek können beispielsweise Bindungs-APIs und Bindungstypen freigeben. Diese Integration erfordert jedoch auch eine engere Kopplung zwischen dem Hostprozess und der .NET-Funktion. Prozessintern ausgeführte .NET-Funktionen müssen beispielsweise die gleiche .NET-Version wie die Functions-Runtime aufweisen. Sie können diese Einschränkungen jetzt umgehen, indem Sie sie in einem isolierten Prozess ausführen. Dank dieser Prozessisolierung können Sie Funktionen für aktuelle .NET-Releases (wie .NET 5.0) entwickeln, die von der Functions-Runtime nicht nativ unterstützt werden.

Da diese Funktionen in einem separaten Prozess ausgeführt werden, bestehen einige Feature- und Funktionsunterschiede zwischen Apps mit isolierten .NET-Funktionen und Apps mit Funktionen der .NET-Klassenbibliothek.

Vorteile der prozessexternen Ausführung

Bei der prozessexternen Ausführung bestehen die folgenden Vorteile für Ihre .NET-Funktionen:

  • Weniger Konflikte: Da die Funktionen in einem separaten Prozess ausgeführt werden, geraten die Assemblys Ihrer App nicht mit unterschiedlichen Versionen der gleichen Assemblys in Konflikt, die vom Hostprozess verwendet werden.
  • Vollständige Kontrolle über den Prozess: Sie steuern den Start der App, die verwendeten Konfigurationen und die Middleware.
  • Dependency Injection: Da Sie die vollständige Kontrolle über den Prozess haben, können Sie aktuelle .NET-Verhaltensweisen für die Dependency Injection und die Einbindung von Middleware in Ihre Funktions-App nutzen.

Supported versions

Versions of the Functions runtime work with specific versions of .NET. To learn more about Functions versions, see Azure Functions runtime versions overview. Version support depends on whether your functions run in-process or out-of-process (isolated).

The following table shows the highest level of .NET Core or .NET Framework that can be used with a specific version of Functions.

Functions runtime version In-process
(.NET class library)
Out-of-process
(.NET Isolated)
Functions 4.x1 .NET 6.0 (preview) .NET 6.0 (preview)
Functions 3.x .NET Core 3.1 .NET 5.0
Functions 2.x .NET Core 2.12 n/a
Functions 1.x .NET Framework 4.8 n/a

1 Azure Functions provides experimental support to let you try out your functions running on the preview release of .NET 6.0. This pre-release version isn't officially supported. To learn more, see the Azure Functions v4 early preview page.
2 For details, see Functions v2.x considerations.

For the latest news about Azure Functions releases, including the removal of specific older minor versions, monitor Azure App Service announcements.

Isoliertes .NET-Projekt

Ein Projekt mit isolierten .NET-Funktionen ist im Grunde ein Projekt für eine .NET-Konsolen-App mit .NET 5.0 als Ziel. Die folgenden Dateien werden in jedem isolierten .NET-Projekt benötigt:

  • host.json
  • local.settings.json
  • C#-Projektdatei (.csproj), definiert das Projekt und die Abhängigkeiten
  • Program.cs, der Einstiegspunkt für die App

Paketverweise

Bei der prozessexternen Ausführung verwendet Ihr .NET-Projekt spezifische Pakete, die die Kernfunktionalitäten und die Bindungserweiterungen implementieren.

Erforderliche Pakete

Die folgenden Pakete werden benötigt, damit Ihre .NET-Funktionen in einem isolierten Prozess ausgeführt werden können:

Erweiterungspakete

Da in einem isolierten .NET-Prozess ausgeführte Funktionen andere Bindungstypen verwenden, benötigen sie spezifische Pakete mit Bindungserweiterungen.

Diese Erweiterungspakete finden Sie unter Microsoft.Azure.Functions.Worker.Extensions.

Start und Konfiguration

Wenn Sie isolierte .NET-Funktionen verwenden, haben Sie Zugriff auf die Startklasse für Ihre Funktions-App, die sich üblicherweise in „Program.cs“ befindet. Sie sind dafür verantwortlich, eine eigene Hostinstanz zu erstellen und zu starten. Sie haben daher auch direkten Zugriff auf die Konfigurationspipeline für Ihre App. Bei der prozessexternen Ausführung sind das Einfügen von Konfigurationen, Dependency Injection und die Ausführung Ihrer eigenen Middleware einfacher.

Im Folgenden sehen Sie ein Beispiel für eine HostBuilder-Pipeline:

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(s =>
    {
        s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
    })
    .Build();

Für diesen Code ist using Microsoft.Extensions.DependencyInjection; erforderlich.

Ein HostBuilder wird zur Erstellung und Rückgabe einer vollständig initialisierten IHost-Instanz verwendet. Diese führen Sie asynchron aus, um Ihre Funktions-App zu starten.

await host.RunAsync();

Konfiguration

Die Methode ConfigureFunctionsWorkerDefaults wird verwendet, um die Einstellungen hinzuzufügen, die für die prozessexterne Durchführung der Funktions-App erforderlich sind. Dies umfasst die folgenden Funktionen:

  • Standardsatz von Konvertern.
  • Legen Sie die Standard-JsonSerializerOptions fest, um die Groß- und Kleinschreibung von Eigenschaftsnamen zu übergehen.
  • Integration in Azure Functions-Protokollierung.
  • Ausgabe verbindlicher Middleware und Funktionen.
  • Middleware für die Funktionsausführung.
  • Standardmäßige gRPC-Unterstützung.
.ConfigureFunctionsWorkerDefaults()

Da Sie Zugriff auf die HostBuilder-Pipeline haben, können Sie auch App-spezifische Konfigurationen während der Initialisierung festlegen. Zum Hinzufügen der Konfigurationen, die für ihre Funktions-APP erforderlich sind, können Sie die MethodeConfigureAppConfiguration mindestens einmal für HostBuilder aufrufen. Weitere Informationen zur App-Konfiguration finden Sie unter Konfiguration in ASP.NET Core.

Diese Konfigurationen gelten für die in einem separaten Prozess ausgeführte Funktions-App. Wenn Sie Änderungen am Funktionshost oder -trigger und der Bindungskonfiguration vornehmen möchten, müssen Sie weiterhin die Datei host.json verwenden.

Dependency Injection

Im Vergleich zu .NET-Klassenbibliotheken ist die Dependency Injection vereinfacht. Anstatt eine Startup-Klasse zur Registrierung von Diensten erstellen zu müssen, können Sie ConfigureServices im HostBuilder aufrufen und die Erweiterungsverfahren in IServiceCollection verwenden, um bestimmte Dienste einzugeben.

Im folgenden Beispiel wird eine Dependency Injection für einen Singletondienst durchgeführt:

.ConfigureServices(s =>
{
    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
})

Für diesen Code ist using Microsoft.Extensions.DependencyInjection; erforderlich. Weitere Informationen finden Sie unter Dependency Injection in ASP.NET Core.

Middleware

Die isolierte .NET-Unterstützung unterstützt auch die Middleware-Registrierung mit einem Modell, das dem in ASP.NET vorhandenen Modell ähnelt. Dieses Modell bietet Ihnen die Möglichkeit, Logik in die Aufrufpipeline einzufügen, und vorher und nachher Funktionen auszuführen.

Die Erweiterungsmethode ConfigureFunctionsWorkerDefaults weist eine Überladung auf, mit der Sie Ihre eigene Middleware registrieren können, wie im folgenden Beispiel zu sehen ist.

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(workerApplication =>
    {
        // Register our custom middleware with the worker
        workerApplication.UseMiddleware<MyCustomMiddleware>();
    })
    .Build();

Ein ausführlicheres Beispiel für die Verwendung von benutzerdefinierter Middleware in ihrer Funktions-APP finden Sie im Beispiel für eine benutzerdefinierte Middleware-Referenz.

Ausführungskontext

Der isolierte .NET-Prozess übergibt ein FunctionContext-Objekt an Ihre Funktionsmethoden. Mit diesem Objekt erhalten Sie eine ILogger-Instanz, um in die Protokolle zu schreiben, indem Sie die GetLogger-Methode aufrufen und eine categoryName Zeichenfolge anbieten. Weitere Informationen finden Sie unter Protokollierung.

Bindungen

Bindungen werden mithilfe von Attributen in Methoden, Parametern und Rückgabetypen definiert. Eine Funktionsmethode ist eine Methode mit einem Function Attribut und einem Trigger-Attribut, die auf einen Ausgabeparameter angewendet werden. Dies wird im folgenden Beispiel veranschaulicht:

[Function("QueueFunction")]
[QueueOutput("myqueue-output")]
public static string Run([QueueTrigger("myqueue-items")] Book myQueueItem,
    FunctionContext context)

Das Trigger-Attribut gibt den Triggertyp an und bindet die Eingabedaten an einen Methodenparameter. Die zuvor gezeigte Beispielfunktion wird durch eine Warteschlangennachricht ausgelöst, und die Warteschlangennachricht wird im Parameter myQueueItem an die Methode übergeben.

Das Attribut Function kennzeichnet die Methode als Funktionseinstiegspunkt. Der Name muss innerhalb eines Projekts eindeutig sein, mit einem Buchstaben beginnen und darf nur Buchstaben, Ziffern, _ und - enthalten. Bis zu 127 Zeichen sind zulässig. Projektvorlagen erstellen oft eine Methode namens Run, aber der Name der Methode kann ein beliebiger gültiger C#-Methodennamen sein.

Da isolierte .NET-Projekte in einem separaten Workerprozess ausgeführt werden, können Bindungen keine umfangreichen Bindungsklassen wie ICollector<T>, IAsyncCollector<T> und CloudBlockBlob verwenden. Außerdem gibt es keine direkte Unterstützung für von zugrunde liegenden Dienst-SDKs abgeleitete Typen wie DocumentClient und BrokeredMessage. Bindungen basieren stattdessen auf Zeichenfolgen, Arrays und serialisierbaren Typen wie Plain Old CLR Objects (POCOs).

Bei HTTP-Triggern müssen Sie HttpRequestData und HttpResponseData verwenden, um auf die Anfrage- und Antwortdaten zugreifen zu können. Das liegt daran, dass Sie bei der prozessexternen Ausführung nicht auf die ursprüngliche HTTP-Anforderung und die Antwortobjekte zugreifen können.

Einen umfassenden Satz von Verweisbeispielen für die Verwendung von Triggern und Bindungen bei prozessexterner Ausführung finden Sie im Referenzbeispiel für Bindungserweiterungen.

Eingabebindungen

Eine Funktion kann über null oder mehr Eingabebindungen verfügen, die Daten an eine andere Funktion übergeben können. Genau wie Trigger werden Eingabebindungen definiert, indem ein Bindungsattribut auf einen Eingabeparameter angewendet wird. Wenn die Funktion ausgeführt wird, versucht die Runtime, die in der Bindung angegebenen Daten abzurufen. Die angeforderten Daten sind häufig von den Informationen abhängig, die vom Triggern mithilfe von Bindungsparametern bereitgestellt werden.

Ausgabebindungen

Wenn Sie eine Ausgabebindung schreiben möchten, müssen Sie ein Ausgabebindungsattribut an die Funktionsmethode anfügen, in der definiert ist, wie in den gebundenen Dienst geschrieben werden soll. Der von der Methode zurückgegebene Wert wird in die Ausgabebindung geschrieben. Im folgenden Beispiel wird ein Zeichenfolgenwert mithilfe einer Ausgabebindung in die Nachrichtenwarteschlange functiontesting2 geschrieben:

[Function("QueueFunction")]
[QueueOutput("myqueue-output")]
public static string Run([QueueTrigger("myqueue-items")] Book myQueueItem,
    FunctionContext context)
{
    var logger = context.GetLogger("QueueFunction");
    logger.LogInformation($"Book name = {myQueueItem.Name}");

    // Queue Output
    return "queue message";
}

Mehrere Ausgabebindungen

Die Daten, die in eine Ausgabebindung geschrieben werden, sind immer der Rückgabewert der Funktion. Wenn Sie in mehr als eine Ausgabebindung schreiben müssen, müssen Sie einen benutzerdefinierten Rückgabetyp erstellen. Bei diesem Rückgabetyp muss das Ausgabebindungsattribut auf eine oder mehrere Eigenschaften der Klasse angewendet werden. Im folgenden Beispiel von einem HTTP-Trigger wird sowohl in eine HTTP-Antwort als auch in eine Ausgabebindung für eine Warteschlange geschrieben:

public static class MultiOutput
{
    [Function("MultiOutput")]
    public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req,
        FunctionContext context)
    {
        var response = req.CreateResponse(HttpStatusCode.OK);
        response.WriteString("Success!");

        string myQueueOutput = "some output";

        return new MyOutputType()
        {
            Name = myQueueOutput,
            HttpResponse = response
        };
    }
}

public class MyOutputType
{
    [QueueOutput("myQueue")]
    public string Name { get; set; }

    public HttpResponseData HttpResponse { get; set; }
}

Die Antwort eines HTTP-Triggers wird immer als Ausgabe angesehen, daher ist ein Ergebnis-Attribut nicht erforderlich.

HTTP-Trigger

HTTP-Trigger übersetzen eingehende HTTP-Anforderungsnachrichten in ein HttpRequestData-Objekt, das an die Funktion übergeben wird. Dieses Objekt liefert die Anforderungsdaten wie Headers, Cookies, Identities, URL und optional Body (Nachrichtentext). Dieses Objekt ist eine Darstellung des HTTP-Anforderungsobjekts, nicht der Anforderung selbst.

Entsprechend gibt die Funktion ein HttpResponseData-Objekt zurück, das die Daten zur Erstellung der HTTP-Antwort enthält, zum Beispiel Nachricht StatusCode, Headers, und optional Nachricht Body.

Der folgende Code stellt einen HTTP-Trigger dar:

[Function("HttpFunction")]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req,
    FunctionContext executionContext)
{
    var logger = executionContext.GetLogger("HttpFunction");
    logger.LogInformation("message logged");

    var response = req.CreateResponse(HttpStatusCode.OK);
    response.Headers.Add("Date", "Mon, 18 Jul 2016 16:06:00 GMT");
    response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
    
    response.WriteString("Welcome to .NET 5!!");

    return response;
}

Protokollierung

Im .NET-isolierten Prozess können Sie in Protokolle schreiben, indem Sie eine ILogger-Instanz verwenden, die von einem FunctionContext-Objekt erhalten wird, das von Ihrer Funktion abgerufen wurde. Rufen Sie die Methode GetLogger auf, und übergeben Sie dabei einen Zeichenfolgenwert, der dem Namen der Kategorie entspricht, in welche die Protokolle geschrieben werden. Die Kategorie ist normalerweise der Name der spezifischen Funktion, aus der die Protokolle geschrieben werden. Weitere Informationen zu Kategorien finden Sie im Artikel zur Überwachung.

Im folgenden Beispiel wird gezeigt, wie ILogger abgerufen wird und Protokolle in einer Funktion geschrieben werden:

var logger = executionContext.GetLogger("HttpFunction");
logger.LogInformation("message logged");

Verwenden Sie verschiedene Methoden von [ILoggerLogError, um verschiedene Protokolliergrade wie ] oder LogWarning zu schreiben. Weitere Informationen zu den Protokolliergraden finden Sie im Artikel zur Überwachung.

ILogger wird auch bereitgestellt, wenn Sie Dependency Injection nutzen.

Unterschiede zu Funktionen der .NET-Klassenbibliothek

In diesem Abschnitt werden die aktuellen Funktions- und Verhaltensunterschiede zwischen prozessextern ausgeführten .NET 5.0-Funktionen und prozessintern ausgeführten Funktionen der .NET-Klassenbibliothek erläutert:

Feature/Verhalten Prozessintern (.NET Core 3.1) Prozessextern (.NET 5.0)
.NET-Versionen LTS (.NET Core 3.1) Aktuell (.NET 5.0)
Erforderliche Pakete Microsoft.NET.Sdk.Functions Microsoft.Azure.Functions.Worker
Microsoft.Azure.Functions.Worker.Sdk
Pakete für Bindungserweiterungen Microsoft.Azure.WebJobs.Extensions.* Unter Microsoft.Azure.Functions.Worker.Extensions.*
Protokollierung An die Funktion übergebene ILogger ILogger aus FunctionContext erhalten
Abbruchtoken Unterstützt Nicht unterstützt
Ausgabebindungen Out-Parameter Rückgabewerte
Ausgabebindungstypen IAsyncCollector, DocumentClient, BrokeredMessage und andere clientspezifische Typen Einfache Typen, serialisierbare JSON-Typen und Arrays
Mehrere Ausgabebindungen Unterstützt Unterstützt
HTTP-Trigger HttpRequest/ObjectResult HttpRequestData/HttpResponseData
Langlebige Funktionen Unterstützt Nicht unterstützt
Imperative Bindungen Unterstützt Nicht unterstützt
function.json-Artefakt Generiert Nicht generiert
Konfiguration host.json host.json und benutzerdefinierte Initialisierung
Dependency Injection Unterstützt Unterstützt
Middleware Nicht unterstützt Unterstützt
Kaltstartdauer Typisch Länger (aufgrund des Just-In-Time-Starts), Ausführung unter Linux anstelle von Windows, um potenzielle Verzögerungen zu vermeiden
ReadyToRun Unterstützt TBD

Bekannte Probleme

Auf dieser Seite finden Sie Informationen zu Problemumgehungen bis hin zu bekannten Problemen bei der Ausführung von Funktionen in isolierten .NET-Prozessen. Wenn Sie ein Problem melden möchten, erstellen Sie ein Issue in diesem GitHub-Repository.

Nächste Schritte