Laden...
D
Duesmannr myCSharp.de - Member
C# Softwareentwickler Münster Dabei seit 28.04.2017 161 Beiträge
Benutzerbeschreibung

Forenbeiträge von Duesmannr Ingesamt 161 Beiträge

16.08.2023 - 20:32 Uhr

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?

16.08.2023 - 20:18 Uhr

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.

16.08.2023 - 19:02 Uhr

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?

16.08.2023 - 18:47 Uhr

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();
16.08.2023 - 18:41 Uhr

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.

16.08.2023 - 17:05 Uhr

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.

16.08.2023 - 13:18 Uhr

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?

16.08.2023 - 12:47 Uhr

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é

15.09.2022 - 22:09 Uhr

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?

15.09.2022 - 19:30 Uhr

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é

30.12.2021 - 09:37 Uhr

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.

05.11.2021 - 20:13 Uhr

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!

23.08.2021 - 16:32 Uhr

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?

23.08.2021 - 16:28 Uhr

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?

23.08.2021 - 16:04 Uhr

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?

23.08.2021 - 12:42 Uhr

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?

23.08.2021 - 12:27 Uhr

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:

Fehlermeldung:
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
>
.

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

14.06.2021 - 08:40 Uhr

Schade, dass uns andere statt Du den Hinweis gegeben haben, dass Du das Thema auch noch wo anders veröffentlich hast 🙂
>

Nicht nett ->
>
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.

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.

13.06.2021 - 22:30 Uhr

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

Es fliegt genau die gleiche Exception.

13.06.2021 - 22:19 Uhr

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

Meinst du damit Blazor WebAssembly?

12.06.2021 - 01:06 Uhr

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.

11.06.2021 - 19:37 Uhr
  • Kannst eine Seite im Router blockieren.
  • Gibt bestimmt eine Extensions dafür
09.06.2021 - 22:27 Uhr

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

Schönen Abend noch 🙂

09.06.2021 - 21:17 Uhr

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.

09.06.2021 - 20:53 Uhr

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.

09.06.2021 - 20:31 Uhr

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

Mit deinem Link:

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);

09.06.2021 - 20:16 Uhr

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.

09.06.2021 - 20:02 Uhr

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

08.06.2021 - 23:00 Uhr

Ich nutze Streaming, wegen der Aussage von mir:

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

Da werde ich IFormFile nicht verwenden 🙂

08.06.2021 - 22:55 Uhr

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.

  • 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?

  • Datenbank-Operationen darf man auch ruhig auslagern, muss man nich mit Logik mischen 😉

Kommt 🙂

Aber danke für dein Feedback 🙂

08.06.2021 - 22:52 Uhr

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

Morgen Abend gibts dann Rückmeldung 🙂

08.06.2021 - 22:42 Uhr

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 🙂

20.05.2021 - 15:10 Uhr

bei Blazor meist über ProtectedSessionStorage; nicht über Cookies.

Der Storage sagt mir auf Anhieb nichts, gucke ich mir mal an.

Soweit ich weiß wird aber nichts in AuthenticationStateProvider per default gespeichert.

Das ist korrekt.

Edit: erster Google Treffer dazu:
>

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

Vorerst danke 🙂

20.05.2021 - 14:44 Uhr

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.

20.05.2021 - 08:59 Uhr

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

15.04.2021 - 15:49 Uhr

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)

27.03.2021 - 14:08 Uhr

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.

27.03.2021 - 02:30 Uhr

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

25.03.2021 - 00:09 Uhr

Geht von Haus aus.

Und auf den Geräte-Manager oder Regedit zugreifen zu können, muss man Admin sein.

10 Sekunden gegooglet.

Erste Seite

Zweite Seite

26.02.2021 - 14:23 Uhr

Durch die Google Suche von @Abt findet man folgende Stackoverflow Einträge:

how-to-change-the-name-of-a-networkadapter-in-c
get-name-of-network-that-a-network-interface-is-connected-to

20.02.2021 - 19:28 Uhr

Zu den Methodenaufruf


OnPropertyChanged()

würde ich keine magic strings nehmen, sondern


nameof(PropertyName)

21.01.2021 - 09:19 Uhr

Moin,

WinForms ist eine Konsolenanwendung.
Es gibt in deinem Projekt die Program.cs, die die Form aufruft.

Das ist der Einstiegspunkt in deiner WinForms Anwendung und in einer Konsolenanwendung ist es ebenfalls der Einstiegspunkt.

Da du die GUI dann nicht brauchst, kannst du die Logic in die Program.cs implementieren die dann 24/7 läuft oder immer wieder ausgeführt wird.

Erstell doch mal eine Konsolenanwendung und guck es dir an, ist sehr einfach.

Grüße

05.01.2021 - 18:28 Uhr

Nabend 😃

versuche seit Gestern ein Postman Request nachzubauen mit dem HttpClient, den ich über den Proxy von einer Anwendung abgefangen habe. (POST Request)

Der Request lädt eine Datei hoch.

Anbei ein Foto von Postman mit den ganzen Request Headers.
Und hier der raw Body vom Request:

----------010521174505517
Content-Disposition: form-data; name="sess_id"

aravtx27odgrsoba
----------010521174505517
Content-Disposition: form-data; name="file"; filename="testfile.txt"
Content-Type: application/octet-stream

\****file content****
----------010521174505517--

Die Authentication läuft über die "sess_id".

In der API Doku steht nur das (und der Support schreibt nur Einzeiler als Antwort)

Endpoint
https://api-v2.ddownload.com/api/upload/server?key=key

Parameters
key : String : API key Example: 1hj124iod23j12gd3

Response

{
    "msg": "OK",
    "server_time": "2017-08-11 04:29:54",
    "status": 200,
    "result": "https://s1.ddownload.com/upload/01"
}

Mein Code ist wie folgt:

Service service = new("api key", null);

            using FileStream fs = File.Open(@"C:\Users\User\Downloads\testfile.txt", FileMode.Open, FileAccess.Read);

            Result<string> result = await service.GetServerUrlAsync();

            string boundary = "--------" + DateTime.Now.Ticks.ToString("x");

            StreamContent sc = new(fs);
            sc.Headers.ContentType = new("application/octet-stream");

            MultipartFormDataContent content = new(boundary)
            {
                { new StringContent(result.SessionId), "sess_id" },
                { sc, "file", "test.dat" }
            };

            content.Headers.ContentType = new("multipart/form-data");

            try
            {
                HttpResponseMessage response = await service.Client.PostAsync(result.Response, content);
                string responseString = await response.Content.ReadAsStringAsync();
            }
            catch (Exception ex)
            {

            }

Service: Erstellt einen default HttpClient.
service.GetServerUrlAsync(): ruft die oben angegeben Endpoint Adresse auf, wo ich als response die Session ID und die URL zum hochladen bekomme.

Response hat einen 200er.
Im Content steht nur:

<h1>Internal Server Error</h1>\n<p>The server encountered an internal error or\nmisconfiguration and was unable to complete\nyour request.</p>

Übersehe ich etwas? Oder fehlt irgendetwas, was man bei dem Request nicht sieht?

Grüße
duesmannr

22.12.2020 - 12:45 Uhr

PS: kurz gegoogelt. Die Webseite bietet sehr wohl eine API; wenn auch sehr rudimentär.
https://www.directupload.net/api/upload.php

Das habe ich auch nie verleugnet.
Nur das die keine Dokumentation dafür besitzen bzw. nicht öffentlich ist.

22.12.2020 - 11:12 Uhr

Die Seite arbeitet mit Content-Type: multipart/form-data; und nicht mit binary/octet-stream.

Der WebClient Code Schnipsel kommt aus deren eigenem Uploader Tool.

Ich habe auch schon versucht mit dem HttpClient und dem MultipartFormDataContent den Request zu senden. Aber gleiches Ergebnis wie beschrieben.

22.12.2020 - 11:03 Uhr

Gib doch den potentiellen Helfern ein wenig mehr Infos zur API.... Name? Docs?

Es gibt keine API Dokumentation.
Der Image Hoster ist der hier.

22.12.2020 - 01:56 Uhr

Nabend,

ich hänge gerade dabei, den WebClient Request zu einem HttpClient Request zu konvertieren.

Der WebClient Request sieht wie folgt aus und funktioniert auch:


string testImagePath = @"Path.png";

            WebClient client = new();

            client.UploadFileCompleted += Client_UploadFileCompleted;

            client.Headers.Add("Content-Type", "binary/octet-stream");
            Uri address = new("API Endpunkt");
            client.UploadFileAsync(address, "POST", testImagePath);

Mein Versuch mit HttpClient sieht wie folgt aus:


string testImagePath = @"Path.png";
            byte[] imageBytes = File.ReadAllBytes(testImagePath);

HttpClient httpClient = new();
            httpClient.DefaultRequestHeaders.Clear();
            httpClient.DefaultRequestHeaders.Accept.Add(new("*/*"));

            ByteArrayContent byteArrayContent = new(imageBytes);
            byteArrayContent.Headers.ContentType = new("binary/octet-stream");

            var resp = await httpClient.PostAsync("API Endpunkt", byteArrayContent);
            var content = await resp.Content.ReadAsStringAsync();

Habt Ihr eine bessere Idee, bzw. was mache ich falsch?

Grüße und schöne Weihnachten 😃

P.S.: Es handelt sich um einem Imagehoster. Die Variante mit dem WebClient liefert lediglich einen String mit der URL zu dem hochgeladenen Foto. Die Variante mit dem HttpClient liefer ebenfalls eine URL zurück, nur wenn ich diesen aufrufe, kommt ein leeres Bild vom Hoster. Quasi "Image not Found" Bild.

09.12.2020 - 11:55 Uhr

@Th69 habe ich nach deiner Antwort auch versucht. Ändert nichts daran.
Hab aber herausgefunden das er "\t" nicht mag. Habe jetzt manuell mit Whitespaces gefüllt und es geht halbwegs.

Neulich beim Hanselman diesen Blog gesehen, wäre das nicht was?

>

Sieht auf dem 1. Blick sehr gut aus. Gucke ich mir an. Danke!

09.12.2020 - 10:04 Uhr

Danke Abt für die schnelle Antwort.

Dann mal warten 😃

09.12.2020 - 03:00 Uhr

Nabend,

hab den Wunsch nicht auf die schnelle über die Suche gefunden, deswegen schreibe ich es mal.

Ich würde es mir wünschen, dass man mehrere Anhänge an einen Beitrag anhängen könnte als einen.

Wenn man bspw. Bilder anhängen will (2, 3 Stück) wäre das deutlich einfacher.

Was haltet Ihr davon?

Grüße