Laden...

ASP.Net-Core-MVC - feststellen, ob Feld in Form geändert wurde

Erstellt von pollito vor 10 Monaten Letzter Beitrag vor 10 Monaten 515 Views
pollito Themenstarter:in
314 Beiträge seit 2010
vor 10 Monaten
ASP.Net-Core-MVC - feststellen, ob Feld in Form geändert wurde

Hallo,

ich mühe mich mit dem Thema ASP.Net-MVC ab, da es für mich neu ist.

In einem Formular gebe ich nur einige Felder eines Datensatzes aus. Eines davon kann vom Anwender geändert werden:

@model Protein_Models.Inproduktion

@{
    ViewData["Title"] = "Produktion ändern";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@{
    string Geaendert		= Model.Geaendert.ToString("d")		?? string.Empty;
    string Synchronisiert	= Model.Synchronisiert.ToString()	?? string.Empty;
}

<form method="post" asp-action="Edit">
    <div class="border p-3">
        <div class="form-group-row">
            <h2 class="pb-2">Produktion @Model.Barcode</h2>
        </div>
        <div class="container">
            <div class="row">
                <div class="col-md-4">
                    <label asp-for="VzAnummer" class="form-label">Auftragsnummer</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="VzAnummer" class="form-control-plaintext" readonly />
                </div>
                
                <div class="col-md-4">
                    <label asp-for="VzNummer" class="form-label">Artikelnummer</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="VzNummer" class="form-control-plaintext" readonly />
                </div>
                
                <div class="col-md-4">
                    <label asp-for="VzBezeichnung" class="form-label">Artikelbezeichnung</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="VzBezeichnung" class="form-control-plaintext" readonly />
                </div>
                
                <div class="col-md-4">
                    <label asp-for="VzKurzbez" class="form-label">Artikelkurzbezeichnung</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="VzKurzbez" class="form-control-plaintext" readonly />
                </div>
                
                <div class="col-md-4">
                    <label asp-for="VzZuDisponieren" class="form-label">Auftragsmenge</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="VzZuDisponieren" class="form-control-plaintext" readonly />
                </div>
                
                <div class="col-md-4">
                    <label asp-for="ProduziertMenge" class="form-label">Produzierte Menge</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="ProduziertMenge" class="form-control"/>
                </div>
                
                <div class="col-md-4">
                    <label asp-for="Status" class="form-label">Status</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="Status" class="form-control-plaintext" readonly />
                </div>
                
                <div class="col-md-4">
                    <label asp-for="Synchronisiert"	class="form-label">Synchronisiert am</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="@Synchronisiert" class="form-control-plaintext" readonly />
                </div>
                
                <div class="col-md-4">
                    <label asp-for="Ersteller" class="form-label">Erstellt von</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="Ersteller" class="form-control-plaintext" readonly />
                </div>
                
                <div class="col-md-4">
                    <label asp-for="Erstellt" class="form-label">Erstellt am</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="Erstellt" class="form-control-plaintext" readonly />
                </div>
                
                <div class="col-md-4">
                    <label asp-for="Username" class="form-label">Zuletzt geändert von</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="Username" class="form-control-plaintext" readonly />
                </div>
                
                <div class="col-md-4">
                    <label asp-for="Geaendert" class="form-label">Zuletzt geändert am</label>
                </div>
                <div class="col-md-8">
                    <input asp-for="@Geaendert" class="form-control-plaintext" readonly />
                </div>
            </div>
        </div>
        <div class="col-md-12">
            <div class="row justify-content-end">
                <div class="col-auto">
                    <button type="submit" class="btn btn-primary btn-lg" data-toggle="tooltip" data-placement="top" title="Speichern"><i class="fas fa-save"></i></button>
                    <a asp-controller="InProduktion" asp-action="Index" class="btn btn-success btn-lg ml-2" data-toggle="tooltip" data-placement="bottom" title="Zurück"><i class="fas fa-arrow-left"></i></a>
                </div>
            </div>
        </div>
    </div>
</form>

Das Modell Inproduktion hat weitere Felder, die man aber an dieser Stelle nicht braucht.

Im Controller habe ich eine Methode, in der das Update stattfindet.

[HttpPost]
public IActionResult Edit(Inproduktion inprodukktion)
{
    _InProduktionRepository.Update(inprodukktion);
    _InProduktionRepository.Save();
    return RedirectToAction("Index", "InProduktion");
}

Wenn ich mir jetzt inproduktion (das, was vom Formular kommt) anschaue,  sind nur die Felder mit sinnvollen Werten gesetzt, die auch im Formular verwendet wurden. Alle anderen haben Null oder einen Standardwert. Das finde ich unpraktisch.

Gibt es eine Möglichkeit, festzustellen, welche Formularfelder manuell geändert wurden?

Oder ist es möglich, das Verhalten so zu abzuändern, dass der gesamte Datensatz inproduktion zurückgegeben wird, also die Feldwerte, die im Formular nicht verwendet wurden mit ihren Datenbankwerten?

Oder geht man schließlich ganz anders vor?

Hintergrund:
Die Update-Methode (hier _InProduktionRepository.Update(inprodukktion)) hat keine Ahnung vom aktuellen Kontext. Diese sollte autark feststellen können, welche Felder geändert wurden. Wenn jedoch nur die Felder mit sinnvollen Werten gesetzt sind, die auch im Formular verwendet wurden, während alle anderen Null oder einen Standardwert aufweisen, entsteht Quark mit Soße.

Ich hoffe, ich konnte das einigermaßen erklären.

Danke und liebe Grüße

René

René

16.842 Beiträge seit 2008
vor 10 Monaten

Gibt es eine Möglichkeit, festzustellen, welche Formularfelder manuell geändert wurden?

Nein. Dafür gibts keinen Automatismus. Musst selbst implementieren.
HTTP kennt nur: Wert übertragen, oder nicht.

Die Update-Methode (hier _InProduktionRepository.Update(inprodukktion)) hat keine Ahnung vom aktuellen Kontext. Diese sollte autark feststellen können, welche Felder geändert wurden.

Das kann die Methode ohne hellseherische Kräfte nicht leisten.

Oder geht man schließlich ganz anders vor?

Ja. Man geht grundlegend anders vor. Angefangen damit, dass man keine Datenbank-Entitäten in der Oberfläche verwendet. Das gehört zu den absoluten Grundlagen.
[Artikel] Drei-Schichten-Architektur
Man verwendet - wie bei jeder anderen Technologie - abstrahierte Modelle, im Web-Kontext meist Request- und Submit-Models genannt. Was Du da mit Deinen Models und Entitäten tust fällt leider eher in die Bezeichnung "Gefrickel". Gelinde gesagt machst Du das, was man allein aus Logik/Sicherheitsgründen niemals tun sollte: blind Modelle aus einem Request annehmen und in eine DB stecken.

Der korrekte Weg wäre extra Update-Modelle zu verwenden, die dann mit einer Patch-Operation mitgeteilt werden.
Property im Patch = null → keine Änderung

Willst Du alles über einen Post-Request lösen, dann brauchst trotzdem eigene Modelle, aber auch eine eigene Logik zur Verarbeitung von Eigenschaften eines DB Modells.

pollito Themenstarter:in
314 Beiträge seit 2010
vor 10 Monaten

Zitat von Abt

Gibt es eine Möglichkeit, festzustellen, welche Formularfelder manuell geändert wurden?

Nein. Dafür gibts keinen Automatismus. Musst selbst implementieren.
HTTP kennt nur: Wert übertragen, oder nicht.

OK. Dann muss eine andere Lösung her.

Die Update-Methode (hier _InProduktionRepository.Update(inprodukktion)) hat keine Ahnung vom aktuellen Kontext. Diese sollte autark feststellen können, welche Felder geändert wurden.

Das kann die Methode ohne hellseherische Kräfte nicht leisten.

Das versteht sich vom selbst. Ich bin auf ModelState.GetFieldValidationState gestoßen. In Verbindung mit einer Feldwertabfrage auf wedernull noch default dürfte es möglich sein, Änderungen festzustellen.

// Ist das Feld gültig und wurde es geändert?
if (ModelState.GetFieldValidationState(fieldName) == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Valid)
{
    // Zusätzlich überprüfen, dass der Wert weder null oder der Standardwert ist.
    var field = inproduktion.GetType().GetProperty(fieldName)?.GetValue(inproduktion, null);

    if (field != null && field != default)
    {
        // An dieser Stelle sind wir sicher, dass das Feld geändert wurde
        // und einen gültigen Wert enthält. Daher kann das das ursprüngliche
        // Feld überschrieben werden.

    }
}

Noch nicht geprüft. Wenn das so funktioniert, würde ich den Code generisch implementieren.

Oder geht man schließlich ganz anders vor?

Ja. Man geht grundlegend anders vor. Angefangen damit, dass man keine Datenbank-Entitäten in der Oberfläche verwendet. Das gehört zu den absoluten Grundlagen.
[Artikel] Drei-Schichten-Architektur

OK, vieles ist bereits in Schichten implementiert und vieles auch abstrahiert. Das ist aber nicht das Thema meiner Frage. Ich bin am Lernen und nun stehe ich vor einem der vielen Probleme. Ein Schritt nach dem anderen – deine Tipps werde ich mir schon zu Herzen nehmen.

Man verwendet - wie bei jeder anderen Technologie - abstrahierte Modelle, im Web-Kontext meist Request- und Submit-Models genannt. Was Du da mit Deinen Models und Entitäten tust fällt leider eher in die Bezeichnung "Gefrickel". Gelinde gesagt machst Du das, was man allein aus Logik/Sicherheitsgründen niemals tun sollte: blind Modelle aus einem Request annehmen und in eine DB stecken.

Der korrekte Weg wäre extra Update-Modelle zu verwenden, die dann mit einer Patch-Operation mitgeteilt werden.
Property im Patch = null → keine Änderung

Willst Du alles über einen Post-Request lösen, dann brauchst trotzdem eigene Modelle, aber auch eine eigene Logik zur Verarbeitung von Eigenschaften eines DB Modells.

Ich nutze bereits Repositories, die auf extra dafür implementierten Schnittstellen basieren. Diese abstrahieren bereits den Datenbankzugriff in einer extra Schicht.

Es gibt sicher Luft nach oben, wie bereits erwähnt, ein Schritt nach dem anderen.

Nochmals vielen Dank!

René

René

16.842 Beiträge seit 2008
vor 10 Monaten

Mh. Mein Vorschlag, den man technisch durchaus als Standard bezeichnen kann, ist simpel, weit verbreitet und unterstützt auch komplexe Szenarien.
Deine Idee ist customized, vergleichsweise komplex und unterstützt nur simple Szenarien.

Klar, eigene Ideen sind toll - aber zu welchem Preis, und so weit weg vom Standard. Wozu?
Nicht falsch verstehen: mir ist im Prinzip egal was Du umsetzt oder nicht; es sind nur Tipps.

Ich nutze bereits Repositories, die auf extra dafür implementierten Schnittstellen basieren.

Das hat damit nix zutun. Was Du machst sind Roundtrips bzw. komplette Replacements mit allen Effizienznachteilen. Repository hin oder her.
EF Core kann Partial Updates, zB mit ExecuteUpdate, wo man nur die Properties angibt - auch dynamisch - die man ändern will.

Man kann also dieses gesamte Thema sehr effizient und standardisiert lösen, wenn man einfach die Mittel nutzt, die existieren.

pollito Themenstarter:in
314 Beiträge seit 2010
vor 10 Monaten

OK, macht Sinn. Dann fokussiere ich mich anders: Ich trenne die Modellschicht komplett von der Darstellungsschicht, indem ich View-Models verwende. Diese enthalten nur die Eigenschaften, die schließlich in der Ansicht angezeigt werden sollen.

Aber auch wenn ich das mache, muss ich wahrscheinlich immer noch sicher gehen, in der Datenbank nur die Felder zu aktualisieren, die auch geändert worden sind. In meinem vorigen Beispiel könnte ich z. B. grob so vorgehen:

public class ProduktionViewModel
{
    public string VzAnummer { get; set; }
    public string VzNummer { get; set; }
    public string VzBezeichnung { get; set; }
    public string VzKurzbez { get; set; }
    public int VzZuDisponieren { get; set; }
    public int ProduziertMenge { get; set; }
    public string Status { get; set; }
    public DateTime Geaendert { get; set; }
}

Und auf der View:

@model ProduktionViewModel

@{
    ViewData["Title"] = "Produktion ändern";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<!-- Rest des Codes -->

Ich konnte das noch nicht überprüfen, da ich zu einem Geburtstag muss 😉 Was geschieht, wenn ich in der View ein Feld auslasse? Ich gehe davon aus, dass dieses dann Null oder einen Default-Wert hat. Aber das kann ich erst morgen oder am Montag (je nachdem, wie feucht der Geburtstag war) überprüfen.

Danke und ein schönes Wochenende!

René

René