Casablanca: Zusammenspiel MVC/Ajax

Hallo Forum,

ich habe ein Problem bezüglich des Zusammenspiels von MVC und Ajax. Ich habe ein div-Element wo im ersten Schritt ein Bild dynamisch geloaded wurde. Ich versuche nun diese Informationen via Ajax an eine MVC-Methode weiterzugeben und diese dann als Stream herunterzuladen.

  
<div id="myPicture"></div>  
<input type="submit" onclick="saveMyImage(document.getElementById('myPicture').innerHTML)" />  

  
    function saveMyImage(myImage) {  
        $.ajax({  
            type: "POST",  
            url: "/Home/SaveImage",  
            data: JSON.stringify({ img: myImage }),  
            contentType: "application/json; charset=utf-8",  
            dataType: 'json',  
            cache: false,  
            success: function(data) {  
                alert("Sccess");  
            }  
        });  
    }  

  
public void SaveMyImage(string img)  
{  
     MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(img));  
     ms.WriteTo(Response.OutputStream);  
     ....  
}  

So weit so gut. Hire soll aber das Downloadfenster aufgehen, was nicht passiert. Ist das, was ich vorhabe überhaupt machbar? Hat jemand eine Idee?

Gruß

  1. Tach!

    function saveMyImage(myImage) {

    $.ajax({
            type: "POST",
            url: "/Home/SaveImage",
        });
    }

      
    Das ist der wichtige Teil. Der ruft also /Home/SaveImage auf.  
      
    
    > ~~~java
    
    public void SaveMyImage(string img) {  
    
    > }
    
    

    So weit so gut.

    Wie kommt man nun von /Home/SaveImage zu dieser Methode SaveMyImage mit My drin? Gibts da einen Router, der das so umsetzt? Du schaust im Browser in die Konsole der Entwicklertools, um die Fehlermeldungen zu sehen?

    dedlfix.

    1. Hi,

      sorry. Die Zeile "url: "/Home/SaveImage"," soll "url: "/Home/SaveMyImage"," heißen. Die Methode wird also direkt vom Ajax heraus aufgerufen.

      Gruß

      1. hi,

        sorry. Die Zeile "url: "/Home/SaveImage"," soll "url: "/Home/SaveMyImage"," heißen. Die Methode wird also direkt vom Ajax heraus aufgerufen.

        Für den Ajax-Request einen weiteren URL? Sorry, das geht am MVC vorbei. Machs besser so, dass Du dem Request einen weiteren Parameter mitgibst und der Controller im MVC entscheidet anhand des Parameters, welche Methode aufgerufen wird.

        MfG

        1. Hallo,

          wie meinst du mit "welche Methode"? Etwa so:

            
          public void SaveMyImage(string img){  
             SaveIt(img);  
          }  
            
          private void SaveIt(string img){  
             MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(img));  
             ms.WriteTo(Response.OutputStream);  
             ...  
          }  
          
          

          Was soll das brigen?

          Gruß

          1. Hallo,

            wie meinst du mit "welche Methode"? Etwa so:

            Nein, nicht etwa so: Ich meine, dass Dein Controller in Deinem MVC das machen sollte, für das was er zuständig ist, nämlich die Kontrolle über Request-Parameter (gelegentlich sind da drin auch die Benutzereingaben zu finden) zu übernehmen und je nach Deiner Programmlogik ggf. den Parametern zugeordnete Methoden aufzurufen.

            Das Stichwort lautet also: Kontrollstruktur, if, elsif, else

            MfG

            1. Tach!

              Nein, nicht etwa so: Ich meine, dass Dein Controller in Deinem MVC das machen sollte, für das was er zuständig ist, nämlich die Kontrolle über Request-Parameter (gelegentlich sind da drin auch die Benutzereingaben zu finden) zu übernehmen und je nach Deiner Programmlogik ggf. den Parametern zugeordnete Methoden aufzurufen.

              Das ist Aufgabe des Routers. Und diese Aufgabe ist bereits erledigt. Beim Controller kommt nur noch die Nutzlast für die jeweilige Action an. Es gibt hier nichts mehr zu entscheiden, was ausgeführt werden soll. Wir sind bereits an der Stelle der zum Request passenden konkreten Ausführung.

              dedlfix.

              1. Tach!

                Nein, nicht etwa so: Ich meine, dass Dein Controller in Deinem MVC das machen sollte, für das was er zuständig ist, nämlich die Kontrolle über Request-Parameter (gelegentlich sind da drin auch die Benutzereingaben zu finden) zu übernehmen und je nach Deiner Programmlogik ggf. den Parametern zugeordnete Methoden aufzurufen.

                Das ist Aufgabe des Routers.

                Kontrolle über Benutzereingaben Aufgabe des Routers? Mag sein dass das Bei Dir so ist.

                In meinen Webanwendungen sind Benutzereingaben Parameter im HTTP-Request. Mein Router routet 'nur' zum path (like /index.html) und interessiert sich nicht im entferntesten für Parameter, gleich ob GET oder POST. Für Parameter in meinen Webanwendungen ist der Controller zuständig.

                War schon immer so ;)

                1. Tach!

                  Das ist Aufgabe des Routers.
                  Kontrolle über Benutzereingaben Aufgabe des Routers? Mag sein dass das Bei Dir so ist.

                  Ein Request hat zunächst einmal eine Ziel-Angabe. Die ist in der Regel nicht vom Benutzer eingegeben, sondern indirekt gewählt, zum Beispiel im href-Attribut beim Klicken eines Links oder beim Absenden eines Formulars im Action-Attribut. Dazu kommen noch gegebenenfalls Nutzereingaben. Diese beiden Sorten von Daten kommen nun in einem einzigen Request beim Server an. Es ist Aufgabe des Routers, sich die passenden Teildaten aus dem Request zu holen und damit den Weg zur Controller-Action zu finden. Die Action holt sich aus dem Request die für sie interessanten Daten und erledigt damit die für sie vorgesehene Aufgabe. Nach diesem Prinzip arbeiten alle mir bekannten Frameworks, die unter anderem einen MVC-Teil enthalten.

                  Beispiele:

                  /customer - customer wird vom Router ausgewertet und daraufhin der Customer-Controller ausgewählt. Jetzt fehlt die aufzurufende Action. Die kommt aus einem Default-Wert in der Router-Konfiguration. Nehmen wir mal index an. Aufgerufen wird damit die Index-Action des Customer-Controllers. Die bekommt keinerlei Daten übergeben und gibt z.B eine Liste der Kunden zurück.

                  /customer?page=23 - Wie oben, nur dass page nicht vom Router ausgewertet wird sondern von der Index-Action, die daraufhin nur einen Teil der Daten zurückgibt.

                  /customer/42 - Vom Router wird erkannt, dass hier ein zweiter Teil mit einer Zahl vorliegt. Der konkrete Wert wird aber nicht weiter berücksichtigt. Der Router ruft deswegen die Details-Action auf. Die bekommt die 42 übergeben und liefert das Ergebnis für einen einzelnen Datensatz.

                  /customer/create - Als GET aufgerufen ruft der Router die Create-Action für GET-Requests des Customer-Controllers auf. Die liefert ein leeres Formular.
                  /customer/create - Als POST aufgerufen ruft der Router die Create-Action für POST-Requests des Customer-Controllers auf. Die wertet die POST-Daten aus und erzeugt einen Kunden in der Datenbank.

                  Um seine Aufgabe zu erfüllen, greift der Router gegebenenfalls auch auf POST-Daten zu. Üblicherweise reichen jedoch die Angaben aus der aufgerufenen URL zum Finden der passenden Controller-Action. Es ist jedenfalls nicht üblich, strikt zwischen vom Router und von der Action ausgewerteten Daten zu unterscheiden und diese in getrennte Teile der URL zu packen (à la Router-Daten in den Path, Action-Daten in den Querystring). Damit würde man die beliebten "SEO-friendly" URLs nicht hinbekommen.

                  Es ist jedoch üblich, dass man den Actions gleich fertig vorgekaute Daten liefert. Die Frameworks extrahieren meist aus dem Request die Daten, befüllen damit gleich ein (in diesem Fall) Customer-Objekt und übergeben dies der Action. Die muss sich dann nicht die Finger schmutzig machen und direkt in den GET/POST/Path-Daten rumwühlen.

                  Beachte bitte auch, dass "Controller" sich hier nur auf den Action-Controller aus dem MVC-Muster bezieht. Es gibt abseits davon auch noch andere Controller, wie zum Beispiel den Front-Controller, der als allererstes mit dem Request in Berührung kommt, Initialisierungen einleitet und dann die Arbeit zum Router weiterreicht.

                  In meinen Webanwendungen sind Benutzereingaben Parameter im HTTP-Request. Mein Router routet 'nur' zum path (like /index.html) und interessiert sich nicht im entferntesten für Parameter, gleich ob GET oder POST. Für Parameter in meinen Webanwendungen ist der Controller zuständig.
                  War schon immer so ;)

                  Kann sein, findet man aber nicht in freier Wildbahn sondern nur bei dir. Insofern kann ich nicht beurteilen, ob das anders arbeitet als die bekannten Frameworks oder ich nur mit deinen zum Teil selbst erfundenen Begrifflichkeiten nicht klarkomme.

                  dedlfix.

                  1. Tach!

                    Das ist Aufgabe des Routers.
                    Kontrolle über Benutzereingaben Aufgabe des Routers? Mag sein dass das Bei Dir so ist.

                    Ein Request hat zunächst einmal eine Ziel-Angabe. Die ist in der Regel nicht vom Benutzer eingegeben, sondern indirekt gewählt, zum Beispiel im href-Attribut beim Klicken eines Links oder beim Absenden eines Formulars im Action-Attribut [..].

                    Naiv bis leichtsinnig: Parameter liegen IMMER in den Händen der Benutzer. Eine diesbezügliche Kontrolle muss absolut wasserdicht sein, damit Deine Anwendung das macht, was Du willst und nicht das, was der Benutzer will.

                    Gerade PHP ist in dieser Hinsicht anfällig und auch mit einer anderen PL (Perl) muss ein Programmierer sehr gut wissen was passiert, wenn ein Benutzer Parameter überschreibt indem er beispielsweise weitere gleichnamige Parameter anhängt.

                    Und das kann nicht Aufgabe eines Routers sein: Parameterkontrolle, Benutzereingaben.

                    Es wird unnötig kompliziert und schwer überschaubar, wenn das jemand so macht. Besser: Das Routing endet in der Pfadangabe (ohne Parameter). Der Router schaut, welche Klasse an den Request-URL gebunden ist, im Default heißt diese Klasse NotFound. Jede Klasse kennt das Model (M im MVC) implementiert ein Interface, die Interface-Methoden werden aufgerufen wobei eine der Methoden den Controller implementiert (C im MVC) und erzeugt damit das View (V im MVC). Einfacher gehts nicht und für Plugins stehen alle Wege offen.

                    Freundliche Grüße, während andere ihre Doktorarbeiten über Design Patterns schreiben...

                    1. Tach!

                      Ein Request hat zunächst einmal eine Ziel-Angabe. Die ist in der Regel nicht vom Benutzer eingegeben, sondern indirekt gewählt, zum Beispiel im href-Attribut beim Klicken eines Links oder beim Absenden eines Formulars im Action-Attribut [..].

                      Naiv bis leichtsinnig: Parameter liegen IMMER in den Händen der Benutzer. Eine diesbezügliche Kontrolle muss absolut wasserdicht sein, damit Deine Anwendung das macht, was Du willst und nicht das, was der Benutzer will.

                      Also meine Anwendungen sollen das machen, was der Benutzer will. Für den schreib ich die ja. Dass der auf meinem System nicht alles machen darf, ist eine andere Geschichte. Und die hat nichts mit der generellen Art der Auswertung der Parameter für das Routing zu tun. Sicherheitsrelevante Sperren stehen im Falle von ASP.NET MVC direkt an den Controllern oder einzelnen Actions. Die starten gar nicht, wenn der angemeldete Nutzer das nicht ausführen darf. Darum kümmert sich das Framework. Ich sage nur, welche Nutzer oder Rollen Zugriff haben.

                      Was die fachliche Plausibilität der Daten angeht, darum kümmert sich das Model mit seinen Validatoren. Das weiß am besten was zulässig ist und was nicht. Das kann nicht Aufgabe des Routers sein. Daten können schließlich auch auf anderen Wegen daherkommen.

                      Gerade PHP ist in dieser Hinsicht anfällig und auch mit einer anderen PL (Perl) muss ein Programmierer sehr gut wissen was passiert, wenn ein Benutzer Parameter überschreibt indem er beispielsweise weitere gleichnamige Parameter anhängt.

                      PHP ist nicht anfälliger als alle anderen Systeme auch. Auch die Frameworks für PHP prüfen Daten an den üblichen sinnvollen Stellen. Programmieren müssen immer wissen, was sie tun. In jedem System.

                      Und das kann nicht Aufgabe eines Routers sein: Parameterkontrolle, Benutzereingaben.

                      Das machen die Router der mir beannten Frameworks auch nicht. Sie werten den Request soweit aus, dass die passende Controller-Action gefunden werden kann. Das Umschreiben von Request-Daten in Objekte der Anwendung ist ja auch nicht Teil des Routing-Prozesses sondern findet zwischen diesem und der Ausführung der Action statt. Weitere Kontrollen sind - siehe oben - auch nicht seine Aufgabe.

                      Es wird unnötig kompliziert und schwer überschaubar, wenn das jemand so macht. Besser: Das Routing endet in der Pfadangabe (ohne Parameter).

                      Unübersichtlich finde ich die üblichen Lösungen nicht. Die Komplexität ist bereits in der Aufgabenstellung vorhanden, weswegen man dafür ja zu solchen Systemen wie den MVC-Frameworks greift. Sie sollen die Übersicht so gut es geht bewahren. Und das geht auch gut, solange man die angebotenen Lösungswege beschreitet.

                      Den Pfad von nicht routing-relevanten Daten freizuhalten, ist nicht mit den Wünschen der Realität vereinbar.

                      http://blog.example/thema-x-y

                      Man möchte aus diversen Gründen eine solche URL haben und keine solche

                      http://blog.example/?post=thema-x-y

                      Das Ausgangsszenario ist also, dass der Schlüssel zum gewünschten Beitrag im Pfad steht. Der Inhalt ist nicht für den Router relevant. Für die Lieferung der Beiträge ist ein und dieselbe Controller-Action zuständig. Sie muss den Pfad-Teil übergeben bekommen, um passend die Datenhaltung abfragen zu können.

                      Der Router muss nur unterschieden können, ob beispielsweise /admin/... aufgerufen wurde, dann gehts zu den Controllern des Admin-Bereichs. Der Rest geht zu der für die Beiträge zuständigen Action. (Das einfache Beispiel-Blog-System kommt mit diesen beiden Mustern aus. Entweder /admin oder Default-Routing zu den Beiträgen.) Wenn /existiert-nicht aufgerufen wird, dann geht auch dieser Aufruf zur Beiträge-Action. Das ist auch kein sicherheitstechnisches Problem. Es kann in dem Fall einfach nur kein Beitrag gefunden werden und dann gibts eine Fehlermeldung. Diese Prüfung ist eine fachliche, sie wird quasi vom Model vorgenommen, wenn die Abfrage mit diesem Wert keine Daten liefert.

                      Einfacher gehts nicht ...

                      Mir scheint, mit deinem System bist du nicht nur einfach auf starre Muster festgelegt, sie kann auch einfach nicht flexibel auf die Anforderungen reagieren (die heutzutage schon existieren). Flexibilität haben zu können, heißt nicht zwangsläufig, dass die Lösung unübersichtlich chaotisch wird.

                      dedlfix.

  2. hi,

    ich habe ein Problem bezüglich des Zusammenspiels von MVC und Ajax. Ich habe ein div-Element wo im ersten Schritt ein Bild dynamisch geloaded wurde. Ich versuche nun diese Informationen via Ajax an eine MVC-Methode weiterzugeben und diese dann als Stream herunterzuladen.

    Kein Problem, wenn Du die Bild-Informationen (URL, Binary, Base64) vom Server ins DOM gekriegt hast, dann hast die auch serverseitig vorliegen (Logisch).

    So weit so gut. Hire soll aber das Downloadfenster aufgehen, was nicht passiert. Ist das, was ich vorhabe überhaupt machbar? Hat jemand eine Idee?

    Entweder über window.location eine Zuweisung (Bild-URL) machen oder mit window.open ein neues Fenster mit dem native URL des Bildes als Webressource oder für das neue Fenster einen Blob-URL erstellen nachdem die Binary des Bilds per Ajax im Browser angekommen ist. Und dann hätten wir noch den data-URL mit der Bild-Binary als Base64-String.

    Es geht also Einiges ;)

    MfG

    1. Hallo,

      danke. Das Bild ist auf dem Server nicht vorhanden. Das Bild wird dynamisch und zur Laufzeit erzeugt (Chart). Ich habe das Ganze Vorher via canvg(...) umgewandelt und gespeichert. Dies hat aber auf einmal von heute auf morgen nicht mehr unter IE funktioniert (Syntax-Fehler bei xml-Parsen). Daher suche ich eine Alternative dazu. Leider habe ich nocht nichts brauchbares gefunden.

      Die MVC Methode öffnet kein Save As-Fenster. Anscheint weil die Methode ein Callback auf die aufrufende Methode (Ajax) durchführt.

      Gruß

      1. Hallo,

        danke. Das Bild ist auf dem Server nicht vorhanden. Das Bild wird dynamisch und zur Laufzeit erzeugt (Chart).

        Achso, im Browser. Da brauchst Du auch kein Ajax-Request zum Server, da kannst du alles im Browser machen. Baue einen Blob zusammen mit dem entsprechenden Content-Type und guck mal auf GitHub nach FileSaver.js (für den Download-Dialog).

        Horst

        1. Hi,

          danke. Ja da geht wieder Mal über Javascript. Ich versuche aber nun diese direkt in einem Controller zu behandeln. Der FileSaver stoßt Fehlermeldung aus.

          Gruß

  3. Tach!

    Ich fang hier quasi nochmal neu zu antworten an, weil sich meine Vermutung bezüglich des My-Fehlers nicht bestätigt hat und ich hier besser zitieren kann.

    ich habe ein Problem bezüglich des Zusammenspiels von MVC und Ajax. Ich habe ein div-Element wo im ersten Schritt ein Bild dynamisch geloaded wurde. Ich versuche nun diese Informationen via Ajax an eine MVC-Methode weiterzugeben und diese dann als Stream herunterzuladen.

    Du hast zwar deinen Code als Java ausgezeichnet, aber ich gehe doch recht in der Annahme, dass es sich stattdessen um C# handelt? Ich tu mal so in der nachfolgenden Antwort.

    <div id="myPicture"></div>

    <input type="submit" onclick="saveMyImage(document.getElementById('myPicture').innerHTML)" />

      
    Was genau befindet sich denn für HTML-Code in dem div-Element? Wenn da ein Bild drin sein soll, müsste .innerHTML einen String à la '<img src="irgendwas">' liefern. Du kannst das prüfen. Breakpoint setzen, Debugger starten und nachschauen ob das was da drin steht deinen Erwartungen entspricht. Visual Studio und der Internet Explorer arbeiten da sehr gut zusammen.  
      
    
    > [Javascript-Teil]  
      
    Dieser String geht also per Ajax auf Reisen.  
      
    
    > ~~~java
    
    public void SaveMyImage(string img)  
    
    > {  
    >      MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(img));  
    >      ms.WriteTo(Response.OutputStream);  
    >      ....  
    > }
    
    

    Und hier kommt also ein Stück HTML-Code an. Wenn du "ASP.NET MVC" nimmst, dann ist das direkte Ansprechen von Response.OutputStream nicht die feine englische Art. Es gibt diverse Ableitungen der ActionResult-Klasse. Da solltest du die passende heraussuchen und das Ergebnis auf diese Weise aus der Action-Methode zurückgeben. JsonResult bietet sich hier an.

    So weit so gut. Hire soll aber das Downloadfenster aufgehen, was nicht passiert. Ist das, was ich vorhabe überhaupt machbar? Hat jemand eine Idee?

    Nach dem Ajax-Request bist du wieder in deinem Javscript-Teil, konkret in der Methode, die du als success übergeben hast. Da müsste also ein Alert-Fenster aufgehen. Du willst aber dass aus Javascript heraus eine Datei gespeichert wird. Das ist üblicherweise dem Javascript nicht gestattet. Die File API von HTML5 kam mir grad in den Sinn, aber die kann nur Dateien lesen. Das wird also nicht auf diese Weise gehen. Oder du findest in den Weiten des Internets einen Beitrag von jemandem, der es irgendwie von hinten durch die Brust ins Auge geschafft hat, Javascript zum Speichern einer Datei anzuregen.

    Alternativer Vorschlag: Setz ein normales HTML-Formular auf. Häng dich ins Submit-Event. Befülle Hidden-Felder mit den gewünschten Inhalten. Möchtest du allerdings an die Bild-Daten kommen und keinen HTML-Code verarbeiten, dann musst du erstmal herausfinden, wie das konkret geht (javascript read image content - oder ähnliche Suchstichwörter bieten sich an). Das Formular schickt es ab (direkt aus dem Browser ohne Javascript-Umweg) und die Verarbeitung landet bei einer Controller-Action. Die kann dann ein FileResult (das hat auch noch drei Unterklassen, eine davon für Streams) oder ein ContentResult zurückliefern. Mit passendem Content-Type kann der Browser das dann auch direkt speichern (force file download als weitere Stichwörter).

    dedlfix.

    1. Hallo dedlfix,

      ... Du willst aber dass aus Javascript heraus eine Datei gespeichert wird. Das ist üblicherweise dem Javascript nicht gestattet. Die File API von HTML5 kam mir grad in den Sinn, aber die kann nur Dateien lesen. Das wird also nicht auf diese Weise gehen. Oder du findest in den Weiten des Internets einen Beitrag von jemandem, der es irgendwie von hinten durch die Brust ins Auge geschafft hat, Javascript zum Speichern einer Datei anzuregen.

      z.B. hier: In Datei schreiben, ohne Server

      Gruß, Jürgen