Ich kenn auch Kunden-Bastellösungen, die das über nen eigenen Middleware-Resolver gemacht haben - alles bisher (von mir) gesehen aber war murks.
Was sind denn die Workarounds? Ich würde die gerne einmal sehen.... Vielleicht kannst du sie ja mal beispielhaft skizzieren. Also wie du schon sagtest, unterstützt das aspnetcore-api-versioning package ja die Versionierung über alle vier gängigen Varianten (Url, Query Parameter, Request Parameter und Media Types). Gleiches gilt für en api-explorer. Zumindest kann man beim api-explorer über den Parameter ApiVersionParameterSource die Quelle der Versionierung (Url, Query Parameter, Request Parameter und Media Types) angeben.
So wie ich das verstanden habe, ist der API Versioning Explorer die Schnittstelle zwischen der API Versionierung und der OpenApi Schnittstelle (OpenAPI/Swagger). Entsprechend funktioniert die Versionierung über meinen Header ja grundsätzlich schon - und es sind auch Implementierungen vorhanden, die diese Variabilität erlauben. Wenn das Vorgehen so nicht unterstützt werden würde, dann macht die Schnittstelle ja so gar keinen Sinn.
Hast Du diesen "Workaround" denn mal gesehen?
Andere Frage: Siehst Du einen Weg, die Version in Swagger UI (es geht um Swagger UI, nicht um die Generierung der Swagger Dokumente) auszublenden bzw. die Version mit der Auswahl des Swagger Dokuments für die Version zu sezten? Das das HTTP Header Feld mit in der API Definition steht, würde ich ja als richtig ansehen
@ alle anderen: Auch wenn ich jetzt in die "Du" Form wechsle, weitere Meinungen / Anregungen anderer sind ausdrücklich erwünscht
Ich verwende Dein Vorgehen generell nirgends, weil es viele Probleme im Alltag macht. Die Grundidee ist ja, dass Versionen unabhängig voneinander betrieben und weiterentwickelt werden, zB. durch Legacy etc. Alles in eine Applikation zu packen, macht nur in ganz ganz ganz wenigen Mini-Szenarien sinn.
Und selbst in diesen würde man eher pro Version einen eigenen Namespace haben, statt alles über Mappings zu lösen. Ich hab das in freier Wildbahn, ausserhalb von Demos, noch nie gesehen.
Genau das wollte ich eigentlich verhindern. Ich weiss, dass Versionierung über Urls beziehungsweise über verschiedene Endpunkte sehr pupulär ist. Wenn man sich aber mal mit dem Thema beschäftgt und ein paar Tutorials liest, wird man schnell merken, dass jedes Vorgehen seine Nachteile hat - auch die Vesionierung über Url Segmente. Aber wollen wir uns mal wieder auf das Problem fokussieren....
Zitat von Abt
Zum Problem: Implizite Versionierung ist im Framework nur dann möglich, wenn Du keinerlei andere Versionsinformationen setzt. Daher knallts.
Was meinst Du mit impliziter Versionierung?
Zitat von Abt
In Deinem Fall dürfte es aber eh knallen, weil NSwag keine Versionierung über Header oder Query unterstützt. Nur URL Versionierung wird unterstützt.
D.h. Dein Header-Vorgehen mit NSwag so geht eh nicht.
Bist Du Dir da sicher? Es gibt da in der Tat ein Issue zu, siehe hier. Dort steht folgender Lösungshinweis:
Zitat
If you use header versioning then you probably want to manually add the global header parameter in PostProcess, apart from that the operations should correctly be reported by ASP.NET Core API explorer and generated in the spec...
Maybe even version filtering works - if the operation versions are correctly reported in API explorer groups.
Hat jemand da vielleicht ein Beispiel oder das schonmal gemacht? Mir ist auch nicht klar, was mit "global header parameter" gemeint ist. Vielleicht kann mir da jemand helfen, dass zu verstehen.
ich habe eine API, die ich gerne Versionieren möchte. Das klappt auch ganz gut, allerdings bekomme ich zwei Probleme. Zuvor möchte ich euch aber mitteilen, dass die Versionierung über den HTTP Header eine bewusste Entscheidung war und das nicht geändert werden soll (ich weiss das es dazu reichlich Diskussionen gibt...).
Dazu hier einmal ein Test Controller:
[Route("api/test")]
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class ValueController : Controller
{
[HttpGet]
[MapToApiVersion("1.0")]
public ActionResult GetAllValues()
{
return Ok("1.0");
}
[HttpGet]
[MapToApiVersion("1.1")]
public ActionResult GetAllValues_V11()
{
return Ok("1.1");
}
}
Problem Nummer 1 ist: Wenn ich das [MapToApiVersion] über GetAllValues (also Version 1.0) weglasse, dann kann das Swaggerdokument nicht erzeugt werden und ich bekomme eine Exception mit dem Hinweis, dass es für GET mehrere Actions für die selbe Resource gibt:
Fehler
System.InvalidOperationException: The method 'get' on path 'api/test' is registered multiple times.
at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.AddOperationDescriptionsToDocument(OpenApiDocument document, Type controllerType, List`1 operations, OpenApiDocumentGenerator swaggerGenerator, OpenApiSchemaResolver schemaResolver)
at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.GenerateApiGroups(OpenApiDocument document, IGrouping`2[] apiGroups, OpenApiSchemaResolver schemaResolver)
at NSwag.Generation.AspNetCore.AspNetCoreOpenApiDocumentGenerator.GenerateAsync(ApiDescriptionGroupCollection apiDescriptionGroups)
at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.GenerateDocumentAsync(HttpContext context)
at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.GetDocumentAsync(HttpContext context)
at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.GetDocumentAsync(HttpContext context)
at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context)
at NSwag.AspNetCore.Middlewares.OpenApiDocumentMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Das zweite Problem ist, dass ich zwar eine Auswahlliste für die Version in SwaggerUi bekomme, aber trotzdem das Header-Feld für die Eingabe der Version (try it out) angezeigt wird und dieses sogar required ist. Das macht aus meiner Sicht keinen Sinn, weil die Version ja durch die Auswahl schon bekannt sein sollte. Kann man das Feld in Swagger UI entfernen?
Beispiel: Du versuchst ein Element zu aktualisieren, das es aber nicht gibt.
Damit hast Du Recht. Entschuldige, dass hatte ich nicht geschrieben, aber mir fehlt der Insert auf die Activities. Ich sehe das die Order gefunden wird (das ist der select vor dem add):
Zitat
Executed DbCommand (207ms) [Parameters=[@__domainEvent_AggregateId_0='1d49e3b2-a121-471f-93d6-fd1445fa250d'], CommandType='Text', CommandTimeout='30']
SELECT [o].[Id], [o].[ConfirmedAt], [o].[CreatedAt], [o].[DeviceId], [o].[FirstDeliveredAt], [o].[LastDeliveredAt], [o].[OrderNumber], [o].[OrderStatus], [o].[OrderText], [o].[PrintedAt]
FROM [Orders] AS [o]
WHERE [o].[Id] = @__domainEvent_AggregateId_0
Ich sträube mich auch etwas dagegen ein DbSet dafür anzulegen. Die Activities sind nur im Subbaum der Orders interessant und keine eigenständige Entität. Von daher wäre es mir Recht, wenn es auch ohne ein DbSet gehen würde....
Ich kann mit dme Begriff "Update or ignore" nichts anfangen. Aber wenn du das meinst: Ich hatte damit angefangen, die Activity der Liste in der Order hinzuzufügen. Allerdings macht es keinen (sichtbaren) unterschied, ob ich:
-Die Order in der Activity setze
- Die Activity in der Liste der Activity des Order-Objekts hinzufüge
- oder beides mache.
Alle 3 Varianten schmeißen den selben Fehler. Von daher bitte nochmal Feedback, ob ich Dich richtig verstanden habe.
ich habe eine Anwendung, die Bestellungen hereinbekommt. Zu der Bestellung sollen nun Activities gespeichert werden. Eine Activity ist eine abstrakte Klasse, für die es die Möglichkeiten 'printed', 'accepted' und 'denied' gibt. Wenn ich nun eine Bestellung ändern möchte, bekomme ich eine fiese Exception:
Fehler
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException : Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException(Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithoutPropagation(Int32 commandIndex, RelationalDataReader reader)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.Consume(RelationalDataReader reader)
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.Execute(IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList`1 entries)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList`1 entriesToSave)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
at Heliprinter.Orders.ReadModel.Denormalizer.OrderDenormalizer.When(OrderPrinted domainEvent)
at Heliprinter.Orders.WebUI.ReadModel.Tests.Tests.Test1() in D:\Source\FluentSoftware\Heliprinter_neu\Heliprinter.Monitoring\Source\Heliprinter.Orders.WebUI.ReadModel.Tests\UnitTest1.cs:line 30
Der Code dazu ist
var savedOrder = _context.Orders.Where(order => order.Id == domainEvent.AggregateId).ToList().SingleOrDefault();
if (savedOrder != null)
{
var activity = new OrderPrintedActivity()
{
Id = domainEvent.Id,
PrinterStatus = domainEvent.PrinterStatus.ToString(),
ResultCode = domainEvent.ResultCode,
Order = savedOrder
};
_context.SaveChanges();
}
public class OrderEntity
{
public Guid Id { get; set; }
// andere Properties ...
public virtual ICollection<CommunicationActivity> Activities { get; set; }
}
public abstract class CommunicationActivity
{
public Guid Id { get; set; }
public virtual OrderEntity Order { get; set; }
public int ResultCode { get; set; }
}
Ich habe extra einen Unit-Test erstellt, um auszuschlißen, dass ein Nebenläufigkeitsproblem existiert. Aber scheinbar tritt das Problem auch Single-Threaded auf. Könnt ihr mir da helfen?
Ich sehe in Deinem Szenario nirgends den sinnvollen Einsatz von Headern; Du brauchst ja prinzipiell im Container nirgends den Hostname.
Denn für das Routing sind diese ja nicht relevant. Das Routing ist Aufgabe von NGINX.
Okay, dazu noch eine Frage zu meiner Config. Ich habe ja nur eine Location für /webui/ bedeutet:
Der Request zur Web-App wird ja richtig geroutet. Nur Links im HTML-Dokument werden nicht richtig aufgelöst. Im übertragenen HTML-Code steht beispielsweise
Sicher. Die Container haben damit nichts zu tun. Ich habe sie hier auch nur genannt, damit ihr wisst, woher die hostnamen kommen. Bei der Anwendung bin ich mir nicht ganz so sicher - scheinbar (siehe code beispiele) muss man der Anwendung schon beibringen, bestimmte Header zu akzeptieren.
In dem Beispiel wird nur ein proxy_pass gemacht, das mache ich ja genauso. Also wo ist der Unterschied? Ich habe das Beispiel jetzt nicht ausprobiert (könnte ich auch nicht), aber ich gehe mal davon aus, dass es funktioniert - nur bei mir eben nicht.
für die Frage werdet ihr mich auslachen, aber ich bekomme es einfach nicht hin und hoffe auf eure Hilfe. Ich frage mich gerade: Wie wird in Asp.net core eigentlich der Hostname aufgelöst? Hintergrund ist folgendes:
Ich habe mehrere Docker container (API's und eine Web UI) und möchte jetzt die container auf konkrete URL's mappen. Der direkte Zugriff über Ports funktioniert. Also:
Die Web UI selber kann auch aufgelöst werden, aber da über den Reverse Proxy die URL's umgebogen werden, können die restlichen Resourcen (CSS, JS, Medien, etc.) nicht mehr geladen werden. Dazu einmal meine nginx.config:
Ich bitte zu entschuldigen, dass noch kein HTTPS da ist. Die im Servernamen eingetragene IP ist die externe IP meines Rechners in meinem Netzwerk. Ich wollte nicht localhost nehmen, um zu sehen, welche URL genommen wird (auf dem Webserver gibt es ja auch ein *anderes* localhost)
Wenn ich jetzt einen Request starte auf http://192.168.7.113/webui wird dieser Request folgerichtig umgeleitet auf http://container1. Soweit funktioniert das auch. Allerdings wird von der Webseite (1:1 Visual Studio Template) unter anderem versucht die bootstrap.css zu laden:
Wie ihr seht, habe ich bereits bestimmte Header mitgeschickt, die meinen Informationen nach bnötigt werden, damit .net den Hostnamen richtig auflösen kann. Jedoch funktioniert das nicht. Ich habe zwei Middlewares gefunden, die man wohl einbinden soll, damit der Hostname richtig aufgelöst wird, die habe ich eingebunden:
Aber irgendwie scheint noch etwas zu fehlen. Ich habe mir auch nie über die Hostauflösung gedanken gemacht. Könnte jemand meine Wissenslücke schließen?
Was "per design" bedeutet, weiss ich leider nicht. Wie gesagt, dass ist nicht meine Aussage, sondern die der Quelle.
Zitat
Normalerweise wird unter VS2017 aktuell nur die override und debug erzeugt.
Also, bei mir wird keine Debug erzeugt sondern die Root (docker-compose.yaml) und die override (docker-compose.override.yaml). Der Container wird dann gebaut mit:
Die letzte Datei ist die, die generiert wird. In meinem Fall ist das jetzt release, das könnte aber auch debug sein. Laut dem Pluralsight Kurs sollte sich im Docker Projekt noch eine Debug dabei befinden - das hast Du ja auch gesagt.
Oder habe ich Dich falsch verstanden?
Zu dem Multistage: Hast du da Links bzw. Infomaterial?
ich habe eine Verständnisfrage und ich hoffe dass ihr mir weiterhelfen könnt. Es geht um Docker, bzw. eigentlich sogar um docker-compose und zwar in Verbindung mit Visual Studio. Ich habe mir auf Pluralsight einige Videos dazu angeschaut und dächte verstanden zu haben, dass es neben der docker-compose.yaml und der docker-compose.override.yaml auch noch die docher-compose.debug.yaml und docker-compose.prod.yaml gibt und die Dateien über docker-compose gemergt werden (sodass ich z.B. das setzen des ASPNETCORE_ENVIRONMENTS in der debug/prod datei machen kann, da die Dateien im Build über docker-compose gemergt werden.
Jetzt habe ich Docker-Support zu meinem aktuellen Projekt hinzugefügt und diese Dateien nicht gefunden. Als ich danach gegoogled habe, habe ich eine Aussage gefunden, dass die Datei für debug/release nun "per design generiert wird". Ich habe leider die quelle nicht mehr.
Meine Frage ist nun: Wenn die Dateien nun generiert werden und somit als Konfigurationsmöglichkeit nicht mehr zur Verfügung stehen, wie kann ich dann dafür sorgen, dass in Abhängigkeit von Debug/Release beim bauen des Images die richtige ASP.NET Core Konfiguration verwendet wird?
Meine Informationen scheinen etwas veraltet zu sein. Ich wäre euch daher dankbar, wenn ihr mich auf den aktuellen Stand bringen könntet.
ich habe ein Problem mit JQuery und Typescript und würde gerne wissen, ob ihr das kennt und eine Lösung dafür habt:
Umgebung ist Visual Studio und Typescript 2.4.0, JQuery in der Version 3.2.1, und @types/jquery auch in 3.2.1.
Wenn ich das Projekt dann bauen möchte, kommt es zu diversen Fehlern in der index.d.ts, die sinngemäß folgendes sagen:
Zitat
Severity Code Description Project File Line Suppression State
Error TS2314 Generic type 'JQueryStatic<TElement, HTMLElement>' requires 2 type argument(s). Scripts (tsconfig project) ..\node_modules\@types\jquery\index.d.ts 28 Active
Es sieht für mich aus wie eine inkompatibilität, aber könnt ihr mir genaueres sagen? Hattet ihr das problem schonmal?
Grundsätzlich werden die Links im Controller hinzugefügt. Beispiel:
public Post Get(int id)
{
var post = Session.Load<Domain.Post>(id);
var response = Mapper.Map<Post>(post);
response.AddLink(new EditLink(Url.Link(...)));
return response;
}
Die Implementierung geht davon aus, das jede Ressource von einer abstrakten Basis erbt, die die Möglichkeit erweitert, der Resource Links hinzuzufügen.
Der Author geht dann noch einen Weg um Duplizierten Code zu vermeiden, nämlich die Resourcen immer wieder hinzuzufügen. Soweit ich das verstehe ich der Mechanismus folgender:
Es gibt für verschiedene Ressourcen verschiedene "Enricher". Enricher sind dann dafür da, die Ressourcen um Links zu erweitern. Die Enricher werden quasi einfach als Service registriert, aber es gibt noch einen Handler.
Ich habe noch nicht viel mit WebApi gemacht, aber ich finde das Vorgehen gar nicht so schlecht. Ich hoffe ihr könnt mir das bestätigen. Meine Idee war es jetzt, den Handler durch eine Middleware abzulösen. Ist das wirklich ein ungünstiges Vorgehen?
Vielleicht habe ich mich auch anfangs nicht konkret genug ausgedrückt?!
Aber eine Frage: so wie ich euch verstanden habe, Ist sowohl Hypermedia als auch HATEOAS als Aufsatz auf REST zu verstehen. Im Internet als auch in dem Link zu dem Artikel von Martin Fowler verstehe ich es eher so, das es dabei eher um die Herbeiführung von Zustandsänderungen geht und somit beides Teil von REST ist, bzw. Genutzt werden sollte um REST zu implementieren.
Ich habe übrigens noch einen anderen Artikel gefunden, der auch für andere hilfreich sein könnte. Es ist zwar ein Artikel aus der Javawelt, Aber da es sich bei REST um ein Protokoll handelt und nicht um eine Implementierung, sollte das egal sein
Verstehe. Dann ist das ja nicht Rest, sondern wieder etwas neues?
Wie würde man denn mit so einer Situation umgehen, Wenn man einen REST Service ohne hypermedia schreiben will?
Ich bin mir nicht sicher, ob es das richtige ist, sich in hypermedia einzuarbeiten, Wenn man sich gerade mit den Grundlagen von REST beschäftigt.
Viele Grüße und danke für die bisherigen Anmerkungen
Fluxy
ich weiss gar nicht, ob mein Vorgehen richtig ist, aber ich habe gelesen, dass man REST-Services so implementiert, dass nicht nur das Ergebnis der Abfrage übermittelt wird, sondern auch Links, die genutzt werden können, um weiter abzufragen. Dazu habe ich versucht eine kleine Middleware zu schreiben, die es mir erlaubt, die Links hinzuzufügen.
Ich habe also zum Beispiel als Resource Projekte, die ich über einen ProjectsController abfrage:
[HttpGet]
public IEnumerable<ProjectWebRepresenation> Get()
{
var queryProjects = _queryProjectsBusinessController.QueryProjects();
if (queryProjects == null)
return null;
return queryProjects.Select(project =>new ProjectWebRepresenation { Number = project.Number, Name = project.Name});
}
Projekte haben neben dem Namen und einer Projektnummer noch andere Daten, zum Beispiel hängen an den Projekten Kategorien. Die Kategorien sollen aber nicht mitgeschickt werden, sondern bei Bedarf abgefragt werden.
Stattdessen soll ein Link in die JSON-Antwort eingefügt werden, über die die abhängigen Objekte nachgeladen werden können. Ich habe dafür einen Artikel gefunden, allerdings ist der nicht für Core.
Den Handler habe ich versucht, in eine Middleware umzuwandeln. Das Erzeugen der Middleware funktioniert ganz gut, allerdings bekomme ich ein Problem weil die Middleware ein HttpContext erwartet und kein HttpResponseMessage.
Vielleicht bin ich auch auf einem ganz falschen Weg. Es wäre schön, von euch zu hören.
ich finde mein Problem ganz witzig, aber das hatte bestimmt schonmal jemand. Ich nutze schon das neue Studio (2017), weiss aber nicht ob das damit etwas zu tun hat.
Ich habe einfach einen Unittest geschrieben, der einen Webapi Controller testet. Das Problem ist, er kann die Assembly nicht laden:
Fehler
Expected: <System.ArgumentNullException>
But was: <System.BadImageFormatException: Could not load file or assembly 'MyProject.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An attempt was made to load a program with an incorrect format.
File name: 'MyProject.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
Das interessante ist das net452, das fügt Visual Studio dem Buildpath hinzu. Die Exe landet auch wirklich da, jedoch sucht .NET sie da nicht:
Fehler
LOG: Attempting download of new URL file:///D:/.../bin/Debug/MyProject.Web.DLL.
LOG: Attempting download of new URL file:///D:/../bin/Debug/MyProject.Web/MyProject.Web.DLL.
LOG: Attempting download of new URL file:///D:/../bin/Debug/MyProject.Web.EXE.
ERR: Failed to complete setup of assembly (hr = 0x8007000b). Probing terminated.
Die ersten beiden versuche schlagen eh fehl, und der dritte kann nicht funktionieren weil er von dem net452 nichts weiss. Dummerweise restored VisualStudio die Projectsettings, wenn man versucht den Pfad wegzusetzen.
Ah Hallo Apt, schön von Dir zu hören!
Tut mir leid, du hast schon recht ich habe ein paar Informationen vergessen....
Also ich nutze das englische Visual Studio 2015 mit Update 3 => en_visual_studio_enterprise_2015_with_update_3_x86_x64_dvd_8923288.iso
Das ganze Läuft auf Windows 10 Professional und einem x64-basierten, also 64-Bit System (die Information könnte interessant sein auch wenn Du das nicht gefragt hast).
Die IIS-Version müsste die von Windows 10 mitgelieferte sein (10.0.14393.0)
.NET core version bin ich mir nicht sicher, aber das ist die version die ich mit dotnet --version rausbekomme oder? Dann ist es die 1.0.0-preview2-003131 (gibt es schon ein release?)
Ausser das von Visual Studio mitgelieferte habe ich kein besonderes SDK installiert. Gleiches gilt für die CLI.
ich hoffe ihr könnt mir helfen. Es geht wahrscheinlich um ein lokales Problem bei mir. Und zwar habe ich meinen PC neu aufgesetzt, und habe Probleme Webapplikationen aus Visual Sutdio heraus zu starten.
Das Witzige ist: "Normale" ASP.NET MVC Webanwendungen laufen - ASP.NET Core Webawendungen nicht. Den Vorschlag das .vs verzeichnis zu löschen und den Port zu ändern habe ich schon durch, das bringt bei mir nicht so viel Erfolg.
Nagut, dann muss ich halt noch etwas konkreter werden. Ehrlich gesagt finde ich das Beispiel etwas ungünstig gewählt (wurde ja auch schon gesagt), das Problem ist das dies was wir da sehen ja ein Daten-Viewmodel ist, und nicht etwa ein Viewmodel, welches direkt an eine View gebunden werden könnte.
Ich werde einfach mal eine Frage stellen und die Antwort offen lassen (auch weil das gerade ein Punkt ist an dem ich selber scheitere, aber vielleicht habt ihr ja eine Lösung).
Bleiben wir doch bei dem Person beispiel. Das Beispiel alleine reicht nur nicht, weil DDD ohne eine Fachdomäne ist irgendwie quatsch. Okay sagen wir, wir entwerfen eine Software für einen kleinen Handwerksbetrieb. Dieser Handwerksbetrieb möchte Löhne abrechnen. In einem ersten Schritt soll eine Software entwickelt werden, in der die Unternehmensstruktur abgebildet werden kann, und jedem Mitarbeiter ein Monatsgehalt, und die anzahl der Wochenstunden zugeordnet werden kann.
Wir setzen uns nun mit der Fachdomäne zusammen und eiigen uns auf auf folgendes kleines Modell, ihr findet es als Anhang. Es ist ganz einfach: Jeder Mitarbeiter hat einen Vertrag (contract), und jeder Mitarbeiter ist Chef von 0..* weiteren mitarbeitern (auf die bildung von Teams wurde erstmal verzichtet). Gut also implementieren wir das Fachmodell, jede Person bekommt also die Möglichkeit, Personen als Mitarbeiter zu seiner Mitarbeiterliste hinzuzufügen, und für seine Mitarbeiter das Gehalt sowie die Wochenarbeitsstunden zu erfassen. Nun gut, soweit so schön.
Jetzt sind wir zurück in der eigenen Firma und man möchte das Projekt wirklich nach DDD umsetzen, also kein Anemic Data model. Der Kunde möchte eine schöne UI, also beschliesst man eine schöne WPF Anwendung mit dem MVVM Pattern umzusetzen. Anforderung ist also
- Wir wollen kein Anemic Data Model, Fachdaten und Fachlogik werden also nicht getrennt
- Wir wollen eine UI mit WPF aufbauen
- Und es soll das MVVM-Pattern eingesetzt werden.
Okay der erste Schluss ist, unsere Viewmodels kennen unser Model nicht. Das Laden von Daten ist relativ einfach. Wir wollen diese Logik nicht im Viewmodel haben, also schreiben wir uns einen ApplicationController. Nach Eric Evans ist das eine Schicht, die keine Logik ausführt, sondern nur Delegiert. Also delegieren wir an ein Repository, das läd für uns alle Personen. Das Viewmodel bekommt Personen-Objekte als Viewmodel (die allerdings keine Cohesion zu einem Person-Objekt haben). Alle Daten die wir erfassen setzen nur Properties auf dem Viewmodel.
Der User kann bekommt sich selbst als Person auswählen und bekommt ein Grid mit Personen angezeigt, welche seine Mitarbeiter sind. Es gibt einen Button um Mitarbeiter zum Grid hinzuzufügen, dafür nutzen wir ein Command. Während des Hinzufügens soll aber nicht nur ein neues Viewmodel erzeugt werden, sondern die Person auf Fachlogikebene auch hinzugefügt und gespeichert werden.
Wie machen wir das nun?
Wie kommen wir an das Personenobjekt, ohne (z.B. über eine Factory uns ein neues zu erstellen)?
Macht ihr doch mal weiter, aber bitte verschmutzt den Thread hier nicht. Ich habe mir soooo viel Mühe gegeben -;)
Vielleicht bekommen wir ja doch noch eine Lösung hin...
Also ich beschäftige mich auch seit einigen Tagen mit dem Thema DDD und habe den Thread hier gerade mal verfolgt. Übrigens ich finde das Thema sehr interessant, aber ich glaube hier geht einiges durcheinander.
Ich muss gestehen, ich sehe bei mir noch viele Fragezeichen, aber das Hauptproblem (auch hier im Thread) ist doch, das so ein "Amenic Data Model", also reine DTO's als Model und die Logik steckt sonstwo (aber woanders) zumindest im Term von DDD ein Anti-Pattern ist.
Die Frage ist doch vielmehr wie man MVVM und DDD gemeinsam nutzen kann. In DDD ist das Model halt der Kern der Software, aber ich glaube zum Thema Validierungen gibt es doch zwei unterschiedliche Arten der Validierung.
Das eine ist, die Validierung innerhalb des Fachmodells. Innerhalb des Fachmodels müssen Validierungen erlaubt sein. Das ist ja auch gerade das Konzept der Factories und der Aggregates. Die Factories sollen ja gerade Zustände erzeugen, die "gültig" sind, das heisst, das erzeugte Objekt erfüllt alle Invarianten und ist somit "valid". Natürlich sollten auch alle Operationen ein Objekt nicht in einen invaliden Zustand überführen, ich denke das erklärt sich von selber.
Die andere Sache ist die UI - und die hat meiner Meinung nach mit dem Fachmodell gar nix zu tun. Bei dem was wir in WPF typischerweise unter "Validierung" verstehen, ist die Überprüfung von User-Eingaben, ob diese richtig oder falsch sind. Das hat meiner Meinung nach nichts mit dem Fachmodell zu tun, weil die UI nix mit dem Fachmodell zu tun hat. Es geht lediglich darum sicherzustellen, dass die Eingaben, die getätigt werden, sinnvoll sind, damit in der Folge dann über Commands oder ähnliches Operationen auf dem Fachmodel (z.B. über einen ApplicationController) mit validen Daten ausgeführt können.
Mit anderen Worten: Das eine ist UI, das andere ist Fachlichkeit.
Ich weiss der Thread ist schon einige Zeit zum erliegen gekommen, aber vielleicht gibt es ja doch noch einige, die das Thema weiterdiskutieren wollen, und vielleicht helft ihr mir mit euren Antworten auch noch ein bisschen, tiefer in das Thema einzusteigen.
Oh wow, tatsächlich nach 2 Jahren ein neues Release von log4net.. sorry, das is für mich nicht wirklich aktiv.
ja gut es ist eine Logging-Bibliothek. Wahrscheinlich gut es da ja auch nicht mehr so viel zu tun. Aber ich frag mal so:
Alternative?!
Grundsätzlich muss ich aber sagen, ich war mit Log4Net immer zufrieden - sobald die Konfiguration lief gab es keine Probleme. Das Einrichten ist nervig, aber wenns einmal läuft, dann lief es immer. Aber zurück zu meinem Problem, ich denke das entscheidene ist dies hier (passt auch zu dem guten Hinweis):
Zitat
A simple call to LogManager.GetLogger will cause the attributes on the calling assembly to be read and processed. Therefore it is imperative to make a logging call as early as possible during the application start-up, and certainly before any external assemblies have been loaded and invoked.
Heisst, LogManager.GetLogger interpretiert das Attribute. Ich habe einige Logs eingebaut (ausserhalb von NHibernate), das funktioniert soweit. Ich habe allerdings noch ein anderes Problem, was vermutlich mit meiner Config zu tun haben wird. Dazu muss ich sagen ich nutze die folgende Kombination:
Visual Studio 2015 + Resharper 10 + NUnit 10
Und zwar ist das Problem, dass irgendwie die Ausgaben teilweile auf dem falschen Output landen (zumindest gefühlt). Und zwar habe ich 2 Appender, console und rolling file. Ich habe den Root Logger und 2 weitere Logger für NHibernate). Nun landen meine Logs (die ich selbst aufrufe im Logfile und im Testoutput) und die NHibernate Logs in der Konsole, aber nicht im Logfile und auch nicht im Testoutput). Könnt ihr mal über die Config schauen ob ich etwas total doofes gemacht habe?
ich hatte gestern das folgende Problem: Ich habe für eine Applikation einen Integratiostest geschrieben, und zwar geht es um eine Art Work-Tracking-System für den eigenen Gebrauch, und in dem System werden halt Arbeitsaufgaben verwaltet. Diese Arbeitsaufgaben haben einen Status, der Lazy geladen wird. Der Test legt per Hand so eine Arbeitsaufgabe in der Datenbank an, und prüft dann ob der richtige Status geladen wird. Das Abholen der Arbeitsaufgaben (ist nur eine) läuft in einer Session (#1), das prüfen auf den richtigen Status (also dann auch das Nachladen des Status läuft in einer neuen Session (#2).
Es wird ziemlich schnell klar, dass ich die Objekte aus Session1 detachen, und in Session 2 attachen muss. Die tue ich auch und zwar für das Item und den Status (wobei der Status eigentlich nicht in Session1 sein kann da er nicht geladen wurde aber sicher ist sicher)
public void Detach(WorkingItem item)
{
_session.Evict(item);
_session.Evict(item.Status);
}
Das selbe mache ich dann beim Attachen in der zweiten Session
public void Attach(WorkingItem item)
{
_session.Lock(item, LockMode.None);
_session.Lock(item.Status, LockMode.None);
}
Kleine Randnotiz: Das sieht jetzt so aus als wäre es die selbe Session, aber das stimmt nicht. Der Code befindet sich in einem Repository, und das wurde von einer UnitOfWork erstellt, und zwar von einer anderen:
using (var unitOfWork = _unitOfWorkFactory.CreateUnitOfWork())
{
var repository = unitOfWork.CreateWorkingItemRepository();
workingItems = repository.GetAll();
workingItems.ForEach(workingItem => repository.Detach(workingItem));
}
using (var checkUnitOfWork = _unitOfWorkFactory.CreateUnitOfWork())
{
var repository = checkUnitOfWork.CreateWorkingItemRepository();
var actualFirstWirkingItem = workingItems.First();
repository.Attach(actualFirstWirkingItem);
Assert.That(repository.RemoveProxy(actualFirstWirkingItem), Is.InstanceOf<InProgressWorkingStatus>());
}
Was noch wichtig ist: Der Test läuft in einer Transaktion, damit er keinen Datenmüll hinterlässt. Der Rollback wird nach dem Test durchgeführt. Da die Transaktion noch offen ist, ist die connection auch noch offen, die für session1 verwendet wurde. Allerdings bekomme ich nun folgende Exception:
Fehler
NHibernate.LazyInitializationException : Initializing[Timecheck.DomainObjects.Enums.WorkingStatus.WorkingStatus#f81ddde2-569a-4f91-a116-b7dd6d10c003]-Illegally attempted to associate a proxy with two open Sessions
bei NHibernate.Proxy.AbstractLazyInitializer.set_Session(ISessionImplementor value)
bei NHibernate.Engine.StatefulPersistenceContext.ReassociateProxy(ILazyInitializer li, INHibernateProxy proxy)
bei NHibernate.Engine.StatefulPersistenceContext.ReassociateIfUninitializedProxy(Object value)
bei NHibernate.Event.Default.ProxyVisitor.ProcessEntity(Object value, EntityType entityType)
bei NHibernate.Event.Default.AbstractVisitor.ProcessEntityPropertyValues(Object[] values, IType[] types)
bei NHibernate.Event.Default.AbstractReassociateEventListener.Reassociate(AbstractEvent event, Object entity, Object id, IEntityPersister persister)
bei NHibernate.Event.Default.DefaultLockEventListener.OnLock(LockEvent event)
bei NHibernate.Impl.SessionImpl.FireLock(LockEvent lockEvent)
bei NHibernate.Impl.SessionImpl.Lock(Object obj, LockMode lockMode)
bei Timecheck.Persistence.NHibernate.Repositories.WorkingItemRepository.Attach(WorkingItem item) in D:\Fluent-Software\VSGit\Timecheck\Implementation\Timecheck.Persistence.NHibernate\Repositories\WorkingItemRepository.cs:Zeile 49.
bei Timecheck.Persistence.NHibernate.Tests.WorkingItemRepositoryTests.LoadWorkingItems() in D:\Fluent-Software\VSGit\Timecheck\Implementation\Timecheck.Persistence.NHibernate.Tests\WorkingItemRepositoryTests.cs:Zeile 83.
Das ich 2 Sessions aufhabe, ist mir klar, aber die Entities sollten aus Session 1 detached worden sein. Ich habe jetzt etwas länger kein NHibernate mehr gemacht, dachte eigentlich das Attach funktioniert über SaveOrUpdateCopy, aber das scheint nicht mehr so zu sein, weil die Methode ist nicht mehr da...
ich habe das Problem eigentlich schon lange, aber ich habe jetzt mal beschlossen der Sache auf den Grund zu gehen. Es geht um log4net und die Konfiguration dieser. Ich habe in einem Projekt jetzt wieder log4net eingebunden, um nhibernate zu debuggen. Wie dem auch sei, ich habe gestern log4net über NuGet bezogen (Aktuelle Version ist 2.0.4) und losgelegt.
Das ist zwar ein Hibernate-Artikel, aber eigentlich ist das auch egal, das Pattern sollte ziemlich identisch sein und dem was ihr vielleicht von log4net aus anderen Quellen kennt. Die Config habe ich übernommen so wie sie dort ist und sicherheitshaltber nicht nur im Testprojekt sondern auch im NHibernate-Projekt in Form einer app.config bereitgestellt.
Jetzt ist die Erwartungshaltung, dass nur eine Attributierung durchgeführt wird, und dadurch log4net gestartet wird. Ich habe erst, wie immer beschrieben, das Attribut über einen zufällig ausgewählten Namespace in der Testassembly und NHibernate Assembly getestet, nachdem das nicht funktioniert hat, habe ich das Attribut in die AssemblyInfo.cs verschoben (google hat mir da einen Artikel zu Tage befördert). Allerdings hilf dies alles nix.
Auf jedenfall wird kein Logfile erzeugt. Ich hatte das Problem wie gesagt schon in der Vergangenheit öfters, die Lösung war dann den XmlConfigurator von Hand irgendwo (z.B. im TestInitialize) zu erzeugen. Dann funktionierte das logging auch (das habe ich jetzt allerdings nicht ausprobiert). Letztendlich würde das bedeuten, dass das Attribut nicht ausgewertet wird, und deshalb der Log4Net Initialisierungsprozess nicht gestartet wird.
Aber kann mir jemand sagen warum? Ich würde das gerne verstehen.
Das ist ja genau das Problem, das würde ich gerne testen. Also ich gehe lokal nicht über Azure. Ich gehe lokal auf einen lokalen SQL Server, und beim Deployment wird der ConnectionString ausgetauscht. Allerdings habe ich Propleme zu checken, ob der Connectionstring auch in der Webconfig in meiner Azure-Webapplikation landet. Ich habe schon viel gesehen, aber da es beim Update schema keine Exceptions gibt, gehe ich mal davon aus, dass er zumindest irgendeine Datenbank hat. Nur welche das ist, das würde ich gerne prüfen. In der Konfiguration der Webapplikation im Azure Portal steht zwar ein ConnectionString, dass ist aber definitiv nicht der selbe, wie der, der angeblich deployed wird....
ich bin auf der Suche nach Infomaterial zu einem Problem, das ich mit Azure habe. Habe eine kleine Webapplikation dort veröffentlicht und was soll ich sagen ich bekomme das ganze nicht zum laufen. Der Fehler ist, dass NHibernate die Tabellen nicht anlegt - das Problem ist aber das ich grundlegende Dinge nicht nachschauen kann.
Wie kann ich zum Beispiel in die Web.config schauen, und zu sehen, ob mein Connectionstring richtig konfiguriert ist? Hat mein User überhaupt die Rechte Tabellen auf der Datenbank anzulegen?
Habt ihr vielleicht infomaterial über das ich mich zu diesem Thema einlesen kann? Eventuell hat auch jemand direkt einen Rat für mich. Bin für jeden link dankbar.
eigentlich dachte ich total simple, aber ich bekomm es irgendwie nicht hin: Ich möchte das beim TFS bei der Erstellung einer neuen US als Defaulttext der Text "In my role as a <role> I want to <decription> in order to do <task>" stehen haben. Das ich dafür dem Description feld eine COPY field rule hinzufügen muss ist sicher, aber die Platzhalter bereiten Probleme (BTW: Das Description field ist ein HTML control).
Wenn ich den Text als value so in die XML-Datei schreibe, dann meckert witadmin, dass man das zeichen "<" verwendet hat
Maskiert man <, > mit < und > dann funktioniert der import zwar, jedoch wird nichts angezeigt. Er macht dann für die Platzhalter eigene HTML-Tags die kein Brwoser auswerten kann, also auch schwachfug...
Und wenn ich den gesamten Text als CDATA Tag schreibe, kommt er damit gar nicht zurecht
Es gibt doch bestimmt jemanden, der sowas schon hat oder? Wäre cool wenn hier eine Lösung zustande kommen könnte....
erstmal dachte ich das wird ein ganz einfacher Test. Einfach den RegionManager mocken und testen ob für jede Region eine View registiert wird, alsp RegisterViewWithRegion aufgerufen wird. Mit MOQ eigentlich ganz einfach dachte ich, ist ja nur ein Verify. Okay dann finde ich aber raus das die Methode RegisterViewWithRegion gar nicht im Interface IRegionManager definiert ist, sondern eine ExtentionMethod auf das Interface ist (was nen schwachsinn...)
Es gibt zwar auch ein anderes Interface IRegionViewRegistry, auf welches die Methode auch wirklich aufgerufen wird, und welches über den ServiceLocator abgerufen wird, aber da wäre die Frage: Wie registriere ich mein Mock in dem ServiceLocator von Prism?
Kann mir jemand helfen? Es hat doch bestimmt schonmal jemand so einen Test geschrieben, oder?!
Deine Welle der Begeisterung was das ASP.NET Prinzip der Authentifizierung angeht, schwingt förmlich mit^^ . Aber nen eigener Custom Manager ist glaub ich ein bisschen aufwendig. Man bekommt den user manager aber aus dem Request. Also gelöst ^^