Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

MVC με Patterns & ASP.NET

Îåêßíçóå áðü ôï ìÝëïò anjelinio. Τελευταία δημοσίευση από το μέλος anjelinio στις 31-07-2006, 22:15. Υπάρχουν 14 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  13-07-2006, 13:29 14661

    MVC με Patterns & ASP.NET

    Τον τελευταίο καιρό στη δουλειά, γράφω κάτι που πολύ καιρό τώρα σκεφτόμουν. Μια MVC ( ModelViewController ) υλοποίηση σε ASP.NET για χρήση στην εφαρμογή μας.

    Το MVC, αν και από τα πιο δημοφιλή ακρώνυμα στον Πληροφορικόκοσμο, κατά την πενιχρή μου εμπειρία σπάνια το συναντάς σε ASP.NET εφαρμογές, τουλάχιστον στην Ελλάδα. Εντάξει, από μια άποψη πιστεύω κι εγώ ότι το ASP.NET και το Visual Studio δε σε σπρώχνουν προς τέτοιες κατευθύνσεις με όλα τα καλούδια που σου έχουν έτοιμα. Μπορεί να είμαι άδικος, αλλά αυτή είναι η εντύπωσή μου … τέλος πάντων.
     

    Η «πρόταση» του MVC είναι απλή. Κατά βάση, σπάς την εφαρμογή σου σε 3 μέρη:
     

    1. Το Μοντέλο ( Model ). Υποτίθεται ότι το μοντέλο είναι ένα encapsulation των δεδομένων που θα χρησιμοποιήσει η εφαρμογή. Μπορεί να είναι οτιδήποτε από ένα απλό data object, ως κάποιο περίπλοκο εσωτερικά business object με χιλιάδες γραμμές κώδικα λογικής μέσα, και όλα ανάμεσά τους. Πολύ συχνά μιλώντας με κόσμο, έχω την εντύπωση ότι ο κόσμος πιστεύει ότι το μοντέλο είναι encapsulation ΟΛΩΝ των δεδομένων / λογικής της εφαρμογής μας, αλλά δεν πιστεύω ότι είναι έτσι. Αντίθετα, πιστεύω ότι το μοντέλο θα πρέπει να είναι ένα fine-grained, focused business object, το οποίο υλοποιεί τη λογική που χρειάζεσαι για μια συγκεκριμένη ενέργεια στα πλαίσια της εφαρμογής. Πάντα φυσικά, τα μεγέθη ποικίλλουν :P
    2. Η «οθόνη», ή το View. Το δύστυχο, μικρό View δεν είναι τίποτα άλλο από ένα UI το οποίο απεικονίζει δεδομένα από το μοντέλο, και το αντίθετο. Τροφοδοτεί το μοντέλο με user input data. Ο προσωπικός μου κανόνας είναι ότι σε ένα view δε θα πρέπει να υπάρχει ποτέ κάποιο block business κώδικα, ή rules κώδικας πέραν απλών input validators. Αυτός ο κώδικας ανήκει στο μοντέλο.
    3. Ο Controller. Ο Controller είναι η κλάση της οποίας η ευθύνη είναι να αναλαμβάνει το κάθε incoming request, να βρίσκει το κατάλληλο view το οποίο θα εξυπηρετήσει το request, και να κάνει instantiate το μοντέλο ( αν δεν υπάρχει ήδη κάπου, accessible από όλους ). Αυτό βέβαια δεν σημαίνει απαραίτητα ότι ο controller πρέπει ντε και καλά να είναι ΜΙΑ κλάση, η οποία κάνει ΟΛΑ αυτά τα πράγματα. Βασικά, οποιοδήποτε από τα 3 μέρη του MVC μπορεί με τη σειρά του να σπάσει σε υπομέρη, με μια σειρά από κλάσεις να υλοποιούν το συνολικό functionality.

    Ο σκοπός μου είναι να γράψω μια σειρά από αρθράκια, τα οποία κάνουν στην ουσία document την υλοποίηση πάνω στα σενάρια χρήσης της. Είναι μια σχετικά απλοϊκή υλοποίηση, αλλά εμπλέκει διάφορα patterns και τεχνικές μέσα της. Απώτερος σκοπός μου είναι να προσπαθήσω να δείξω ( και να δώ ) τη real-life χρήση κάποιων design patterns που ακούμε «ακαδημαϊκά» κάθε μέρα, όπως τα singletons, factories, filter chains κ.α.

    Τα δύο σενάρια χρήσης με τα οποία θα ασχοληθώ είναι η προβολή λιστών από data ( grids ), και σελίδες επεξεργασίας δεδομένων ( edit pages ).

    Και στις δύο αυτές περιπτώσεις, η νοοτροπία είναι η ίδια. Σπάω τα διάφορα κομμάτια της διαδικασίας σε επιμέρους υλοποιήσεις, τι ς οποίες «δένει» ένα filter chain το ποίο στην ουσία κάνει το όλο orchestration της διαδικασίας. Με απλούστερα λόγια, η διαδικασία είναι η εξής:

     

    1. Ο Controller, υλοποιημένος σαν ένα IHttpHandlerFactory «παίρνει» πρώτος κάθε incoming request του τύπου /../myviewname.list & /…/myviewname.edit
    2. O Controller δημιουργεί ένα instance μιας κλάσης η οποία είναι στην ουσία ένα απλό data object, ένα struct ας πούμε και του δίνει αρχικές τιμές όπως το HttpContext.
    3. Περνάει μετά το data object αυτό από ένα Filter Chain, τα επιμέρους στάδια του οποίου βρίσκουν το view, φέρνουν data κτλ.
    4. Στο πρώτο φίλτρο του chain, ο ViewSelector διαβάζει απ’το querystring τις παραμέτρους που χρειάζεται, δηλαδή το view type (list / edit) και το view name (myviewname). Μετά, χρησιμοποιώντας ένα factory object, τον ViewManager παίρνει πίσω ένα instance ενός view descriptor. Αυτό περιέχει τη σελίδα η οποία τελικά είναι το view, και ένα spec των δεδομένων που θα χρησιμοποιηθούν από το view.
    5. Στο τελευταίο στάδιο του chain, ο ViewInitializer δημιουργεί ένα instance της σελίδας η οποία είναι το view, και της δίνει τα data ή data objects ( βασικά εμείς εδώ τα λέμε FormDataSources, και μοιάζουν πολύ με τα DataSources του v.2.0 του framework ) που χρειάζεται για να παίξει …
    6. Και τέλος, ο αρχικός handler παίρνει το αποτέλεσμα του chain, και γυρίζει στο σύστημα το instance της σελίδας για να εκτελεστεί


    Δυστυχώς δεν έχω σήμερα κώδικα να δώσω μαζί με το post, γιατί είναι τίγκα στα specific classes που χρησιμοποιούμε εσωτερικά και δε θα κάνει compile ποτέ και πουθενά εκτός από ‘δω μέσα, οπότε … σιγά σιγά πορτάρω στο σπίτι ένα πιο απλό version  σε πολύ απλό “nativeASP.NET χωρίς third party components.  

     

    Ελπίζω τις επόμενες μέρες να βρώ το χρόνο και να συνεχίσω αυτή τη σειρά από αρθράκια καθώς υλοποιώ … για σήμερα όμως … times up δυστυχώς :(  Καλό απόγευμα !

     

     


    Angel
    O:]
  •  13-07-2006, 13:30 14662 σε απάντηση της 14661

    Απ: MVC με Patterns & ASP.NET

    .. χμμ ... μεγάλο font δε βγήκε αυτο; .. χμμμ ...
    Angel
    O:]
  •  13-07-2006, 17:15 14685 σε απάντηση της 14662

    Απ: MVC με Patterns & ASP.NET

    πολύ ενδιαφέρον. Πριν από τον κώδικα θα ήθελα να μας πεις και τα πλεονεκτήματα από αυτήν προσέγγηση σε σχέση με το (n-tier) μοντέλο.

    Αν κατάλαβα καλά το model είναι το business logic layer, το view είναι το UI, το data layer δεν το αποφεύγεις (και ας το κρύβεις μέσα στο model) και απλά φτιάχνεις ένα δικό σου httpHandler (ο controller) αντί να χρησιμοποιείς αυτόν του .net

    Δδεν πιστεύω ότι όλοι αυτοί που ασχολούνται με την σχεδίαση νέων προγραμματιστικών μοντέλων απλά παιρνούν τον καιρό τους, θα υπάρχει κάποιος λόγος για όλα αυτά (και για τις ώωωωωωωρες σχεδίασης και υλοποίησης που αφιερώνεις), οπότε περιμένω με πραγματική ανυπομονεσία την συνέχεια των άρθρων σου.
  •  13-07-2006, 17:29 14686 σε απάντηση της 14662

    Απ: MVC με Patterns & ASP.NET

    Το MVC δεν καταργεί τα layers, αναλαμβάνει απλά να οργανώσει μπλεγμένα πράγματα μέσα στο ίδιο layer (Συνήθως στο front end).
    Χρήστος Γεωργακόπουλος
  •  13-07-2006, 17:53 14689 σε απάντηση της 14686

    Απ: MVC με Patterns & ASP.NET

     cgeo wrote:
    Το MVC δεν καταργεί τα layers, αναλαμβάνει απλά να οργανώσει μπλεγμένα πράγματα μέσα στο ίδιο layer (Συνήθως στο front end).


    Ακριβώς αυτό είναι που είπε ο Χρήστος φίλε Μάριε. Δεν καταργώ το n-tier, απλως το κρύβω πίσω απο το μοντέλο του εκάστοτε view ( όπου view, μπορείς τις περισσότερες φορές να αντικαταστήσεις με .. use case )

    Αυτό που κερδίζεις είναι:
     uniformity -> δε μπορεί ο καθένας να κάνει ότι του καπνίσει
    Λιγότερο - τελικά - κώδικα -> για το χρήστη/implementer ενος feature, ο οποίος θα βασιστεί στα έτοιμα ...
    Flexibility -> αλλάζω το config file των views, και άλλαξα το απο που τραβάει data ας πούμε, η τη σελίδα που τα δείχνει αυτα τα data, το view ...
    More Flexibility -> Εφ' όσον έχω μια "αλυσίδα" μπροστά, μπορώ να προσθέσω / αφαιρέσω filters ανα πάσα στιγμη, at runtime

    Αυτού του τύπου είναι τα gains. Ακόμη ενα πολύ σημαντικό είναι οτι πιθανώς   α ν ά μ ε σ α   σε αυτά τα steps, μπορείς να παρεμβάλλεις πράγματα όπως logging, ή γενικότερου τύπου functionality δυναμικά. Αλλά το κύριο gain είναι η ομοιομορφία του κώδικα της εφαρμογής, και ο ελάχιστος δυνατός νέος κώδικας.

    Περισσότερα, στα επόμενα ...

    Angel
    O:]
  •  13-07-2006, 19:08 14691 σε απάντηση της 14689

    Απ: MVC με Patterns & ASP.NET

    Ας πάρω τα πράγματα με τη σειρά:

    1. Πώς βάζω στον editor ένα κομμάτι από το post κάποιου άλλου (το έχω καταφέρει μία και μοναδική φορά)Embarrassed
    2. Πρέπει να παραδεχτώ ότι στο front end πολλές φορές μπαίνουν πράγματα που δεν έχουν καμία θέση εκεί.
    3. Ίσως να μην καταλαβαίνω την σημασία γιατί δεν έχω δουλέψει ποτέ σε κανονική ομάδα.
    4. Πιθανότατα όλα αυτά να ξεκαθαρίσουν όταν εμφανιστεί ο κώδικας.
    5. Τα πλεονεκτήματα που αναφέρεις δεν έχουν να κάνουν περισσότερο με την πιστή εφαρμογή σχεδιαστικών κανόνων και την αποφυγή "μπακαλίστικων λύσεων";
    6. "Περισσότερα, στα επόμενα ... " περισσότερες απορίες στα επόμενα

    Μάριος
    υ.γ. Νομίζω ότι αντιδρώ όπως κάποιος που οργάνωνε τον κώδικά του σωστά σε functions και libraries πριν από 10-13 χρόνια και άκουγε για objects και classes. Ευτυχώς αυτός ξεπεράστηκεBig Smile

  •  14-07-2006, 13:40 14731 σε απάντηση της 14689

    Απ: MVC με Patterns & ASP.NET

    Ρϊξε μια ματιά και σε αυτόν εδώ με MVP: http://msdn.microsoft.com/msdnmag/issues/06/08/DesignPatterns/default.aspx

    "As UI-creation technologies such as ASP.NET and Windows® Forms become more and more powerful, it's common practice to let the UI layer do more than it should. Without a clear separation of responsibilities, the UI layer can often become a catch-all for logic that really belongs in other layers of the application. One design pattern, the Model View Presenter (MVP) pattern, is especially well suited to solving this problem. In order to illustrate my point, I will build a display screen that follows the MVP pattern for customers in the Northwind database..." (χτίζει web form)


    Χρήστος Γεωργακόπουλος
  •  14-07-2006, 13:42 14732 σε απάντηση της 14731

    Απ: MVC με Patterns & ASP.NET

    Θα μου πεις βέβαια, ότι εσύ έχει παίξει με chaining στον controller, αλλά δεν μπορώ να φανταστώ πολύ καλά πραγματικά παραδείγματα που οφελεί σε γενικές γραμμές. Μπορείς να μας δώσεις παραδείγματα που το έχεις εφαρμόσει και αξίζει τον κόπο;
    Χρήστος Γεωργακόπουλος
  •  14-07-2006, 18:22 14762 σε απάντηση της 14661

    Απ: MVC με Patterns & ASP.NET

    Σιγά - σιγά Χρήστο ... ένα-ένα αρθράκι θα περάσω πολύ αναλυτικά απο ολα τα μέρη, γιατί διάλεξα τί, οσο πιο καλά μπορώ, τα πάντα ...

    Δυστυχώς ενώ κάθησα χτες το βράδυ κι έγραφα 1-μισι ώρα άρθρο για τον Controller ... όταν πάτησα submit είχα γίνει log-out και τα έχασα όλα ! :(

    Θα το ξαναγράψω σήμερα όμως ... :P τί να κάνεις ...
    Angel
    O:]
  •  14-07-2006, 18:32 14764 σε απάντηση της 14731

    Απ: MVC με Patterns & ASP.NET

     cgeo wrote:

    Ρϊξε μια ματιά και σε αυτόν εδώ με MVP: http://msdn.microsoft.com/msdnmag/issues/06/08/DesignPatterns/default.aspx

    "As UI-creation technologies such as ASP.NET and Windows® Forms become more and more powerful, it's common practice to let the UI layer do more than it should. Without a clear separation of responsibilities, the UI layer can often become a catch-all for logic that really belongs in other layers of the application. One design pattern, the Model View Presenter (MVP) pattern, is especially well suited to solving this problem. In order to illustrate my point, I will build a display screen that follows the MVP pattern for customers in the Northwind database..." (χτίζει web form)



    Χμμμ ... ο φίλος μας εδώ ακολουθεί το variation του MVC, το MVP - "έτσι το κάνουμε στη Microsoft" - όπου συνήθως ο Controller είναι στην ουσία το ίδιο το view, λόγω οτι το code behind μιας σελίδας κάνει inherit μια base class. Προσωπικά δε μ'αρέσει αυτή η ιδέα, γαιτί πολύ συχνά καταλήγει η base να έχει ένα σωρό static μεθόδους για τις ανάγκες όλου του project - ανάλογα πάντα με τον ποιός το σχεδίασε και ποιός το υλοποίησε :]

    Προτιμώ το ξεκάθαρο separation, ανα concern. Ο COntroller είναι μόνο controller, ο dispatcher μόνο dispatcher και τέτοια :)

    Τέλος πάντων ... αργότερα περισσότερα, και με κώδικα :P

    Angel
    O:]
  •  16-07-2006, 13:57 14800 σε απάντηση της 14764

    O Controller: MVC με ASP.NET

    Συνημμένα: MVC-ASP.NET.p1.rar
    O Controller λοιπόν ....

    Στα πλαίσια του MVC, Ο Controller είναι ένα instance μίας κλάσης, απο τον οποίο περνάνε όλα τα incoming requests προτού εξυπηρετηθούν απο το κατάλληλο view. Μέσα στα πλαίσια της δουλειάς του Controller είναι το "φιλτράρισμα" των requests για authentication / authorization, η επιλογή του κατάλληλου view το οποίο θα εξυπηρετήσει τελικά το request, πιθανώς το initialization αυτού του view και του αντίστοιχου μοντέλου για αυτό, και τέλος η προώθηση του request στο ίδιο το view για το τελικό processing.

    Στο ASP.NET, έχουμε τρείς βασικές επιλογές όσον αφορά τον τύπο ενός Controller. *

    The Page is the controller.

    Κάθε σελίδα κάνει inherit μια base class. Έτσι ο κοινός μας κώδικας, αυτός που είναι μέσα στον controller, "τρέχει" πάντα, λόγω του ότι τον περιλαμβάνει η base class κάθε σελίδας μας. Αυτή είναι και η variation του MCV που προωθεί η Microsoft, και ονομάζεται MVP ( Model - View - Presenter ).

    Προσωπικά, δε μ'αρέσει αυτή η υλοποίηση. Η βασική "ιδεολογική" διαφωνία μου είναι οτι δεν προωθεί τον "καθαρό" διαχωρισμό των components του MVC. Σε ωθεί πολλές φορές να ξεφύγεις, και τελικά βρίσκεις μια base class η οποία είναι γεμάτη static μεθόδους οι οποίες τελικά αναλαμβάνουν .. τα πάντα. Και σύντομα ο controller σου έχει γίνει και data access helper, και το ένα και το άλλο, και σεντόνια κώδικα υπάρχουν μέσα σε μια κλάση η οποία δε διαβάζεται πλέον.

    Επίσης, δυσκολεύει πολύ με τη χρήση του Page Controller η χρήση "ψεύτικων" urls - ένα είδος url rewritting αν προτιμάτε. Π.χ. δε μπορείς με εύκολο τρόπο να αντιστοιχήσεις το url http://mysite.com/products/carparts.list σε μια, μοναδική σελίδα. Βέβαια, αυτό είναι υποκειμενικό επιχείρημα, υπάρχει τρόπος, απλώς εμένα δε μ'αρέσει αυτή η μεθοδολογία, μ'αρέσει τα διαφορετικά μέρη ενός συστήματος να είναι και διαφορετικές κλάσεις/instances :P


    IHttpHandler Controller.

    Αν έχετε ασχοληθεί καθόλου με Java, η έννοια του http handler θα σας είναι πολύ οικεία. Κάνει "map" ακριβώς στα Java Servlets. Για τους πιο παλιούς, σκεφτείτε το σαν ένα CGI script. Δεν έχει designer και controls, είναι μια απλή κλάσση η οποία υλοποιεί το IHttpHandler interface, και παράγει το content του response με κώδικα.

    Το IHttpHandler interface ορίζεται ώς εξής:

    bool IsReusable  - Gets a value indicating whether another request can use the IHttpHandler instance.

    void ProcessRequest( HttpContext context ); - Enables processing of HTTP Web requests by a custom HttpHandler that implements the IHttpHandler interface.


    Μέσα στην ProcessRequest, η παράμετρος context μας δίνει πρόσβαση στο Request & Response, στο Application, Session και όλα τα άλλα objects σχετικά με το Web App μας, και το συγκεκριμένο request που εξυπηρετούμε. Ένα απλοϊκό παράδειγμα απο το MSDN ακολουθεί:
    using System.Web;

    namespace HandlerExample
    {
        public class MyHttpHandler : IHttpHandler
        {
            // Override the ProcessRequest method.
            public void ProcessRequest(HttpContext context)
            {
                context.Response.Write("<H1>This is an HttpHandler Test.</H1>");
                context.Response.Write("<p>Your Browser:</p>");
                context.Response.Write("Type: " + context.Request.Browser.Type + "<br>");
                context.Response.Write("Version: " + context.Request.Browser.Version);
            }

            // Override the IsReusable property.
            public bool IsReusable
            {
                get { return true; }
            }
        }
    }
    Μπορούμε να κάνουμε map μέσα στο Web.Config αυτή την κλάσση σε οποιοδήποτε pattern ενό url επιθυμούμε. Π.χ.

    <httpHandlers>
        <add verb="*" path="*.aspx" type="HandlerExample.MyHttpHandler , HandlerExample"/>
    </httpHandlers>

    Αυτή η γραμμή στο web config, ορίζει στο σύστημα οτι οποιοδήποτε url ακολουθεί το pattern *.aspx θα εξυπηρετείται απο ένα instance της κλάσης HandlerExample.MyHttpHandler, η οποιά βρίσκεται στο assembly HandlerExample.

    Tέτοιοι handlers είναι ιδανικοί για περιπτώσεις που για οποιονδήποτε λόγο θες να "κρύψεις" τα urls των σελίδων σου, ή θες να σερβίρεις binary content, ή τελος πάντων θες να κάνεις κατι που πέφτει στην αρμοδιότητα ενός Page class. Π.χ. αν επιστρέφεις κάποιο γράφημα, είναι overkill να το κάνεις απο ένα aspx - κάνοντάς το απο έναν Htpp Handler είναι πολύ πιο "καθαρό". Κατα συνέπεια, η χρήση ενός Http Handler ως ο Controller τοτυ MVC μας φαίνεται οτι είναι πολύ καλή επιλογή.

    Υπάρχει όμως ένα μειονέκτημα. Ο IHttpHandler πρεέπι να επιστρέψει content στο Response. Kι άλλο ένα, μεγαλύτερο ... δεν μπορείς να κάνεις Server.Transfer μέσα σε έναν IHttpHandler.

    Στο υποθετικό σενάριο "είμαι ο controller, διαβάζω το request, αποφασίζω ποιό view θα δείξω, κάνω instantiate το μοντέλο, και προωθώ στο view" με τη χρήση IHttpHandler κολλάμε στο .. "προωθώ στο view". Άντε, πες οτι ξέρω ποια σελίδα είναι το view, και κάπως έχω καταφέρει να βρώ ένα instance αυτής της σελίδας, compiled and ready and willing ... τι κάνω; Κάνω Response.Redirect ή εκμεταλλεύομαι το γεγονός οτι το Page υλοποιεί το IHttpHandler interface, και του καλώ την ProcessRequest του. 

    Καλά ως εδώ ... αλλά κάτι δε μ' αρέσει σε αυτή την ιστορία. Ίσως το μόνο που χρειάζεται είναι όντως να καλέσω την Processrequest στο Page instance. Αλλά αυτή τη δουλειά την κάνει πολύ καλά το σύστημα για 'μένα υπο Κ.Σ. - μήπως εγώ κάνω καμμιά #$%^# και χαλασω τα πράγματα; Μήπως μπλέκω πολύ με το σύστημα;

    Θα προτιμούσα λοιπόν μια λύση κατα την οποία εγώ το μόνο που χρειάζεται να κάνω είναι να βρώ αυτο το page, να κάνω τα initializations που πιθανώς χρειάζονται, και να την επιστρέψω στο σύστημα για να κάνει αυτό τη δουλειά του - να μην την κάνω εγώ γι' αυτό.  
    Η λύση που μου δίνει το ASP.NET για κάτι τέτοιο, είναι ο ...


    IHttpHandlerFactory Controller.

    Το interface IHttpHandlerFactory νοηματικά μοιάζει πολύ με τον IHttpHandler. Με τον ίδιο ακριβώς τρόπο στο web config, και με την ίδια "νοοτροπία" κάνω map ένα url pattern σε ένα instance της κλάσσης μου, η οποία υλοποιεί το IHttpHandlerFactory interface.

    Το IHttpHandlerFactory interface ορίζεται ώς εξής:

    IHttpHandler GetHandler( HttpContext context, string requestType, string url, string pathTranslated );

    void ReleaseHandler( IHttpHandler handler );


    H βασική του διαφορά απο τον απλό IHttpHandler είναι οτι το IHttpHandlerFactory είναι .. ο πατέρας του, ένα σκαλί πιο κάτω στο σύστημα. Δέχεται το incoming request, και επιστρέφει το instance ενός IHttpHandler το οποίο τελικά θα εξυπηρετήσει το request.

    Επίσης, υποστηρίζει out-of-the-box, τη δυνατότητα να κάνεις pooling στα instances των handlers που επιστρέφεις, μέσω της ReleaseHandler. Κάθε instnce που έχεις επιστρέψει απο την GetHandler τελικά θα σου επιστραφεί απο το σύστημα μόλις τελειώσει τη δουλειά της, για να κάνεις με αυτό οτι γουστάρεις. Όπως είναι προφανές, το IHttpHandlerFactory δίνει ξεκάθαρα την εντύπωση οτι είναι γραμμένο για να ικανοποιεί τυπάκια σαν την πάρτη μου, ούτε παραγγελία να το είχα !

    Στο ταπεινό παράδειγμα που έχω attached, θα βρείτε την υλοποίηση ενός IHttpHandlerFactory σε ένα ξεχωριστό class library, κι ένα web projectτο οποίο τον χρησιμοποιεί. Δεν κάνει κάτι σπουδαίο - κάνει intercept όλα τα requests για aspx σελίδες, κάνει αν χρειάζεται compile τη σελίδα, και επιστρέφει το compiled page instance στο σύστημα. Τρέχοντας το project και ζητώντας την default.aspx ... θα πάρετε πίσω κανονικά την default.aspx, λες και ο handler factory δεν υπήρξε ποτέ :P .. ένα breakpoint στην GetHandler όμως θα ξεκαθαρίσει τα πράγματα. Κάθε incoming request για *.aspx περνάει μέσα απο 'κεί.

    Το πολύπλοκο μέρος θα σκεφτόταν κανείς, είναι στο να κάνεις compile το .aspx και να πάρεις πίσω class instance. Ευτυχώς, το framework περιέχει την κλάση System.Web.UI.PageParser η οποία κάνει ακριβώς αυτό, οπότε η υλοποίηση είναι παιχνιδάκι. Αλλά το θέμα δεν είναι πλέον αυτό.
    Αυτό που είναι το θέμα, είναι το γεγονός(τα) ότι:

    1. Σε κάθε request, έχω τη δυνατότητα να τρέχω κώδικα κάπου κεντρικά.

    2. Το URL του request δε με εμποδίζει πιά, και μπορώ να χρησιμοποιήσω οποιοδήποτε pattern θέλω, είτε αυτό είναι http://mysite/default.aspx?CatID=3&SubCatID=2 ή Http://mysite/company/about-us/ 

    3. Έχω τον έλεγχο πλέον να επιλέξω ποια θα είναι η σελίδα που τελικά θα εξυπηρετήσει το request. Αν θέλω, μπορεί και να μην είναι καν σελίδα !

    4. Προτού η σελίδα αρχίσει να εξυπηρετεί, έχω το instance της. Μπορώ να της κάνω ότι θέλω. Σκεφτείτε σελίδες όπου δεν υπάρχει data access κώδικας μέσα, γιατί το data set τους ( ή το "μοντέλο" αν προτιμάτε ) έρχεται απο ένα property το οποίο έχει θέσει ο handler απ' έξω προτού εκτελεστεί η σελίδα απο το σύστημα.

    Κοντολογίς, προτιμώ την επιλογή του IHttpHandleFactory για τον controller μου, γιατί με αφήνει να επικεντρωθώ μόνο στις δουλειές που πρέπει να γίνουν μέσα σε έναν controller, χωρίς αν χρειάζεται να ασχοληθώ με το lifetime management της σελίδας / view. Κάνω ότι χρειάζεται να κάνω, και αφήνω το ASP.NEΤ να κάνει ότι κάνει ήδη καλά για 'μένα.

    Nομίζω οτι στο επόμενο post θα ασχοληθώ με το πόσο "μονολιθικός" μπορεί στο τέλος να γίνει ένας τέτοιος controller, και πως επέλεξα να κάνω abstract away το πρόβλημα του "τι κομμάτια κώδικα θα τρέξω στο handler και πως θα τα προσθαφαιρώ ευέλικτα" με τη χρήση ενός ακόμα pattern, του Filter Chain.

    Ως τότε όμως ... καλό απόγευμα Κυριακής :)

    Y.G. Έπρεπε να σταματήσω, έχει και Φόρμουλα 1 σε μισή ωρίτσα :D



    Angel
    O:]
  •  16-07-2006, 21:17 14810 σε απάντηση της 14800

    Απ: O Controller: MVC με ASP.NET

    Μήπως κάνεις το θέμα υπερβολικά περίπλοκο; Ποιες είναι οι πραγματικές ανάγκες που σε οδηγούν σε ένα τέτοιο σχεδιασμό;
    Χρήστος Γεωργακόπουλος
  •  16-07-2006, 22:23 14819 σε απάντηση της 14810

    Απ: O Controller: MVC με ASP.NET

     cgeo wrote:
    Μήπως κάνεις το θέμα υπερβολικά περίπλοκο; Ποιες είναι οι πραγματικές ανάγκες που σε οδηγούν σε ένα τέτοιο σχεδιασμό;


    Όσο περισσότερο κώδικα και orchestration φτιάξω εγώ, τόσο λιγότερο θα γράψει κάποιος junior μηχανικός, για να υλοποιήσει κάτι καινούργιο.

    Βασικά, η νοοτροπία είναι η δυνατότητα να προσθέτουμε καινούργια πράγματα πολύ γρήγορα. Και αυτό επιτυγχάνεται - πιστεύω - απο το "patternization"των features μιας εφαρμογής, έχοντας το όποιο framework να κάνει τα περισσότερα πράγματα για 'σενα, κι εσυ απλώς "γεμίζεις τα κενά", υλοποιώντας νέα features γράφοντας τον ελάχιστο δυνατό κώδικα, ο οποίος ακολουθεί πολύ "στανταρισμένη" δομή. 

    Η πολυπλοκότητα προκύπτει απο την απαίτηση να μπορώ να προσθαφαιρώ κώδικα δυναμικά, και με ελεγχόμενο τρόπο. Άρα, χρειάζομαι πολύ συγκεκριμένο, και πολύ δυναμικό orchestration. Και επίσης, performant ! Πρέπει να εξυπηρετώ 1500 με 2000 ταυτόχρονα sessions στις ζόρικες ώρες της μέρας. Όλα αυτά δεν μπορείς να τα πετύχεις με μερικά aspx και user ή custom controls. Χρειάζεται πραγματικό orchestration και οργάνωση.

    Angel
    O:]
  •  30-07-2006, 14:24 15306 σε απάντηση της 14819

    O Controller / Dispatcher: Inversion of Control, και Dependency Injection.

    Controller / Dispatcher: Inversion of Control, και Dependency Injection.

     

    Κάλιο αργά παρά ποτέ … :P

     

    Μέχρι τώρα, είδαμε τις επιλογές μου για το μέρος του Controller σε ένα Model View Controller σχήμα, πάνω σε ASP.NET.

     

    Προς το παρόν, έχουμε έναν τρόπο με τον οποίο μπορούμε να επεξεργαστούμε όλα τα incoming requests, ο οποίος μας αφήνει να «επεξεργαστούμε» κεντρικά τόσο το request, όσο και τη σελίδα η οποία τελικά θα το εξυπηρετήσει.

     

    Σε αυτό ακριβώς το σημείο, κρύβεται το «μαγικό» feature του MVC. Έχω όλα τα δεδομένα από το request, και έχω και το instance της σελίδας η οποία θα την εξυπηρετήσει. Άρα, εδώ είναι το σημείο στο οποίο μπορώ να «ξεφορτώσω» κώδικα από την ίδια τη σελίδα, και να τον μεταφέρω σαν  κάποιο pluggable module σε αυτό το σημείο, προτού ο controller επιστρέψει το page instance στο σύστημα.

     

    Υπάρχουν 2 patterns τα οποία είναι πολύ σχετικά με αυτό το σημείο. To Dependency Injection, και το Invesion of Control.  Μάλλον, για να το θέσω καλύτερα, τα DI & IoC είναι περισσότερο τεχνικές, οι οποίες υλοποιούνται με τη χρήση διάφορων patterns, αντί για «καθαρά» ατομικά patterns τα ίδια. Πολύ συχνά ο κόσμος φαίνεται να τα χρησιμοποιεί ως ακρώνυμα εννοώντας το ίδιο πράγμα, αλλά η ταπεινή μου γνώμη είναι ότι πρόκειται για δύο εντελώς διαφορετικά πράγματα.

     

    To Dependency Injection είναι πολύ απλό. Ας πούμε ότι η σελίδα μας που υλοποιεί το View, χρειάζεται κάποια δεδομένα τα οποία θα παρουσιάσει στο χρήστη. Άρα, η σελίδα μας έχει ένα dependency σε αυτά τα δεδομένα. Δε μπορεί να λειτουργήσει χωρίς αυτά.

     

    Συνήθως, στον κώδικα της σελίδας, θα περίμενε να βρεί κάποιος τον κώδικα ο οποίος διαβάζει τις όποιες παραμέτρους έρχονται στη σελίδα, και φέρνει τα ανάλογα δεδομένα από τη βάση, ή από κάποιο business ή data object. Εξυπηρετεί το dependency του στα δεδομένα χωρίς τη βοήθεια κανενός. 

     

    Χρησιμοποιώντας το dependency injection paradigm, το σύστημα είναι υπεύθυνο να δημιουργήσει αυτά τα δεδομένα, και να τα δώσει αυτό στη σελίδα μας, ικανοποιώντας έτσι το dependency στα δεδομένα.

     

    Προσωπικά εκτιμώ ότι χρησιμοποιώντας τη dependency injection νοοτροπία, κάνω ευκολότερη τη μετάβαση από ένα view implementation σε ένα άλλο. Μη σκεφτείτε μόνο την αλλαγή κάποιου UI, σκεφτείτε την πιθανότητα διαφορετικού τύπου content ανα view. Π.χ. όταν κάποτε δούλευα σε μια καθαρά web design εταιρία, παρουσιάστηκε η ανάγκη να δίνουμε το content ενός site στη vodaphone σε ένα πολύ συγκεκριμένο xml format, απαραίτητο για να μετασχηματιστεί σε content για το vodaphone live. Έχοντας όλες τις σελίδες στο site μας να περιέχουν τον data access κώδικά τους, μας οδήγησε στο να κάνουμε όλες αυτές τις σελίδες copy-paste και rewrite για να σερβίρουν Vodaphone XML

     

    Σε ένα διπλανό site, στο οποίο χρησιμοποιούσα πολύ πειραματικά MVC, dependency injection και XSLT για την παραγωγή του content, η προσθήκη ενός ακόμη content-type μας πήρε 1-2 μέρες.

     

    To Inversion of Control ίσως είναι κι αυτό απλώς παρεξηγημένο από τον κόσμο. Για παράδειγμα, το προηγουμενο σενάριο, αυτό του  dependency injection θα μπορούσε να χαρακτηριστεί ως inversion of control από την οπτική του ότι δεν είναι πλέον το view στον έλεγχο της «παραγωγής» των δεδομένων του. Αυτός ο έλεγχος έχει παραδοθεί στον controller, ή στο εκάστοτε IoC container. Αλλά η δική μου ταπεινή γνώμη είναι ότι το Inversion of Control δεν πρέπει να μένει στο μυαλό μας μόνο σε αυτό το επίπεδο.

     

    Η άποψή μου είναι ότι όταν ξέρεις τα processes που πρέπει να τρέξουν για να εκτελεστεί μια ενέργεια στο σύστημα σου, σχεδιάζεις τα πάντα ως ένα sequence από ατομικά tasks τα οποία πρέπει να εκτελεστούν, ορίζεις τα interfaces τα οποία καθένας τύπος task πρέπει να υλοποιεί, και αφήνεις το instrumentation / orchestration στο σύστημα από κάτω.

     

    Με άλλα λόγια, δεν αφήνεις τα εκάστοτε μέρη του συστήματος να παίρνουν αποφάσεις, πέραν από το τι είναι απολύτως απαραίτητο για να εκτελέσουν την ατομική τους εργασία. Κάθε υλοποίηση απλώς γεμίζει κώδικα σε προκαθορισμένες μεθόδους οι οποίες καλούνται από το σύστημα, ποτέ η μια από την άλλη.

     

    Δεν αφήνεις τον προγραμματιστή να πάρει αποφάσεις … άρα … Inversion of Control !!! :P

     

     

    Και το sample

     

    Όλα αυτά τα «pattern-ικά» δεν τα σκεφτόμουν υλοποιώντας το σημερινό «επεισόδιο» κώδικα. Τα αναγνωρίζεις όμως μετά, βλέποντας το σύστημά σου, από ψηλά.

     

    Όπως έλεγα και στο προηγούμενο post, συνδυάζω τον controller, με ακόμη ένα pattern, το filter chain. Πρό λίγων εβδομάδων, είχα γράψει ένα άρθρο όπου έδειχνα μια υλοποίηση του filter chain το οποίο μπορείτε να βρείτε εδώ, σαν επιπλέον reference τόσο για το pattern όσο και για τον sample κώδικα που θα δείτε στο attachment.  

     

    Η αλυσίδα που θα δείτε στο sample είναι σχετικά απλή. Ορίζεται σαν xml ως εξής:

     

    <?xml version="1.0" encoding="utf-8" ?>

    <simple-view-chain>

      <filters>

       

        <authorization-filter/> 

       

        <view-dispatcher-filter/>

       

        <view-init-filter>

          <!-- define the class type that implements this view's datasource -->

          <datasource-type class="Controller.DataSources.CustomerEditBean" assembly="Controller" >

            <parameters>

              <!-- add an http-param to auto-set the UserID property of the datasource instance -->

              <http-param id="CustomerID" auto-set="true"/>

            </parameters>

          </datasource-type>

        </view-init-filter>

       

      </filters>

    </simple-view-chain>

     

    Ορίζει τρία είδη φίλτρων:

     

    • AuthorizationFilter. Θεωρητικά ελέγχει αν έχουμε current χρήστη, και αν όχι και χρειάζεται authentication για το συγκεκριμένο view κάνει redirect στη login σελίδα μας. Στο sample δεν κάνει στην πραγματικότητα τίποτα … :P
    • ViewDispatcherFilter. Αναγνωρίζει σε ποιο view πρέπει να προωθήσει το request, και δημιουργεί ένα instance του.
    • ViewInitFilter. Αναλαμβάνει να «αρχικοποιήσει» properties του View, κάνοντας στην ουσία dependency injection τις τιμές από τυχόν request parameters, data source objects κτλ.

     

    Παρακάτω έχω το class diagram του SimpleViewChain


    Όπως βλέπετε, έχω το SimpleViewChain, subclass του FilterChain για το οποίο είχα μιλήσει στο προηγούμενο post μου, τα τρία ειδικά φίλτρα και ένα generic logging φίλτρο, και το data object για το SimpleViewChain, το SimpleViewData.

     

    To SimpleViewData ορίζει ένα structure το οποίο περιέχει όλα τα απαραίτητα δεδομένα για να ολοκληρωθεί η λειτουργία της αλυσίδας:

     

    using System;

    using System.Web;

     

    using Controller.View;

     

    namespace Controller.ViewChain

    {

        /// <summary>

        /// Wraps all the required data for the successful processing of the SimpleViewChain

        /// chain of filters. It is the object that will get passed through all the filters, before

        /// it is returned to the caller of the chain for final processing

        /// </summary>

        public class SimpleViewData

        {

            private HttpContext m_Context = null;

            private ISimpleView m_View = null;

            private string m_Url = null;

     

            public SimpleViewData() { }

     

            public SimpleViewData(HttpContext context) {

                this.Context = context;

            }

     

            public SimpleViewData(HttpContext context, string url) : this(context) {

                this.Url = url;

            }

     

            /// <summary>

            /// Wraps the HttpContext property of this simple data object, so

            /// as to ensure that properties are intialized correctly, and not used

            /// before they are initialized

            /// </summary>

            public string Url

            {

                get

                {

                    if (null == m_Url)

                        throw new ApplicationException("Url has not yet been initialized");

     

                    return m_Url;

                }

                set

                {

                    if (null == value)

                        throw new ArgumentNullException("Url", "Cannot be initialized to Null");

     

                    m_Url = value;

                }

            }

     

            /// <summary>

            /// Wraps the HttpContext property of this simple data object, so

            /// as to ensure that properties are intialized correctly, and not used

            /// before they are initialized

            /// </summary>

            public HttpContext Context {

                get {

                    if (null == m_Context)

                        throw new ApplicationException("Context has not yet been

                    return m_Context;

                }

                set {

                    if (null == value)

                        throw new ArgumentNullException("Context", "Cannot be initialized to Null");

     

                    m_Context = value;

                }

            }

     

            /// <summary>

            /// Wraps the ISimpleView property of this simple data object, so

            /// as to ensure that properties are intialized correctly, and not used

            /// before they are initialized

            /// </summary>

            public ISimpleView View

            {

                get

                {

                    if (null == m_View)

                        throw new ApplicationException("View has not yet been initialized");

     

                    return m_View;

                }

                set

                {

                    if (null == value)

                        throw new ArgumentNullException("View", "Cannot be initialized to Null");

     

                    m_View = value;

                }

            }

        }

    }

     

     

    Όπως βλέπετε, όλα τα getters πετάνε exception σε περίπτωση που το αντίστοιχο member variable δεν έχει αρχικοποιηθεί. Αυτό το κάνω για καλύτερο debugging της αλυσίδας σε περίπτωση που κάποια υλοποίηση ενός φίλτρου έχει τοποθετηθεί σε λάθος σειρά ας πούμε, και κάποιο member του SimpleViewData που χρειάζεται δεν έχει αρχικοποιηθεί από κάποιο προηγούμενο φίλτρο.

     

    Το άλλο ενδιαφέρον στο SimpleViewData, είναι το View property το οποίο επιστρέφει ένα ISimpleView. Ένα interface. Αυτό μας δίνει τη δυνατότητα να αλλάζουμε implementations. Για παράδειγμα, μπορεί το view μου να κάνει render HTML. Αλλά μπορεί να κάνει render και οποιοδήποτε άλλο content-type. Εφ’ όσον η υλοποίηση κάνει implement το ISimpleView, έχω τη δυνατότητα να αλλάζω implementations χωρίς να «σπάω» το compatibility με το υπάρχον σύστημα.

     

    Ας ρίξουμε τώρα μια ματιά και στο ίδιο το ISimpleView:
     


    Όνομα και πράμα έτσι; Ένας πολύ απλός βασικός ορισμός για ένα view. Έχει ένα property που λέγεται Data και είναι οποιουδήποτε τύπου. Υπο κανονικές συνθήκες, δε θα αφήναμε τον τύπο σε object, θα ορίζαμε view interfaces ανάλογα με τον τύπο των δεδομένων τους, όπως π.χ. DataSets ή κάποια business objects. Π.χ. AsyncOperationView, του οποίου το Data property θα ήταν κάποιο αντικείμενο που λειτουργεί ως façade για μια ασύγχρονη διαδικασία.

     

    Για το πολύ απλό παράδειγμά μας  όμως, αυτό το πολύ απλό interface μας φτάνει. ( Σκέφτομαι τώρα ότι το IView είναι φοβερός πειρασμός για μια generic κλάση … ) 

     

    Οκ, ως εδώ είδαμε ότι η αλυσίδα μας έχει ένα κάποιο data object, και το τελικό μας view, η σελίδα, υλοποιεί κάποιο συγκεκριμένο interface. Πως «δένουμε» τα δύο; Το όλο .. μυστικό, βρίσκεται στο τρίτο φίλτρο της αλυσίδας, τον ViewInitFilter.

     

    Ο ορισμός του φίλτρου στην xml είναι ως εξής:

     

    <view-init-filter>

     <!-- define the class type that implements this view's datasource -->

     <datasource-type class="Controller.DataSources.CustomerEditBean" assembly="Controller" >

        <parameters>

         <!-- an http-param to auto-set the CustomerID property of the d-source instance -->

         <http-param id="CustomerID" auto-set="true"/>

       </parameters>

     </datasource-type>

    </view-init-filter>

     

    Στο datasource-type element, ορίζεται το class name και το assembly της κλάσης η οποία θα αποτελέσει το datasource για το view μας. Ένα instance αυτού του τύπου θα μας δώσει το .Data του view.

     

    Πέρα από αυτό, μπορούμε στην xml να ορίσουμε κάποιες παραμέτρους, οι τιμές των οποίων θα δωθούν αυτόματα στο datasource instance. Μαγεία; Για παράδειγμα, από το definition παραπάνω, εάν στο Request υπάρχει παράμετρος CustomerID, και στο datasource ένα property με το ίδιο όνομα, η τιμή της παραμέτρου θα τεθεί αυτόματα ως τιμή στο property, κάνοντας μάλιστα και το parse στον σωστό τύπο ! ( Είχα αναφερθεί σε αυτό σε ένα πολύ παλιό αρθράκι, αν θέλετε να δείτε περισσότερα, είναι εδώ )

     

    Έτσι, το ViewInitFilter θα δημιουργήσει ένα instance του Controller.DataSources.CurstomerEditBean, και αν υπάρχει CustomerID παράμετρος στο Request, θα τη θέσει στο CustomerID property του instance αυτού. Στη συνέχεια, θα δώσει αυτό το instance στο view μας μέσω του Data property … και η ζωή συνεχίζεται.

     

    Ας ρίξουμε και μια ματιά σε αυτό το datasource class, και μετά στον κώδικα του view μας.

     

    using System;

    using System.Data;

    using System.Data.OleDb;

     

     

    namespace Controller.DataSources

    {

        /// <summary>

        /// Retieves / Updates data regarding Customers

        /// </summary>

        public class CustomerEditBean

        {

            private int m_CustomerID = -1;

     

            public CustomerEditBean() { }

     

            public int CustomerID {

                get {

                    return m_CustomerID;

                }

                set {

                    // always check !

                    if (-1 >= value)

                        throw new ArgumentException("Cannot initialize CustomerID to zero or less");

     

                    m_CustomerID = value;

                }

            }

     

            private DataSet m_CustomerData = null;

     

            public DataView Data {

                get {

                    // initialize the data if we have none ...

                    if (null == m_CustomerData)

                        FetchData();

     

                    // Check what data I need to fetch ...

                    if (this.CustomerID > 0)

                    {

                        // Fetch single customer data

                        m_CustomerData.Tables[0].DefaultView.RowFilter = string.Format("CustomerID = {0}", this.CustomerID);

                    }

     

                    return m_CustomerData.Tables[0].DefaultView;

                }

            }

     

            public bool Update() {

                return true;

            }

     

            private void FetchData() {

     

                m_CustomerData = new DataSet();

     

                OleDbDataAdapter adapter = new OleDbDataAdapter("select * from Customer", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=F:\\projects\\MVC\\mvc_sample.mdb");

                adapter.Fill(m_CustomerData);

            }

        }

    }

     

    Τίποτα φοβερό. Παίζω με μια τοπική Access, κι ένα μόνο πίνακα, τον customers. Η υλοποίηση είναι λίγο … άτσαλη, σε καμία περίπτωση δε θα γράφατε τέτοιο κώδικα για production υλοποίηση, αλλά για ένα γρήγορο παράδειγμα με 2 rows στο table είναι μια χαρά :P

     

    Θεωρητικά, όπως και τα views, έτσι και τα data sources θα πρέπει να υλοποιούν κάποια συγκεκριμένα interfaces ανάλογα τη χρήση και τα data τους. Προς χάρην … χρόνου, όμως, στο sample κώδικα το έχω αφήσει «ελεύθερο» αυτό το σημείο, και έχω «δέσει» το datasource με το view στον ίδιο τον κώδικα του view:

     

    using System;

    using System.Data;

    using System.Configuration;

    using System.Web;

    using System.Web.Security;

    using System.Web.UI;

    using System.Web.UI.WebControls;

    using System.Web.UI.WebControls.WebParts;

    using System.Web.UI.HtmlControls;

     

    using Controller.View;

    using Controller.DataSources;

     

    public partial class _Default : System.Web.UI.Page, ISimpleView

    {

        protected void Page_Load(object sender, EventArgs e)

        {

            if(null!=this.Data)

                Response.Write(string.Format("Injected DataObject: {0}", this.Data.GetType().ToString()));

     

            if (this.Data is CustomerEditBean) {

                CustomerEditBean bean = (CustomerEditBean)this.Data;

     

                this.lstCustomers.DataSource = bean.Data;

                this.lstCustomers.DataBind();

            }

        }

     

        #region ISimpleView Members

     

        private object m_Data = null;

     

        public object Data

        {

            get

            {

                return m_Data;

            }

            set

            {

                m_Data = value;

            }

        }

     

        #endregion

    }

     

    Βλέπετε, ότι το view απαιτεί ένα CustomerEditBean instance για να παίξει. Σε αυτό το σημείο, εάν υλοποιούσαμε ένα interface για τα datasources, θα μπορούσαμε να αλλάζουμε implementations κατά βούληση, από το xml αρχείο. Στο επόμενο sample όμως αυτά … :P

     

    Για όσους ως τώρα δεν έχουν καταλάβει τελικά τι συμβαίνει σε αυτό το sample ( μεταξύ αυτών κι εγώ .. είναι Κυριακή πρωί και μάλλον δεν έχω συνέλθει από χτές … ), συνοψίζω:

     

    1. Έρχεται ένα request για τη σελίδα http://locallhost/mvc/Default.aspx?CustomerID=1
    2. O Controller μας, δέχεται το request, και φτιάχνει μια SimpleViewChain για αν το επεξεργαστεί.
    3. Το πρώτο φίλτρο στην αλυσίδα ελέγχει αν ο χρήστης είναι logged in. Στη συγκεκριμένη υλοποίηση … είναι πάντα.
    4. Το δεύτερο φίλτρο διαβάζει το request path και κάνει compile ένα instance της σελίδας.
    5. Τα τρίτο φίλτρο δημιοργεί ένα instance του datasource, του κάνει inject τις τιμές των όποιων παραμέτρων του, και το κάνιε με τη σειρά του inject στο view instance.
    6. Και τέλος, η αλυσίδα επιστρέφει το data structure της στον Controller, οποίος δίνει το initialized πλέον view πίσω στο ASP.NET για να επιστρέψει content στον client.

     

    To απλό sample που έχω attached δείχνει μια σελίδα, η οποία περιέχει το grid με τους customers. Αν περάσετε ως GET παράμετρο ένα CustomerID, η σελίδα θα επιστρέψει τα δεδομένα μόνο του συγκεκριμένου αυτού customer.

     

    Ως επίλογο, πρέπει να πώ ότι το sample αυτό είναι μόνο μια «διατομή» ενός και μόνο σεναρίου. Δε γίνεται όλα τα .aspx ενός site να έχουν το ίδιο datasource – αυτό συμβαίνει στο σημερινό sample. Στη συνέχεια αυτών των άρθρων, θα ασχοληθώ λίγο με το view management, κοιτώντας πως μπορούμε να δημιουργήσουμε έναν απλό μηχανισμό για να ορίζουμε τα views της εφαρμογής μας, και τα επιμέρους στοιχεία τους όπως datasources, παραμέτρους κτλ. Μετά κι από αυτό, θα ήταν χρήσιμο να δούμε που μπορούμε να  κάνουμε πιο optimized όλο αυτό το μηχανισμό, με τεχνικές caching και object pooling.

     

    Αλλά όλα αυτά … μετά από μια ‘βδομάδα ! Άυριο φεύγω για μια ‘βδομάδα διακοπές στο όμορφο Γύθειο Λακωνίας !!!

     

    Καλή μας εβδομάδα λοιπόν. 

     

    Υ.Γ. Κάποια κομμάτια κώδικα στο sample, χρησιμοποιούν κώδικα που έχω γράψει για την εταιρία στην οποία δουλεύω. Έχω αφήσει τα namespaces ίδια, ελπίζοντας ότι εφ’ όσον δεν ενοχλεί την εταιρία να δίνει μέρη του κώδικά της ελεύθερα, δεν πειράζει και αυτόν που το διαβάζει, ως τυχόν έμμεση διαφήμιση. Σε περίπτωση που χρησιμοποιήσετε αυτόν τον κώδικα, παρακαλώ βάλτε κάπου ένα σχόλιο ως την ελάχιστη αναγνώριση. Σας ευχαριστώ.


    Angel
    O:]
  •  31-07-2006, 22:15 15355 σε απάντηση της 15306

    O Controller / Dispatcher. View Management & Reusability.

    Συνημμένα: MVC-ASP.NET.p3.rar
    Πόσο λατρεύω την άδεια ! Έχω τόσο χρόνο, που δεν έφυγα τελικά, και να ‘μαι εδώ να γράφω sequel στο χτεσινό post. Είμαι τόσο καμένος; 

    Είχαμε μείνει στο σημείο χτες, που πλέον έχουμε κάνει map όλα τα .aspx αρχεία στο μικρό μας mvc, και με το κάθε request τα «γεμίζει» με το απαραίτητο data αντικείμενο που χρειάζονται. Μια μικρή αλλαγή στη διατύπωση θα μπορούσε όμως να πεί ότι κάναμε map σε όλα τα .aspx αρχεία της εφαρμογής μας, στο ίδιο, μοναδικό data source ! Είπαμε, reusability, αλλά αυτό παραπάει :D

    Στο σημερινό επεισόδιο κώδικα, πρόσθεσα στο xml της αλυσίδας την πληροφορία που κάνει map τα views με urls, και τα αντίστοιχα data sources για το κάθε view. Τώρα πλέον, ο Dispatcher διαβάζει το request url, και το αντιστοιχεί με το κατάλληλο view που είναι ορισμένο στην xml. Από ‘κεί θα βρεί το path της πραγματικής σελίδας η οποία θα εξυπηρετήσει το request, καθώς και το data source αντικείμενο για το view.

     
    Ας δούμε τη νέα
    xml που περιέχει όλα αυτά τα ωραία …


    <?xml version="1.0" encoding="utf-8" ?>

    <simple-view-chain>

      <filters>   

        <authorization-filter/>

     

        <view-dispatcher-filter>

          <view-set>

           

            <view id="/MVC/CustomersList.aspx" url="/MVC/CustomersList.aspx">

              <datasource-type class="Controller.DataSources.CustomerEditBean" assembly="Controller" >

                <parameters>

                  <!-- add an http-param to auto-set the UserID property of the datasource instance -->

                  <http-param id="CustomerID" auto-set="true"/>

                </parameters>

              </datasource-type>

            </view>

     

            <view id="/MVC/UsersList.aspx" url="/MVC/CustomersList.aspx">

              <datasource-type class="Controller.DataSources.UserEditBean" assembly="Controller" >

                <parameters>

                  <!-- add an http-param to auto-set the UserID property of the datasource instance -->

                  <http-param id="UserID" auto-set="true"/>

                </parameters>

              </datasource-type>

            </view>

     

            <view id="/MVC/CustomerEdit.aspx" url="/MVC/CustomerEdit.aspx">

              <datasource-type class="Controller.DataSources.CustomerEditBean" assembly="Controller" >

                <parameters>

                  <!-- add an http-param to auto-set the UserID property of the datasource instance -->

                  <http-param id="CustomerID" auto-set="true"/>

                </parameters>

              </datasource-type>

            </view>

           

          </view-set>

        </view-dispatcher-filter>   

      </filters>

    </simple-view-chain>

     

     

    Σαφώς αλλαγμένη, έτσι;

    Οι βασικές αλλαγές είναι λείπει ένα φίλτρο, ο ViewInitFilter, του οποίου οι αρμοδιότητες έχουν πλέον μεταφερθεί μέσα στο View objectτο οποίο θα δούμε μετά. Η επόμενη και βασικότερη αλλαγή είναι μέσα στο ViewDispatchFilter, όπου έχει προστεθεί το element <view-set>, το οποίο περιέχει περιγραφές των διαφόρων views που έχουν οριστεί στο σύστημα.

     

    Ας δούμε από κοντά το definition ενός view:

     

    <view id="/MVC/UsersList.aspx" url="/MVC/CustomersList.aspx">

    <datasource-type class="Controller.DataSources.UserEditBean" assembly="Controller" >

       <parameters>

       <!-- add an http-param to auto-set the UserID property of the datasource instance -->

          <http-param id="UserID" auto-set="true"/>

       </parameters>

    </datasource-type>

    </view>

     

    Περιέχει το virtual url στο οποίο «ακούει», στην περίπτωσή μας /MVC/UsersList.aspx. Επίσης περιέχει το path της πραγματικής σελίδας η οποία θα εξυπηρετήσει το request, και τέλος περιέχει τις πληροφορίες για το data source object του view.  Στην ουσία, πέρα από τα url mappings, περιέχει ότι περιείχε στο χτεσινό post το ViewInitFilter.
     

    Στον κώδικα, έχουν προστεθεί οι αντίστοιχες 2 κλάσεις, όπως φαίνονται στο διάγραμμα:

      

    Μια ακόμη αλλαγή που έκανα, ήταν ότι πλέον τα data sources υλοποιούν συγκεκριμένα interfaces, ανάλογα με τον τύπο των δεδομένων που επιστρέφουν. Όρισα λοιπόν 2 interfaces, ένα για DataView και ένα για DataSet.
     

    Το πολύ όμορφο όμως σε αυτή την ιστορία, ήταν ο κώδικας που χρησιμοποίησα για να τα ορίσω !
     

    using System;

    using System.Collections.Generic;

    using System.Text;

     

    using System.Data;

     

    namespace Controller.DataSources

    {

        public interface IDataSource<DataType> {

            DataType Data {get;}

        }

     

        public interface IDataViewSource : IDataSource<DataView>

        {

        }

     

        public interface IDataSetSource : IDataSource<DataSet>

        {   

        }

    }

     

    GENERICS ABSOLUTELY RULE. Δε θα ξαναδώ ποτέ το μισητό «object» στα interfaces μου.

     

    Έκανα λοιπόν στη συνέχεια το CustomerEditBean να implements IDataViewSource, και έκανα copy-paste κι ένα UserEditBean που παίζει με το διπλανό table στη βάση, το Users:

     

     

     

    Μ’ αυτά και μ’ αυτά, έχω πλέον 2 data sources, τα οποία μου επιστρέφουν λίστα και details για 2 tables στη βάση μου. Προς το παρόν, έχω μόνο 2 σελίδες στο project, μια για τη λίστα πελατών, και μια για details του εκάστοτε πελάτη. Έκανα όμως μια μικρή αλλαγή στον κώδικα της εκάστοτε σελίδας:

     

    protected void Page_Load(object sender, EventArgs e)

    {

        if(null!=this.Data)

            Response.Write(string.Format("Injected DataObject: {0}", this.Data.GetType().ToString()));

     

         if (this.Data is IDataViewSource) {

            IDataViewSource bean = (IDataViewSource)this.Data;

     

            this.lstCustomers.DataSource = bean.Data;

            this.lstCustomers.DataBind();

        }

    }

     

     Άλλαξα δηλαδή τον τύπο του data source που χρησιμοποιούσε, στο interface. Το αποτέλεσμα; Μπορώ πλέον να χρησιμοποιήσω την ίδια σελίδα είτε με το CustomerEditBean, είτε με το UserEditBean. Με λίγα λόγια, η σελίδα μου τώρα μπορεί να δείξει οποιαδήποτε από τις δύο λίστες.

     

    Αυτό το χρησιμοποιώ, όπως φαίνεται στο xml παρακάτω:

     

    <view id="/MVC/CustomersList.aspx" url="/MVC/CustomersList.aspx">

    <datasource-type class="Controller.DataSources.CustomerEditBean" assembly="Controller" >

        <parameters>

        <!-- add an http-param to auto-set the UserID property of the datasource instance -->

            <http-param id="CustomerID" auto-set="true"/>

        </parameters>

    </datasource-type>

    </view>

     

    <view id="/MVC/UsersList.aspx" url="/MVC/CustomersList.aspx">

    <datasource-type class="Controller.DataSources.UserEditBean" assembly="Controller" >

        <parameters>

        <!-- add an http-param to auto-set the UserID property of the datasource instance -->

           <http-param id="UserID" auto-set="true"/>

        </parameters>

     </datasource-type>

     </view>

     

     Όπως βλέπετε, χρησιμοποιώ την ίδια σελίδα ως το view implementation, για 2 διαφορετικές λίστες. Επιπλέον, θα μπορούσα να περνάω παραμέτρους στη σελίδα, όπως headers, navigation info, ή ότι άλλο μπορείτε να σκεφτείτε. Για παράδειγμα στη δουλειά κρύβω κάποια sections / custom controls στις σελίδες, ή κόβω fields που ο τρέχον χρήστης δεν επιτρέπεται να δεί.

     
    ( Παρατήρησα μετά από λίγο καιρό που χρησιμοποίησα ένα τέτοιο μηχανισμό παραμέτρων ότι, όσο το συνηθίζεις, να έχεις μια υποδομή που δίνει «τσάμπα» αυτό το
    functionality, κατά κάποιο τρόπο … σου ανοίγει το μυαλό, και προχωράς πλέον σε άλλες κατευθύνσεις που δε θα συνειδητοποιούσες ότι υπήρχαν πριν … η απλώς είμαι ψώνιο με την πάρτη μου :D )

     

    τρέχοντας την εφαρμογή, θα σας βγάλει στην ίδια CustomersList.aspx σελίδα. Αν όμως αλλάξετε το url σε UsersList.aspx, θα σας φέρει πλέον τη λίστα με τους χρήστες. Περνώντας CustomerID ή UserID παραμέτρους στο url θα σας φέρουν τα δεδομένα μόνο για τον εκάστοτε πελάτη ή χρήστη, ακριβώς όπως και πρίν.

    Νομίζω οτι πλέον πρέπει να αρχίζουν να φαίνονται τα πλεονεκτήματα του MVC «πακέτου». Με τη βοήθεια του χρόνου, στα επόμενα posts θα κοιτάξω πώς να το «μαζέψουμε» λίγο από άποψη performance όλο αυτό, και κάποια πιο advanced παραδείγματα της χρήσης του.
     

    Ως τότε … ελπίζω να μαυρίζω την υπόλοιπη ‘βδομάδα, και να μη γράφω ούτε κώδικα, ούτε posts!
    Καλό μας καλοκαίρι !!!

     


    Angel
    O:]
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems