Laden...

Validierug im VM - Models in einer Collection

Erstellt von t0ms3n vor 9 Jahren Letzter Beitrag vor 9 Jahren 3.593 Views
T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 9 Jahren
Validierug im VM - Models in einer Collection

Hallo zusammen,

ich bin auf der Suche nach einem Denkanstoß im Bereich Validierung. Selbsverständlich nutze ich bereits IDataErrorInfo.

Ich setze derzeit auf eine Kombination aus Validierung im Model und im ViewModel. Das Model übernimmt dabei einfachste Validierungen wie "Not Empty" und das ViewModel alles weitere, wo z.B. ein Service genutzt werden muss.

Ich habe nun allerdings folgende Situation.

Ich habe ein Item. Dieses besitzt diverse ChildItems welche einen Status haben. Dabei kann der Status der einzelnen Items innerhalb einer ListView geändert werden.
Zusätzlich besitzt jedes ChildItem nochmal eine Liste von Items welche auch einen Status haben.
Es muss nun beim Ändern eines Statuses geprüft werden ob alle ChildItems des ChildItems in einem bestimmten mindest Status sind.

Ich möchte nun vermeiden, dass ich an dieser Stelle einen Service aus dem Model heraus aufrufen muss.

Welche Möglichkeiten habe ich noch, außer nicht direkt die Models als ChildItems zu haben sondern diese vorher noch in ViewModels zu wandeln (nur um den ViewModel willen 😃)?

Gruß
t0ms3n

W
955 Beiträge seit 2010
vor 9 Jahren

Hallo,

warum muß das so kompliziert gemacht werden? Kann der Parent nicht einfach seine Kinder fragen ob Status "5" OK ist und die fragen dann ihre Kinder?

T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 9 Jahren

Ja das soll das Ziel sein, allerdings soll natürlich über das ErrorTemplate im entsprechenden Control direkt der Fehler angezeigt werden.

W
955 Beiträge seit 2010
vor 9 Jahren

Ich verstehe das Problem nicht. Du brauchst doch nur IDataErrorInfo richtig zu implementieren, den Fehler bindest Du doch im XAML. Wenn Du das im Grandchild anzeigen willst brauchst Du doch nur Parent.Parent zu befragen ob dessen Status passend ist, wenn der Rootknoten das anzeigen soll muß er eben seine Kinder befragen ob jmd ein Problem damit hat. Mußt halt INotifyPropertyChanged entsprechend implementieren.

T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 9 Jahren

Das Child kennt seinen Parent nicht. Das Problem wäre auch dann nicht vorhanden, wenn IDataErrorInfo "Item.Value" unterstützen würde.

Für einfache "Item.Value" Bindings kann man sich helfen, indem das VM eine eigene Property anbietet "ItemValue" und diese auf die Value vom Item zeigt.

Was aber wenn es eben eine Collection ist? (Abgesehen von den zwei genannten Möglichkeiten)

Es hat auch nichts mit dem INotify zu tun, denn eine einfache Validierung würde natürlich funktionieren, allerdings möchte ich einerseits keine Servicecalls im Model haben und das Model (der Status) kennt eben seinen Parent nicht.

F
10.010 Beiträge seit 2004
vor 9 Jahren

Und?

Du sagst doch selber das Du ViewModels hast, da ghört das rein.

T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 9 Jahren

Nein habe ich für die Childs nicht.

Ich möchte nun vermeiden, dass ich an dieser Stelle einen Service aus dem Model heraus aufrufen muss.

Welche Möglichkeiten habe ich noch, außer nicht direkt die Models als ChildItems zu haben sondern diese vorher noch in ViewModels zu wandeln (nur um den ViewModel willen 😃)?

W
955 Beiträge seit 2010
vor 9 Jahren

Weiß nicht ob es hilft, aber Du kannst Dir mal den Automapper anschauen, der kann rekursiv mappen (falls Du den Aufwand scheust manuell die ViewModels für die Children zu füllen)

P
157 Beiträge seit 2014
vor 9 Jahren

Hallo,

ein typisches Problem deiner Modelarchitektur :

Du setzt die Eigenschaft eines Childs in Abhängikeit der Eigenschaft eines "NachbarChilds" als in der gleichen Liste.

Also du hast nun mehrere Lösungen :

  1. Dein Child löst ein Changed-Event aus, welches du am Parent abfangen und behandeln kannst.
  2. Du setzt die Eigenschaft deines Childs über seinen Parent mit einer Set-Methode, in dieser SetMethode kannst du vorher die anderen Childs überprüfen.
  3. Verwende eine abstraktere Lösung. Du kannst die Statusänderung über ein Command lösen, das zugriff auf parent und child hat. (command ans parent hängen und child über commandparameter)
  4. VisitorPattern über ein command verwenden

Manchmal ist es aber viel einfacher wenn du dem Child sein Parent bekannt machst, dort eine Eigenschaft/Methode dran hängst um die rechtlichen Childs zu prüfen und dann in Abhängigkeit des Rückgabewertes deine Statusänderungen vorzunehmen (parent.GetStateofChildsExcept(this) : status)...irgendwas in der Art. Ist zwar nicht die sauberste Lösung, aber die am wenig aufwändigsten. Architektur ist immer ein Balanceakt zwischen Pragmatismus und "Schönheit".

Kleine Nebenfrage :Wieso eigentlich "natürlich dataerrorinfo" ? Ich verwende die Dinger so gut wie nie, irgendwie fehlt mir meist der praktische Nutzen davon, der Aufwand ist weitaus größer, als eine ValidationRule zu verwenden

vg

Wenn's zum weinen nicht reicht, lach drüber!

T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 9 Jahren

Wie lässt sich 1-4 sinnvoll mit dem ErrorTemplate verbinden bzw. einem IsValid?

Manchmal ist es aber viel einfacher wenn du dem Child sein Parent bekannt machst, dort eine Eigenschaft/Methode dran hängst um die rechtlichen Childs zu prüfen und dann in Abhängigkeit des Rückgabewertes deine Statusänderungen vorzunehmen (parent.GetStateofChildsExcept(this) : status)...irgendwas in der Art. Ist zwar nicht die sauberste Lösung, aber die am wenig aufwändigsten. Architektur ist immer ein Balanceakt zwischen Pragmatismus und "Schönheit".

Schon, allerdings gefallen mir z.B. Motoren die den Status 'Kaputt' haben und der Status 'Kaputt' den Motor kennt überhaupt nicht 😦.

Kleine Nebenfrage :Wieso eigentlich "natürlich dataerrorinfo" ? Ich verwende die Dinger so gut wie nie, irgendwie fehlt mir meist der praktische Nutzen davon, der Aufwand ist weitaus größer, als eine ValidationRule zu verwenden

Mit dem natürlich wollte ich vorbeugen Hinweise auf nutze dies zu bekommen.
Über ValidationRules habe ich auch schon nachgedacht, allerdings habe ich hier nur über "Umwege" die Möglichkeit Commands zu beeinflussen.

P
157 Beiträge seit 2014
vor 9 Jahren

Wie gesagt, was die Error-Templates angeht, verwende ich hauptsächlich ValidationRules, die können ziemlich viel behandeln. Hält dich ja niemand von ab, eigene zu schreiben. Du kannst doch im IsValid über deine Childs iterieren, hält dich auch niemand von ab.

Wenn du im Child nicht aufs Parent zugreifen willst, bleibt dir nur die auswert vom Parent aus. Ich kann dir nun aus dem Stehgreif schlecht sagen wie du dein Problem konkret lösen kannst, wenn du nix postest.

vg

Wenn's zum weinen nicht reicht, lach drüber!

T
t0ms3n Themenstarter:in
314 Beiträge seit 2013
vor 9 Jahren

Hmmm mir ist nicht klar, wie Code hier an der Stelle hätte helfen können.

Ich habe es nun (fürs erste) mit einer ValidationRule umgesetzt.
Dazu habe ich nun auch eine BindingGroup im VM, um entsprechend festzustellen, ob Fehler vorhanden sind. Zusätzlich noch ein Command um Änderungen mitzubekommen.
Richtig zufrieden bin ich damit jedoch nicht.

Das XAML sieht so (reduziert) aus:


<ListView x:Name="itemListView" Margin="10 4 5 4" ItemsSource="{Binding ChildItems}" HorizontalContentAlignment="Stretch" 
			SelectedItem="{Binding SelectedItem}"  BindingGroup="{Binding ItemValidationGroup}">                                                       
	<ListView.View>
		<GridView>
			<GridViewColumn Width="200" DisplayMemberBinding="{Binding Item.Name}" Header="Name" />
			<GridViewColumn Width="100" Header="State">
				<GridViewColumn.CellTemplate>
					<DataTemplate>
						<Grid >
							<ContentPresenter Content="{Binding}">
								<ContentPresenter.Style>
									<Style TargetType="{x:Type ContentPresenter}">
										<Style.Triggers>
											<DataTrigger Binding="{Binding DataContext.IsItemEditable, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" Value="False">
												<Setter Property="ContentTemplate">
													<Setter.Value>
														<DataTemplate>
															<TextBlock Text="{Binding Item.State.Name}" />
														</DataTemplate>
													</Setter.Value>
												</Setter>
											</DataTrigger>
											<MultiDataTrigger>
												<MultiDataTrigger.Conditions>
													<Condition Binding="{Binding DataContext.IsItemEditable,  ElementName=itemListView}" Value="True" />
													<Condition Binding="{Binding EntityState}" Value="Unchanged" />
												</MultiDataTrigger.Conditions>
												<Setter Property="ContentTemplate">
													<Setter.Value>
														<DataTemplate>
															<ComboBox Validation.ErrorTemplate="{StaticResource simpleErrorTemplate}" 
																	   ItemsSource="{Binding DataContext.ItemStates, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
																	  >
																<i:Interaction.Triggers>
																	<i:EventTrigger EventName="SelectionChanged">
																		<catel:EventToCommand Command="{Binding DataContext.ItemStateChangedCommand, ElementName=itemListView}" PassEventArgsToCommand="False" DisableAssociatedObjectOnCannotExecute="False" />
																	</i:EventTrigger>
																</i:Interaction.Triggers>
																<ComboBox.SelectedItem>
																	<Binding BindingGroupName="ItemValidationGroup" Path="Item.State" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
																		<Binding.ValidationRules>
																			<DataErrorValidationRule />
																			<vr:ItemStateValidationRule ValidationStep="UpdatedValue"  ValidatesOnTargetUpdated="True">
																			</vr:ItemStateValidationRule>
																		</Binding.ValidationRules>
																	</Binding>
																</ComboBox.SelectedItem>
															</ComboBox>
														</DataTemplate>
													</Setter.Value>
												</Setter>
											</MultiDataTrigger>
										</Style.Triggers>
									</Style>
								</ContentPresenter.Style>
							</ContentPresenter>
						</Grid>
					</DataTemplate>
				</GridViewColumn.CellTemplate>
			</GridViewColumn>
		</GridView>
	</ListView.View>
</ListView>
P
157 Beiträge seit 2014
vor 9 Jahren

Das ist ja noch verknoteter als ich dachte.

http://www.scottlogic.com/blog/2008/11/28/using-bindinggroups-for-greater-control-over-input-validation.html

Das dürfte dir vielleicht helfen. Mit den BindingGroups kann man mehrere Werte - auch gegeneinander-validieren.

Noch ein paar kleine Tips :

RelativeSourceBindings sind ziemliche Zeitfresser, da man immer durch den Visual-Tree iteriert. es ist auch nicht so klug, über mehrere hierarchieebenen zu binden, gibt deinen objekten die eigenschaften die sie brauchen -> Wrap deine Entität in ein eigenes ViewModel wenn du keine Dto's verwenden möchtests...dann kannst du sowas wie isselected/ischanged binden, das macht deinen arbeitsaufwand um einiges einfacher.

vg

Wenn's zum weinen nicht reicht, lach drüber!