ASP.NET – Diskuzní forum (1)

V dnešním dílu začneme vytvářet komponentu pro diskusní fórum. Cesta bude trnistá a náročná, ale na jejím konci bychom měli mít pokročilou komponentu pro použití v našich ASP.NET aplikacích.
Jak to už bývá na začátku každého projektu, musíme si i v případě komponenty Forum definovat cílové požadavky. Snažil jsem se o výběr toho nejlepšího, co jsem zatím na webu pochytil. Komponenta by tedy měla:
  • Obsahovat libovolný počet příspěvků, které by měli být uspořádány do stromové hierarchie (threaded discussion)
  • Umožňovat více nezávislých diskuzí, např. diskuze k článkům, diskuze v sekci know-how atd.
  • Být nezávislá na datovém zdroji. Jako datový zdroj bychom mohli použít některý z následujících přístupů:
    • SQL server s vloženými procedurami
    • Databázi Access s přístupem pomocí čistého SQL
    • NNTP news server
    • XML soubor
  • Být rychlá - neměla by zdržovat samotné zobrazení článku, nebo sekce.
  • Označovat již přečtené zprávy, podobně jak je to např. ve Outlook News Readeru.
  • Mít možnost odebírat reakce na daný příspěvek emailem.
Po této počáteční analýze můžeme ke každému s uvedených bodů přiřadit požadavek na implementaci, který danou funkci umožní:
  • 1. Obsahovat libovolný počet příspěvků, které by měli být uspořádány do stromové hierarchie (threaded discussion)

Stromovou strukturu docílíme například tím, že daný objekt Message bude jako jedno ze svých polí obsahovat kolekci všech dceřiných příspěvků, které budou také typu Message.

Na straně databázi může uvedená implementace vypadat například takto:

  • 2. Umožňovat více nezávislých diskuzí, např. diskuze k článkům, diskuze v sekci know-how atd.

To docílíme vytvořením samostatné tabulky Forum a odpovídajícího objektu. Ten bude obsahovat kolekci Messages, ve které budou zprávy z kořenové úrovně pro dané forum. Pro každé forum běžící v aplikaci pak bude vytvořena samostatná instance.

  • 3. Být nezávislá na datovém zdroji.

Co je to vlastně "nezávislost na datovém zdroji"? Jak docílíme toho, aby se při změně způsobu ukládání dat změnil náš kód co nejméně? Odpovědí je oddělení funkcí pro přístup k databázi od funkcí pro samotnou logiku fóra. Nejlépe by bylo, kdybychom mohli z Fóra volat určité funkce, například pro uložení dat do datového zdroje i bez toho, abychom znali konkrétní implementaci třídy, která to zajišťuje. Pro tento účel můžeme velice hezky využít rozhraní (interface), které pojmenujeme IForumDataAdapter. To bude definovat funkce pro načtení dat z datového zdroje do třídy Forum, a nebo naopak pro zápis dat zpátky v případě změny.

  • 4. Být rychlá - neměla by zdržovat samotné zobrazení článku, nebo sekce.

Pro aktuální fóra, které běží například pod novými články a jsou často zobrazované, by bylo dobré nastavit možnost cachování dat pomocí aplikační cache - rekurzivní získávání by bylo hodně pomalé. Na druhou stranu není určitě vhodné cachovat všechny fóra kvůli paměťové náročnosti, proto by bylo dobré kdyby samotná komponenta uměla tuto vlastnost vypnout.

  • 5. Označovat již přečtené zprávy, podobně jak je to např. ve Outlook News Readeru.

Této funkci můžeme docílit pomocí cookie nebo pomocí ukládání seznamu přečtených článků do proměnné session. Cookie odlehčí našemu serveru, umožní ale pouze sledování několika desítek posledních přečtených zpráv, kvůli omezení přenosové kapacity a velikosti cookie podporované jednotlivými prohlížeči. Naproti tomu použití session bude poněkud náročnější na implementaci.

  • 6. Mít možnost odebírat reakce na daný příspěvek emailem.

Implementovat tuto funkci by neměl být problém - stačí vytvořit tabulku se seznamem emailů a propojit ji s danou zprávou. Na úrovni databázového adaptéru IForumDataAdapter pak vytvoříme funkci, která nám pro zadaný příspěvek vrátí seznam odběratelů. Při zakládání nového příspěvku si necháme vypsat veškeré odběratele pro nadřazené rodičovské příspěvky a všem pošleme email s daným příspěvkem.

Výsledná architektura naší komponenty bude tedy vypadat:

Základní kostra implementace ve C# by pak mohla vypadat :

  namespace Components
  {
   
    /// <summary>A message for Forum. Contains child messages in _messages field.</summary>
    public class ForumMessage
    {
      private string _author;
      private string _subject;
      private string _authorEmail;
      private string _body;
      private string _IPAddress;
      private string _attachment;
      private DateTime _postDate;
      private ForumMessageCollection _messages;

      /// <summary>Author`s name or nickname typed into the submit form.</summary>
      public string Author{set{_author = value;}get{ return _author;}}
     
      /// <summary>
      /// Description of the message
      /// </summary>
      public string Subject{set{_subject = value;}get{ return _subject;}}

      /// <summary>The email address author has entered into submit form</summary>
      public string AuthorEmail{set{_authorEmail = value;}get{ return _authorEmail;}}

      /// <summary>Text of the message</summary>
      public string Body{set{_body = value;}get{ return _body;}}
     
      /// <summary>Author`s IP address</summary>
      public string IPAddress{set{_IPAddress = value;}get{ return _IPAddress;}}

      /// <summary>Name of attached file residing in special directory for uploading/downloading purposes only (no running scripts)</summary>
      public string Attachment{set{_attachment = value;}get{ return _attachment;}}

      /// <summary>Name of attached file residing in special directory for uploading/downloading purposes only (no running scripts)</summary>
      public DateTime PostDate{set{_postDate = value;}get{ return _postDate;}}

      /// <summary>Collection of child messages</summary>
      public ForumMessageCollection Messages{set{_messages = value;}get{ return _messages;}}
    }

    /// <summary>A collection of Forum messages.</summary>
    public class ForumMessageCollection:ArrayList
    {
      /// <summary>Adds a message to the collection</summary>
      /// <param name="q">message object to add</param>
      /// <returns>Index of inserted message</returns>
      public int Add(ForumMessage q)
      {
        return base.Add(q);         
      }
      /// <summary>Gets or sets message with index</summary>
      public new ForumMessage this[int index]
      {
        get
        {
          return (ForumMessage)base[index];
        }
        set
        {
          base[index] = value;
        }
      }
    }

    /// <summary>Implementation of Forum</summary>
    public class Forum
    {
      private ForumMessageCollection _messages;

      /// <summary>Collection of root messages for this Forum.</summary>
      public ForumMessageCollection Messages
      {
        set
        {
          _messages=value;
        }
        get
        {
          return _messages;
        }
      }
    }
    /// <summary>
    /// IForumDataAdapter defines an interface responsible for filling data from/into the Forum, given as a read-only
    /// property CurrentForum. This should be initialized by constructor of implementing object. This interface allows to have
    /// multiple possibilities for storing data. For example an SQL server with stored procedures, Access database with pure SQL
    /// access, XML file, or NNTP server.
    /// </summary>
    public interface IForumDataAdapter
    {
      Forum CurrentForum{get;}
      void Load();
      void Save();
      Forum Create();
      // additional implementation possible for checking for list of read messages.
    } 
  }

Flexibilita návrhu spočívá v těchto klíčových bodech:

  • Pokud budeme v budoucnu potřebovat změnit datový zdroj, přidáme pouze třídu implementující rozhraní IForumDataAdapter. Toto rozdělení nám umožní vyvarovat se chyb v důsledku změn v kódu.
  • Pokud budeme chtít vyvinout forum pro např. pro WAP, WinForms, nebo WebService, celá základna je již hotová, stačí jenom přidat samotnou implementaci komponenty.

Co bude příště ? Návrh sice může být hezký, spustit jej a přidávat příspěvky na něm ale nemůžeme. Takže příště vzhůru do programování a implementování uvedeného návrhu. Naše implementaci bude zahrnovat pouze OleDBForumDataAdapter pro ukládání dat do Accessu, pokud bude zájem, můžeme se později podívat na další možnosti (NNTP, XML).

Diskuze (4) Další článek: Finální KDE 3.0

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,