Laden...
D
Duesmannr
myCSharp.de - Member
37
Themen
161
Beiträge
Letzte Aktivität
vor 4 Monaten
Dabei seit
28.04.2017
Beruf
C# Softwareentwickler
Herkunft
Münster
Erstellt vor einem Jahr

Zitat von Abt

Wer sagt denn, dass die GetAll-Methode bei allen API Versionen a) die gleichen Parameter hat oder b) den gleichen Rückgabetyp.

Ist ein Punkt, an den ich noch nicht gedacht habe. Die Azure DevOps Api bietet ja jetzt schon zich Versionen an.
Hast du eine Empfehlung, sowas umzusetzen? Das man sich nicht immer wiederholt.

Also zusammenfassend, würdest du dazu tendieren, die einzelnen Provider zur Verfügung zu stellen?

Erstellt vor einem Jahr

Ja kann ich voll und ganz nachvollziehen.

Der "Client" soll auch nicht alles direkt instanziieren, sondern erst, wenn es gebraucht wird. Dafür will ich Lazy<T> nutzen, wozu es auch gedacht ist.

Ebenfalls könnte man die einzelnen Provider hernehmen, wenn man dies möchte. Auch ist die ApiVersion die genutzt wird, änderbar, wie im Code zu sehen.

Erstellt vor einem Jahr

Zitat von Abt

Flow sollte sein:

  • Client (Dein Refit Interface)
  • Provider bekommt Dein Client injiziert
  • Deine Logik bekommt den/die Provider injiziert

Das ist genau das was ich meine.

Nur meine ich mit "Client", nicht den Refit Client (Interface, oder wie man es auch nennen mag).

"Deine Logik bekommt den/die Provider injiziert", meine ich mit Client.

Und dieser "Client" hat als Properties die Provider, die von außen aufgerufen werden können.

Nur bin ich wieder an dem Punkt, an dem ich nicht weiß, wie ich die Provider injizieren soll, wenn es z.B. 10 Stück sind, ohne den Konstruktor zu überladen.

Vielleicht bin ich da auch gebrandmarkt vom Microsoft Graph Client: ieht exakt so aus und ist die mit Abstand schlimmste und qualitativ schlechteste Client-Implementierung, die mir je unter gekommen ist.

So ähnlich hatte ich mir das vorgestellt, weil ich persönlich die Struktur gut finde. Darf ich Fragen, was du daran so schlimm findest? Und ggfs. andere Beispiele hast für so einen "Client" der deiner Meinung nach, eine gute Struktur besitzt?

Erstellt vor einem Jahr

Zitat von Abt

Ist das überhaupt ein guter Weg, dass so als Library anzubieten?

Prinzipiell ja; für jeden Endpunkt eine eigenen Service anbieten nicht. Service wäre hier auch der falsche Begriff. Es wäre ein Provider.
Services bilden i.d.R Logik ab - Provider macht externe Schnittstellen zugänglich.

Wir machen das generell auch immer so, wobei der Client (das Refit Interface) die gesamte API abbildet, und der Provider je nach Aufgabe getrennt wird.

Dann verstehe ich dein "Prinzipiell ja" nicht. Habe ich dich missverstanden?

Meine Frage war ja, einen Client zu haben, der die "Provider" als Properties anbietet um nicht jeden Provider manuell holen zu müssen.

Also meine Eingangsidee war:

IAzDevOpsClient client = GetClientFromAnyWhere();

//Methoden sind nur Beispiele
client.Accounts.GetAllAsync();
client.Builds.GetLogsAsync();
client.Audit.DeleteAllAsync();

Anstatt sowas machen zu müssen:

IAccountProvider accountProvider = GetProviderFromAnyWhere();
accountProvider.GetAllAsync();

IBuildProvider buildProvider = GetProviderFromAnyWhere();
buildProvider.GetLogsAsync();
Erstellt vor einem Jahr

Du sagst, dass der Provider den Client kennt.

Aber was bringt mir das? Sobald man den Client nutzt, wie wird denn dann mit den Providern interagiert?

Oder soll der Entwickler sich die Provider holen anstatt den Client? Das war ja meine Eingangsfrage.

Erstellt vor einem Jahr

Zitat von Abt

Aber nichtsdestotrotz werden viele Provder zusammen kommen. Und die alle im Konstruktor von dem Client aufzulisten, wär auch zu viel.

Der Provider kennt den Client, nicht umgekehrt.

Kannst du mir dafür ein Beispiel geben? Ich habe gerade keine Idee wie das umgesetzt werden sollte.

Warum das bei mir so ist? Ist einfach nach Aufgaben getrennt, dass jeder Provider bestimmte Endpunkte abdeckt.

Erstellt vor einem Jahr

Zitat von Abt

Ist das überhaupt ein guter Weg, dass so als Library anzubieten?

Prinzipiell ja; für jeden Endpunkt eine eigenen Service anbieten nicht.

Ging auch nicht um jeden einzelnen Endpunkt, sondern wie du es ungefähr geschrieben hast.
Accounts, Pipelines, Build, Git, Audit..

Werde die "Services" auch zu Provider umbenennen. 
Aber nichtsdestotrotz werden viele Provder zusammen kommen. Und die alle im Konstruktor von dem Client aufzulisten, wär auch zu viel.
Wie könnte man das denn realisieren?

Erstellt vor einem Jahr

Moin,

ich will eine Wrapper Library für die Azure DevOps Api schreiben.
Und für mich und/oder andere Entwickler, soll die Anwendung der Library so einfach wie möglich gemacht werden.
Dafür war meine Überlegung, dass ich eine "AzDevOpsClient" Klasse habe, die instanziiert werden muss und man damit jeden Endpunkt ansprechen kann.

Für jeden verschiedenen Endpunkt der Api, dachte ich an jeweils einen Service.
Und die Services die ich dadurch erhalte, stelle ich in dem Client als Property zur Verfügung, somit diese bei notwendigkeit genutzt werden können, ohne den Service selber zu instanziieren oder irgendwie zu erhalten. Das ganze soll mit Lazy<T> umgesetzt werden, damit die Services erst erstellt werden, sobald der Service gebraucht wird.

Das ist derzeit mein 1. Entwurf von einem Service:

public class AccountService : IAccountService
{
   #region Fields
   private readonly IAccountApi _accountApi;
   #endregion //Fields
   public AccountService(HttpClient httpClient, ILogger<AccountService>? logger = null)
   {
       this._accountApi = RestService.For<IAccountApi>(this._httpClient);
   }
   public async Task<string> GetAllAsync(string? memberId, string? ownerId, string apiVersion = "7.0")//Implementation ignorieren
   {
       return await this._accountApi.GetAllAsync(memberId, ownerId, apiVersion);
   }
}

Und das ist mein 1. Entwurf der AzDevOpsClient Klasse:

public sealed class AzDevOpsClient
{
   #region Fields
   private Lazy<IAccountService> _account;
   private readonly IHttpClientFactory _httpClientFactory;
   private readonly ILoggerFactory? _loggerFactory;
   #endregion //Fields
   #region Properties
   public IAccountService Account => this._account?.Value ??
       (this._account = new(new AccountService(this._httpClientFactory.CreateClient(),
           this._loggerFactory?.CreateLogger<AccountService>()))).Value;
   #endregion //Properties
   public AzDevOpsClient(IHttpClientFactory httpClientFactory, ILoggerFactory? loggerFactory = null)
   {
       this._httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
       this._loggerFactory = loggerFactory;
       //AuthenticationService
   }
}

In der Client Klasse instanziiere ich derzeit noch hart den AccountService. Das wird noch geändert, sobald ich weiß, wie ich DI unterstützen kann.

Am Ende existieren bestimmt 30+ Services. Aber alle Interfaces der Services via den Konstruktor zu injecten, ist nicht zielführend. Wie handhabt man das am besten?

Ist das überhaupt ein guter Weg, dass so als Library anzubieten? Wär es vllt. besser, nur die einzelnen Services bereitzustellen ohne den generellen Client?
Ich als Entwickler, fänd das mit dem Client selbsterklärend, weil ich nicht Wissen muss (bzw. nach schauen), welche Services existieren, sondern auf die Properties zugreifen kann.

Das ganze soll nachher mit DI und ohne funktionieren können.

René

Erstellt vor 2 Jahren

Danke für euren Input.

Dann werde ich wohl auf XUnit umsteigen und werde mir die HttpHandler genauer ansehen.
Ans Mocken hatte ich gar nicht gedacht, aber muss man ja, wenn man die externe API nicht testen will.

@Abt ich habe noch keine Idee wie ich das Exception Handling mit Refit machen soll. Die Snippets auf der Github Seite helfen mir da bedingt weiter.
Was ich damit meine ist, was ist der Way2Go für Exceptions in einer Library? Die ApiException sollte ich ja schon behandeln oder nicht?
Werfe ich dann eine neue custom Exception?

Erstellt vor 2 Jahren

Nabend,
falls dies nicht der richtige Bereich sein sollte, gerne verschieben.

Ich habe eine Library für die API von TMDB geschrieben in .NET 6.
API Doku ist hier zu finden.
Dabei nutze ich Refit um die Requests zu senden.

Für die Library würde ich gerne Tests schreiben um die Funktionalität der Library zu gewährleisten.
Ich nutze das Test Framework von Microsoft "MSTest".

Ich möchte gerne Wissen ob mein Vorgehen dabei das richtige ist.

Es gibt mehrere Endpunkte wo man Informationen holen kann, zB. von Filmen/Serien/Personen.
Da würde ich einen Test schreiben wo der Service authentifiziert ist und eine richtige Film Id übergibt. Sowie der Service nicht authentifiziert ist und eine Exception wirft und ich eine Id übergebe die nicht existiert.

Es gibt auch einen Endpunkt wo man Filme und Serien als Favorit markieren kann. Meine Library Implementation sieht wie folgt aus:


private async Task<Response> MarkAsFavoriteAsync(int accountId, int mediaId, AccountMediaType mediaType, bool favorite)
{
    this.CheckAuthentication();
    this.CheckSession();

    MarkAsFavoriteRequest markAsFavoriteRequest = new(mediaType.GetString(), mediaId, favorite);

    try
    {
        return this._authenticationType == AuthenticationType.ApiKey
                ? await this.AccountApi.MarkAsFavoriteAsync(accountId, this._sessionId!, markAsFavoriteRequest, this._token).ConfigureAwait(false)
                : await this.AccountApi.MarkAsFavoriteAsync(accountId, this._sessionId!, markAsFavoriteRequest).ConfigureAwait(false);
    }
    catch (ApiException ex)
    {
        if(string.IsNullOrWhiteSpace(ex.Content))
        {
            return new();
        }

        return JsonSerializer.Deserialize<Response>(ex.Content) ?? new();
    }
}

AccountApi ist das Interface um den API Call mittels Refit zu senden.
Die Methode wird von dieser hier aufgerufen:


public async Task<Response> MarkMovieAsFavoriteAsync(int accountId, int movieId, bool favorite)
{
    return await this.MarkAsFavoriteAsync(accountId, movieId, AccountMediaType.Movie, favorite).ConfigureAwait(false);
}

Die 2. Methode ist in einem Interface angegeben und das Interface benutze ich in der Test Klasse.
Jetzt weiß ich nicht, wie ich diese Methode am besten teste.
Wenn ich einen Film als Favorit markiere, bekomme ich die Rückmeldung, dass ich den Film erfolgreich als Favorit markiert habe.
Sende ich den Request jetzt nochmal, bekomme ich die Rückmeldung, dass der Eintrag geupdated worden ist.
Müsste ich die Markierung nach dem Ende des Tests nicht zurücksetzen? Wie handhabt man das bei einer öffentlichen API?
Und wie teste ich das in diesem konkreten Fall am besten?

Das sind zwei Tests die ich für die Methode geschrieben habe:


[TestMethod]
[TestProperty(nameof(AccountService), nameof(AccountService_MarkMovieAsFavorite))]
[TestCategory(IntegrationTestCategoryName)]
public async Task AccountService_MarkMovieAsFavorite()
{
	//Arrange
	this._accountService.UseExistingSessionId(this.AccountSession!);
	int movieId = 60735;
	bool favorite = true;
	bool success = true;

	//Act
	Response response = await this._accountService.MarkMovieAsFavoriteAsync(this.AccountId, movieId, favorite);

	//Assert
	Assert.IsNotNull(response);
	Assert.AreEqual(success, response.Success);
}

[TestMethod]
[TestProperty(nameof(AccountService), nameof(AccountService_MarkMovieAsFavorite_InvalidValues))]
[TestCategory(IntegrationTestCategoryName)]
public async Task AccountService_MarkMovieAsFavorite_InvalidValues()
{
	//Arrange
	this._accountService.UseExistingSessionId(this.AccountSession!);
	int movieId = 0;
	bool favorite = true;
	bool success = false;

	//Act
	Response response = await this._accountService.MarkMovieAsFavoriteAsync(this.AccountId, movieId, favorite);

	//Assert
	Assert.IsNotNull(response);
	Assert.AreEqual(success, response.Success);
}

Schönen Abend noch!
René