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

 

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

Multi config File caching utility

Îåêßíçóå áðü ôï ìÝëïò anjelinio. Τελευταία δημοσίευση από το μέλος anjelinio στις 01-04-2006, 18:23. Υπάρχουν 0 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  01-04-2006, 18:23 11448

    Multi config File caching utility

    Συνημμένα: TestFileHandlers.rar

    Εδώ και καιρό, στα πλαίσια της ανάπτυξης μια αρκετά μεγάλης web εφαρμογής, παρουσιάστηκε η ανάγκη να μπορούμε να αλλάζουμε το configuration της εφαρμογής, ή των επιμέρους κομματιών της on the fly, με τη χρήση configuration files  σε xml, text ή κάποιας άλλης μορφής.

     

    Πολύ συχνά σε τέτοιες περιπτώσεις, βρίσκεις υλοποιήσεις οι οποιές ανακτούν πληροφορίες απο αυτά τα configuration files χρησιμοποιώντας τα κατευθείαν απ’το δίσκο. Με άλλα λόγια, για κάθε client request, διαβάζουν τα περιεχόμενα του αρχείου απ’το δίσκο, τα επεξεργάζονται και επιστρέφουν το response στον client. Αυτή η πρακτική αν μή τί άλλο, σου εξασφαλίζει οτι κάθε φορά έχεις up-to-date δεδομένα, αυτά της τελευταίας αλλαγής στο αρχείο. Το downside όμως είναι οτι για κάθε request, ανοίγεις και διαβάζεις ένα αρχείο απο το δίσκο. Πολλά ταυτόχρονα requests σου thrash-άρουν το δίσκο, και σύντομα αυτό θα γίνει bottleneck στην εφαρμογή σου.

     

    Οπότε, η επόμενη επιλογή, είναι να κρατάς αυτά τα δεδομένα στη RAM, εξασφαλίζοντας οτι ειδοποιείσαι για τις αλλαγές στα configuration αρχεία και ανανεώνεις την cache σου.
    Για αυτό ακριβώς το σκοπό γράφτηκε μια library, η οποία επιτρέπει το caching και την επεξεργασία των περιεχομένων configuration αρχείων. Η base class όλων είναι ο FileHandler. Ένας FileHandler αναλαμβάνει να παρακολουθεί ένα αρχείο, και λαμβάνοντας ειδοποίηση οτι τα περιεχόμενα του αρχείου άλλαξαν, ξαναφορτώνει τα περιεχόμενά του στη μνήμη, δίνοντας την επιπλέον δυνατότητα ν ακαθορίσουμε custom κώδικα ο οποίος επεξεργάζεται τα περιεχόμενα του αρχείου προτού αυτά αποθηκευθούν στην cache.
     

    Το βασικό του interface είναι το παρακάτω:

    using System;

    using System.IO;

    using System.Collections;

    using System.Text;

     

    using System.Xml.Serialization;

     

    namespace FileHandler

    {

        /// <summary>

        /// FileHandler is responsible for monitoring a specific file,

        /// and reload it when required

        /// </summary>

        [XmlRoot("file-handler")]

           [ReturnTypeAttribute(typeof(byte[]))]

           public class FileHandler : IDisposable

        {

            /// <summary>

            /// Defines a function pointer - the function this points to will

            /// process the contents of the file upon initialization and reload.

            /// </summary>

                  public delegate object ProcessFile(Stream file, FileHandler source);

     

            /// <summary>

            /// Public event definition. This is where listeners will append

            /// their own function pointers

            /// </summary>

                  public event ProcessFile OnFileLoaded;

     

     

     

                  /// <summary>

                  /// Default, no-args constructor for Serialization purposes

                  /// </summary>

                  public FileHandler() {}

     

            /// <summary>

            /// Initializes the Handler, for a given file path, and a key

            /// </summary>

            /// <param name="sectionKey"></param>

            /// <param name="filePath"></param>

            public FileHandler(string sectionKey, string filePath) : this(sectionKey, filePath, false) {  }

     

            /// <summary>

            /// Initializes the object, specifying whether we need to reload the file immediately

            /// after it's been changed

            /// </summary>

            /// <param name="sectionKey"></param>

            /// <param name="filePath"></param>

            /// <param name="initImmediate"></param>

            public FileHandler(string sectionKey, string filePath, bool initImmediate)

            {

                ...            

            }

     

            /// <summary>

            /// Performs whatever processing is required upon File load

            /// </summary>

            /// <param name="filePath"></param>

            protected virtual void Initialize(string filePath){

                  ...

            }

     

            /// <summary>

            /// Executed whenever the file we're handling is changed

            /// </summary>

            /// <param name="sender"></param>

            /// <param name="e"></param>

            void FileWatcher_Changed(object sender, FileSystemEventArgs e)

            {

    ...

            }

     

            /// <summary>

            /// Retrieves the unique key name of this handler

            /// </summary>

            [XmlAttribute("id")]

           public string Key {

                ...

            }

     

            /// <summary>

            /// Indicates whether the FileHandler will reload the file

            /// it handles immedaitely after any change, or wait until

            /// its value is required

            /// </summary>

            [XmlAttribute("lazy-load")]

           public bool EnableLazyLoading {

                ...

            }

     

                  /// <summary>

                  /// Sets / Retrieves the path to the file we're watching

                  /// </summary>

                  [XmlAttribute("file-path")]

                  public string FilePath {

                         ...

                  }

     

            /// <summary>

            /// Overridable member that returns the processed contents

            /// of the file we're handling. By default, it will return the

            /// contents of the file as a byte array

            /// </summary>

            [XmlIgnore]

                  public byte[] Value {

                ...

            }

     

                  #region IDisposable implementation

        }

    }

     

    Το επόμενο στάδιο, είναι να δημιουργήσουμε sub classes του FileHandler, οι οποίες θα αναλαμβάνουν να επεξεργάζονται τα περιεχόμενα του αρχείου, αποθηκεύοντας έτσι ένα object στην cache, το οποίο θα ανανεώνεται κάθε φορά που το αντίστοιχο configuration αρχείο στο δίσκο αλλάζει.

     

    Το project που είναι attached στο post, περιέχει τις παρακάτω υλοποιήσεις του FileHandler:
     

    •   FileHandler. Αναλαμβάνει να διατηρεί στη μνήμη και να ανανεώνει τα περιεχόμενα ενός byte[], με τα περιεχόμενα του αρχείου
    • TextFileHandler. Subclass του FileHandler, η οποία επιστρέφει τα περιεχόμενα του αρχείου σε μορφή String.
    •   XmlFileHandler. Επιστρέφει τα περιεχόμενα του αρχείου ως XmlDocument
    • XmlObjectFactoryHandler. Χρησιμοποιεί XML Deserialization και επιστρέφει instance ενός τύπου ο οποίος προσδιορίζεται στον constructor.

     

    Η υλοποίηση ενός subclass δεν είναι δύσκολη. Όλη η υποδομή που χρειάζεται να υλοποιηθεί για να φορτώνει και να ανανώνει τα περιεχόμενα του configuration αρχείου υπάρχει ήδη στη base class, τον FileHandler. Το μόνο που χρειάζεται να υλοποιηθεί έιναι η επεξεργασία των περιεχομένων του αρχείου η οποία επιστρέφει το instance που θα μπεί στην cache.

    Το πιο απλό παράδειγμα, είναι ο TextFileHandler. Τη στιγμή που χρειάζονται τα περιεχόμενα του αρχείου, υλοποιεί την παρακάτω απλή ενέργεια:
     

    /// <summary>

                  /// Returns the contents of the file as a string, using the specified Encoding

                  /// </summary>

                  public new string Value {

                         get {

                               return m_Encoding.GetString(base.Value);

                         }

                  }    

     

    Ακόμα και το απλό αυτό task, θα μπορούσε να γίνει καλύτερα. Αντί να γίνεται η επεξεργασία κάθε φορά που ζητείται η τιμή του Value, θα μπορούσαμε να κρατάμε την τίμή σε ένα έτοιμο string ως private member, και να κάνουμε την επεργασία μέσω του Encoding μόνο όταν γίνεται set το Value. Αυτό υλοποιείται καλύτερα στον XmlFileHandler. Επειδή η επεξεργασία ενός αρχείου μέσω του XmlDocument είναι πολύ πιο επίπονη απο την απλή μετατροπή ενός byte array  σε string, ο XmlFileHandler cache-άρει ένα live instance, το οποίο και επιστρέφει ως Value:
     

    namespace FileHandler

    {

           /// <summary>

           /// XmlFileHandler extends FileHandler to return a strongly-typed XmlDocument value.

           /// </summary>

           [XmlRoot("xml-file-handler")]

           [ReturnTypeAttribute(typeof(System.Xml.XmlDocument))]

           public class XmlFileHandler : FileHandler

           {

                  private XmlDocument m_Document = new XmlDocument();

     

                  /// <summary>

                  /// Default no-args constructor

                  /// </summary>

                  public XmlFileHandler() : base() {

                         base.OnFileLoaded += new ProcessFile(XmlFileHandler_OnFileLoaded);

                  }

     

                  public XmlFileHandler(string sectionKey, string filePath) : this(sectionKey, filePath, false) {

                 

                  }

     

                  public XmlFileHandler(string sectionKey, string filePath, bool initImmediate) : base(sectionKey, filePath, initImmediate)

                  {

                         base.OnFileLoaded += new ProcessFile(XmlFileHandler_OnFileLoaded);

                  }

     

                  private object XmlFileHandler_OnFileLoaded(System.IO.Stream file, FileHandler source) {

                         // Load the stream into an XmlDocument ...

                         m_Document.Load(file);

     

                         return m_Document;

                  }

     

                  /// <summary>

                  /// New property definition, to return a type-safe value

                  /// </summary>

                  [XmlIgnore]

                  public new XmlDocument Value {

                         get {

                               return m_Document;

                         }

                  }

     

           }

    }

     

     Δύο είναι τα ενδιαφέροντα τμήματα του παραπάνω κώδικα:
     

    public XmlFileHandler() : base() {

                         base.OnFileLoaded += new ProcessFile(XmlFileHandler_OnFileLoaded);

                  }

     

    Εδώ γίνεται register ο event handler που θα κληθεί κάθε φορά που τα περιεχόμενα του αρχείου αλλάξουν και φορτωθούν στη μνήμω ώς byte[]. Ο event handler αναλαμβάνει να χρησιμοποιήσει αυτό το byte[] και μετά απο κάποια επεξεργασία να δημιουργήσει την τιμή / instance το οποίο και θα επιστραφεί ως value.
     

    private object XmlFileHandler_OnFileLoaded(System.IO.Stream file, FileHandler source) {

                         // Load the stream into an XmlDocument ...

                         m_Document.Load(file);

                         return m_Document;

                  }

     

     Εδώ είναι η επεξεργασία του XmlFileHandler. Το Stream το οποίο δίνεται ως παράμετρος είναι ένα MemoryStream το οποίο περιέχει τα περιεχόμενα του αρχείου. Αυτή η επιλογή τύπου, ενός Stream, δίνει μεγαλύτερη ελευθερία απο ένα απλό byte[], όπως και μεγαλύτερο εύρος επιλογών όσον αφορά ακόμα και την base κλάσση και μελοντικές προσθήκες χαρακτηριστικών σε αυτήν. Το επόμενο χαρακτηριστικό που χρειάστηκε, ήταν ένας τρόπος να «γκρουπάρουμε» αυτούς τους handlers, και να μπορούμε να ανακτούμε την τρέχουσα τιμή τους by nameΧρειαζόμασταν ένα MultiFileHandler.

     

    using System;

    using System.IO;

    using System.Collections;

    using System.Collections.Specialized;

    using System.Configuration;

    using System.Reflection;

     

    namespace FileHandler

    {

           /// <summary>

           /// MultiFileHandler is a collection of FileHandlers, retrievable by name.

           /// </summary>

           public class MultiFileHandler

           {

                  // Dictionary that will hold the FileHandler instances

                  private IDictionary m_FileHandlers = new ListDictionary();

     

                  private string m_Name = null;

     

                  private string m_BasePath = null;

     

                  private bool m_InitImmediate = false;

     

                  public MultiFileHandler();

     

                  public MultiFileHandler(string groupName;

     

                  public MultiFileHandler(string groupName, string basePath;

     

                  public MultiFileHandler(string groupName, string basePath, bool initImmediate;

     

                  /// <summary>

                  /// Sets / Retrieves a 'group' Name for this MultiFileHandler

                  /// </summary>

                  public string Name ;

     

                  /// <summary>

                  /// Sets / Retrieves the base path for all subordinate FileHandlers

                  /// </summary>

                  public string BasePath ;

     

                  /// <summary>

                  /// Indicates whether subordinate FileHandlers will re-initialize their contents

                  /// immediately after a watched file has changed

                  /// </summary>

                  public bool InitImmediate ;

     

                  /// <summary>

                  /// Appends a new FileHandler for the given file, with the specified name

                  /// </summary>

                  /// <param name="handlerKey"></param>

                  /// <param name="filePath"></param>

                  public void AddHandler(string handlerKey, string filePath;

     

                  /// <summary>

                  /// Appends a new FileHandler for the given file, with the specified name, indicating whether it will

                  /// init immediately after it's watched file has changed

                  /// </summary>

                  /// <param name="handlerKey"></param>

                  /// <param name="filePath"></param>

                  /// <param name="initImmediate"></param>

                  public void AddHandler(string handlerKey, string filePath, bool initImmediate;

     

                  /// <summary>

                  /// Adds the specified FileHandler instance to the contained list of Handlers

                  /// </summary>

                  /// <param name="handler"></param>

                  public void AddHandler(FileHandler handler;

     

                  /// <summary>

                  /// Removes & disposes the specified FileHandler

                  /// </summary>

                  /// <param name="handlerKey"></param>

                  public void RemoveHandler(string handlerKey;

     

                  /// <summary>

                  /// Retrieves the current value of the specified handler

                  /// </summary>

                  /// <param name="handlerKey"></param>

                  /// <returns></returns>

                  public object GetValue(string handlerKey);

     

                  /// <summary>

                  /// Gets the ReturnType for this handler, from a ReturnTypeAttribute attached to the instance

                  /// </summary>

                  /// <param name="handler"></param>

                  /// <returns></returns>

                  private Type GetReturnType(object handler);

     

                  /// <summary>

                  /// Retrieves the specified FileHandler

                  /// </summary>

                  /// <param name="handlerKey"></param>

                  /// <returns></returns>

                  protected FileHandler GetHandler(string handlerKey);

                 

     

           }

    }

    Αυτό που κάνει ο MultiFileHandler είναι απλό. Διατηρεί τα instances των FileHandlers που του προτίθενται, και επιστρέφει τις τιμές που του δίνουν. Θεωρητικά, ακόμη και αυτός θα μπορούσε να είναι subclass του FileHandler, και να δημιουργεί όλους του FileHandlers – παιδιά του, απο ένα configuration αρχείο. 

    Αλλά αυτό ας το αφήσουμε ως άσκηση στον αναγνώστη ... ( ή και στον γράφοντα :D ) ... ή για κάποια άλλη φορά :P

    Το attached project είναι  υλοποιημένο σαν console application, και περιέχει ένα μικρό test application. Enjoy ! :P

     

     


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