Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Portal
  • |
  • Mitglieder
Beiträge von Duesmannr
Thema: Testen einer API Wrapper Library
Am im Forum: Rund um die Programmierung

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?

Thema: Testen einer API Wrapper Library
Am im Forum: Rund um die Programmierung

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é

Thema: Taskleiste-Überlauf-App W11
Am im Forum: Rund um die Programmierung

Zitat von BulliM
Ich bin auf GitHub fündig geworden. Damit kann ich mir eine Übergangslösung erstellen.

Wäre evtl. gut, wenn du das Github Repository hier auch verlinkst, für andere User, die die selbe Problematik haben.

Thema: Records vs. Class für Models/DTOs
Am im Forum: Rund um die Programmierung

Nabend,

ich schreibe mir gerade eine Library mit .NET 6 um entspannter mit einer API zu kommunizieren.
Dahingehend, erstelle ich für jede Response, explizite Klassen/Records, damit ich die direkt typisieren kann.

Bin noch nicht ganz auf den grünen Zweig mit Records gekommen.
Bei meiner Recherche, bin ich auch auf die Top Antwort von dem Stackoverflow Eintrag gestoßen.

Also habe ich meine Models/DTOs als Records definiert.

Funktioniert auch soweit so gut. Nur ist mir dann ein Problem aufgefallen.

Ich habe als Beispiel diesen Record:


public record CreateSessionRequest([property: JsonPropertyName("request_token")] string RequestToken);

Den Record nutze ich bspw. für ein Request.

Für ein zweiten Request brauche ich zu der genannten Property aber auch noch Username und Password. Also Vererbung. Um auch weniger schreiben zu müssen, aber auch die Übersichtlichkeit zu behalten.

Also ein 2. Record, der den oberen Record erbt.


//Ohne Vererbung
public record CreateSessionWithCredentialsRequest(
        [property: JsonPropertyName("username")] string Username,
        [property: JsonPropertyName("password")] string Password
        );

//Mit Vererbung
public record CreateSessionWithCredentialsRequest(
        [property: JsonPropertyName("username")] string Username,
        [property: JsonPropertyName("password")] string Password
        ) : CreateSessionRequest(); //Hier in den Klammern muss die Property RequestToken angegeben werden

Wie im Kommentar erwähnt, kommt hier meine Frage?/Problem auf.
Ich müsste dem 2. Record ebenfalls eine Property 'RequestToken' hinzufügen, damit ich den Wert zum Basis Record übergeben kann.

Das ist jetzt eine Property. Aber ich habe auch Fälle, da sind es gleich mal 20 Properties und da lohnt sich die Vererbung halt nicht, wenn ich jede Property neu definieren muss.

Ich könnte den Record jetzt genau so definieren, wie eine Klasse, weil ich dann einen parameterlosen Konstruktor habe, aber damit entfällt zB. auch die Funktionalität des Deconstructs (sinnig oder nicht, sei dahingestellt).

Und da stelle ich mir die Frage, macht ein Record überhaupt Sinn?
Da kann ich auch gleich normale Klassen verwenden. Und dahingehend, fällt mir dann auch kein Beispiel ein, wann ich denn Records nutzen soll.
Sie sind immutable und reichen Werte weiter. Aber sobald man mit Vererbung arbeiten möchte, würde nach meinem jetzigen Kenntnisstand, Records wegfallen, weil man eben die Properties alle neu definieren muss.

Ich freue mich auf auf Ideen, womit ich das besser verstehen kann oder auch, wenn ich etwas übersehe.

Schönen Abend noch!

Thema: EF Core - Mappen von Expressions
Am im Forum: Datentechnologien

Ich wüsste aber auch nicht, wie ich in meiner Architektur auf Projektionen setze.

Wie soll ich denn die Expression für die Include Methode typsiert machen?

Thema: EF Core - Mappen von Expressions
Am im Forum: Datentechnologien

Im Endeffekt also, wenn ich bei Eager Loading bleibe, muss ich für unterschiedliche Includes, jeweils eine seperate Methode haben, weil ich die Expressions für Includes nicht mappen kann. Korrekt?

Thema: EF Core - Mappen von Expressions
Am im Forum: Datentechnologien

Da ich eine BLL Ebene und eine Repository Ebene besitze, wären die Projections in der Repository Ebene.
Oder verstehe ich das falsch? Da .ProjectTo auf IQueryable setzt und IQueryable in der Repository Ebene bleiben sollte.

Ich weiß aber noch nicht so ganz, wie ich auf das Query Specification Pattern verzichten kann.

Nehmen wir ein Beispiel:
Schüler, Klassen, Schule.

Ich brauche im Frontend eine Klasse und auch nur die dazugehörigen Schüler, nicht die Schule. (Eager Loading wäre ein Include auf Schüler)
Rufe eine generische GET Methode (die nur die Klasse zurückgeben würde ohne die Schüler) in der BLL auf.

Die BLL-Methode ruft die GET-Methode im Repository auf und dann mappe ich den Result in der BLL Methode und gib es zum Frontend zurück. (Als Bsp. im Repository: context.Classes.ToList()).

Wie halte ich das denn jetzt generisch, dass ich bis zum Repository weiterleiten kann, dass ich die Schüler auch brauche?

Oder habe ich einen Denkfehler?

Thema: EF Core - Mappen von Expressions
Am im Forum: Datentechnologien

Also würdest du das Query Specification Pattern nicht empfehlen und im DAL eine extra Methode dann dafür haben, die mir explizit die Pages zurück gibt, mit den Includes die ich benötige?

Edit:
Bzw. das Pattern nur für alles andere verwenden außer Includes?

Thema: EF Core - Mappen von Expressions
Am im Forum: Datentechnologien

Technologie: Blazor Server, .NET 5
DB: MS SQL
Genutzt wird AutoMapper 10.1.1, AutoMapper.Extensions.ExpressionMapping 4.1.1, Microsoft.EntityFrameworkCore 5.0.6

Ich nutze das Query Specification Pattern, DTO's im Frontend und das Repository Pattern.

Im Frontend erstelle ich eine neue Instanz von der Query mit dem benötigten DTO.


ISpecification<PageDTO> specification = new BaseSpecification<PageDTO>(null);
specification.AddInclude(x => x.Topic);

"x.Topic" ist eine Property, die auf das DTO von Topic zeigt.

Die Query übergebe ich in die benötigte BLL Methode und will dann die Query von DTO auf die richtige Entität mappen.

protected virtual ISpecification<TEntity> MapSpecification(ISpecification<TDTOEntity> specification)
{
    Expression<Func<TEntity, bool>> criteriaExpression = this.Mapper.Map<Expression<Func<TEntity, bool>>>(specification.Criteria);

    ISpecification<TEntity> result = new BaseSpecification<TEntity>(criteriaExpression);

    foreach (Expression<Func<TDTOEntity, object>> includeDTOExpression in specification.Includes)
    {
        Expression<Func<TEntity, object>> includeExpression = this.Mapper.Map<Expression<Func<TEntity, object>>>(includeDTOExpression);

        result.AddInclude(includeExpression);
    }

    specification.IncludeStrings.ForEach(x => result.AddInclude(x));

    result.AddPaging(specification.PagingSpecification);

    return result;
}

Wenn ich beispielsweise bei Criteria sage

Topic:
x => x.Name == "Test";

funktioniert das Mapping. Reicht auch für Criteria.

Aber bei dem Include funktioniert das Mapping nicht.
Sobald ich das gemappte Include zum Repository übergebe und EF Core das Statement verarbeitet, bekomme ich diese Fehlermeldung:
Fehler
Error: System.InvalidOperationException: The expression 'Convert(x.Topic, Object)' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.

Kann man die Navigation Property überhaupt Mappen? Oder kann ich mit dem Include gar nicht auf DTO's gehen und muss direkt auf die Entitäten zurück greifen?

Grüße
duesmannr

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Zitat von Abt
Schade, dass uns andere statt Du den Hinweis gegeben haben, dass Du das Thema auch noch wo anders veröffentlich hast :-)
unable-to-upload-files-that-are-larger-than-2-gb-to-web-api-blazor-server-side

Nicht nett -> [Hinweis] Wie poste ich richtig? 2.2 Keine Crossposts
Ebenso verstößt Du damit den Regeln von Stackoverflow..

Hatte ich im nachhinein erstellt, weil die englische .NET Community nicht in diesem Forum vertreten ist.
Ist aber gelöscht.
Zitat von Abt
Dadurch hab ich mir durchgelesen, wie das aktuell funktioniert; und es ist wohl so, dass es derzeit keine Streaming Möglichkeit von WebAssembly gibt (egal ob Blazor oder was anderes).

Ja läuft^^
Wie erwähnt, habe ich das dann mit Javascript umgesetzt.

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Ich habe den Upload auch gerade nochmal mit Blazor WebAssembly versucht.

Es fliegt genau die gleiche Exception.

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Zitat von Abt
Sieht man im Endeffekt auch am Markt: alle, die können, setzen bereits auf den Client.

Meinst du damit Blazor WebAssembly?

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Nabend,

zur Info, dass Github Issue wurde ins Backlog verschoben.
Falls da was neues kommen sollte, sag ich hier Bescheid.

Dann dachte ich, löse ich das nicht über Streaming der ganzen Datei, sondern transferiere die Datei in Chunks.


buffer = new byte[8120];
using Stream stream = file.OpenReadStream(long.MaxValue); //file = IBrowserFile vom <InputFile> Element
await stream.ReadAsync(buffer, 0, buffer.Length);

Der Code-Schnipsel sorgt ebenfalls für die gleiche Exception. (3. Zeile)
Also gibt es derzeit mit der Blazor Componente keine Möglichkeit, Dateien über 2? GB zu handlen.
Für meinen Test war es eine Datei mit 9,7 GB.

Werde das ganze über Plain JS lösen, weil da funktioniert das..

Grüße

Edit:
Habe gerade nochmal gegooglet und spontan vom 15.11.2020 das Github Issue gefunden. Gleicher Fehler, ist aber auch nicht gefixt.

Thema: Gibt es eine Lösung, um eine Website in Chrome zu blockieren?
Am im Forum: Smalltalk

- Kannst eine Seite im Router blockieren.
- Gibt bestimmt eine Extensions dafür

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Issue ist erstellt.
Sollte was dabei raus kommen, werde ich die Lösung hier bekannt geben.

Schönen Abend noch

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Dann dürfte es laut einem Github Issue eine Fehlermeldung seitens SignalR werfen.

Aber auch mit


services.Configure<HubOptions>(options =>
            {
                options.MaximumReceiveMessageSize = null;
            });

Was mit Workaround in dem Issue gemeint ist, ändert es nichts.

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Dann erstelle ich mal ein Github Issue.

Habe jetzt mal mehrere Dateien getestet.

3,56GB => Kommt an, kann aber nichts von gelesen werden (die while Schleife wird übersprungen)
über 2GB gleiches Verhalten.

Unter 2 GB => Kommt ebenfalls an, aber der Content von der Datei kann auch gelesen werden.

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Wie ich es herausfinden soll, stellt mich gerade auf die Probe.

Mit deinem Link:

Zitat
Der folgende Ansatz wird für Microsoft Azure Blob Storage empfohlen, da die Stream-Klasse der Datei direkt für UploadBlobAsync bereitgestellt wird:

Das ganze will ich ja auch und mache ich eig. ja auch. Nur das es nicht Azure ist.

Edit:
Selbst wenn ich den StreamPart von Refit weg lasse, bekomme ich das gleiche Ergebnis.


Stream stream = file.OpenReadStream(long.MaxValue);

response = await this.FileApi.UploadFileAsync2(stream);

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Da stimme ich dir zu.
Nur komme ich gar nicht bis zur API Methode.
Die wird quasi nicht ausgeführt und hab daher auch keine Response vom Server.

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Da melde ich mich wieder.

[Blazor App]
Refit installiert, Interface erstellt, Services hinzugefügt => Exception.

[Console App]
Copy Paste => Funktioniert. (Lokale Datei, 8.7GB)

Hier ist einmal das Interface


public interface IFileApi
    {
        [Multipart]
        [Post("/api/v1/File")]
        Task<HttpResponseMessage> UploadFileAsync(StreamPart streamPart);
    }
So registriere ich die Services in der Startup Klasse


services.AddRefitClient<IFileApi>()
                .ConfigureHttpClient(c => c.BaseAddress = new Uri(apiBaseAddress)); //apiBaseAddress = https://localhost:5003/

In der Razor Datei injecte ich das Interface, iteriere durch die IBrowserFiles und rufe die Methode auf.


foreach (IBrowserFile file in this._selectedFiles)
        {
            try
            {
                StreamPart streamPart = new(file.OpenReadStream(long.MaxValue), file.Name);

                var res1 = await this.FileApi.UploadFileAsync(streamPart);
            }
            catch (Exception ex)
            { }
        }

Lande im Catch. Siehe UI Info im Anhang. Und aus dem Stacktrace, kann ich nicht erkennen, woran es liegt.
Ich denke es liegt an dem IBrowserFile Interface bzw. der OpenReadStream Methode die aufgerufen wird.
Die Datei die hochgeladen wurde, war 8,7GB groß. Eine TextDatei von 0 Bytes konnte ich ohne Probleme hochladen bzw. ist der Call zur API durch gegangen.
Aber woran kann es liegen?



Hier noch einmal der Stacktrace von der Exception:

   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at Microsoft.AspNetCore.Components.Forms.RemoteBrowserFileStream.<FillBuffer>d__9.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.IO.Pipelines.PipeCompletion.ThrowLatchedException()
   at System.IO.Pipelines.Pipe.GetReadResult(ReadResult& result)
   at System.IO.Pipelines.Pipe.ReadAsync(CancellationToken token)
   at System.IO.Pipelines.Pipe.DefaultPipeReader.ReadAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Components.Forms.RemoteBrowserFileStream.<CopyFileDataIntoBuffer>d__10.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ValueTaskAwaiter`1.GetResult()
   at Microsoft.AspNetCore.Components.Forms.BrowserFileStream.<ReadAsync>d__22.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.IO.Stream.<CopyToAsyncInternal>d__29.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Net.Http.HttpContent.<<CopyToAsync>g__WaitAsync|56_0>d.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Net.Http.MultipartContent.<SerializeToStreamAsyncCore>d__23.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Net.Http.HttpContent.<<CopyToAsync>g__WaitAsync|56_0>d.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Net.Http.HttpConnection.<SendRequestContentAsync>d__62.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Net.Http.HttpConnection.<SendAsyncCore>d__56.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Net.Http.HttpConnection.<SendAsyncCore>d__56.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.Http.HttpConnectionPool.<SendWithRetryAsync>d__72.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Threading.Tasks.ValueTask`1.get_Result()
   at System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable`1.ConfiguredValueTaskAwaiter.GetResult()
   at System.Net.Http.RedirectHandler.<SendAsync>d__4.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.Http.DiagnosticsHandler.<SendAsyncCore>d__5.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.<SendAsync>d__5.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.<SendAsync>d__5.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.Net.Http.HttpClient.<SendAsyncCore>d__85.MoveNext()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at Refit.RequestBuilderImplementation.<>c__DisplayClass14_0`2.<<BuildCancellableTaskFuncForMethod>b__0>d.MoveNext() in /_/Refit/RequestBuilderImplementation.cs:line 296
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at RD.Intelligence.Web.Components.AddFilesComponent.<UploadFilesAsync>d__14.MoveNext() in D:\Repositories\RD.Intelligence\RD.Intelligence.Web\Components\AddFilesComponent.razor:line 87

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Ich nutze Streaming, wegen der Aussage von mir:

Zitat von Duesmannr
Der Upload soll gestreamt werden, weil Dateien von 0-20 GB übertragen werden.

Da werde ich IFormFile nicht verwenden

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Zitat von Abt
Aber Feedback zum Code:
- Du hast viel obsolete Code vermutlich aus verschiedenen Quellen zusammenkopiert, selbst die Error Code haste übernommen (???)

Stimmt. Derzeit aber nur zum testen, bis es funktioniert, danach wird der Code deutlich verbessert.
Zitat von Abt
- Man sollte IFormFile verwenden

Warum? Werden die Files dann nicht im Arbeitsspeicher zwischen gespeichert? Warum nutzt die Microsoft Doku das ebenfalls nicht um große Dateien hochzuladen?
Zitat von Abt
- Datenbank-Operationen darf man auch ruhig auslagern, muss man nich mit Logik mischen ;-)

Kommt

Aber danke für dein Feedback

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Ich werde es mal einbauen und dann gucken ob es funktioniert.

Morgen Abend gibts dann Rückmeldung

Thema: Datei Upload über eine Web-API
Am im Forum: Web-Technologien

Nabend,

Ausgangslage:
Asp.Net Core WEB API .NET 5
Blazor Server-Side .NET 5

Ich habe eine Controller Methode die den Upload verwaltet, was ich durch diese Seite realisiert habe.

Der Upload soll gestreamt werden, weil Dateien von 0-20 GB übertragen werden.

Mein Code sieht so aus:


[HttpPost]
        [DisableRequestSizeLimit]
        public async Task<IActionResult> UploadFileAsync()
        {
            if (!MultipartRequestHelper.IsMultipartContentType(this.Request.ContentType))
            {
                this.ModelState.AddModelError("File", "The request couldn't be processed (Error 1).");

                return this.BadRequest(this.ModelState);
            }

            string boundary = MultipartRequestHelper.GetBoundary(
                MediaTypeHeaderValue.Parse(this.Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);

            MultipartReader reader = new(boundary, this.HttpContext.Request.Body);

            MultipartSection section = await reader.ReadNextSectionAsync();

            while (section != null)
            {
                bool hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out ContentDispositionHeaderValue contentDisposition);

                if (hasContentDispositionHeader)
                {
                    if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
                    {
                        try
                        {
                            await this._fileBLL.UploadFileAsync(section, contentDisposition);
                        }
                        catch (Exception ex)
                        {
                            this.ModelState.AddModelError("Processing", ex.ToString());
                        }
                    }
                    else
                    {
                        this.ModelState.AddModelError("File", "The request couldn't be processed (Error 2).");
                    }
                }

                section = await reader.ReadNextSectionAsync();
            }

            return this.Ok(this.ModelState);
        }

Die UploadFileAsync BLL Methode ist diese hier


public async Task UploadFileAsync(MultipartSection section, ContentDispositionHeaderValue contentDisposition)
        {
            string trustedFileName = WebUtility.HtmlEncode(contentDisposition.FileName.Value);

            IMongoDatabase mongoDatabase = this._mongoClient.GetDatabase(this._mongo_DatabaseName);

            GridFSBucket bucket = new(mongoDatabase, new()
            {
                BucketName = this._mongo_CollectionName,
                ChunkSizeBytes = this._mongo_ChunkSizeBytesLength
            });

            Guid videoId = Guid.NewGuid();

            using GridFSUploadStream uploadStream = await bucket.OpenUploadStreamAsync(trustedFileName, new()
            {
                Metadata = new()
                {
                    { "Id", videoId.ToStringValue() }
                }
            });

            int bytesRead = 0;
            byte[] buffer = new byte[8192];

            while ((bytesRead = await section.Body.ReadAsync(buffer.AsMemory(0, buffer.Length))) != 0)
            {
                await uploadStream.WriteAsync(buffer.AsMemory(0, bytesRead));
            }

            await uploadStream.CloseAsync();
            await section.Body.DisposeAsync();
        }

Mit Postman (Foto im Anhang) funktioniert der Upload einwandfrei und die Datei wird in die MongoDB geschrieben.

Das ganze will ich nun auch mit der Blazor Server-Side App realisieren.
Dafür habe ich den Code


public async Task UploadFileAsync(IBrowserFile browserFile)
        {
            HttpRequestMessage requestMessage = new(HttpMethod.Post, "api/v1/File");

            MultipartFormDataContent multipartFormDataContent = new();
            multipartFormDataContent.Add(new StreamContent(browserFile.OpenReadStream(long.MaxValue)), 
                browserFile.Name, browserFile.Name);

            //requestMessage.Content = multipartFormDataContent;

            //HttpResponseMessage response = await this._client.SendAsync(requestMessage);
            HttpResponseMessage response = await this._client.PostAsync("api/v1/File", multipartFormDataContent);

            if (response.IsSuccessStatusCode)
            {
                string responseString = await response.Content.ReadAsStringAsync();
            }
        }


IBrowserFile browserFile
kommt von der UI:

<input type="file" multiple />

Das ganze funktioniert nur bedingt.
Der Code-Block der nicht ausgeführt wird auf diesem Weg ist der:


int bytesRead = 0;
            byte[] buffer = new byte[8192];

            while ((bytesRead = await section.Body.ReadAsync(buffer.AsMemory(0, buffer.Length))) != 0)
            {
                await uploadStream.WriteAsync(buffer.AsMemory(0, bytesRead));
            }

            await uploadStream.CloseAsync();
            await section.Body.DisposeAsync();

Er überspringt die while Schleife, weil der Body anscheinend leer ist.

Aber was mache ich beim Post Request falsch? Durch googlen bin ich bis jetzt auch nicht schlauer geworden.
Ob ich HttpClient.PostAsync oder HttpClient.SendAsync aufrufe, ändert nichts.

Habt Ihr Anregungen?

Grüße und einen schönen Abend

Thema: Blazor Server-Side Cookie Authentication
Am im Forum: Web-Technologien

Zitat von Abt
bei Blazor meist über ProtectedSessionStorage; nicht über Cookies.
Der Storage sagt mir auf Anhieb nichts, gucke ich mir mal an.
Zitat von Abt
Soweit ich weiß wird aber nichts in AuthenticationStateProvider per default gespeichert.
Das ist korrekt.
Zitat von Abt
Edit: erster Google Treffer dazu: Session Authentication with ASP.NET Core 3.1 Blazor Server Side

Habe ich auch schon des Öfteren überflogen. Dann gucke ich mir das nochmal genauer an.

Vorerst danke

Thema: Blazor Server-Side Cookie Authentication
Am im Forum: Web-Technologien

Moin Abt,

ich meine die Standard implementierte Authentifizierung von Microsoft, wenn man bei der Erstellung den Authentication Type auf Individual Accounts setzt.

Der dadurch erzeugte ApplicationDbContext der vom IdentityDbContext erbt, sowie die Standard Login/Logout etc. Seiten von Identity, brauche ich alles nicht.

Bei Blazor Server-Side gibt es keinen direkten HttpContext, weil die Kommunikation über SignalR läuft.
Dafür ist ja dann der AuthenticationStateProvider da.

Mit dem Code kann ich mich auch anmelden (Razor Page, User Verifizierung seitens DB ausgeschlossen)


List<Claim> claims = new()
        {
            new(ClaimTypes.Email, this.Model.Username)
        };

        ClaimsIdentity claimsIdentity = new(claims, CookieAuthenticationDefaults.AuthenticationScheme);

        ClaimsPrincipal claimsPrincipal = new(claimsIdentity);

        AuthenticationState authenticationState = new AuthenticationState(claimsPrincipal);

        (AuthenticationStateProvider as ServerAuthenticationStateProvider).SetAuthenticationState(Task.FromResult(authenticationState));

Durch das setzen des AuthenticationState sehe ich beispielsweise diese h1

<AuthorizeView>
    <Authorized>
        <h1>You´re authorized.</h1>
    </Authorized>
</AuthorizeView>

auf der gleichen Seite.

Was mir aber jetzt fehlt, dass der State gespeichert wird, was ja über das Cookie laufen würde, wenn ich es richtig verstehe.
Und wie dieser dann wieder dazu führt, dass der User weiterhin angemeldet ist.

Thema: Blazor Server-Side Cookie Authentication
Am im Forum: Web-Technologien

Morgen,

Ausgangssituation: Blazor Server-Side .NET 5
Ziel: Einfache Authentication ohne Identity via Cookie.

Soweit ich das verstanden habe, wird man ein- und ausgeloggt über den


AuthenticationStateProvider

Dieser kann bei den Services mit


ServerAuthenticationStateProvider

registriert werden.

Dann kann man mit einem AuthenticationState


AuthenticationState authenticationState = new AuthenticationState(ClaimsPrincipal)

als Parameter den StateProvider aufrufen


(AuthenticationStateProvider as ServerAuthenticationStateProvider).SetAuthenticationState(authenticationState);

Was mir jetzt fehlt, dass die Informationen gespeichert und ausgelesen werden. Und da kommt Cookie ins Spiel.

Bei den Services habe ich das hinzugefügt


services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.LoginPath = "/User/Login";
                    options.LogoutPath = "/User/Logout/";
                });

und bei der App, dass


app.UseAuthentication();
app.UseAuthorization();

Aber wie geht es jetzt weiter mit dem Cookie?
Was man noch machen kann, ist eine eigene Implementierung vom AuthenticationStateProvider, aber das hilft mir auch nicht weiter.

Alle Beispiele die ich finde nehmen irgendein HttpContext um sich anzumelden etc. (Bspw. hier: how-to-authenticate-a-user-with-blazor-server)
Aber ich habe noch keine Lösung ohne Identity gefunden bzw. auch keine mit Identity die ich auf meinen Fall adaptieren könnte.

Habt Ihr eine Idee?

Grüße
duesmannr

Thema: Code Sharing between Projects
Am im Forum: Web-Technologien

Moin,

ich bin gerade am überlegen, wie ich das folgende Problem löse.

Ich habe eine
- Web-Anwendung (Blazor, Server-Side, .NET5)
- Razor Class Library (.NET5) (RCL)

Ich habe in der RCL mehrere Komponenten, die auch den CSS Scope Identifier von Blazor nutzen.

Die hinterlegte css Datei wird von einer scss Datei generiert.
Die genannte scss Datei importiert eine "_mixins.scss" Datei, um mixins zu nutzen, die das Leben vereinfachen. (_mixins.scss Datei liegt auch in der RCL)

Funktioniert alles soweit.

Problem ist jetzt, dass ich die "_mixins.scss" Datei auch gerne in der Web-Anwendung in eine scss Datei importieren würde, nur weiß ich nicht wie.
Ein einfacher Import über einen relativen Pfad funktioniert nicht, weil Web Compiler, die Datei nicht findet.

Dann hatte ich gegooglet und bin auf "Shared Project" gestoßen.

Habe ich dann mal ein Projekt erstellt, die scss Datei hinzugefügt und in der Web-Anwendung hinzugefügt. Konnte die Datei ohne Probleme in einer anderen scss Datei importieren.
Nur kann ich die RCL nicht in das Shared Project migrieren, weil da die CSS Isolation fehlt.

Und ein drittes Projekt für nur ein paar scss Dateien finde ich, ist ein zu großer Overhead.

Andere Möglichkeit die ich noch gesehen hatte, die scss Datei zu linken in einen Ordner in der Web-Anwendung. Funktioniert aber ebenfalls auch nicht, weil der die Datei ebenfalls nicht findet.

Habt Ihr eine Idee?
Grüße

P.S.: Noch einmal hier die Struktur:


RazorClassLibrary
    - Components
        - Component.razor
        - Component.razor.css
        - Component1.razor
        - Component1.razor.css
    - wwwroot
        - scss
            - _mixins.scss
            - Component.scss (importiert die _mixins.scss)

BlazorServerSide
    - wwwroot
        - scss
            - anotherScssFile (soll die _mixins.scss auch importieren)

Thema: Expression<Func> Mapping - InvalidOperationException - Could not be translated
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Als Nachtrag, hab es über Automapper geschafft.
Aber nicht das ganze Objekt direkt, sondern die Properties einzelnt zu mappen.

Man braucht dafür das Nuget Package von Automapper 'AutoMapper.Extensions.ExpressionMapping'.
Und dann muss man noch beim hinzufügen des Automappers im Service die Methode aufrufen


automapper.AddExpressionMapping();

//So sieht das dann ganz aus
services.AddAutoMapper((serviceProvider, automapper) =>
            {
                automapper.AddExpressionMapping();
                automapper.AddProfile<AutoMapping>();
            }, typeof(ApplicationDbContext).Assembly);

Das hatte ich davor auch schon alles.

Was ich nun mache, das ich die einzelnen Properties vom Specification Objekt durch gehe und diese mappe:


protected virtual ISpecification<TEntity> MapSpecification(ISpecification<TDTOEntity> specification)
        {
            Expression<Func<TEntity, bool>> criteriaExpression = this.Mapper.Map<Expression<Func<TEntity, bool>>>(specification.Criteria);

            ISpecification<TEntity> result = new BaseSpecification<TEntity>(criteriaExpression);

            foreach (Expression<Func<TDTOEntity, object>> includeDTOExpression in specification.Includes)
            {
                Expression<Func<TEntity, object>> includeExpression = this.Mapper.Map<Expression<Func<TEntity, object>>>(includeDTOExpression);

                result.AddInclude(includeExpression);
            }

            specification.IncludeStrings.ForEach(x => result.AddInclude(x));

            result.AddPaging(specification.PagingSpecification);

            return result;
        }

Und das funktioniert auch. Nur unvorteilhaft, wenn Properties dazu kommen, muss man diese ebenfalls mit aufnehmen.

Thema: Expression<Func> Mapping - InvalidOperationException - Could not be translated
Am im Forum: Basistechnologien und allgemeine .NET-Klassen

Nabend Leute,

Verwendete Systeme/Bibs: EF Core 5, Automapper

Ich kann von der UI eine Specification erhalten, die verschiedene Properties enthält, um den Get Request zu steuern.


public interface ISpecification<TEntity>
    {
        #region Properties

        Expression<Func<TEntity, bool>> Criteria { get; }

        List<Expression<Func<TEntity, object>>> Includes { get; }

        List<string> IncludeStrings { get; }

        PagingSpecification PagingSpecification { get; }

        #endregion //Properties

        #region Methods

        void AddInclude(Expression<Func<TEntity, object>> includeExpression);

        void AddInclude(string includeString);

        void AddPaging(PagingSpecification pagingSpecification);

        #endregion //Methods
    }

Das TEntity ist bei mir ein DTO.
Und in der BLL will ich dann die Specification<DTO> zu Specification<Entity> mappen.

Das würde z.B. ankommen:


ISpecification<CategoryDTO> specification = new BaseSpecification<CategoryDTO>(x => x.Id == idToGet);
//idToGet = Guid variable.


Mit Automapper schaffe ich es nicht, es zu mappen. Der Wert ist immer null. (Criteria)
Deshalb manuell mappen.
Wie mappt man eine Expression? I dont know => google.

Bin dann auf den Eintrag gestoßen.

Wobei ich bei der _parameter Variable das 'c' entfernt habe.
Mapping erfolgreich.

1. Bild im Anhang: So sieht es dann im Debugger aus.

Lass ich die Expression nun gegen die Db laufen, erhalte ich eine InvalidOperationException, sobald ich auf den Entites mit Where und dem Criteria filtere.
Message:

The LINQ expression 'DbSet<Category>()
    .Where(c => x.Id == __idToGet_0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See [url]https://go.microsoft.com/fwlink/?linkid=2101038[/url] for more information.

In der Message steht "c => x.Id....".
Ich gehe davon aus, dass es an dem "c" liegt. Aber woher kommt das?

P.S.: Ich weiß wie Expressions oberflächlich funktionieren, aber so genau, wie man diese mappt, woraus die konkret bestehen weiß ich nicht. Deswegen habe ich vorerst die Methoden aus dem SO Beitrag 1:1 übernommen.

Grüße
Duesmannr