Laden...

Forenbeiträge von Palladin007 Ingesamt 2.094 Beiträge

20.05.2024 - 16:35 Uhr

Bei mir lädt alles sofort

20.05.2024 - 15:50 Uhr

Bei C# hast Du normalerweise gar keine erkennbare Reihenfolge, weil Du nur Methoden hast.
Also ja: Ist egal, Du kannst die Methoden herum sortieren, wie Du lustig bist - idealerweise so, wie es am übersichtlichsten ist.

Eine scheinbare Ausnahme ist die Program.cs, wenn die keine Klassenstruktur (top-level statements) hat, dann generiert der Compiler die Klassenstruktur drum herum. Dort definierte Methoden werden dann als Local functions übersetzt, die in der Main-Methode liegen.
Local functions sind die zweite Ausnahme, allerdings ist bei nicht statischen Local functions die Reihenfolge des Aufrufs doch relevant, da von in der Local function auf die lokalen Variablen der Methode zugegriffen werden kann, in der sie definiert wird, entsprechend kann die Methoden auch nur auf die Variablen zugreifen, die vor dem Aufruf definiert wurden und bekommt auch nur diese Werte. Das ist der Grund, warum ich Local functions möglichst am Ende definiere, oder einfach direkt statisch, oder ich nutze sie einfach gar nicht.

Mir wurde damals zu Ausbildungszeiten aber der Stil beigebracht, dass die Methoden in der Reihenfolge definiert werden, in der sie aufgerufen werden.
Dadurch ergibt sich dann zwangsläufig das, was Du bemerkt hast: Die Methoden werden nach der Nutzung definiert.

Ich persönlich folge dem aber nicht mehr, ich sortiere und gruppiere Methoden lieber nach Aufgabenbereich und Sichtbarkeit.
Darüber hinaus sortiere ich alle Inhalte einer Klasse nach Art des Inhalts:

  • Klassenvariablen
  • Properties
  • Konstruktoren
  • Methoden
20.05.2024 - 15:37 Uhr
var a1 = new int[] { 1, 5, 7, 10 };
var a2 = a1[1..];
var b1 = new int[] { 2, 4, 6, 8 };
var b2 = b1[..2];

Console.WriteLine(string.Join(" ", a1));
Console.WriteLine(string.Join(" ", a2));
Console.WriteLine();
Console.WriteLine(string.Join(" ", b1));
Console.WriteLine(string.Join(" ", b2));

// 1 5 7 10
// 5 7 10
// 
// 2 4 6 8
// 2 4
20.05.2024 - 00:03 Uhr

Was Du da geschrieben hast, ist ein binäres Oder.
Das Contains sucht also nicht 1 oder 5, sondern nur 5.

Richtig ist:

bool containsNumber = wuerfel.Contains(1) || wuerfel.Contains(5);
19.05.2024 - 23:04 Uhr

Contains ist generisch, das kann mit jedem Typ umgehen, den Du da hast.
Wenn es nur string kann, hast Du also irgendetwas anderes falsch gemacht.

Zum Lernen solltest Du es selber schreiben, ein Dictionary hilft dabei.
Damit kannst Du dann die Zahl als Key und deinen Zähler als Wert behandeln.

Oder in kurz, ohne viel Lerneffekt:

foreach (var group in wuerfel.GroupBy(x => x))
    Console.WriteLine($"{group.Key} => {group.Count()}");

Zumindest wenn ich deine Frage richtig verstanden habe

19.05.2024 - 22:58 Uhr

Ich habe nie mit Unity gearbeitet, aber nach dem, was ich gesehen/gehört habe, könnten Unity und gängige UI-Frameworks kaum weiter voneinander entfernt sein.

Wenn dein Ziel also nur ist, mit Unity helfen zu können, warum lernst Du dann nicht Unity?
Aber zum Lernen der Sprach-Grundlagen ist WPF ganz gut geeignet, es ist nicht zu komplex, zwingt aber zur Verwendung von fortgeschrittenen Dingen.
Anfangen solltest aber auch Du mit der Konsole, die Frage, wo eine Klassenvariable hin muss, darfst Du bei WPF nicht stellen müssen, das muss alles fest sitzen.

19.05.2024 - 19:18 Uhr

Man guckt zig Youtube Videos und liest hier und da und jeder macht es anders.

Ich halte nichts von YouTube Videos ^^
Ich hab noch keine gute Video-Serie gesehen, die meisten sind entweder schlecht oder bleiben oberflächlich.

Aber es gibt ohne Ende andere Quellen im Internet.
Z.B. Microsoft bietet selber eine Tutorial-Reihe an.
Oder Du liest ein gutes Buch, ein kostenloses Buch wäre z.B. "Visual C# 2012", besser wäre aber der kostenpflichtige Nachfolger von 2019, weil sich dazwischen einiges geändert hat.

Es gibt auch hier einen Artikel zu dem Thema:
https://mycsharp.de/forum/threads/78856/faq-wie-finde-ich-den-einstieg-in-csharp

Windows Forms nutzt man nicht mehr und ist veraltet.
Man nutzt jetzt WPF.

Damit bist Du leider auch nicht mehr aktuell 😉

Ja, WinForms ist alt, sehr alt und wird nicht mehr weiterentwickelt.
Das gleiche gilt allerdings auch für WPF.

WPF verfolgt allerdings einen deutlich moderneren Ansatz, der damals Maßstäbe gesetzt hat, die man auch heute noch wiedererkennen kann.

Wenn Du nur eine einfache kleine Windows-Desktop-Anwendung entwickeln willst, ist WPF keine schlechte Wahl. Es ist alt, aber immer noch ausreichend und war damals gut durchdacht. Der Quasi-Nachfolger ist MAUI (basiert eigentlich auf Xamarin), allerdings hat das noch einige Macken, weshalb ich das ganz besonders einem Anfänger nicht empfehlen kann. Alternativ gibt's noch Community-Projekte wie Avalonia, das noch aktiv gepflegt wird, auf mich aber nicht so gut durchdacht wirkt.

Die Zukunft geht aber eher in Richtung Blazor und Web im allgemeinen.
MAUI kann ein Blazor-Frontend darstellen, so hast Du die Möglichkeit, den riesigen Umfang an Frameworks aus dem Web in einer Desktop-App zu nutzen. Mit Blazor funktioniert MAUI dann auch ausreichend gut. Alternativ kenne ich noch Electron und Photino.NET, Letzteres wirkt noch halb fertig, funktioniert aber schon jetzt sehr gut und ist sehr leichtgewichtig
Das alles macht Blazor sehr interessant, da man mit dem Web eine Frontend-Basis hat, die jeder mehr oder weniger versteht und die überall funktioniert, man muss also nicht das ganze Frontend doppelt und dreifach bauen, nur weil man mehrere Ziel-Geräte unterstützen will.
Ob das für dich ein relevantes Argument ist, musst Du dir aber selbst überlegen.

WPF hat damals Maßstäbe gesetzt, aber einen wirklich würdigen Nachfolger gibt es leider noch nicht.
Also ist WPF nicht pauschal falsch und zum Lernen auch deutlich einfacher, als die Alternativen.
Aber wenn Du "man nutzt jetzt WPF" schreibst, ist das leider falsch, "man" (also generell) nutzt es heute nicht mehr, außer man hat gute Gründe.
Die Zukunft liegt eher bei Web-Dechnologien und bei .NET heißt das aktuell Blazor.

Ob Du nun mit WPF lernen, oder nochmal umsatteln solltest, kann ich dir nicht sagen.
Blazor arbeitet mit ASP.NET Core und die Einstiegshürde ist vermutlich um einiges größer (dafür gibt's aber sehr gute Artikel von Microsoft), allerdings ist dieser Technologie-Stack dafür auch um einiges sicherer und der geradlinigere Ansatz für die Kommunikation von der UI mit dem "Code Behind" ist mit Blazor leichter zu verstehen, als MVVM mit WPF.

19.05.2024 - 18:49 Uhr

WPF kann man prinzipiell genauso nutzen, wie Windows Forms, ist dann halt umständlicher und man legt sich selber Steine in den Weg. An deinem Code sollte das konzeptionell nur wenig ändern.
Wenn man es richtig machen will, dann nutzt man aber MVVM, darauf wurde WPF ausgelegt. [Artikel] MVVM und DataBinding

Und in dem verlinkten Artikel findest Du Beispiele.
Die Klassenvariable landet dann einfach in der Klasse, wo Du sie nutzen willst.

Aber wenn Du es in deinem Code sehen willst:

public partial class MainWindow : Window
{
    private int _zahl;

    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        _zahl++;
    }

    private void wuerfel1_img_MouseDown(object sender, MouseButtonEventArgs e)
    {
        MessageBox.Show(_zahl.ToString());
    }
}

Aber wie gesagt: Mit WPF arbeitet man eigentlich anders.

19.05.2024 - 18:37 Uhr

Sorry, aber wenn Du nicht weißt, wo/wie man Klassenvariablen nutzt, dann sind sowohl WinForms, als auch WPF zu hoch für dich.
Du solltest mit den Grundlagen anfangen und erst wenn die sitzen, machst Du mit komplexen Frameworks wie WinForms oder WPF weiter.

Klassenvariablen:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/fields

19.05.2024 - 18:16 Uhr

Wenn Du dir einen Zustand merken willst, leg in der Klasse eine Variable an.
Die kannst Du dann in einer Methode setzen und in der anderen den Wert abrufen.

Und gegen Spagetthi Code:

wuerfel1_img.Source = new BitmapImage(new Uri($@"C:\Users\Marco\source\repos\Spielereien\Farkle\Ressources\img\wuerfelbild {wuerfel[0]}.png"));

Oder Du baust ein Mapping:

var myMapping = new Dictionary<int, Uri>()
{
    [1] = new Uri(@"C:\Users\Marco\source\repos\Spielereien\Farkle\Ressources\img\wuerfelbild 1.png"),
    [2] = new Uri(@"C:\Users\Marco\source\repos\Spielereien\Farkle\Ressources\img\wuerfelbild 2.png"),
    // ...
}

wuerfel1_img.Source = myMapping[wuerfel[0]];

Die for-Schleife sieht by the way aus, als sollte sie da nicht doppelt stehen.

Und Dateipfade so anzugeben ist eine blöde Idee, wie soll das denn auf einem anderen PC funktionieren?
WinForms kann Ressourcen verwalten, die Du dann direkt abrufen kannst, ohne dass Du irgendwo Dateien herum liegen hast.
Oder Du stellst in den Eigenschaften der Datei ein, dass sie ins Ziel-Verzeichnis kopiert wird, dann landet sie neben der exe und Du kannst sie mit einem relativen Pfad suchen.
Auf diese Weise sparst Du dir den hardcoded Pfad und dein Programm funktioniert auch auf einem anderen PC:

19.05.2024 - 18:00 Uhr

Es gibt die Einsteiger-Doku zu ASP.NET Core von Microsoft

Und für Postman gibt's auch entsprechende Anleitungen.
SoapUI habe ich nie genutzt.

19.05.2024 - 16:23 Uhr

Ich hab das Gefühl, Du wirfst hier einiges durcheinander. SoapUI, Postman, VS2019 - die Liste passt nicht?
Du solltest dich lieber erst einmal mit den Grundlagen befassen, sonst passiert genau das, was Du schon bemerkt hast: Du wirst erschlagen.

SoapUI, PostMan oder direkt mit VS2019

SoapUI und Postman sind zwei Testing-Tools für Web-APIs.
Beide können REST, aber SoapUI ist auf SOAP ausgelegt.
SOAP und REST sind zwei verschiedene Ansätze für eine Web-API, Du willst REST, nicht SOAP.
Ich persönlich nutze Postman am liebsten, ich musste es aber auch noch nie für SOAP nutzen.

Und VS2019 ist eine Entwicklungsumgebung, also ein völlig anderes Thema. Außerdem ist VS2022 aktuell.

ich muss ein REST Interface erstellen und mich zu einem Server verbinden.

Mit einer REST-API kommuniziert man mit einem HttpClient. Das macht man aber nicht so wie in deinem Snippet, sondern mit der HttpClientFactory.
Es gibt allerdings auch das Framework "Refit", das arbeitet intern genauso, nimmt dir aber einiges an Arbeit ab.

Ich an deiner Stelle würde aber erst einmal ohne Refit experimentieren - natürlich nur experimentieren, für den Lerneffekt.

Dabei bekomme ich keinen Server und muss diesen selbst erstellen.

Eine REST-API erstellst Du mit ASP.NET Core.
Wichtig: ASP.NET Core, es gibt noch einen Vorgänger, den meine ich nicht.
Für ASP.NET Core gibt's eine gute Einsteiger-Doku von Microsoft.

Und zum XML Serialisieren bietet .NET auch entsprechende Klassen.
Oder Du setzt auf JSON, das kommt mit weniger Overhead aus und Du musst in ASP.NET Core nichts weiter konfigurieren, da es von Haus aus mit JSON arbeitet.

05.05.2024 - 11:39 Uhr

Bau eine ASP.NET Core Web-API, die kannst Du dann (selber) hosten und von der App aus darauf zugreifen.

Die Web-API ist auch der übliche Weg für sowas, sowohl privat, als auch professionell.
Die solltest Du auch bauen, wenn Du den Server selber hostest, da Du über die API weit mehr Kontrolle hast.

Einen eigenen Server mit einer kleinen DB drauf möchte ich nicht betreiben, da mir das Risiko durch Angriffe zu hoch ist.

Ich hab bei mir einen RaspberryPi laufen und greife von unterwegs per VPN von der FritzBox (gibt's noch andere? Alternativ ein eigener VPN-Server) darauf zu.
Einrichtung ist nicht ganz einfach, aber es läuft und ich bin in öffentlichen Netzwerken sicher und kann auch auf sowas wie einen eigenen PasswortManager oder mein PiHole zugreifen.

Risiko für Angriffe sehe ich nicht, da die App ja nur für euch beide ist, da müsste es schon jemand auf euch beide direkt abgesehen haben.
Außerdem ist die Kommunikation ja durch das VPN abgesichert.

21.04.2024 - 19:26 Uhr

Naja, ich will dir ja nur ungern den Wind aus den Segeln nehmen, aber das gibt es schon 😃

Visual Studio bringt die resx-Dateien mit, Visual Studio generiert auf der Basis dann Code.
Den Code kannst Du entweder direkt nutzen, oder Du nutzt eine Abstraktion, z.B. Microsoft.Extensions.Localization.
Und zusätzlich gibt's noch die Visual Studio Extension ResXManager, das müsste auch die restlichen deiner Ideen unterstützen.

Ebenso würde ich gerne wissen, hat jemand von euch schon Erfahrungen gesammelt in der Entwicklung von VS Erweiterungen?

Schon ja - wenn Du konkrete Fragen hast, frag ^^

16.03.2024 - 09:04 Uhr

Ok, es hat sich als bescheuerter Fehler herausgestellt ...

Es hat einfach nur folgendes gefehlt:

permissions:
  contents: write

Danach läuft die Authentifizierung automatisch ...

13.03.2024 - 18:10 Uhr

Die InProcess-Implementierung ist trivial zu coden.

Nur wenn es ein Unary-Call ist.

Bei Streaming-Calls unterscheidet sich der Client-Aufruf mit der Server-Implementierung sehr.

// Server

public override async Task DuplexStreamingCall(IAsyncStreamReader<Request> requestStream, IServerStreamWriter<Response> responseStream, ServerCallContext context)
{
    while (await requestStream.MoveNext())
    {
        _logger.LogInformation("DuplexStreamingCall: {RequestData}", requestStream.Current.RequestData);

        var response = new Response
        {
            ResponseData = $"Duplex response to {requestStream.Current.RequestData}"
        };

        await responseStream.WriteAsync(response);

        await Task.Delay(100);
    }
}

// Client

public static async Task DuplexStreamingCall(TestService.TestServiceClient client)
{
    using (var call = client.DuplexStreamingCall())
    {
        var responseTask = Task.Run(async () =>
        {
            await foreach (var response in call.ResponseStream.ReadAllAsync())
            {
                Console.WriteLine($"Duplex Streaming Response: {response.ResponseData}");
            }
        });

        for (int i = 0; i < 5; i++)
        {
            await call.RequestStream.WriteAsync(new Request { RequestData = $"Message {i}" });
            await Task.Delay(100);
        }

        await call.RequestStream.CompleteAsync();
        await responseTask;
    }
}

Insofern wäre die mMn. einzige wirklich praktikable Option um sowas zu erreichen, das Mediator-Pattern. Die Services nutzen dann den Mediator und geben die Daten durch und wenn die Arbeit im selben Prozess stattfinden soll, wird der Mediator (hinter einer Abstraktion natürlich) aus dem Client heraus genutzt und kann sich dabei auch um den DI-Scope kümmern.

DI-Scope muss verwaltet werden

Damit meine ich, dass bei einer Client-Server-Architektur jeder Request/Call auf dem Server in einem DI-Scope resultiert.
Wenn ich nun auf eine reine Client-Architektur umstellen will und dafür eine einfache Implementierung schaffe, fällt das weg, das muss ich dann auch erst umsetzen. Mache ich das nicht, dann habe ich unterschiedliches Verhalten zwischen den zwei Varianten mit potentiell weitreichenden Auswirkungen.

Nach meiner Erfahrung wird eben eine proto eher selten geändert

Mit proto habe ich keine Erfahrungen, aber wenn ich meine Erfahrungen mit REST darauf übertrage, sind meine Erfahrungen sehr anders.
Tatsächlich muss bei meiner derzeitigen Arbeit eigentlich ständig irgendwas an der API geändert werden.

Ggf. arbeiten wir in anderen Gebieten?
Wenn die API für die Verwendung nach außen für externe Systeme gedacht ist, kann ich mir sehr gut vorstellen, dass sie sich nur selten ändert.
Wenn die API aber für die Kommunikation zwischen zwei zusammen (im selben Team) entwickelten Projekten (Client-Server) gedacht ist, kann das wieder anders aussehen.
Bei uns dient die REST-API hauptsächlich zur Kommunikation zwischen dem Web-Frontend und dem ASP.NET-Backend sowie zu anderen intern entwickelten System und bei der derzeitigen Auftragslage ist da ziemlich viel Bewegung.

Tatsächlich arbeiten wir aktuell mit einem Source-Generator, der Client-Code auf Basis der Controller-Definitionen generiert, allerdings ist der ziemlich komplex und bringt auch ein paar Nachteile mit sich.

Daher würd eich diese einfach händisch coden -- aus Kosten/Nutzen-Sicht.

Ja das stimmt natürlich, aus Sicht einer Firma wäre das für ein Projekt definitiv besser. Für viele Projekte könnte das aber wieder anders aussehen.
Und ich schreibe hier gar nicht für meine Firma, das ganze ist meine private Idee und würde dann auch auf NuGet landen, sodass man in den Projekten gar keinen Aufwand mehr hat - im Idealfall natürlich.

13.03.2024 - 13:21 Uhr

Natürlich, man kann es auch "zu Fuß" machen, muss das dann aber auch immer bei jeder Änderung tun.
Und dadurch gäbe es auch eine ganze Menge Overhead:

  • Das Interface muss gepflegt werden
  • Netzwerk-Implementierung muss gepflegt werden
  • InProcess-Implementierung muss gepflegt werden
  • z.B. DI-Scope muss verwaltet werden

Und ich kann für die InProcess-Implementierung nicht einfach nur das Interface im Server-Service implementieren, da die Methoden-Signaturen völlig anders sind.

Man könnte natürlich eine weitere Abstraktion z.B. mit dem Mediator-Pattern dazwischen schieben, da würde das etwas einfacher werden, aber es gibt immer noch mehr Aufwand, der für jede Änderung anfällt.

Mit meiner Idee wäre das alles nicht nötig, man hat genau den Aufwand, den man bei gRPC sowieso immer hat und auch braucht, aber darüber hinaus nicht mehr.

Oder übersehe ich etwas?

13.03.2024 - 01:32 Uhr

'n Abend,

ich versuche mich gerade an den GitHub Actions (meine Erfahrung hält sich also in Grenzen ) und möchte für ein VSIX Projekt einen Workflow erstellen, der automatisch baut und einen Release inkl. Tag erstellt.

Da es ein altes VSIX Projekt ist, muss das alles auf Windows 2019 laufen und alle bereits vorhandenen Actions, die ich gefunden habe, liefen nur unter Linux, die einzige Option, die ich sehe, ist also die git cli.

Das ist mein Test-Workflow:

name: Test

on:
  workflow_dispatch:

jobs:
  test:
    runs-on: windows-2019
    steps:
      - uses: actions/checkout@v4.1.1
        with:
          fetch-depth: 0
      
      - name: Create version tag
        run: |
          $url = git config --get remote.origin.url
          $url = $url -replace "https://", "https://${{ secrets.GITHUB_TOKEN }}@"
          git remote set-url origin $url
          git config user.name "GitHub Action"
          git config user.email "<>"
          git tag run${{ github.run_number }}
          git push origin run${{ github.run_number }}

Mein Problem ist, dass ihm die Berechtigungen fehlen:

remote: Write access to repository not granted.

fatal: unable to access 'https://meine/schöne/url: The requested URL returned error: 403

Das oben ist einer meiner Versuche.
Ansonsten habe ich auch versucht:

  • git config credential.helper '!f() { sleep 1; echo "username=git token=${{ secrets.GITHUB_TOKEN }}"; }; f'
  • git -c http.extraheader="AUTHORIZATION: bearer ${{ secrets.GITHUB_TOKEN }}" push origin run${{ github.run_number }}

Beides habe ich online gefunden und einfach nur Just4Fun ausprobiert

Hat jemand eine Idee, wie ich das ans Laufen bekomme?

Besten Dank für die Hilfe 😃

12.03.2024 - 18:15 Uhr

Z.B. können die vom gRPC-Tooling erstellten Services auch ohne gRPC aufgerufen werden.

Genau das meine ich

Ich versuche es mal mit etwas Code zu verdeutlichen:

// Netzwerk - so wie man gRPC normalerweise benutzt
using var netChannel = GrpcChannel.ForAddress("https://localhost:1234");
await TestAsync(new TestService.TestServiceClient(netChannel));

// InProcess - meine Idee
await using var services = new ServiceCollection()
    .AddInProcessGrpcChannel(b => b
        .WithService(InProcessServiceInfo.Create<TestServiceImpl>())
        .UseScopedServiceProvider())
    .BuildServiceProvider();

var inpChannel = services.GetRequiredService<InProcessGrpcChannel>();

await TestAsync(new TestService.TestServiceClient(inpChannel));

// TestAsync()
static async Task TestAsync(TestService.TestServiceClient client)
{
    // Der Code hier weiß nicht, ob die Kommunikation über das Netzwerk oder im selben Prozess stattfindet.
}

Der entscheidende Faktor ist die TestAsync-Methode, die für beides identisch ist.

Mein InProcess-Channel (bzw. der Invoker) sucht in seiner Liste von bekannten Services nach dem richtigen Service und der Methode und ruft beides auf. Es findet keine Netzwerk-Kommunikation statt, keine Serialisierung, kein Protokoll, es wird nur die Methode direkt aufgerufen.

Das, was ich von gRPC nutze, ist den vom gRPC-Tooling erstellte Code als Abstraktion, um die Services/Methoden zu suchen, zu instanziieren (ggf. in einem Scope) und aufzurufen.

11.03.2024 - 14:05 Uhr

Ich glaube, Du hast mich falsch verstanden.
Ich nutze gRPC im Wesentlichen nur als Kommunikations-Layer, es wird nichts serialisiert und es gibt kaum zusätzlichen Overhead.
Die aus den proto-Dateien generierten Typen bieten mir aber alles, was ich brauche, um die Services und Methoden zu finden und genau das ist es, was ich nutze.

Effektiv ist mein Idee (bzw. mein erster Entwurf) also nur ein Quasi-Ersatz für die gRPC-Kommunikation hinter der generierten Abstraktion. Das Protokoll dahinter gibt es also gar nicht mehr, es gibt nur noch die Abstraktion der offiziellen .NET-Implementierung.

Aber klar, wenn man nur im selben Prozess kommunizieren will, ist das ganze überflüssig und unnötig. Aber es geht ja gerade um die Projekte, wo man beides haben möchte, abhängig von der Konfiguration.

10.03.2024 - 22:04 Uhr

Hallo zusammen,

ich habe mich mit gRPC beschäftigt und dabei über eine Idee nachgedacht:
Ist ein nahtloser Wechsel zwischen Client-Server und Monolith ohne großem zusätzlichen Aufwand möglich?

Die Idee ist, eine Channel-Implementierung zu entwickeln, die in der Lage ist, Client-Aufrufe im selben Prozess zu verarbeiten, möglicherweise mit einem DI-Scope. Je nach Anwendungs-Konfiguration wird dann der originale Channel oder der InProcess-Channel verwendet.

Die Anwendung wäre weiterhin strikt in zwei Bereiche unterteilt: Client und Server. Der Client kommuniziert mit dem für gRPC generierten Client und je nach Konfiguration läuft die Kommunikation entweder über das Netzwerk, oder im selben Prozess.

Diese Idee richtet sich natürlich an eine sehr spezifische Zielgruppe, da sie nur für Anwendungen sinnvoll ist, die diese Art des Wechsels benötigen. Denkbar wäre z.B. eine Anwendung, die entweder als eigenständige Desktop-Anwendung oder im Team mit einem zentralen Server genutzt werden soll. Bei einer Desktop-Anwendung möchte man natürlich keinen separaten Server starten müssen.

Für Tests könnte es auch nützlich sein, ich halte es aber für besser, die Business-Logik vernünftig zu abstrahieren und dann direkt ohne gRPC zu testen. Außerdem kann ein InProcess-Channel niemals das Verhalten einer echten Netzwerk-Kommunikation vollständig abbilden.


Warum ich nun dieses Thema auf mache:

Was denkt ihr über diese Idee?
Welche Funktionen haltet ihr für wichtig?
Welche Risiken sollten explizit getestet werden?

Besten Dank 😃

21.01.2024 - 19:11 Uhr

Zitat von Peter Bucher

Wenn man eine private Nachricht schreiben möchte - im Darkmode! - ist die Schrift weiss auf weissem Hintergrund, man sieht das geschriebene also leider nicht.

Schau dir mal die Browser-Extension "Dark Reader" an - hat mir Abt mal empfohlen.
Ist natürlich nicht so gut, wie das Original-DarkTheme, aber es funktioniert ganz gut.

12.01.2024 - 14:51 Uhr

Entweder CSV mit leeren Zellen, oder Du erstellst eine xlsx-Datei, z.B. damit:
https://github.com/ClosedXML/ClosedXML oder https://github.com/ClosedXML/ClosedXML.Report

CSV ist einfacher, XLSX erlaubt natürlich sehr viel mehr Anpassungen.

09.01.2024 - 18:58 Uhr

Und wenn Du es stattdessen aus dem CKEditor raus in ein Panel rechts daneben über/unter dem Tipps-Panel legst?
Da könntest Du dann auch noch andere nützliche Tools unterbringen.

09.01.2024 - 16:29 Uhr

Es gibt ja zwei Ansichten:

Die "gerederte" Ansicht und die "Quellcode"-Ansicht vom Markdown.
In der "gerenderten" Ansicht kannst Du ganz normal "List<T>" schreiben, im Markdown wird das dann escaped.
Wenn Du aber im Markdown-Quellcode "List<T>" schreibst, dann musst Du auch selber escapen, dafür musst Du vor dem "<" ein Slash schreiben: "List\<T>"

Aus meiner Sicht funktioniert also alles korrekt.


Ich hab gerade den "Links"-Dropdown gefunden - schön, dass es das jetzt gibt 😃
Aber kannst Du da noch eine Schnell-Suche im Dropdown einbauen? So ist das etwas unpraktisch und die normale Forensuche ist vermutlich immer noch schneller.

31.12.2023 - 15:10 Uhr

Stimmt, daran hatte ich gar nicht gedacht - einfach Brushes nehmen 😄

cmbFontColors.ItemsSource = typeof(Brushes).GetProperties()
    .Select(p => new BrushInfo()
    {
        Name = p.Name,
        Brush = (Brush)p.GetValue(null)
    });
31.12.2023 - 12:53 Uhr

wenn ich einen Default setzen möchte

Die ComboBox hat einen SelectedValuePath-Property, kann sein, dass die dein Problem löst, wenn Du dort "Name" einträgst.
Ich bin mir aber nicht sicher, was dann in e.AddedItems steht, kann sein, dass da dann auch der Name steht, das müsstest Du prüfen.

Oder Du nutzt Folgendes:

var defaultItem = typeof(Colors).GetProperty(nameof(Colors.Blue));

Das liefert dir das selbe (ganz wichtig) PropertyInfo-Objekt, sodass Du das einfach als ausgewähltes Item in der ComboBox nutzen kannst.

ob ich in zwei, drei Jahren direkt Blicke, warum da kein Brush drinne ist

Das typeof(Colors) ist nicht die Colors-Klasse, sondern ein Type-Objekt der Colors-Klasse, was Typ-Informationen bereithält - ganz wichtiger Unterschied ^^

Die Type-Klasse ist dafür gedacht, Metadaten aller Typen (Klassen, Structs, Delegates, Generics, etc.) verfügbar zu machen und genau das tut sie. Und die GetProperties- und GetProperty-Methoden liefern dir das Gegenstück für eine einzelne Properties. Mit dem Type-Objekt kannst Du eine Instanz des Typs erzeugen (bzw. mit der Activator-Klasse) und das PropertyInfo-Objekt kann den Wert abrufen oder setzen - vorausgesetzt der Typ bzw. die Property lassen das zu. Aber es bleiben Metadaten, nicht mehr und nicht weniger.

31.12.2023 - 01:27 Uhr

Das ursprüngliches Problem ist (war), wie Du schon vermutet hast, folgende Zeile:

cmbFontColors.ItemsSource = typeof(Colors).GetProperties();

Die GetProperties-Methode gibt dir nicht die einzelnen Farben (Color-Werte) zurück, sondern PropertyInfo-Objekte, die Metadaten zur jeweiligen Property enthalten. Konkret bekommst Du RuntimePropertyInfo-Objekte zurück, das ist eine Ableitung von PropertyInfo und wird von .NET intern benutzt. Diese Metadaten sind dann z.B. der Typ oder der Name der Property. Recherchier mal zu Reflection, dann wird es vielleicht klarer.

Dein Layout (das {Binding Name}) funktioniert trotzdem, weil die PropertyInfo auch eine Name-Property hat. Microsoft hat in diesem Fall die ganzen Properties in der Colors-Klasse so benannt, wie auch die Farben heißen, deshalb wird der richtige Farb-Name dargestellt.

Und deine vorläufige Lösung funktioniert, weil WPF einen ValueConverter für die ForegroundProperty hat, der automatisch in einen Brush konvertieren kann. Das ist nötig, damit Du im XAML einfach nur den Namen der Farbe eintragen kannst, der ValueConverter sucht diesen Namen dann in den bekannten Farben und konvertiert automatisch. Und dass die Namen der Properties in der Colors-Klasse identisch zu den bekannten Farb-Namen sind, hatte ich ja schon erwähnt.


Probier mal folgendes:

cmbFontColors.ItemsSource = typeof(Colors).GetProperties().Select(p => p.GetValue(null));

Damit sollten die einzelnen Items dann vom Typ Color sein, dann kannst Du in der cmbFontColors_SelectionChanged-Methode auch darauf casten.

Allerdings wirst Du damit dein Ziel auch nicht erreichen, aus zwei Gründen:

  • Die ForegroundProperty ist nicht vom Typ Color, sondern vom Typ Brush. Der ValueConverter konvertiert aber nur String (Farb-Name) und Brush hin und her, nicht aber die Color-Werte. Gleiches gilt für die Fill-Property von Rectangle, das funktioniert dann auch nicht mehr.
  • Die Color-Werte haben keine Name-Property, dein Binding auf den Namen funktioniert also nicht.

Daher ist deine (vorläufige) Lösung sogar genau richtig so.

Du könntest aber auch einen Umweg über eine Zwischen-Klasse gehen. Dafür musst Du dann wie gehabt die PropertyInfo-Objekte abrufen und pro Item dann eine Instanz der Zwischen-Klasse erstellen:

cmbFontColors.ItemsSource = typeof(Colors).GetProperties()
    .Select(p => new ColorInfo()
    {
        Name = p.Name,
        Brush = new SolidColorBrush((Color)p.GetValue(null))
    });

Im XAML dann:

<Rectangle Fill="{Binding Brush}" Width="16" Height="16" Margin="0,2,5,2" />
<TextBlock Text="{Binding Name}"/>

Und in der cmbFontColors_SelectionChanged-Methode kannst Du dann auf deine Zwischen-Klasse casten und die Brush-Property verwenden:

ApplyPropertyValueToSelectedText(TextElement.ForegroundProperty, ((ColorInfo)e.AddedItems[0]).Brush);

Diese Lösung hätte den Vorteil, dass Du beliebig viele eigene Farben und die volle Bandbreite der Möglichkeiten der Brush-Klasse nutzen kannst, da gibt's nicht nur SolidColorBrush. Oder Du möchtest die Farben lokalisiert darstellen, dabei würde dir diese Zwischen-Klasse auch in die Hände spielen.

Welche Lösung jetzt besser ist, da kann man sicher hervorragend drüber streiten und hängt auch von deinen Anforderungen ab.
Bedenke aber auch, dass deine Lösung nur dann verständlich ist, wenn man weiß, wie WPF bei dem Thema tickt, das hast Du ja auch schon gemerkt.
Insofern ist der Weg über die Zwischen-Klasse definitiv leichter verständlich.


Abgesehen davon:

Ein try mit leerem catch macht man nicht, das würde ich dir beim Code-Review um die Ohren hauen 😉
Das macht man nur, wenn man auch einen sehr guten Grund dafür hat und dieser Grund gehört dann auch kommentiert.


@Th69:

Zitat von Th69

Außerdem wird dort TextBlock.ForegroundProperty verwendet.

Das ist identisch. In der TextBlock-Klasse wird TextElement.ForegroundProperty.AddOwner(typeof(TextBlock)) verwendet, was wiederum this zurück gibt, siehe hier und hier.

06.12.2023 - 09:53 Uhr

Wenn das alles so doof ist, frage ich mich, warum das dann in C# eingebaut wird?

Es ist nicht doof, aber man muss es - wie Abt schreibt - überlegt verwenden.

Microsoft baut viele Funktionen ein, wie Du sie benutzt, ist deine Sache.
Und sie haben auch eine Funktion eingebaut, dass man vor dem ganzen Code auf null prüfen und dann ein Default-Verhalten implementieren kann, das macht die ganzen null-Checks danach überflüssig.
Der Null-conditional Operator lohnt sich besonders dann, wenn man nur eine Null-Prüfung braucht, dann kann man es kompakter schreiben, aber das ist hier nicht der Fall, hier setzt sich das ganze fort.

if (collection is null)
    return;

var enumerator = collection.GetEnumerator ();
enumerator.Reset();

while ( enumerator.MoveNext() )
{
    var value = enumerator.Current;
    ...
}

By the way: Das Reset() braucht man nach dem GetEnumerator()-Aufruf nicht.
Nur wenn man erneut iterieren möchte, braucht man Reset(), was ich persönlich bisher noch nicht hatte.

04.12.2023 - 09:03 Uhr

Zu .NET Framework, .NET Standard, .NET Core und .NET >5 findest Du hier einen ausführlichen Artikel von Abt.
Und ja, das neue .NET ist plattformunabhängig, läuft also auch auf den von dir genannten Betriebssystemen.

Und wenn es dir um die Zukunft geht und Du das alte .NET Framework nicht unterstützen musst, dann nimm das aktuellste .NET, beachte dabei aber die Support Policy.
Wenn Du auch das alte .NET Framework unterstützen musst (viele Frameworks müssen/wollen das), ist .NET Standard die einfachste, aber nicht die einzige Möglichkeit. Du kannst alternativ auch mehrere .NET Versionen unterstützen.

Wenn es dir um Desktop- und Mobile-Anwendungen geht, sieht das ganze aber komplizierter aus.

Von Microsoft gibt es MAUI, das ist prinzipiell plattformunabhängig, läuft aber (noch) nicht auf Linux.
Alternativ kannst Du auch vollständig auf Web setzen und z.B. mit Blazor eine Web-Anwendung entwickeln, die Du dann in verschiedenen Desktop-Clients darstellen kann, das braucht auch nicht unbedingt einen Web-Server, mit MAUI Hybrid läuft Blazor auch als reine Desktop-Anwendung.

Außerhalb der Microsoft-Welt kenne ich noch:

  • Avalonia
  • UNO
  • Electron (Web)
  • Photino (Web)

Das vermutlich flexibelste wäre, Du setzt auf Blazor und suchst dann nach Clients, die das unterstützen, damit hast Du die ganze Bandbreite von Web-Technologien zur Verfügung. Umgekehrt bist Du aber eingeschränkt, wenn es um reine Desktop-Technologien geht, da musst Du dann auf die Möglichkeiten des jeweiligen verwendeten Clients zurückgreifen.

Oder Du willst kein Web, dann kannst Du zusätzlich noch WPF nutzen, das läuft zwar nur auf Windows, wird aber immer noch unterstützt.

Ich persönlich habe für ein privates Projekt Photino (für Blazor) rausgepickt, da es schlanker ist, als Electron und .NET 8 auch ohne Dirty-Fix unterstützt.

28.11.2023 - 18:07 Uhr

Als Tipp am Rande:

In der DbContext-Ableitung kann man in der OnConfiguring-Methode (oder wenn man die DbContextOptions baut) einen Interceptor registrieren:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.AddInterceptors(new EnableForeignKeysConnectionInterceptor());
}

Und die Klasse dazu:

internal sealed class EnableForeignKeysConnectionInterceptor : DbConnectionInterceptor
{
    public override void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData)
    {
        // PRAGMA foreign_keys = ON
    }
    public override async Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData eventData, CancellationToken cancellationToken)
    {
        // PRAGMA foreign_keys = ON
    }
}

Dann passiert das bei jeder Connection automatisch.

13.11.2023 - 19:15 Uhr

Ich habe soeben Version 0.3.0 hochgeladen:

v0.3.0 - 2023-11-13

Hinzugefügt

  • PackScan.Tool-Parameter, um das TargetFramework für das Projekt anzugeben.
  • PackScan.Tool-Parameter, um den RuntimeIdentifier für das Projekt anzugeben.
  • Funktion, um die maximale Größe für heruntergeladene Icons im Format "<Breite>x<Höhe>" festzulegen.

v0.2.1 - 2023-11-06

Behoben

  • Ein Fehler wurde behoben, der Probleme mit spezifischen Plattformversionen verursachte, wie sie in MAUI-Projekten verwendet werden.

v0.2.0 - 2023-11-01

Hinzugefügt

  • Neue Konfigurationsoption, die es ermöglicht, Inhalte nur aus Dateien zu laden, wenn keine URL angegeben ist und umgekehrt.
01.11.2023 - 00:05 Uhr

Ich habe soeben die erste Version hochgeladen.

30.10.2023 - 16:06 Uhr

Danke für das Lob 😃

Angenommen ich würde das nun in mehrere CI-CD Pipelines einbauen, wie hast Du Dir gedacht, dass das Setup zentral gemanaged werden könnte?

Das Projekt bietet ja einen SourceGenerator oder ein .NET Tool an, beide können MSBuild-Eigenschaften lesen. Das .NET Tool bietet zusätzlich noch eigene Parameter an, die dann die MSBuild-Eigenschaften überschreiben.

Du könntest deine zentrale Verwaltung der Einstellungen also mit Hilfe einer props-Datei lösen und in jedem Projekt einbinden.

Wenn die mehreren Projekte aber zusammengehören, solltest Du aber nur für das Start-Projekt der Code generiert lassen, da dieses Projekt als einziges alle Referenzen hat. Das Ergebnis kann man dann über Dependency Injection verteilen und dort nutzen, wo man es tatsächlich braucht.

29.10.2023 - 23:33 Uhr

Hallo zusammen,

Ich möchte Euch mein Projekt PackScan vorstellen, das die Handhabung von NuGet-Paketen erleichtert.

Kurz gesagt, PackScan ist ein Code-Generator, der die project.assets.json ausliest und sowohl die darin enthaltenen Daten als auch die Informationen aus den NuGet-Paketen selbst in Code umwandelt. Darüber hinaus kann es konfiguriert werden, um Texte und Icons aus dem Internet zu holen und in den Code zu integrieren. Dies erleichtert es, alle genutzten Pakete und Lizenzen im Blick zu halten und auf anschauliche Weise in der Anwendung darzustellen – eine Anforderung, die in früheren Projekten von mir aufkam.

Hier die Highlights:

  • Automatische Sammlung von Informationen der verwendeten Pakete.
  • Analyse der Lizenzen und Warnungen bei unerwünschten Lizenzen.
  • Verfügbar als .NET Tool und Source-Generator für MSBuild.
  • Umfangreiche Konfigurationsmöglichkeiten für individuelle Anpassungen und Performance-Optimierung.

Ich freue mich auf eure Meinungen, Vorschläge und Kritik! Bitte hinterlasst eure Kommentare hier oder auf der GitHub-Seite des Projekts.

Beste Grüße

23.10.2023 - 20:00 Uhr

Hilft vielleicht:

Uninstall or remove Visual Studio
Dort gibt's einen Bereich "Remove all with InstallCleanup.exe"

06.10.2023 - 16:19 Uhr

Zum Lesen von Excel such mal nach "ClosedXML". InterOp brauchst Du mit dem OpenXML-Format nicht, aber ohne NuGet-Packages geht's nicht.

Wie wäre es, wenn Du eine DLL programmierst, als Datei irgendwo hin legst, in dem Script die Datei lädst und mit Reflection irgendeine Methode ausführst?

22.09.2023 - 09:11 Uhr

So funktioniert das nicht.
Was Du da unbewusst nutzt, heißt Pattern-Matching.

Diese Switch-Schreibweise wird vom Compiler in "typeof(T) is IClassItemMethodArgumentAttribute" (und so weiter) aufgelöst und das "is" prüft, ob die Instanz links dem Typ rechts zugeordnet werden kann. In deinem Fall heißt das, dass Du prüfst, ob das RuntimeType-Objekt (das wird von "typeof) zurückgegeben) das jeweilige Interface implementiert, was natürlich nie der Fall ist.

Deine Prüfung musst Du manuell mit Reflection vornehmen, Du brauchst die Type.IsAssignableFrom(Type) Methode.

13.09.2023 - 14:14 Uhr

"Key" ist ein Enum

if (e.Key == Key.D4)
{
}
22.08.2023 - 13:16 Uhr

Zitat von Abt

Es ist absolut valide (und notwendig) dass Handler andere Handler aufrufen.

Hierzu hatte ich vor einer Weile eine gegenteilige Aussage gelesen - Quelle finde ich nicht mehr.

Verstehe ich das richtig, dass ein Handler auch andere Handler aufrufen darf, also eine theoretisch beliebig lange Kette von Handlern entstehen kann?
Mit MediatR würde also ein RequestHandler eine IMediator-Dependency erhalten und andere Requests absenden.

Das ist ein Detail, was mir auch noch unklar ist:
Was ist, wenn mehrere Handler teilweise die gleiche oder ähnliche Logik haben?
Es wäre einfach, das alles in einen weiteren Handler auszulagern und den aufzurufen, aber ist das der beste Weg?

21.08.2023 - 14:45 Uhr

Wozu auch immer sie ein Repository haben wollten, ich hätte gedacht, der Code reicht 😄
Naja, jedenfalls gibt's jetzt ein Repository mit dem Bug.

Hatte zwar auch mal im Source geschaut, bin aber leider damit nicht sonderlich vertraut.

Dito - ich werde aus dem Code auch nicht schlau, zumindest sehe ich keinen Grund, warum er IRootObjectProvider nicht finden sollte, alles andere aber schon.

19.08.2023 - 20:55 Uhr

Ne, hilft leider nicht.

Ich brauch den IRootObjectProvider ja abhängig vom Kontext der MarkupExtension.
Die Services der Application sind dagegen ohne Kontext und enthalten auch keinen IRootObjectProvider.

Ich habe auch keine Lösung für das Problem gefunden, ist scheinbar ein Bug in MAUI.
Ich habe aber einen Workaround gefunden, sodass ich zumindest weiterarbeiten kann.

18.08.2023 - 18:07 Uhr

Hallo zusammen,

ich hab das Problem, dass mein MAUI-Projekt nach dem Publish sich mit einem EventLog-Eintrag sofort wieder beendet.

Das Problem ist Folgendes:
Ich habe eine MarkupExtension, die anhand des IServiceProvider-Parameters den IRootObjectProvider-Dienst abruft.
Genau diesen Dienst scheint es im Relase-Mode aber nicht zu geben? Im Debug-Mode ist alles korrekt da.

Das ist der Code der ServiceProvider-Klasse, die da zum Einsatz kommt:
https://github.com/dotnet/maui/blob/main/src/Controls/src/Xaml/XamlServiceProvider.cs
Der Code ergibt für mich keinen Sinn, wenn es den IRootObjectProvider nicht gibt, müsste der context null sein, aber dann gäbe es auch andere Dienste nicht, die aber da sind.

Hat jemand eine Idee, woran das liegt und wie ich es beheben kann?

Besten Dank 😃

04.08.2023 - 14:15 Uhr

Zitat von gfoidl

Aber von vornhinein würde ich (heute) keine Projekte mehr zwangsmäßig in (thematische) Assemblies aufteilen (XYZ.View, XYZ.ViewModel, XYZ.Models, etc.), sondern das nach Anwendungsfällen (use cases), Benutzeraktionen, usw. aufteilen.

Kannst Du das näher erleutern, woran Du das normalerweise orientierst?
Z.B. Feature, Modul, Dialog getrieben, etc.?

30.07.2023 - 15:41 Uhr

Probier's aus, ob das von Haus aus geht, weiß ich nicht, ich rate mal Nein.
Aber sowohl in Newtonsoft.Json, als auch System.Text.Json kann man Converter bauen.
Ich würde das auch tun, einfach um den Color-Type verwenden zu können.

30.07.2023 - 15:32 Uhr

Beide Varianten sind suboptimal, Variante 1 ist aber ein bisschen weniger suboptimal, als Variante 2. 😉
So spontan fällt mir kein einziger sinnvoller Fall ein, wo ein Methoden-Ergebnis als Property bereitgestellt wurde.

Richtig wäre:

public class Test
{
    private int _ctorArg;
    
    // Wenn Du den Wert public brauchst, dann eine Property:
    // public int CtorArg { get; }

    public Test(int arg)
    {
         _ctorArg = arg;
    }

    public int CalculateSomething(int arg)
    {
        return _ctorArg * arg;
    }
}

Ist natürlich schwer zu beurteilen, wie man etwas macht, ohne das Ziel zu kennen.
Es gibt viele verschiedene Wege, das selbe Ziel zu erreichen, doch normalerweise sollte man diesen Weg wählen (außer man braucht etwas anderes), da es am einfachsten ist und am wenigsten fehleranfällig.

(Und natürlich sollten bessere Namen gewählt werden)

30.07.2023 - 14:03 Uhr

Woher soll der geneigte Programmierer wissen, welche Objekte aus einer UI-Klasse stammen?

DispatcherObject ist das magische Stichwort, alle die Klassen, die von DispatcherObject ableiten, dürfen nur im UI-Thread verwendet werden.

aber ich werde die PK-Klasse auf String-Brushes (Stringwert, der ein Color-Wert darstellt) umstellen

Nimm doch direkt die Color-Werte? Ist etwas intuitiver, man sieht am Typ, was es ist.

Und den BrushConverter hast Du schon, Du musst ihn nur noch so umstellen, dass es ein ValueConverter ist (IValueConverter) und dann im Binding benutzen. Du bindest dann die String/Color-Property, als Converter deinen ColorToBrushConverter und siehe da, es funktioniert 😉

... zumindest solange es kein anderes Problem gibt.