Breadcrumb a modifikovanie SiteMapy

Modifikovanie SiteMapy je bežnou potrebou. Predstavme si, že máme nejaké článočky pozaraďované do kategórií. V MasterPage máme užitočný breadcrumb(SiteMapPath control), prostredníctvom ktorého spoločne pre všetky podstránky v rámci nášho webu zobrazujeme aktuálnu cestu. Pri browsnutí stánky, ktorá zobrazuje článok chceme zahrnúť do cesty aj názov kategórie(alebo kategórií ak sú hierarchické) a rovnako aj nadpis aktuálneho článku. Toto len pomocou pridania záznamu do mapy stránky nedosiahneme. Jediné čo môžeme dosiahnuť je spoločná cesta pre všetky články.

<siteMapNode url="Default.aspx" title="Domov"  description="Domov">
    <siteMapNode url="~/Clanok.aspx" title="Článok" description="Články"/>
</siteMapNode>

Domov – Článok

To má ale veľmi ďaleko ku požadovaného správaniu. SiteMapProvider nás notifikuje o vyhodnocovaní uzla v kontexte každej stránky, kde je to potrebné prostredníctvom udalosti SiteMapResolve. V ňom môžeme vykonať potrebné mofikácie, ak nám nevyhovuje to, čo sa vyhodnotilo na základe mapy stránky.Keď som kedysi hľadal spôsob, ako ľudia riešia problém mofikácií, našiel som v zásade dva prístupy. Prvý, pomerne často sa vyskytujúci, odporúčal registrovať sa na udalosť SiteMapResolve v rámci metód životného cyklu stránky(OnLoad,OnInit). Výsledkom samozrejme je, že zavesený event handler sa potom volá aj v kontexte iných stránok, kedže existuje je len jedna inštancia providera per aplikácia. Tomu sa snažil cowboy programátor predísť záplatou v podobe rôznych podmienok vyhodnocujúcim, či sa udalosť vyvolala alebo nevyvolal v kontexte správnej stránky. Druhý prístup, kvalitatívne lepší, správne pridával jeden spoločný handler pre SiteMapResolve vrámci application start. Samozrejme, problémom takto je, ako získať kontext požadovanej stránky.Jeden prístup staval na url parametroch a čítal nezávisle dáta z databázy, aby mohol vyskladať cestu.(V praxi to znamená zvačša duplicitné dotazovanie sa na databázu, lebo všetky potrebné dáta načítal v stránke) Ďalší prístup aspoň staval na Handler atribúte z HttpContextu, čo je v tomto prípade inštancia našej stránky a ťahal údaje cez inteface priamo z inštancie stánky.Objektový design a prehľadnosť dostali dovolenku. Stop. To chce štýl ;-) Vytvorme si najprv interface pre stránky, ktoré potrebujú mofikovať SiteMapu pri vyhodnocovaní s jedinou metódou, v ktorej zmeny vykonáme.

public interface ISiteMapHandler
{
    SiteMapNode ProcessSiteMap(SiteMapProvider oProvider);
}

V zásade sa nám stačí zavesiť na SiteMapResolve event pri štarte aplikácie(Application_Start handler v global.asax), zistiť si, či aktuálna inštancia HttpHandlera je stránka, ktorá implementuje tento interface a ak áno, pretypovať ju na náš interface, zavolať z neho metódu a vrátiť modifikovaný uzol mapy stránky(SiteMapNode) alebo null v prípade, že stránka tento interface neimplementuje, čím oznámime že sme spokojný s pôvodne resolvnutým uzlom z danej mapy stránky. My si však chceme uľahčiť život a preto si vyrobíme kompoment, do ktorého stačí zaregistrovať správneho providera, ktorého uzly niektorá zo stránok modifikuje a o všetko ostatné sa postará za nás. Ja som urobil thread safe implementáciu, ale pokiaľ si vystačíme iba s registáciou v rámci štartu aplikácie, môžeme zostať pri jedinej statickej metóde, ktorá nám zabezpečí registráciu event handlera s volaním ProcessSiteMap metódy.(SiteMapResolver je implementovaný ako singleton s lazy inicializáciou kôly thread safety a zoznam registrovaným providerov je implementovaný ako synchronizovaný arrayList z rovnakých dôvodov)

public class SiteMapResolver
{
        private static readonly SiteMapResolver m_oInstance = new SiteMapResolver();

        private ArrayList m_cProviders;

        private SiteMapResolver() 
        {            
            m_cProviders = ArrayList.Synchronized(new ArrayList());
        }           
            
        public static SiteMapResolver GetInstance()
        {
            return m_oInstance;
        }

        public bool IsRegistered(string sProviderName)
        {
            return IsRegistered(GetProviderByName(sProviderName));
        }

        public bool IsRegistered(SiteMapProvider oProvider)
        {
            lock (m_cProviders.SyncRoot)
            {
                return m_cProviders.Contains(oProvider);
            }
        }

        public void Register(string[] arrProviderNames)
        {
            foreach (string sProviderName in arrProviderNames)
                Register(GetProviderByName(sProviderName));
        }

        public void Register(string sProviderName)
        {
            Register(GetProviderByName(sProviderName));
        }

        public void Register(SiteMapProvider oProvider)
        {
            lock (m_cProviders.SyncRoot)
            {
                if (m_cProviders.Contains(oProvider))
                    throw new Exception("Provider is already registered.");

                oProvider.SiteMapResolve += new SiteMapResolveEventHandler(m_SiteMapResolve);
                m_cProviders.Add(oProvider);
            }
        }
        
        public void Unegister(string sProviderName)
        {
            Unregister(GetProviderByName(sProviderName));
        }

        public void Unregister(SiteMapProvider oProvider)
        {
            if (oProvider == null)
                throw new ArgumentNullException("oProvider");

            lock (m_cProviders.SyncRoot)
            {
                if (!m_cProviders.Contains(oProvider))
                    throw new Exception("Provider is not registered.");

                oProvider.SiteMapResolve -= new SiteMapResolveEventHandler(m_SiteMapResolve);
                m_cProviders.Remove(oProvider);
            }
        }

        private SiteMapProvider GetProviderByName(string sProviderName)
        {
            SiteMapProvider oProvider = SiteMap.Providers[sProviderName];
            if (oProvider == null)
                new Exception(String.Format("Site map provider {0} doesn't exist.", sProviderName));
            return oProvider;
        }


        private SiteMapNode m_SiteMapResolve(object oSender, SiteMapResolveEventArgs oEArgs)
        {
            return OnSiteMapResolve((SiteMapProvider)oSender, oEArgs.Context);
        }

        protected virtual SiteMapNode OnSiteMapResolve(SiteMapProvider oProvider, HttpContext oHttpCtx)
        {
            IHttpHandler oHttpHandler = oHttpCtx.Handler;
            if (oHttpHandler is ISiteMapHandler)
            {
                ISiteMapHandler oSiteMapHandler = (ISiteMapHandler)oHttpHandler;
                return oSiteMapHandler.ProcessSiteMap(oProvider);
            }
            return null;
        }
}

Použitie?

V global.asax zaregistrujeme zvolený provider. Defaultný sa dá jednoducho získať ako SiteMap.Provider. Ja ich v aplikáciách vždy používam viac, takže som tomu prispôsobil rozhranie.

void Application_Start(object sender, EventArgs e) 
{        
        SiteMapResolver oSiteMapResolver= 
SiteMapResolver.GetInstance();
        oSiteMapResolver.Register("breadcrumb");        
      //pre default      
      //oSiteMapResolver.Register(SiteMap.Provider);       
}

Ako by mohla vyzerať implementácia ProcessSiteMap metódy modifikujúca SiteMapu?

public SiteMapNode ProcessSiteMap(SiteMapProvider oProvider)
{
        if (oProvider.Name == "breadcrumb")
        {
            SiteMapNode oCurrentNode = oProvider.CurrentNode.ParentNode.Clone(true);

            SiteMapNode oCategNode = new SiteMapNode(
                oProvider,
                "cat" + m_oDAC.Category.Id.ToString(),
                String.Format("~/Clanky.aspx?c={0}", m_oDAC.Category.Id),
                m_oDAC.Category.Name,
                m_oDAC.Category.Name);

            SiteMapNode oArtNode = new SiteMapNode(
                oProvider,
                "art" + m_oDAC.Id.ToString(),
                String.Format("~/Clanok.aspx?a={0}", m_oDAC.Id),
                m_oDAC.Title,
                m_oDAC.Title);

            oCategNode.ParentNode = oCurrentNode;
            oArtNode.ParentNode = oCategNode;

            return oArtNode;
        }
        return null;
}

Výsledok?

Domov – Novinky – Mám inteligetný breadcrumb?

Vieme si pomocť samozrejme aj nejakými dalšími helper metódami, keďže naša implementácia v metóde ProcessSiteMap bude vždy veľmi "podobná", ale to už asi zvládne každý sám.

Komentáre

# T said:

Kto/co hento sformatuje? Som confused :-(

Friday, June 15, 2007 3:32 AM
# skippo said:

To je ten visivig editor :-(, akurat som robil svoj post na blogu a mal som dost trable, kym som ho dostal pod kontrolu = pustil som si dashboard v opere a tam nastastie nie je :-) Hrozne formatuje HTML, horsie to robia uz len asi nastroje MS Office. Co ma naviac vytocilo, ze pri save funkcii modifikuje moj HTML zdrojovy kod ako sa ulozi v DB.

Friday, June 15, 2007 10:07 AM
# T said:

:) Asi si to budem musiet prepnut do HTML modu, ale obavam sa, ze aj tak si velmi nepomozem. Keby to malo taky isty zalamovac kodu ako pouziva spigy na portale, to by uplne stacilo :)

Dalsia vec, nedaju sa tu kategorizovat prispevky.

Dalej, urobil som novu page ale netusim, ako ho prelinkujem niekde z pravej listy.(zaradit do menu)

Na uvodnej stranke chcem zobrazovat len nadpis nejaky perex a nie cele clanky, zase netusim ako na to :-(

Friday, June 15, 2007 10:33 AM
# spigi said:

Ahoj,

prispevky sa kategorizovat daju. Na to sluzia TAGy, ktore sa vyplnaju pri editacii prispevku. Kazdy tag je jedna "kategoria", ktora sa Ti zobrazi vpravo.

Co sa tyka toho editoru - myslim, ze s nim velmi nepohnem, nakolko nemam zdrojaky od Community Servera :-) a to mam licenciu v hodnote 2000 USD :-)

Na pastovanie kodu by som asi skusil soft, co tu spominal tusim Vlko www.jtleigh.com/.../CopySourceAsHtml

Tie dobra rada, nepastovat z Wordu, ale ako plain text (prehnat to cez napriklad notepad a formatovat az tu na blogu). V tomto pripade je ten editor celkom pouzitelny.

Friday, June 15, 2007 10:27 PM
# spigi said:

Este ma napadla jedna vec. Aby si mal TAGy samostatne (lebo pozeram, ze ti ich nejako pospajalo) musis ich oddelovat bodkociarkou. Je to tam napisane pri tagoch v tooltipe na tom otaznicku :-)

Friday, June 15, 2007 11:00 PM
# T said:

Dakujem, blednu mi nejako vlasy vdaka tymto slnecnym dnom, takze dakujem. Idem to poopravovat :)

Saturday, June 16, 2007 12:51 AM
# T said:

Tento blog je choroba. (2000 dolacov - je mi jasne, ze community server nie je len blog, ale aj tak). Paneboze, toto je choroba...oescapovalo mi to cssko. Nevladzem.

Saturday, June 16, 2007 2:02 AM
# spigi said:

Ahoj,

ak zapisujes CSS priamo do HTML je dobre ho zapisat takto

<style>

<!--

.csharp {}

-->

</style>

Problem vsak je, ze ho asi nebude akceptovat. Ak budes aj v buducnosti pouzivat Manoliho na farbenie kodu (ako som videl tu), tak odporucam tento CSS hodit do CSScka celeho blogu a uz to nemusis zakazdym opakovat v clanku a mas istotu, ze ho bude clanok akceptovat.

CSS mozes doplnit v Control paneli v sekcii Change How My Blogs Look.

Saturday, June 16, 2007 9:09 AM
# T said:

Jezisi, jasne, hodim to do css templatne blogu, zase som strasne prakticky :-))  Sorry, bolo vela hodin.

Saturday, June 16, 2007 8:58 PM
Prihlásiť | Registrovať | Pomoc