Struts2 Problem: Parameter geht bei ..-validation einer Form verloren...

Tservarius

Mitglied
Hi,

mal wieder ein dolles Struts Problem...
Das Szenario in Kürze...
habe einen Link
Mitarbeiter_aendern.Action?userid=100

Anhand des Parameters 100 wird nun per Hibernate Objekt mit der ID 100 aus der Datenbank geladen und in ein Objekt überführt. Der Anwender kann die Daten des Objektes bearbeiten.
Ist eine strinknormale <s:form...> , User klickt auf abschicken, alles funktioniert wunderbar..

Nun möchte ich gerne die Eingaben validieren lassen, sprich: Wenn der User keinen Namen eingibt, bzw. diesen Wert mit blank überschreibt, gibts nen Fehler, da ja immer ein Name vorhanden sein sollte. Also schwupps, einen -validation.xml für die ActionKlasse gebastelt.
Diese Funktioniert auch und gibt brav Fehlermeldungen aus.

Nun zum Problem: Sobald ich die Validation eingebunden habe und diese einen Fehler wirft, verschwindet der Parameter "userid". Es kann also kein Objekt mehr geladen werden und das schlamassel ist da...

Gibts dort irgendwie eine Lösungsmöglichkeit ? Denke dass Problem hatten ja schon andere vor mir, hab jedoch bei Freund Google und in der Struts2 Doku nix passendes gefunden. Habe hier zwar immer von Interceptor-Definitionen im zusammenhang mit den Validations gelesen, kann jedoch damit nichts richtiges anfangen.

Irgendwie kommt es mir auch so vor, als wenn die Validation einen Fehler wirft, die Input Action auch gar nicht mehr durchlaufen wird. Kann man dies irgendwie erzwingen?
 
Zunächst vorweg:
die Validierung über -validation.xml ist wegen der Möglichkeit der Lokalisierung über Bundels zunächst charmant, hat aber diverse Nachteile.
Zum einen sind die Möglichkeiten der Validierung nur relativ schlicht und reichen in vielen Fällen nicht aus.
Zum anderen funktionieren sie nicht gemeinsam mit Tiles (zumindest nicht in der Fassung, die mit Struts 2.0.6 mitkam, danach habe ich es nicht mehr probiert).

Dein Problem hängt damit zusammen, dass wir uns trotz der massiven Erleichterungen, die Struts 2 uns bietet, in einer zustandsfreien Umgebung bewegen. Auch Struts "vergisst" in seiner Action mit jedem Client-Aufruf alles, was nicht als Inhalt eines Formularfeldes wieder zurück kommt. Dabei muss man beachten, dass dies schon innerhalb derselben Action passieren kann. Wenn etwa über die Methode prepare() eine Variable gefüllt wird, ist deren Inhalt beim Aufruf von validate() oder execute() schon wieder verloren. Klar, denn jedes Mal wird eine neue Instanz der Action geladen und die ist erst mal leer.

Du müsstest also in Deinem Fall den Parameter in ein - möglicherweise unsichtbares - Formularfeld umlesen, wenn Du ihn über den nächsten Request/Response-Zyklus retten willst.
Eine andere Alternative sind Inhalte, die in den Session Context gelegt werden, aber das ist für solche temporären Inhalte keine glückliche Lösung.

Das Problem besteht übrigens generell für alle Informationen, die über mehrere Maskenwechsel erhalten bleiben sollen, aber nicht Bestandteil der Formularfelder sind (z.B. ein Recordset).

Dann empfiehlt sich die Nutzung eines Interceptors, mit dem ein sog. "Conversation Scope" hergestellt wird.
Grob umrissen funktioniert der so:
Jede Action, die an einer "Conversation" teilnehmen soll, implementiert ein bestimmtes Interface, z.B. ConversationAware. Daran erkennt der Interceptor, ob die Action das Verfahren unterstützt.
Wenn ja, wird über eine Methode der Action nachgefragt, welches "Thema" die Action unterstützt (z.B. Adressen). Dann wird überprüft, ob ein passendes Conversation Object (vulgo: Session Bean) im Session Context hinterlegt ist.
Wenn nicht, wird ein neues von der Action abgefordert und in der Session hinterlegt.
Wenn ja, wird dieses Object der Action übergeben, also "injiziert".
Auf diese Weise werden alle Informationen, die in diesem Objekt enthalten sind, über beliebig viele Request/Response-Zyklen erhalten.

Und noch genialer:
der Interceptor übergibt das Conversation Object nicht der Action, sondern legt es noch auf dem Value Stack ab.
Dadurch stehen die Eigenschaften des Conversation Objects direkt in der View zur Verfügung. Das heißt, die Getter und Setter werden nur einmal im ConversationObject definiert und alle Actions, die das gleiche Object verwenden, also zum selben "Thema" gehören, brauchen diese nicht mehr zu implementieren, sondern kommunizieren nur noch mit diesem Objekt.

Wenn der Interceptor auf eine Action trifft, die entweder nicht ConversationAware ist oder ein anderes Thema unterstützt, wird das bisherige Objekt verworfen (wenn man es so will - andere Verhaltensweisen sind ja möglich).

Hört sich zunächst etwas kompliziert an, ist aber relativ einfach, wenn man den Mechanismus erst mal verstanden hat.

Das Verfahren ist sehr schön bei Mark Menard beschrieben: http://www.vitarara.org/cms/struts_2_cookbook/using_a_conversation_scope

Viel Erfolg!
 
Hi,

erstmal danke für die umfangreiche und sehr gute Antwort. Denke dass hat mich einen Schritt weiter gebracht.

Dennoch trotzdem zum Struts2 Verständnis (ich fange ja gerade erst mit dem Framework an)....

Du hast von einer prepare() methode gesprochen, diese wird aber nicht standardmäßig aufgerufen, oder ?

derzeit sieht mein verständnis so aus:
1) INPUT ( wird vor Anzeige der Form durchlaufen und momentan vom mir zum Daten laden, vorbelegen von Feldern genutzt)
2) Formular
3) validate()
4) .... -validation.xml
5) execute()

Habe ich diesen "Workflow" so richtig verstanden? Die prepare() methode von der du gesprochen hast, konnte ich leider in keiner Doku finden...

Noch eine kleine Frage: Gibt es eine Möglichkeit Struts irgendwie dazu zu bringen, direkt nach der -validation.xml, welche einen Fehler geworfen hat, abermals eine Action (z.B. input zu durchlaufen) . Momentan wird ja gar keine Action mit eingebunden, sondern lediglich, sobald die Validierung durch die -validation.xml fehlschlägt, zum Formular mit den entsprechenden Fehlermarkierungen, zurückgesprungen.

Vorab danke für deine ausgezeignete Hilfe!
 
Hallo, sorry wegen der Wartezeit, ich war die letzten Tage komplett zu.

Zu Deinen Fragen:

prepare()
Die Methode prepare() wird standardmäßig nicht aufgerufen, richtig. Das passiert erst, wenn die Action das Interface Preparable implementiert. Dann greift der passende, im Standard-Stack eingebundene Interceptor und wertet diese Methode aus.
Das ist einer der wirklich frischen Ansätze bei Struts 2, dass man mit etwas Hintergrundwissen unnötigen Ballast abwerfen, benötigte Funktionalität aber gezielt einbinden kann. Diese Mechanismen sind übrigens Spring entliehen.
Wenn Du die Methode prepare() regelmäßig bei Dir benötigst, lohnt es sich ggf. eine eigene Action-Basisklasse dazwischen zu schalten, die Preparable bereits implementiert.

Workflow
Nach meinem Verständnis ist es noch ein wenig komplizierter.
Als erstes wird die Action selbst aufgerufen, füllt die Eigenschaften, legt diese auf dem Stack ab und ruft dann die View auf, die sich aus dem Stack bedient.

Dabei werden nacheinander (z.T. per Interceptor) die Methoden der Action aufgerufen:
- prepare()
- validate()
- execute()

Das führt schnell zu einer Falle, z.B. wenn Pflichtfelder beim Erstaufruf noch leer sind.

Ich verwende daher ein hidden field in der View, dem ich entnehmen kann, ob es sich um den Erstaufruf der Action (bestimmte Prüfungen werden übergangen) oder nachfolgende Aufrufe handelt (alle Prüfungen werden durchgeführt).
Die passenden Methoden befinden sich bei mir in der Action-Basisklasse.

Auch execute() wird ausgeführt, wenn bei validate() keine ActionErrors oder ActionMessages hinterlegt werden. Andernfalls geht die Kontrolle sofort an die View; und execute() wird nicht ausgeführt.
Auch hier steckt eine potentielle Falle: beim Erstaufruf sollten natürlich keine Fehler oder Warnungen ausgegeben werden und dann wird auch execute() brav durchlaufen.
Genau deswegen gibt's den Return Code INPUT.

Wichtig bei diesen Zyklen ist eben, dass nur die Inhalte der Formularfelder transportiert werden. Alle Variablen der Action gehen zwischendurch verloren, weil diese jedes Mal neu instanziiert wird.

Validierung über -validate.xml
Der Mechanismus mit -validate.xml ist in diesem Ablauf fast schon ein Fremdkörper.
Ich meine, dass er noch vor validate() greift und die Ausführung der Action unterdrückt. Ein Mischbetrieb ist deswegen nicht möglich.
Ich halte das für ein abwärtskompatibles Relikt, das in einfachen Fällen durchaus nützlich ist, z.B. wegen der einfachen Möglichkeit der Lokalisierung, aber schnell seine Grenzen erreicht.

Es lohnt sich in jedem Fall, innerhalb der Struts-Doku zu googeln. Damit findet man auch verstecktere Beschreibungen ganz gut und stößt immer wieder auf Tips, die mit dem momentanen Thema zwar nicht unmittelbar zu tun haben, sich aber bei anderer Gelegenheit schnell als nützlich erweisen, oder beantworten Fragen, die man sich schon lange gestellt hat ;-)

Danke für das Lob übrigens, aber das gebe ich gern an Jungs wie Mark Menard weiter, die mir die Möglichkeiten überhaupt erst eröffnet haben.

Viel Erfolg!
 
Zuletzt bearbeitet:
wieder mal eine super präzise und gute Antwort von dir, danke !

hört sich alles recht durchdacht an - wenn ich da noch so an struts 1 denke ;-) ... -
mir stellt sich derzeit nur die Frage, wozu prepare() implementieren, wenn input() standardmäßig durchlaufen wird und einfach überschrieben werden kann.
imo nutze ich input(), validate() und execute().

input() um die Formularfelder mit setXYZ() vorzubelegen, Listenwerte aus der Datenank zu selektieren und Dropdowns u.ä. zuzuweisen.

validate() nun an Stelle der der -validation.xml mit der Rückgabe von entsprechenden ActionErrors und einem return INPUT; ....

execute() um die Daten dann wieder über Hibernate in die Datenbank zu schmeissen.


Läuft nun auch alles wunderbar. Siehst du irgendwelche performancemäßigen Probleme?
So wie ich deine Ausführung jetzt verstanden habe sollte es ja so i.O. sein, jedoch an seine Grenzen stoßen, wenn es darum geht Paramter von Action zu Action weiterzureichen. In diesem Fall wäre dann das von dir beschriebene ConversionScope notwendig.

nochmal danke für deine Ausführungen!
 
Hallo,
zunächst zu Deiner Frage:

wenn ich das richtig verstanden habe, dient prepare() dazu, Dinge vorzubereiten oder Daten abzurufen, noch bevor die die übrigen Methoden der Action durchlaufen werden.

Als Beispiel wird gern das Szenario genommen, dass bestimmte Daten bzw. Objekte zunächst aus der Persistenzschicht (Datenbank bzw. ein objektorientiertes Framework wie Hibernate) geholt werden müssen.
Wie so oft führen viele Wege nach Rom und ich persönlich nutze prepare() gerne für Vorbereitungsaufgaben, die in Dialogsituationen, bei denen die Action wiederholt durchlaufen wird (und das war ja Dein Ausgangsszenario) nur einmal, und zwar vor dem ersten Aufruf des damit zusammen gehörigen Formulars, durchlaufen werden sollen.
Das "entschlackt" die nachfolgenden Methoden nicht unerheblich und wirkt deutlich strukturierter. Ist aber (wahrscheinlich) eine etwas weitergehende Nutzung als von den Struts-Entwicklern ursprünglich beabsichtigt.

Siehe hierzu die Beschreibung zum Prepare Interceptor, der die Methode prepare auswertet: http://struts.apache.org/2.0.11/docs/prepare-interceptor.html

Du machst das Gleiche offenbar mit der input-Methode.
Dass es die Bypass-Möglichkeit über die Methode input() gibt, war mir noch gar nicht bewusst geworden und ist wieder mal gut in der Doku versteckt:
http://struts.apache.org/2.0.11/docs/validating-input.html

Danke für den Tip!

Im übrigen sind die Validierungsmechanismen noch weiter ausgebaut worden, seit ich mich intensiv damit beschäftigt hatte (im Frühjahr). Es lohnt sich, die entsprechende Beschreibung genau zu studieren:
http://struts.apache.org/2.0.11/docs/validation.html

Die Entwicklung ist ganz offensichtlich noch immer sehr dynamisch.
Ich bewundere diese Struts-Entwickler - wann schlafen die eigentlich?
 
Zuletzt bearbeitet:

Neue Beiträge

Zurück