Archiv der Kategorie: Controller

ASP.NET MVC HandleUnknownAction zum Zurückgeben des aufgerufenen Views verwenden


In vielen meiner MVC Projekten, in denen ich auf AngularJS für das UI setze, verwende ich immer noch die Standard MVC Views mit “Layout = null”, um das HTML Template auch .NET Seitig noch manipulieren zu können. Dabei gibt es dann eine Menge Controller Funktionen die einfach nur ein ActionResult mit dem zugehörigen View zurückgeben z.B.:

public class TodoController : Controller
{
   #region Views
   public ActionResult TodoOverview()
   {
       return View();
   }
   #endregion

   #region Modals
   public ActionResult TodoEditModal()
   {
       return View();
   }
   #endregion
}

Dabei handelt es sich immer wieder um den gleichen Boilerplate Code und zum Glück gibt es auch hier eine Möglichkeit dies mit ASP.NET MVC zu optimieren und nur noch eine Funktion “HandleUnknownAction” zu verwenden.

public class TodoController : Controller
{
    protected override void HandleUnknownAction(string actionName)
    {
       try
        {
           this.View(actionName).ExecuteResult(this.ControllerContext);
         }
         catch (System.Exception)
         {
            this.View("404").ExecuteResult(this.ControllerContext);
          }
     }
}

Diese Überschriebene Funktion wird immer aufgerufen, wenn zum zugehörigen Controller Aufruf keine passende Action im aktuellen Controller gefunden werden kann und versucht dann automatisch den übergebenen Actionnamen auf einen existierenden View zu Mappen.

Wenn es hier noch andere oder bessere Lösungen gibt freue ich mich über einen Kommentar, aber aktuell habe ich nichts besseres finden können.

Quelle:

http://stephenwalther.com/archive/2008/07/21/asp-net-mvc-tip-22-return-a-view-without-creating-a-controller-action

Advertisements

Leichtgewichtige Controller – wohin mit meiner Controllerlogik


Auch wenn man bisher nur an kleineren ASP.NET MVC Projekten gearbeitet hat, kann der Code in einem Controller sehr schnell, sehr unübersichtlich werden. Denn im Controller wird meist zu viel Logik “abgelegt”, die sich eigentlich sehr gut auslagern lässt.

In meinen Webprojekten versuche ich, dass ein Controller nur prüft ob der Modelstate Valide ist und um dann den passenden View aufzurufen/laden (“routen”) . Das Laden der Viewmodeldaten übernimmt bei mir dann entweder Variante 1, “das intelligente Model” oder Variante 2 der “Modelbuilder”, natürlich immer nur eine Variante pro Projekt. Damit erhält man einen sehr übersichtlichen und leichtgewichtigen Controller.

Variante 1: Das intelligente Model

Hier enthält das jeweilige Viewmodel, nicht nur die Properties die angezeigt werden sollen für den jeweiligen View, sondern es enthält ebenfalls die Logik sich selbst mit Daten zu füllen oder sich selbst zu speichern. Bei dieser Variante erntet man oft Kritik, da viele die starre Meinung vertreten, das ein Model niemals Logik enthalten sollte. Dem stimme ich prinzipiell auch zu, aber ich finde hier stellt es eine gute Alternative dar, auch dem Model mal etwas Logik zu verpassen.

In meinem Fall benutzte ich Dependency Injection (Ninject) um im Controller das passende Repository einzubinden. Das Repository übergebe ich dem Model in der jeweiligen Funktion, wo das Repository benötigt wird. Wichtig ist das man darauf achtet, wenn es sich um ein Postbackmodel handelt, das ein Parameterloser Konstruktor existiert, sonst kann der Modelbinder von .NET keine Instanz erstellen und das Model mit Daten füllen.

public class AutoController : Controller
{
    #region Injections
    [Inject]
    public IAutoRespository AutoRespository { protected get; set; }
    #endregion

    #region View Functions
    public ActionResult AutoList()
    {
        AutoListModel model = new AutoListModel();
        model.Load(AutoRespository);
        return View("AutoList", "Auto", model);
    }

    public ActionResult AutoView(int id)
    {
        AutoViewModel model = new AutoViewModel();
        model.Load(id, AutoRespository);
        return View("AutoView", "Auto", model);
    }

    public ActionResult AutoEdit(int id)
    {
        AutoEditModel model = new AutoEditModel();
        model.Load(id, AutoRespository);
        return View("AutoEdit", "Auto", model);
    }

    [HttpPost]
    public ActionResult AutoSave(AutoEditModel model)
    {
        if (ModelState.IsValid)
        {
            model.SaveOrUpdate(AutoRespository);
            RedirectToAction("AutoView", "Auto", new {id = model.Id});
        }

        return View();
    }
    #endregion
}

Dann z.B. noch das passende AutoEditModel welches eine “Load” und eine “SaveOrUpdate” Methode enthält um sich selbst mit Daten zu füllen oder sich selbst zu speichern.

public class AutoEditModel : IAutoEditModel
{
    #region Member
    public int Id { get; set; }

    [Required]
    public string Marke { get; set; }

    [Required]
    public string Nummernschild { get; set; }
    #endregion

    #region Public Functions
    public void Load()
    {
        Marke = string.Empty;
        Nummernschild = string.Empty;
        Id = 0;
    }

    /// <summary>
    /// Füllen unseres Models mit den passenden Daten.
    /// </summary>
    /// Autorepository für die Abfragen
    public void Load(int autoId, IAutoRespository autoRespository)
    {
        try
        {
            var auto = autoRespository.Load(autoId);
            if (auto == null)
            {
                //TODO Fehlermeldungen kapseln, damit diese entsprechend im View angezeigt werden kann.
                return;
            }

            Id = autoId;
            Marke = auto.Marke;
            Nummernschild = auto.Nummernschild;
        }
        catch (Exception exception)
        {
            //TODO Fehlermeldungen kapseln, damit diese entsprechend im View angezeigt werden kann.
        }
    }

    /// <summary>
    /// Speichern oder Aktualisieren der aktuellen Modeldaten
    /// </summary>
    public void SaveOrUpdate(IAutoRespository autoRespository)
    {
        try
        {
            Auto auto = new Auto();
            auto.Id = Id;
            auto.Marke = Marke;
            auto.Nummernschild = Nummernschild;

            var savedAuto = autoRespository.AddOrUpdateAuto(auto);
            Id = savedAuto.Id;
        }
        catch (Exception exception)
        {
            //TODO Fehlermeldungen kapseln, damit diese entsprechend im View angezeigt werden kann.
        }
    }
    #endregion
}

Es ist auch möglich die Models noch weiter aufzuteilen, das man ein Model zum Füllen/Anzeigen des “Editmodels” hat und ein “Postbackmodel” zum Speichern der Daten. Im Beispiel handelt es sich um ein Model welches als Anzeige- und Postbackmodel benutzt wird. Die Viewmodels können auch sehr gut mit Unit Tests getestet werden, da im Normalfall keinen Verweis auf den HTTP Context enthalten ist und alle notwendigen Abhängigkeiten wie das Repository bei Bedarf entsprechend übergeben werden.

Wenn man wie bereits erwähnt, die Models sehr strikt in Anzeige- und Postbackmodels trennt, dann ist es auch kein Problem im Controller zumindest die Anzeigemodels direkt mit zu Injecten.

In der Solution selbst sieht die Ordner- und Dateistruktur dann z.B. folgendermaßen aus.

image

Variante 2: Modelbuilder

In Variante 2 wird wieder alles strikt getrennt und man hat ein sauberes Model und einen sauberen Controller. Hier wird ein Modelbuilder erstellt der die Daten für unsere entsprechenden Models lädt oder speichert. Hier muss man selbst prüfen ob man für jedes Viewmodel einen eigenen Modelbuilder erstellt oder wie in meinem Beispiel, nur einen Modelbuilder.

Es wird der Modelbuilder in den Controller injected und das Repository wird hier direkt in den Modelbuilder injected.

public class PersonController : Controller
{
    #region Initialize
    public PersonController(IPersonModelBuilder personModelBuilder)
    {
        if (personModelBuilder == null)
        {
            throw new NullReferenceException("PersonModelBuilder ist Null.");
        }

        _personModelBuilder = personModelBuilder;
    }

    private IPersonModelBuilder _personModelBuilder;
    #endregion

    #region Views
    /// <summary>
    /// Model mit einem leeren PersonenModel anlegen
    /// </summary>
    public ActionResult AddPerson()
    {   
       return View(_personModelBuilder.Load());
    }
    
    /// <summary>
    /// Erstellen einer neuen Person
    /// </summary>
    [HttpPost]
    public ActionResult AddPerson(PersonEditModel editModel)
    {
        if (ModelState.IsValid)
        {
            _personModelBuilder.AddOrSavePerson(editModel);
            return RedirectToAction("PersonList");
        }

        return View(editModel);
    }

    /// <summary>
    /// Bearbeiten einer einzelnen Person mit der übergebenen Id
    /// </summary>
    public ActionResult EditPerson(int id)
    {
        return View(_personModelBuilder.Load(id));
    }

    /// <summary>
    /// Speichern der Personendaten die übergeben wurden im Post
    /// </summary>
    [HttpPost]
    public ActionResult EditPerson(PersonEditModel editModel)
    {
        if (ModelState.IsValid)
        {
            _personModelBuilder.AddOrSavePerson(editModel);
            return RedirectToAction("Person", "Person", new {id = editModel.Id});
        }

        return View(editModel);
    }

    /// <summary>
    /// Anzeigen der Details einer einzelnen Person
    /// </summary>
    public ActionResult Person(int id)
    {
        return View(_personModelBuilder.Load(id));
    }

    /// <summary>
    /// Laden aller Personen 
    /// </summary>
    public ActionResult PersonList()
    {
        return View();
    }
    #endregion 
}

Der Modelbuilder Selbst enthält in meinem Fall alle wichtigen Funktionen zum Füllen der entsprechenden Personen Models. Hier werden die Daten vom Repository auf das passende Viewmodel gemappt bzw. die entsprechenden Daten gespeichert.

public class PersonModelBuilder : IPersonModelBuilder
{
    #region Initialize
    /// <summary>
    /// Initialize und der ModelContainer wird per Di übergeben.
    /// </summary>
    public PersonModelBuilder(IPersonRespository personRespository)
    {
        if (personRespository == null)
        {
            throw new NullReferenceException("PersonRespository ist Null.");
        }
        _personRespository = personRespository;
    }

    private IPersonRespository _personRespository;
    #endregion

    #region Public Functions
    /// <summary>
    /// Laden aller Personendaten
    /// </summary>
    public PersonListModel LoadAllPersons()
    {
        PersonListModel model = new PersonListModel();
        try
        {
            //abrufen der Personendaten aus der Datenbank
            var persons = _personRespository.AllPersons();

            //Füllen unseres ListModels mit den passenden Personendaten
            foreach (EfModel.Person person in persons)
            {
                PersonViewModel viewModelPerson = new PersonViewModel();
                MapPersonViewModel(viewModelPerson, person);
                model.PersonList.Add(viewModelPerson);
            }

            model.AnzahlAllerAutos = 10;
        }
        catch (Exception exception)
        {
            //TODO Fehlermeldungen kapseln, damit diese entsprechend im View angezeigt werden kann.
        }
        return model;
    }

    /// <summary>
    /// Laden eines leeren ViewModels
    /// </summary>
    public PersonViewModel Load()
    {
        PersonViewModel model = new PersonViewModel();
        try
        {
            model.Alter = 18;
            model.Nachname = string.Empty;
            model.Vorname = string.Empty;
            model.Id = 0;

            //Da Neues Objekt, kann es auch keine Autos haben :-)
            model.AnzahlAutos = 0;
            model.IsAltePerson = model.Alter &gt; 30;
        }
        catch (Exception exception)
        {
            //TODO Fehlermeldungen kapseln, damit diese entsprechend im View angezeigt werden kann.
        }
        return model;
    }

    /// <summary>
    /// Laden einer Person mit der übergebenen PersonenId
    /// </summary>
    /// Id der Person die geladen werden soll
    public PersonViewModel Load(int personId)
    {
        PersonViewModel model = new PersonViewModel();
        try
        {
            var dbPerson = _personRespository.Load(personId);
            if (dbPerson == null)
            {
                //TODO Fehlermeldung das die Person nicht gefunden werden konnte.
                return model;
            }

            MapPersonViewModel(model, dbPerson);
        }
        catch (Exception exception)
        {
            //TODO Fehlermeldungen kapseln, damit diese entsprechend im View angezeigt werden kann.
        }
        return model;
    }

    /// <summary>
    /// Erstellen oder Updaten des übergebenen PersonenEditModels
    /// </summary>
    public PersonViewModel AddOrSavePerson(PersonEditModel editModel)
    {
        PersonViewModel viewModel = new PersonViewModel();
        try
        {
            EfModel.Person person = new EfModel.Person();
            person.Id = editModel.Id;
            person.Nachname = editModel.Nachname;
            person.Vorname = editModel.Vorname;
            person.Benutzername = editModel.Benutzername;
            person.Alter = editModel.Alter;

            var dbPers = _personRespository.AddOrUpdatePerson(person);
            MapPersonViewModel(viewModel, dbPers);
        }
        catch (Exception exception)
        {
            //TODO Fehlermeldungen kapseln, damit diese entsprechend im View angezeigt werden kann.
        }
        return viewModel;
    }
    #endregion

    #region private Functions
    /// <summary>
    /// Mappen unserer DB Person auf das aktuelle ViewModel
    /// </summary>
    /// unser aktuelles ViewModel
    /// DB Personen Objekt
    private void MapPersonViewModel(PersonViewModel viewModel, EfModel.Person person)
    {
        viewModel.Alter = person.Alter;
        viewModel.Nachname = person.Nachname;
        viewModel.Vorname = person.Vorname;
        viewModel.Benutzername = person.Benutzername;
        viewModel.Id = person.Id;
        viewModel.AnzahlAutos = 10;
        viewModel.IsAltePerson = person.Alter &gt; 30;
    }
    #endregion
}

Das Model ist in der Modelbuilder Varianten wieder nur ein simples Viewmodel, nur mit Properties.

public class PersonEditModel
{
    #region Member
    public int Id { get; set; }

    [Required]
    public string Vorname { get; set; }

    [Required]
    public string Nachname { get; set; }

    [Required]
    public string Benutzername { get; set; }

    public int Alter { get; set; }
    #endregion
}

Die Ordner- und Dateistruktur in der Solution sieht in der Modelbuildervariante z.B. folgendermaßen aus.

image

Zusammenfassung

Beide Varianten lösen natürlich wieder nur einen kleinen Teil der “Probleme” auf die man beim Erstellen von Viewmodels mit der Zeit stößt und sicherlich müssen beide Varianten auch noch an die jeweilige Implementierung angepasst werden. Wenn man sich aber erst einmal daran gewöhnt hat, das ein Controller auch wirklich sauber und übersichtlich aussehen kann, dann benutzt man seine Bevorzugte saubere Implementierung in all seinen Webprojekten.

Ich habe bisher mit beiden Varianten gearbeitet und ziehe aktuell die mit dem Modelbuilder vor, da man hier den Modelbuilder in den Controller injected und man den Controller sehr leicht mit Unit Tests testen kann.

Da es sich hier nur um ein Beispiel handelt wie beide Varianten prinzipiell umgesetzt werden, handelt es sich um sehr “einfache” Beispiele die im Detail natürlich noch besser umgesetzt werden können.

Ein einfaches Beispiel mit den Passenden Models und Controllern findet man in meinem Codeplex Account. Wenn man bei Google mehr über das Thema erfahren möchte, sucht man am besten nach “Thin / Skinny Controller .NET MVC” hier gibt auch einige Beispiele mit Pros und Cons zu den einzelnen Varianten.

Sollte ich noch weitere Möglichkeiten für einen übersichtlicheren Controller übersehen haben, dann freue ich mich immer gerne über entsprechende Hinweise in den Kommentaren.

ASP.NET MVC – Model Binding und Postback für eine Liste mit Einträgen


Wenn man eine Liste z.B. mit Personen hat und man möchte die Liste in einer Tabelle darstellen und alle Felder sollen bearbeitbar sein, dann ist hier die Lösung mit ASP.NET MVC wahrscheinlich viel einfacher als man zuerst denken würde. Zumindest war ich überrascht wie einfach das ganze doch ist, wenn man die “richtige” Schleife zum Anzeigen der Werte benutzt.

Denn wenn man das ganze mit Hilfe einer “for” Schleife und dem jeweils passenden Index für einen Listeneintrag verwendet, dann baut MVC bei einem Postback die Liste genauso wieder zusammen.

Als erstes benötigen wir eine Liste aus z.B: Personen die angezeigt werden soll, am Besten mit unterschiedlichen Datentypen um den Vorteil von “Html.EditorFor”  zu verdeutlichen.

public class Person
{
    [Display(Name = "Loginname")]
    public string Loginname { get; set; }

    [Display(Name = "Nachname")]
    public string Name { get; set; }

    [Display(Name = "Vorname")]
    public string Vorname { get; set; }

    [Display(Name = "Alter")]
    public int Age { get; set; }

    [Display(Name = "Geburtstag")]
    public DateTime Birthdate { get; set; }

    [Display(Name = "Ist männlich?")]
    public bool IsMale { get; set; }
}

Dann noch ein einfaches Model welches eine Liste von Personen enthält. (alles sehr einfach gehalten)

public class EditListItemsModel
{
    public List PersonenListe { get; set; }

    public EditListItemsModel()
    {
        PersonenListe = new List();
    }

    public void LoadListItems()
    {
        PersonenListe.Add(new Person() { Loginname = "SquadWuschel@gmx.de", Vorname = "Squad", Name = "Wuschel", Age = 32, Birthdate = DateTime.Now.AddYears(-32), IsMale = true});
        PersonenListe.Add(new Person() { Loginname = "Jackson@gmx.de", Vorname = "Jack", Name = "Son", Age = 24, Birthdate = DateTime.Now.AddYears(-24), IsMale = true});
        PersonenListe.Add(new Person() { Loginname = "Fridi@gmx.de", Vorname = "Fri", Name = "di", Age = 52, Birthdate = DateTime.Now.AddYears(-52), IsMale = false});
    }

}

Im Controller die passenden Funktionen anlegen zum Anzeigen des Views und zum Verarbeiten der Postback Werte

 public ActionResult EditListItems()
 {
     EditListItemsModel model = new EditListItemsModel();
     model.LoadListItems();

     return View(model);
 }

 [HttpPost]
 public ActionResult EditListItems(EditListItemsModel model)
 {
     //Die Postback Modeldaten verarbeiten

     return View(model);
 }

Im View selbst dann in ein Formular unsere Einträge hinzufügen und hier verwendet man eine “for” Schleife und greift bei jedem EditorFor und LabelFor auf den passenden Index der Liste zurück. Denn dann erstellt MVC im HTML für das jeweilige “Namens”-Tag des Elements z.B. den folgenden Eintrag für das Alter vom Eintrag mit dem Index 1 “EditItems[1].Age”, bei einem Postback kann .NET dann die passenden Einträge wieder der Liste zuordnen.

@using (Html.BeginForm("EditListItems", "Home", FormMethod.Post, new { @class = "form-horizontal" }))
{</pre>
@for (int i = 0; i 0) { } }
<table class="table table-hover">
<tbody>
<tr>
<td colspan="4"></td>
</tr>
<tr>
<td>@Html.LabelFor(p => p.PersonenListe[i].Loginname)</td>
<td>@Html.EditorFor(p => p.PersonenListe[i].Loginname)</td>
</tr>
<tr>
<td>@Html.LabelFor(p => p.PersonenListe[i].Vorname)</td>
<td>@Html.EditorFor(p => p.PersonenListe[i].Vorname)</td>
</tr>
<tr>
<td>@Html.LabelFor(p => p.PersonenListe[i].Name)</td>
<td>@Html.EditorFor(p => p.PersonenListe[i].Name)</td>
</tr>
<tr>
<td>@Html.LabelFor(p => p.PersonenListe[i].Age)</td>
<td>@Html.EditorFor(p => p.PersonenListe[i].Age)</td>
</tr>
<tr>
<td>@Html.LabelFor(p => p.PersonenListe[i].Birthdate)</td>
<td>@Html.EditorFor(p => p.PersonenListe[i].Birthdate)</td>
</tr>
<tr>
<td>@Html.LabelFor(p => p.PersonenListe[i].IsMale)</td>
<td>@Html.EditorFor(p => p.PersonenListe[i].IsMale)</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2">Speichern</td>
</tr>
</tfoot>
</table>
<pre>}

Das ganze sieht dann im Browser z.B. folgendermaßen aus:

image

Beim “Speichern” wird dann der passende View im Controller aufgerufen und hier ist dann die Liste im Postbackmodel mit den hier eingetragenen Werten entsprechend gefüllt und kann verarbeitet werden.

MVC Multiple Submitbuttons “verarbeiten” / “auswerten”


Oft geht es schneller als man “Multiple Submitbuttons” aussprechen kann und da steht man vor dem “Problem”, das man mehrere Submit Buttons in seinem Formular untergebracht hat. Spätestens dann fragt man sich, wie bekomme ich heraus welcher Button geklickt wurde.

Hier findet man im Netzt die unterschiedlichsten Herangehensweisen. Ich werde hier auf zwei einfache Varianten eingehen, die beim Submit immer die gleiche Controllermethode aufrufen und diese entscheidet dann was zu tun ist.

Die HTML Button Tags für Nummer eins schauen folgendermaßen aus

<button type="submit" class="btn" name="BtnIdPrevDay"><i class="icon-chevron-left"></i></button>
<button type="submit" class="btn" name="BtnIdNextDay"><i class="icon-chevron-right"></i></button>

Hier wurde zur Anzeige ein Symbol/Icon (Twitter Bootstrap) verwenden und kein Text. Wir ermitteln welcher Button geklickt wurde, anhand des Buttonnamen der angegeben wurde, d.h. aber auch das wir jedem Button in unserem Formular einen eindeutigen Namen geben müssen. Dann kann man im Controller überprüfen welcher Button geklickt wurde über die folgende Funktion. Das Model oder die Controlermethode muss für diese Methode auch nicht extra erweitert werden.

[HttpPost]
public ActionResult Worktime(CreateWorktimeEntryModel model)
{
    if (ModelState.IsValid)
    {
        if (Controller.HttpContext.Request.Form.AllKeys.Contains("BtnIdNextDay"))
        {
           //Do some Stuff for this Button
        }
        else if (Controller.HttpContext.Request.Form.AllKeys.Contains("BtnIdPrevDay"))
        {
	        //Do some Other Stuff other button was Clicked          
        }
    }
    return View(model);
}

Das HTML Konstrukt für Nummer zwei schaut folgendermaßen aus

 <button type="submit" class="btn" name="SubmitType" value="Erstellen mit Pause">Erstellen mit Pause</button>
 <button type="submit" class="btn" name="SubmitType" value="Erstellen ohne Pause">Erstellen ohne Pause</button>

Für Variante zwei muss das Model oder die Controllermethode für den Postback entsprechend angepasst werden. Denn bei Methode zwei greifen wir auf MVC Funktionalitäten zurück, die automatisch Werte aus dem Postback Methodenparametern oder Modelproperties zuweisen.

Der erste Lösungsweg sieht eine Erweiterung der Controllermethode für den Postback vor. Diese wird um den string Parameter mit dem Namen “SubtmitType” erweitert. Wenn jetzt ein Button geklickt wird, bei dem als Name “SubmitType” eingetragen ist, steht im Variablennamen der value z.B. “Erstellen mit Pause” und man kann im Controller entscheiden was gemacht werden soll.

[HttpPost]
public ActionResult Worktime(CreateWorktimeEntryModel model, string SubmitType)
{
    if (ModelState.IsValid)
    {
        if (SubmitType == "Erstellen mit Pause")
        {
           //Do some Stuff for this Button
        }
        else if (SubmitType == "Erstellen ohne Pause")
        {
	        //Do some Other Stuff other button was Clicked          
        }
    }
    return View(model);
}

Der zweite Lösungsweg sieht eine Erweiterung des Models vor. Im Model muss ein string Property mit dem Namen “SubmitType” angelegt werden. Bei einem Postback steht dann im Model in unserem Neuen Property welcher Button “value” das Postback ausgelöst hat. Dann können wir wie vorher entscheiden welche Funktion ausgelöst werden soll.

[HttpPost]
public ActionResult Worktime(CreateWorktimeEntryModel model)
{
    if (ModelState.IsValid)
    {
        if (model.SubmitType == "Erstellen mit Pause")
        {
           //Do some Stuff for this Button
        }
        else if (model.SubmitType == "Erstellen ohne Pause")
        {
	        //Do some Other Stuff other button was Clicked          
        }
    }
    return View(model);
}

Update: Eine weitere noch einfachere Lösung sieht folgendermaßen aus:

<button type="submit" name="submitAction" value="option1">click me 1</button>
<button type="submit" name="submitAction" value="option2">click me 2</button>

Hier wird der gleiche Name für alle Buttons hinterlegt und beim Klicken wird der hinterlegte Value dann an unsere Action im Controller übergeben wo wir dann anhand des Values entscheiden was wir ausführen wollen.

public ActionResult MyAction(string submitAction, MyModel model)
{
  ....
}

Quelle:

http://forums.asp.net/p/2000724/5749339.aspx?Re+MVC+with+multiple+submit+buttons

Messages in MVC zwischen Views bzw. Controllern “übergeben/anzeigen”


Zum übermitteln von Nachrichten zwischen den Views gibt es die unterschiedlichsten Möglichkeiten, man könnte z.B. die Meldungen auch in der URL übergeben, was den Nachteil hat, die URL sieht “unsauber” aus und der Fehler kann evtl. mehrfach angezeigt werden. Denn mit den Methoden die bereits in MVC bereitgestellt werden “ModelState.AddModelError” lassen sich nur Fehler auf dem aktuellen View und Controller ausgeben, sobald man eine Fehlermeldung auf einer anderen Seite ausgeben möchte, ist dies nicht mit “Bordmitteln” von MVC möglich.

Daher habe ich mich für das Abspeichern der Nachrichten im Controller TempData entschieden, da diese Daten nach dem ersten Abrufen wieder aus TempData gelöscht werden. Dazu muss man einmal eine Klasse erstellen auf die man im Controller zugreift und am besten noch einen HTML-Helper der einem die Nachricht dann ganz einfach im View ausgibt.

Außerdem wollte ich gerne unterschiedliche Nachrichtentypen haben, und nicht nur Fehler ausgeben, denn es gibt ja z.B. auch noch Bestätigungen das das Speichern erfolgreich war oder Warnungen.

Als erstes erstellen wie eine Nachrichtenklasse:

image

Die wir später als Grundlage für die Nachricht nehmen die wir übermitteln wollen und dann benötigen wir noch eine Klasse für die Logik, damit wir die Nachrichten so einfach wie möglich unserem TempData hinzufügen können. Am besten mit einem Singleton Pattern, damit wir weniger schreiben müssen beim Hinzufügen der Message.

Im Folgenden also die Logikklasse, welche im Konstruktor den aktuellen Controller benötigt, damit auf TempData zugegriffen werden kann und die Message abgelegt wird.

image

(Leider habe ich bisher noch keine andere Lösung gefunden wie man evtl. noch auf TempData zugreifen kann ohne den Controller übergeben zu müssen, denn dann wäre der Aufruf noch kürzer, über Hinweise freue ich mich sehr!)

Hinzufügen einer Message in Controller oder einer anderen Klasse, hier muss nur jeweils der aktuelle Controller mit übergeben werden.

image

image

Die Anzeige der Fehlermeldung ist, dann ganz Einfach über eine HTML-Helper Extension gelöst und kann in jedem View zum Einsatz kommen, dabei spielt es keine Rolle in welchem Controller oder View die Meldung hinzugefügt wurde.

image

Es müssen dann natürlich noch für die jeweiligen MessageTypes die passenden Css Klassen angelegt werden, damit die Messages auch in der passenden Farbe angezeigt werden können.

Im View reicht es dann einfach nur an der passenden Stelle wo der Fehler angezeigt werden soll den Helper aufzurufen und schon funktioniert das ganze einwandfrei:

image

MVC3 Dynamic Datentyp in Controller zur Linkerstellung benutzen


Ich habe beim Erstellen von MVC Anwendungen gemerkt. das ich zumindest im Controller häufig z.B. RedirectToRoute verwendet habe und hier oftmals die gleichen Parameter übergebe im Dynamischen Teil des Aufrufs mit “new { … }”, das habe ich dann in eine zusätzliche Funktion ausgelagert.

image

die dann einfach den passenden Dynamic Typ zurück gibt.

image

fand ich sehr hilfreich, da ich diesen Aufruf mehrmals verwende in unterschiedlichen Controllerfunktionen und ich dann nicht jedes mal alle Parameter neu schreiben muss und außerdem lässt dich der Link bei Änderungen schneller anpassen, da nur eine Stelle angepasst werden muss.

Funktioniert leider nicht in den Views da in der HTML-Helper Klasse keine Dynamischen Datentypen übergeben werden können soweit ich weiß.

ASP.NET MVC 3 Nutzer Authentication mit Attributen


Bei einer Webseite mit einer Nutzerverwaltung, ist mit das wichtigste das man nicht einfach die Daten anderer User betrachten darf oder sich Zugriff zum Adminbereich verschafft, nur weil man etwas mit der URL “umherspielt”.

Dafür gibt es zum einen das Attribut “Authorize” was direkt mitgeliefert wird und prinzipiell prüft, ob überhaupt ein User eingeloggt ist und wenn nicht wird auf die Loginseite verwiesen.

Dann besteht aber noch die Möglichkeit ein Custom Attribut zu erstellen, in dem man alle wichtigen Daten für den aktuellen Controller und den Aufgerufenen View überprüfen kann.

Zum einen haben mir hier die folgenden Links sehr weitergeholfen:

http://msdn.microsoft.com/en-us/library/gg416513%28VS.98%29.aspx

http://msdn.microsoft.com/en-us/library/dd381609%28v=VS.98%29.aspx

http://geekswithblogs.net/brians/archive/2010/07/08/implementing-a-custom-asp.net-mvc-authorization-filter.aspx

Dann noch ein kleines Beispiel wie so eine Umsetzung aussehen könnte. Als erstes benötigen wird eine neue Klasse die vom AuthorizeAttribute ableitet und wir können hier eigene Werte im Konstruktor übergeben, die wir evtl. später benötigen. Dann muss man noch OnAuthorization überschreiben und kann hier seine gewünschte Funktionalität einbinden.

image

Mit der Hilfe von filterContext.Result und mit HttpUnauthorizedResult() wird auf die Loginseite verwiesen, kann auch in einem der oberen Links nachgelesen werden.

image

oder mit new RedirectResult(“BeliebigeURL”) kann auch direkt auf eine URL verwiesen werden.image

Das Attribut kann dann in jedem beliebigen Controller eingesetzt werden und wird vor allen anderen Attributen ausgeführt.

image