kurmann / Videoschnitt.Engine

Steuereinheit für spezialisierte Servicemodule zur Teilautomatisierung für privaten Videoschnitt
Apache License 2.0
0 stars 0 forks source link

add web interface that logs output #98

Closed kurmann closed 2 months ago

kurmann commented 2 months ago

Eine Frage: Wie kann ich die Live-Updates allgemein anderen Applikationen, bspw. anderen Dockercontainer zur Verfügung stellen über eine API, falls ich das später implementieren möchte? Welche technischen Übertragungswege bieten sich mir an?


Um Live-Updates anderen Anwendungen, einschließlich Docker-Containern, zur Verfügung zu stellen, gibt es mehrere technische Übertragungswege, die Du verwenden kannst. Hier sind einige der gängigsten Methoden:

1. WebSockets

WebSockets bieten eine bidirektionale Kommunikationskanal zwischen dem Server und den Clients, der für Echtzeit-Updates ideal ist. SignalR, das Du bereits verwendest, basiert auf WebSockets und fällt bei Bedarf auf andere Protokolle zurück.

Implementierung:

2. gRPC

gRPC ist ein modernes, performantes Remote Procedure Call (RPC) Framework, das standardmäßig HTTP/2 verwendet. Es ermöglicht die Kommunikation zwischen Diensten in verschiedenen Sprachen und ist sehr effizient.

Implementierung:

3. Kafka oder andere Message Brokers

Apache Kafka und andere Message-Broker (z.B. RabbitMQ) sind großartig für verteilte Systeme und Microservices, um Nachrichten asynchron zu verarbeiten und zu verteilen.

Implementierung:

4. HTTP Long Polling oder Server-Sent Events (SSE)

HTTP Long Polling und Server-Sent Events (SSE) sind Techniken, um quasi-Echtzeit-Updates über HTTP zu ermöglichen.

Implementierung:

Beispiel für WebSockets mit SignalR

Hier ist ein erweitertes Beispiel, wie Du SignalR nutzen kannst, um Live-Updates an andere Docker-Container zu senden:

SignalR Hub

public class NotificationHub : Hub
{
    private readonly string logFilePath = "messages.log";

    public async Task SendMessage(string type, string content)
    {
        var message = new { Type = type, Content = content, Timestamp = DateTime.UtcNow };
        var messageJson = System.Text.Json.JsonSerializer.Serialize(message);

        // Nachricht in die Logdatei schreiben
        await File.AppendAllTextAsync(logFilePath, messageJson + "\n");
        await Clients.All.SendAsync("ReceiveMessage", message);
    }
}

Minimal API in Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<VideoProcessingService>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapGet("/hello", () => "Hello, World!")
   .WithName("GetHello")
   .WithTags("Sample Endpoints");

app.MapGet("/messages", async (string type = null) =>
{
    var logFilePath = "messages.log";
    if (File.Exists(logFilePath))
    {
        var messages = await File.ReadAllLinesAsync(logFilePath);
        var messageObjects = messages.Select(m => JsonSerializer.Deserialize<dynamic>(m));

        if (!string.IsNullOrEmpty(type))
        {
            messageObjects = messageObjects.Where(m => m.Type == type);
        }

        return Results.Ok(messageObjects);
    }
    return Results.Ok(Enumerable.Empty<dynamic>());
}).WithName("GetMessages")
  .WithTags("Messages");

app.MapHub<NotificationHub>("/notificationHub");

app.Run();

SignalR Client in einem anderen Docker-Container

Du kannst einen SignalR-Client in einem anderen Docker-Container implementieren, der die Nachrichten vom NotificationHub empfängt. Hier ist ein Beispiel in .NET:

using Microsoft.AspNetCore.SignalR.Client;
using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        var connection = new HubConnectionBuilder()
            .WithUrl("http://<URL_TO_NOTIFICATIONHUB>/notificationHub")
            .Build();

        connection.On<dynamic>("ReceiveMessage", (message) =>
        {
            Console.WriteLine($"{message.Timestamp} - {message.Type}: {message.Content}");
        });

        await connection.StartAsync();
        Console.WriteLine("Connection started. Listening for messages...");

        // Keep the application running
        await Task.Delay(-1);
    }
}

Zusammenfassung

Um Live-Updates anderen Anwendungen zur Verfügung zu stellen, kannst Du verschiedene Technologien nutzen. WebSockets (SignalR), gRPC, Message-Broker (Kafka, RabbitMQ), HTTP Long Polling und Server-Sent Events sind allesamt geeignete Ansätze. Jede Methode hat ihre eigenen Vor- und Nachteile, abhängig von den spezifischen Anforderungen Deiner Anwendung und Infrastruktur. SignalR ist besonders einfach zu implementieren, wenn Du bereits ASP.NET Core verwendest.

kurmann commented 2 months ago

Die einfachste Möglichkeit zum Beginnen ist WebSockets?

Ist es nicht sinnvoll, wenn ich die "statische" WebAPI derzeit weglasse. Ich benötige derzeit nur Live-Updates.


Ja, wenn Du gerade erst anfängst und nur Live-Updates benötigst, ist die Verwendung von WebSockets über SignalR die einfachste und effektivste Lösung. Du kannst die statische WebAPI weglassen und Dich zunächst ausschließlich auf die Implementierung der Echtzeit-Kommunikation konzentrieren.

Hier ist eine einfache Anleitung, wie Du das mit ASP.NET Core und SignalR umsetzen kannst:

1. SignalR-Hub erstellen

Erstelle eine neue Klasse für den SignalR-Hub:

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

public class NotificationHub : Hub
{
    // Methode zum Senden von Nachrichten an alle verbundenen Clients
    public async Task SendMessage(string type, string content)
    {
        var message = new { Type = type, Content = content, Timestamp = DateTime.UtcNow };
        await Clients.All.SendAsync("ReceiveMessage", message);
    }
}

2. Videoverarbeitungsdienst

Der Videoverarbeitungsdienst sendet Nachrichten an den SignalR-Hub:

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

public class VideoProcessingService
{
    private readonly IHubContext<NotificationHub> _hubContext;

    public VideoProcessingService(IHubContext<NotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task ProcessVideoAsync()
    {
        // Simuliere Videobearbeitung
        await Task.Delay(5000); // Simuliert die Videobearbeitung

        // Sende Statusmeldung
        await _hubContext.Clients.All.SendAsync("ReceiveMessage", "Info", "Video wurde verarbeitet");
    }
}

3. Minimal API konfigurieren

Erstelle und konfiguriere die Minimal API in Deiner Program.cs-Datei:

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR();
builder.Services.AddSingleton<VideoProcessingService>();

var app = builder.Build();

app.MapHub<NotificationHub>("/notificationHub");

app.Run();

public class NotificationHub : Hub
{
    public async Task SendMessage(string type, string content)
    {
        var message = new { Type = type, Content = content, Timestamp = DateTime.UtcNow };
        await Clients.All.SendAsync("ReceiveMessage", message);
    }
}

public class VideoProcessingService
{
    private readonly IHubContext<NotificationHub> _hubContext;

    public VideoProcessingService(IHubContext<NotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task ProcessVideoAsync()
    {
        // Simuliere Videobearbeitung
        await Task.Delay(5000); // Simuliert die Videobearbeitung

        // Sende Statusmeldung
        await _hubContext.Clients.All.SendAsync("ReceiveMessage", "Info", "Video wurde verarbeitet");
    }
}

4. Live-Update mit HTML-Webinterface

Füge den SignalR-Client in Deine HTML-Benutzeroberfläche ein:

<!DOCTYPE html>
<html>
<head>
    <title>Live Status</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/5.0.11/signalr.min.js"></script>
</head>
<body>
    <div id="status"></div>
    <script>
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/notificationHub")
            .build();

        connection.on("ReceiveMessage", function(message) {
            const statusDiv = document.getElementById("status");
            const p = document.createElement("p");
            p.textContent = `${message.Timestamp} - ${message.Type}: ${message.Content}`;
            statusDiv.appendChild(p);
        });

        connection.start().catch(function (err) {
            return console.error(err.toString());
        });
    </script>
</body>
</html>

5. Dockerfile anpassen

Stelle sicher, dass Dein Dockerfile korrekt konfiguriert ist:

# Build Stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /app

# Copy everything from the src directory
COPY src/ ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish ./Application/Application.csproj -c Release -o out

# Runtime Image
FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled

# Set the working directory
WORKDIR /app

# Copy the built application
COPY --from=build-env /app/out .

# Specify the non-root user to use (default user ID 1000)
USER 1000:1000

# Expose any necessary ports
EXPOSE 80

# Set the entry point for the application
ENTRYPOINT ["dotnet", "Kurmann.Videoschnitt.Kraftwerk.Application.dll"]

Zusammenfassung

Wenn Du später zusätzliche Funktionen wie Filterung oder Historie der Nachrichten benötigst, kannst Du die WebAPI jederzeit erweitern.

kurmann commented 2 months ago

Ich möchte dennoch zum Vergleich wissen, wie ich eine Live-API mit gRPC umzusetzen wäre?


Natürlich! Hier ist eine Übersicht, wie Du eine Live-API mit gRPC umsetzen kannst. gRPC bietet hohe Leistung und Effizienz, insbesondere für inter-service Kommunikation.

Schritte zur Implementierung einer gRPC-basierten Live-API

  1. Einrichten von Protobuf-Dateien:

    • Definiere die Protobuf-Datei (.proto), die die gRPC-Dienste und Nachrichten beschreibt.
  2. Implementierung des gRPC-Servers:

    • Implementiere die gRPC-Dienste in einer ASP.NET Core-Anwendung.
  3. Implementierung des gRPC-Clients:

    • Erstelle einen gRPC-Client in einer anderen Anwendung oder einem Docker-Container, der den gRPC-Dienst abonniert.

1. Definiere die Protobuf-Datei

Erstelle eine Datei namens notification.proto:

syntax = "proto3";

option csharp_namespace = "NotificationService";

package notification;

service Notification {
  rpc Subscribe(SubscribeRequest) returns (stream NotificationMessage);
}

message SubscribeRequest {}

message NotificationMessage {
  string type = 1;
  string content = 2;
  string timestamp = 3;
}

2. Implementierung des gRPC-Servers

2.1. Erstelle das ASP.NET Core gRPC-Projekt

Erstelle ein neues ASP.NET Core gRPC-Projekt:

dotnet new grpc -o NotificationService

2.2. Füge die Protobuf-Datei zum Projekt hinzu

Bearbeite die NotificationService.csproj, um die Protobuf-Datei einzubinden:

<ItemGroup>
  <Protobuf Include="Protos\notification.proto" GrpcServices="Server" />
</ItemGroup>

2.3. Implementiere den gRPC-Dienst

Erstelle die Implementierung des gRPC-Dienstes in Services/NotificationService.cs:

using Grpc.Core;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace NotificationService
{
    public class NotificationServiceImpl : Notification.NotificationBase
    {
        private readonly ILogger<NotificationServiceImpl> _logger;
        private static readonly ConcurrentBag<IServerStreamWriter<NotificationMessage>> _subscribers = new();

        public NotificationServiceImpl(ILogger<NotificationServiceImpl> logger)
        {
            _logger = logger;
        }

        public override async Task Subscribe(SubscribeRequest request, IServerStreamWriter<NotificationMessage> responseStream, ServerCallContext context)
        {
            _subscribers.Add(responseStream);
            while (!context.CancellationToken.IsCancellationRequested)
            {
                await Task.Delay(1000);
            }
            _subscribers.TryTake(out _);
        }

        public static async Task BroadcastMessage(string type, string content)
        {
            var message = new NotificationMessage
            {
                Type = type,
                Content = content,
                Timestamp = DateTime.UtcNow.ToString("o")
            };

            foreach (var subscriber in _subscribers)
            {
                await subscriber.WriteAsync(message);
            }
        }
    }
}

2.4. Konfiguriere den gRPC-Dienst

Bearbeite Program.cs, um den gRPC-Dienst zu konfigurieren:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGrpc();

var app = builder.Build();

app.MapGrpcService<NotificationServiceImpl>();
app.MapGet("/", () => "Notification gRPC Service");

app.Run();

2.5. Dockerfile für den gRPC-Server

# Build Stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /app

# Copy everything from the src directory
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out

# Runtime Image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build-env /app/out .

EXPOSE 80

ENTRYPOINT ["dotnet", "NotificationService.dll"]

3. Implementierung des gRPC-Clients

3.1. Erstelle das gRPC-Client-Projekt

Erstelle ein neues .NET Konsolenprojekt:

dotnet new console -o NotificationClient

3.2. Füge die Protobuf-Datei zum Client-Projekt hinzu

Bearbeite die NotificationClient.csproj, um die Protobuf-Datei einzubinden:

<ItemGroup>
  <Protobuf Include="Protos\notification.proto" GrpcServices="Client" />
</ItemGroup>

3.3. Implementiere den gRPC-Client

Erstelle die Implementierung des Clients in Program.cs:

using Grpc.Net.Client;
using NotificationService;
using System;
using System.Threading.Tasks;

var channel = GrpcChannel.ForAddress("http://localhost:80");
var client = new Notification.NotificationClient(channel);

using var call = client.Subscribe(new SubscribeRequest());

var responseReaderTask = Task.Run(async () =>
{
    while (await call.ResponseStream.MoveNext())
    {
        var notification = call.ResponseStream.Current;
        Console.WriteLine($"{notification.Timestamp} - {notification.Type}: {notification.Content}");
    }
});

Console.WriteLine("Press any key to exit...");
Console.ReadKey();

3.4. Dockerfile für den gRPC-Client

# Build Stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /app

# Copy everything from the src directory
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out

# Runtime Image
FROM mcr.microsoft.com/dotnet/runtime:8.0
WORKDIR /app
COPY --from=build-env /app/out .

ENTRYPOINT ["dotnet", "NotificationClient.dll"]

Zusammenfassung

Durch die Verwendung von gRPC kannst Du eine leistungsstarke und effiziente Lösung für Echtzeit-Updates zwischen verschiedenen Anwendungen und Containern implementieren.

kurmann commented 2 months ago

Du hast jetzt, zwei .NET-Server-Applikationen gezeigt, die miteinander mit gRPC kommunizieren. Kann ich mit gRPC auch zwischen einer .NET Server-Anwendung und einem WebClient kommunizieren und ist diese überhaupt sinnvoll? Beachte, dass meine Anwendung bisher eine MinmalAPI hat, die ein kleines WebInterface zeigt, dass ich überPort 80 aufrufen kann und damit die Live-Nachrichten anzeigt. Quasi als Ersatz für SignalR oder stelle ich mir das falsch vor?


gRPC ist für die Kommunikation zwischen Servern oder Backend-Diensten sehr leistungsfähig und effizient. Es unterstützt bidirektionale Streaming-Verbindungen und ist stark typisiert. Für Web-Clients (JavaScript) ist gRPC jedoch nicht so direkt anwendbar wie SignalR oder WebSockets.

gRPC-Web

Um gRPC für die Kommunikation zwischen einer .NET-Server-Anwendung und einem Web-Client zu nutzen, gibt es gRPC-Web, eine Technologie, die es ermöglicht, gRPC-Dienste über HTTP/1.1 zu nutzen, was von Browsern unterstützt wird.

Warum gRPC-Web?

Implementierung einer Live-API mit gRPC-Web

Hier ist, wie Du eine gRPC-Web-API implementieren kannst:

1. Erstelle die Protobuf-Datei

Definiere die Protobuf-Datei (notification.proto):

syntax = "proto3";

option csharp_namespace = "NotificationService";

package notification;

service Notification {
  rpc Subscribe(SubscribeRequest) returns (stream NotificationMessage);
}

message SubscribeRequest {}

message NotificationMessage {
  string type = 1;
  string content = 2;
  string timestamp = 3;
}

2. Implementiere den gRPC-Server

Erstelle ein neues ASP.NET Core gRPC-Projekt:

dotnet new grpc -o NotificationService

Füge die Protobuf-Datei zum Projekt hinzu:

<ItemGroup>
  <Protobuf Include="Protos\notification.proto" GrpcServices="Server" />
</ItemGroup>

Implementiere den gRPC-Dienst:

using Grpc.Core;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace NotificationService
{
    public class NotificationServiceImpl : Notification.NotificationBase
    {
        private readonly ILogger<NotificationServiceImpl> _logger;
        private static readonly ConcurrentBag<IServerStreamWriter<NotificationMessage>> _subscribers = new();

        public NotificationServiceImpl(ILogger<NotificationServiceImpl> logger)
        {
            _logger = logger;
        }

        public override async Task Subscribe(SubscribeRequest request, IServerStreamWriter<NotificationMessage> responseStream, ServerCallContext context)
        {
            _subscribers.Add(responseStream);
            while (!context.CancellationToken.IsCancellationRequested)
            {
                await Task.Delay(1000);
            }
            _subscribers.TryTake(out _);
        }

        public static async Task BroadcastMessage(string type, string content)
        {
            var message = new NotificationMessage
            {
                Type = type,
                Content = content,
                Timestamp = DateTime.UtcNow.ToString("o")
            };

            foreach (var subscriber in _subscribers)
            {
                await subscriber.WriteAsync(message);
            }
        }
    }
}

Konfiguriere den gRPC-Dienst in Program.cs:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGrpc();
builder.Services.AddGrpcWeb(); // Füge gRPC-Web hinzu
builder.Services.AddSingleton<VideoProcessingService>();

var app = builder.Build();

app.UseGrpcWeb(); // Nutze gRPC-Web
app.MapGrpcService<NotificationServiceImpl>().EnableGrpcWeb(); // Aktiviere gRPC-Web für den Dienst

app.Run();

public class NotificationServiceImpl : Notification.NotificationBase
{
    private static readonly ConcurrentBag<IServerStreamWriter<NotificationMessage>> _subscribers = new();

    public override async Task Subscribe(SubscribeRequest request, IServerStreamWriter<NotificationMessage> responseStream, ServerCallContext context)
    {
        _subscribers.Add(responseStream);
        while (!context.CancellationToken.IsCancellationRequested)
        {
            await Task.Delay(1000);
        }
        _subscribers.TryTake(out _);
    }

    public static async Task BroadcastMessage(string type, string content)
    {
        var message = new NotificationMessage
        {
            Type = type,
            Content = content,
            Timestamp = DateTime.UtcNow.ToString("o")
        };

        foreach (var subscriber in _subscribers)
        {
            await subscriber.WriteAsync(message);
        }
    }
}

public class VideoProcessingService
{
    private readonly IHubContext<NotificationHub> _hubContext;

    public VideoProcessingService(IHubContext<NotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task ProcessVideoAsync()
    {
        // Simuliere Videobearbeitung
        await Task.Delay(5000); // Simuliert die Videobearbeitung

        // Sende Statusmeldung
        await NotificationServiceImpl.BroadcastMessage("Info", "Video wurde verarbeitet");
    }
}

3. Implementiere den Web-Client

Erstelle eine HTML-Datei mit gRPC-Web Unterstützung:

<!DOCTYPE html>
<html>
<head>
    <title>Live Status</title>
    <script src="https://cdn.jsdelivr.net/npm/@grpc/grpc-js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@improbable-eng/grpc-web"></script>
</head>
<body>
    <div id="status"></div>
    <script>
        const {grpc} = require("@improbable-eng/grpc-web");
        const {Notification} = require("./notification_pb_service");
        const {SubscribeRequest} = require("./notification_pb");

        const client = new Notification.NotificationClient("http://localhost:80");

        const request = new SubscribeRequest();

        const statusDiv = document.getElementById("status");

        client.subscribe(request, {}).on("data", (message) => {
            const p = document.createElement("p");
            p.textContent = `${message.getTimestamp()} - ${message.getType()}: ${message.getContent()}`;
            statusDiv.appendChild(p);
        }).on("error", (err) => {
            console.error(err);
        });
    </script>
</body>
</html>

4. Dockerfile für den gRPC-Server

# Build Stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /app

# Copy everything from the src directory
COPY . ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish -c Release -o out

# Runtime Image
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build-env /app/out .

EXPOSE 80

ENTRYPOINT ["dotnet", "NotificationService.dll"]

Zusammenfassung

Während gRPC und gRPC-Web leistungsstark sind, bleibt SignalR für viele Web-basierte Echtzeit-Kommunikationsszenarien einfacher zu implementieren und zu nutzen. Wähle die Technologie, die am besten zu Deinen Anforderungen passt.

kurmann commented 2 months ago

Ich sehe hier aber keine wesentliche Vereinfachung zu SignalR.

Es scheint mir, dass ich gRPC wirklich (es benutzt ja HTTP 2.0) zwischen Serveranwendungen benutzen kann.


Du hast recht, gRPC eignet sich besonders gut für die Kommunikation zwischen Serveranwendungen und anderen Backend-Diensten aufgrund seiner Effizienz, Leistung und Unterstützung für HTTP/2. Für Web-Clients gibt es zusätzliche Schritte und Bibliotheken, die die Komplexität erhöhen können, im Vergleich zu SignalR, das speziell für Echtzeit-Webanwendungen entwickelt wurde.

Vergleich zwischen gRPC und SignalR

Da Dein Anwendungsfall die Bereitstellung von Echtzeit-Updates über ein Webinterface beinhaltet, bleibt SignalR die einfachste und effektivste Lösung. Hier ist eine nochmalige, klare Implementierung mit SignalR, die die Einfachheit betont:

Implementierung mit SignalR

1. SignalR-Hub erstellen

Erstelle eine neue Klasse für den SignalR-Hub:

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

public class NotificationHub : Hub
{
    public async Task SendMessage(string type, string content)
    {
        var message = new { Type = type, Content = content, Timestamp = DateTime.UtcNow };
        await Clients.All.SendAsync("ReceiveMessage", message);
    }
}

2. Videoverarbeitungsdienst

Der Videoverarbeitungsdienst sendet Nachrichten an den SignalR-Hub:

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

public class VideoProcessingService
{
    private readonly IHubContext<NotificationHub> _hubContext;

    public VideoProcessingService(IHubContext<NotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task ProcessVideoAsync()
    {
        // Simuliere Videobearbeitung
        await Task.Delay(5000); // Simuliert die Videobearbeitung

        // Sende Statusmeldung
        await _hubContext.Clients.All.SendAsync("ReceiveMessage", "Info", "Video wurde verarbeitet");
    }
}

3. Minimal API konfigurieren

Erstelle und konfiguriere die Minimal API in Deiner Program.cs-Datei:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSignalR();
builder.Services.AddSingleton<VideoProcessingService>();

var app = builder.Build();

app.MapHub<NotificationHub>("/notificationHub");

app.Run();

public class NotificationHub : Hub
{
    public async Task SendMessage(string type, string content)
    {
        var message = new { Type = type, Content = content, Timestamp = DateTime.UtcNow };
        await Clients.All.SendAsync("ReceiveMessage", message);
    }
}

public class VideoProcessingService
{
    private readonly IHubContext<NotificationHub> _hubContext;

    public VideoProcessingService(IHubContext<NotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task ProcessVideoAsync()
    {
        // Simuliere Videobearbeitung
        await Task.Delay(5000); // Simuliert die Videobearbeitung

        // Sende Statusmeldung
        await _hubContext.Clients.All.SendAsync("ReceiveMessage", "Info", "Video wurde verarbeitet");
    }
}

4. Live-Update mit HTML-Webinterface

Füge den SignalR-Client in Deine HTML-Benutzeroberfläche ein:

<!DOCTYPE html>
<html>
<head>
    <title>Live Status</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/5.0.11/signalr.min.js"></script>
</head>
<body>
    <div id="status"></div>
    <script>
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/notificationHub")
            .build();

        connection.on("ReceiveMessage", function(message) {
            const statusDiv = document.getElementById("status");
            const p = document.createElement("p");
            p.textContent = `${message.Timestamp} - ${message.Type}: ${message.Content}`;
            statusDiv.appendChild(p);
        });

        connection.start().catch(function (err) {
            return console.error(err.toString());
        });
    </script>
</body>
</html>

5. Dockerfile anpassen

Stelle sicher, dass Dein Dockerfile korrekt konfiguriert ist:

# Build Stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
WORKDIR /app

# Copy everything from the src directory
COPY src/ ./
# Restore as distinct layers
RUN dotnet restore
# Build and publish a release
RUN dotnet publish ./Application/Application.csproj -c Release -o out

# Runtime Image
FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled

# Set the working directory
WORKDIR /app

# Copy the built application
COPY --from=build-env /app/out .

# Specify the non-root user to use (default user ID 1000)
USER 1000:1000

# Expose any necessary ports
EXPOSE 80

# Set the entry point for the application
ENTRYPOINT ["dotnet", "Kurmann.Videoschnitt.Kraftwerk.Application.dll"]

Zusammenfassung

Wenn Du später auf komplexere Szenarien mit mehreren Backend-Diensten und Microservices erweiterst, kannst Du gRPC verwenden, um die Kommunikation zwischen diesen Diensten effizient zu gestalten.

kurmann commented 2 months ago

Herzlichen Dank für deine detaillierten und kompetenten Antworten.


Gern geschehen! Es freut mich, dass ich Dir weiterhelfen konnte. Wenn Du weitere Fragen hast oder zusätzliche Unterstützung benötigst, stehe ich Dir jederzeit zur Verfügung. Viel Erfolg bei Deinem Projekt und bei der Implementierung Deiner Live-Updates!

kurmann commented 2 months ago

(...)


Jetzt etwas allgemeiner (..). Wenn ich bspw. eine .NET Konsolenanwendung baue, dann nimmt die Anwendung die Konsolenausgabe und Eingabe als Basis und ohne Konsolenverbindung ist die Anwendung gar nicht lauffähig.

Ich komme aus der Welt der Webentwicklung, darum ist die Welt der CLI für mich bis vor kurzer Zeit neu.


Das ist eine gute Frage und zeigt, dass du dich mit den Grundlagen der Command Line Interface (CLI) und deren Interaktionen beschäftigst. Lass uns die allgemeinen Konzepte der CLI, der Nutzung in Anwendungen (wie .NET Konsolenanwendungen), und deren Betrieb in Docker-Containern durchgehen.

Grundlagen der CLI-Anwendungen

CLI-Anwendungen sind Programme, die in einer Kommandozeile (Terminal) laufen und Eingaben von der Tastatur sowie Ausgaben an das Terminal ausgeben. Solche Anwendungen sind in der Regel textbasiert und bieten keine grafische Benutzeroberfläche (GUI). Hier sind einige grundlegende Konzepte:

  1. Standard-Eingabe (stdin): Dies ist der Datenstrom, aus dem das Programm seine Eingaben erhält. Bei CLI-Anwendungen ist dies oft die Tastatur.
  2. Standard-Ausgabe (stdout): Dies ist der Datenstrom, auf den das Programm seine Ausgaben schreibt. Normalerweise ist dies das Terminal.
  3. Standard-Fehler (stderr): Ein separater Datenstrom für Fehlerausgaben, die ebenfalls ins Terminal gehen.

Beispiel: .NET Konsolenanwendung

Eine typische .NET Konsolenanwendung sieht etwa so aus:

using System;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Gib deinen Namen ein:");
        string name = Console.ReadLine();
        Console.WriteLine($"Hallo, {name}!");
    }
}

Ausführung ohne Konsole

Wie du schon bemerkt hast, benötigen viele CLI-Anwendungen eine Konsole, um zu funktionieren. Ohne eine Konsole gibt es keine Möglichkeit, Eingaben zu erhalten oder Ausgaben anzuzeigen, was dazu führt, dass solche Anwendungen nicht richtig funktionieren.

Nutzung in Docker

Wenn du eine CLI-Anwendung in einem Docker-Container ausführen möchtest, benötigst du oft den interaktiven Modus (-i) und Pseudo-TTY (-t), um sicherzustellen, dass die Anwendung richtig mit den Ein- und Ausgabeströmen interagieren kann.

Docker-Container für .NET Konsolenanwendung

Hier ist ein Beispiel-Dockerfile für eine .NET Konsolenanwendung:

# Verwende ein offizielles .NET SDK-Image als Basis
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /app

# Kopiere das CSPROJ und restore Abhängigkeiten
COPY *.csproj .
RUN dotnet restore

# Kopiere den Rest der Anwendung und baue sie
COPY . .
RUN dotnet publish -c Release -o out

# Verwende ein kleineres Runtime-Image
FROM mcr.microsoft.com/dotnet/runtime:6.0
WORKDIR /app
COPY --from=build /app/out .

# Setze den Startbefehl für den Container
ENTRYPOINT ["dotnet", "YourApp.dll"]

Docker-Container im interaktiven Modus starten

Um die Anwendung im interaktiven Modus zu starten, könntest du folgenden Befehl verwenden:

docker run -it your-dotnet-app

CLI-Anwendungen in Docker

Wenn du eine CLI-Anwendung in Docker betreibst, kannst du mehrere Szenarien haben:

  1. Interaktiver Modus:

    • Verwende -it, um den Container im interaktiven Modus zu starten und eine Konsole bereitzustellen.
    • Dies ist nützlich für Debugging oder wenn die Anwendung Benutzerinteraktionen erfordert.
  2. Automatisierte Ausführung:

    • Wenn die Anwendung keine Benutzerinteraktionen erfordert, kannst du sie im Hintergrund ausführen und die Ausgaben in Logs umleiten.
    • Beispiel:
      docker run -d your-dotnet-app
  3. Eingabe über Dateien oder Umgebungsvariablen:

    • Du kannst Eingaben auch über Dateien oder Umgebungsvariablen bereitstellen, anstatt die Standardeingabe zu verwenden.
    • Beispiel:
      docker run -e MY_VARIABLE=value your-dotnet-app

Zurück zur AWS CLI

Wenn du die AWS CLI oder ähnliche Tools in Docker verwendest, sind diese Konzepte ebenfalls anwendbar. Du kannst die AWS CLI im interaktiven Modus nutzen, um Befehle direkt einzugeben und Ergebnisse sofort zu sehen, oder Skripte und automatisierte Prozesse erstellen, die keine direkte Benutzereingabe erfordern.

Beispiel: AWS CLI im Container

Hier ist ein Beispiel, wie du die AWS CLI in einem Docker-Container nutzen könntest:

  1. Dockerfile:

    FROM amazonlinux:2
    
    RUN yum install -y python3 python3-pip
    RUN pip3 install awscli
    
    CMD ["bash"]
  2. Build und Run:

    docker build -t aws-cli-container .
    docker run -it aws-cli-container
  3. Verwendung der AWS CLI im Container:

    aws configure
    aws s3 ls

Zusammenfassung

CLI-Anwendungen basieren auf der Interaktion mit der Kommandozeile für Eingaben und Ausgaben. In Docker-Containern kannst du den interaktiven Modus nutzen, um diese Interaktionen zu ermöglichen. Dies gilt sowohl für einfache Anwendungen wie .NET Konsolenanwendungen als auch für komplexere Tools wie die AWS CLI. Der interaktive Modus in Docker (-it) stellt sicher, dass die Anwendung ein Terminal hat, mit dem sie interagieren kann, was besonders für Anwendungen wichtig ist, die Benutzerinteraktionen oder Konsolenausgaben erfordern.

kurmann commented 2 months ago

Herzlichen Dank. Du hast das sehr gut erklärt und insbesondere auch den Unterschied zwischen interaktiven Modus und automatisierter Ausführung. Du hast übrigens gerade ein Problem gelöst, dass ich bei einer gehosteten .NET-Anwendung hatte "Videoschnitt-Kraftwerk". Diese Anwendung starte ich mit dem Start des Container, konfiguriert über Umgebungsvariablen und diese überwacht Verzeichnisse und reagiert darauf.

Um jetzt die Anwendung überwachen zu können, was sie eigentlich macht, wollte ich zu Beginn eine ASP.NET-Anwendung daraus machen mit Minimal API und dann eine Weboberfläche über Port 80, die mir dann mit SignalR Live die Events anzeigt.

Das ist zwar durchaus "fancy" und diesen Schritt halte ich mir noch offen, aber ich bemerkte, wie viel "Ballast" ich der Anwendung auferlege nur um sie zu überwachen.

Der Ansatz mit dem interaktiven Modus bei Docker Container war mir nicht bewusst. Ich muss in einem ersten Schritt nur die Videoschnitt-Kraftwerk-Applikation im interaktiven Modus starten und habe dann eine Live-Überwachung gleich mit dabei.


Es freut mich sehr, dass die Erklärung dir weitergeholfen hat und dass du eine Lösung für dein Problem gefunden hast! Der interaktive Modus in Docker kann wirklich nützlich sein, um Anwendungen zu überwachen und zu debuggen, ohne unnötigen Ballast hinzuzufügen. Es ist großartig zu hören, dass du diesen Ansatz in deinem Projekt anwenden kannst. Lass mich noch ein paar Details hinzufügen, die dir helfen könnten, die interaktive Überwachung effizient zu gestalten.

Interaktiver Modus für Überwachung und Debugging

Hier ist ein praktisches Beispiel, wie du deine .NET-Anwendung im interaktiven Modus betreiben kannst, um die Live-Überwachung zu ermöglichen:

Dockerfile für deine .NET-Anwendung

Angenommen, deine .NET-Anwendung heißt Videoschnitt-Kraftwerk und überwacht Verzeichnisse auf neue Videos:

# Verwende ein offizielles .NET SDK-Image als Basis
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /app

# Kopiere das CSPROJ und restore Abhängigkeiten
COPY *.csproj .
RUN dotnet restore

# Kopiere den Rest der Anwendung und baue sie
COPY . .
RUN dotnet publish -c Release -o out

# Verwende ein kleineres Runtime-Image
FROM mcr.microsoft.com/dotnet/runtime:6.0
WORKDIR /app
COPY --from=build /app/out .

# Setze den Startbefehl für den Container
ENTRYPOINT ["dotnet", "Videoschnitt-Kraftwerk.dll"]

Build und Run

  1. Docker-Image bauen:

    docker build -t videoschnitt-kraftwerk .
  2. Container im interaktiven Modus starten:

    docker run -it --name videoschnitt-kraftwerk-container videoschnitt-kraftwerk

Logs überwachen

Während die Anwendung im interaktiven Modus läuft, kannst du direkt im Terminal sehen, was die Anwendung macht. Alle Konsolenausgaben der Anwendung werden dir in Echtzeit angezeigt.

Nachträgliches Einsteigen in den Container

Falls der Container im Hintergrund läuft, kannst du jederzeit in den laufenden Container einsteigen und interagieren:

  1. Container starten (ohne interaktiven Modus):

    docker run -d --name videoschnitt-kraftwerk-container videoschnitt-kraftwerk
  2. In den laufenden Container einsteigen:

    docker exec -it videoschnitt-kraftwerk-container /bin/bash

Vorteile des interaktiven Modus

Beispiel für eine einfache Überwachungslogik

Falls deine .NET-Anwendung bereits Konsolenausgaben für wichtige Ereignisse wie das Starten und Beenden von Videoprozessen oder Fehler erzeugt, kannst du diese direkt im interaktiven Modus sehen. Hier ist ein einfaches Beispiel, wie die Anwendung Meldungen ausgeben könnte:

using System;
using System.IO;

class Program
{
    static void Main(string[] args)
    {
        string watchDirectory = Environment.GetEnvironmentVariable("WATCH_DIRECTORY") ?? "/watch";
        Console.WriteLine($"Überwache Verzeichnis: {watchDirectory}");

        FileSystemWatcher watcher = new FileSystemWatcher
        {
            Path = watchDirectory,
            Filter = "*.mp4",
            NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite
        };

        watcher.Created += OnCreated;
        watcher.Changed += OnChanged;
        watcher.Deleted += OnDeleted;

        watcher.EnableRaisingEvents = true;

        Console.WriteLine("Drücke 'q' zum Beenden.");
        while (Console.Read() != 'q') ;
    }

    private static void OnCreated(object source, FileSystemEventArgs e)
    {
        Console.WriteLine($"Neue Datei erstellt: {e.FullPath}");
        // Logik für die Bearbeitung der neuen Datei
    }

    private static void OnChanged(object source, FileSystemEventArgs e)
    {
        Console.WriteLine($"Datei geändert: {e.FullPath}");
        // Logik für die Bearbeitung der geänderten Datei
    }

    private static void OnDeleted(object source, FileSystemEventArgs e)
    {
        Console.WriteLine($"Datei gelöscht: {e.FullPath}");
        // Logik für die Bearbeitung der gelöschten Datei
    }
}

Zusammenfassung

Der interaktive Modus in Docker ist ein sehr nützliches Werkzeug, insbesondere für die Überwachung und das Debugging von Anwendungen wie deiner .NET Videoschnitt-Kraftwerk-Anwendung. Durch die direkte Nutzung der Konsolenausgaben kannst du in Echtzeit sehen, was die Anwendung macht, ohne zusätzlichen "Ballast" wie eine Weboberfläche einführen zu müssen. Wenn du weitere Anpassungen oder spezifische Anforderungen hast, stehe ich dir gerne zur Verfügung.