MorzelProgramming

a programozásról

Archive for the ‘C#’ Category

Asp.Net GridView–Sor kiválasztása kulcs alapján

leave a comment »

 

A minap kénytelen voltam egy oldal visszatöltésekor egy megadott azonosító alapján kiválasztani egy GridView az azonosítónak megfelelő sorát. Nos, mit is mondjak, ez annyira triviális mint a természetes levezetés (nem két ember bukott meg miatta logikából)…

 

A kényelmes megoldás: adatkötöttük a GridView-t, ismerjük a kulcsot valamint mivel adatkötöttünk, ismert az összes kulcs (igen, még a GridView számára is) és beállítjuk a SelectedDataKey tulajdonságot:

  1. grid.SelectedDataKey = …;

Király. ReadOnly, ne is próbálkozzunk.

 

Keresni kell megoldás: Van egy Rows nevű tulajdonságunk. Ez tartalmazza a GridView _összes_ sorát:

A GridViewRowCollection that contains all the data rows in a GridView control.

Király. Kár, hogy a lapozható gridem esetében 4 sorból áll egy lap, két oldal van, összesen 6 elem és a Rows kollekció számossága 4. Az is szomorú hogy az általam keresett kulcs a második lapon van. Itt megjegyzendő, hogy amennyiben a GridView nem lapozható, tökéletesen alkalmazható az a módszer, hogy megkeressük hogy melyik sorban van az adott kulcs, és ennek az indexére beállítjuk a táblázatunkat.

Továbbá megjegyzendő, hogy az előző állítás igaz a DataKeys tulajdonságra is.

 

 

Ezek után nézzük hogy mi az a tényleges megoldás, ami a számomra működött:

  1. public static void SelectRowByKey(string key, GridView grid)
  2. {
  3.     int idx;
  4.  
  5.     int i = 0, j = 0;
  6.  
  7.     while (j < grid.PageCount && null != grid.DataKeys[i].Value && grid.DataKeys[i].Value.ToString() != key)    /// A kulcsot itt is ellenorizni kell, különben a grid elso lapjának elso sorában nem adna vissza semmit sem.
  8.     {
  9.         grid.PageIndex = j++;   // Beállítjuk az aktuális lap sorszámát és növeljük a számláló értékét.
  10.         grid.DataBind();        // Ha nem bindolunk akkor a lapok (így az DataKeys értéke is) megmarad a hívási állapotban.
  11.         i = 0;
  12.         while (i < grid.DataKeys.Count && null != grid.DataKeys[i].Value && grid.DataKeys[i].Value.ToString() != key)   // Kulcs ellenorzése.
  13.             ++i;
  14.     }
  15.  
  16.     // Ha nem találtuk meg a keresett kulcsot akkor nem csinálunk semmit.
  17.     // Itt másik megoldás lehetne hogy kivételt dobunk, szerintem mindenki igazítsa a saját igényeihez.
  18.     if ((i == grid.DataKeys.Count && j == grid.PageCount) || (null != grid.DataKeys[i].Value && grid.DataKeys[i].Value.ToString() != key))
  19.         return;
  20.  
  21.     idx = i;
  22.  
  23.     //grid.PageIndex = idx / grid.PageSize;             // ezt már beállítottuk a keresés során. Mindenesetre jó fejben tartani hogy ezt is be kell állítani.
  24.     grid.SelectedIndex = idx;                           // beállítjuk az aktuális lapon az aktuális sort
  25.     grid.SelectedPersistedDataKey = grid.DataKeys[i];   // be kell állítani a kiválasztott kulcsot is
  26.  
  27.     grid.DataBind();/// Akkora kalap szar ez a gridview hogy ezt nehéz szavakkal kifejezni… Mindenesetre úgy tunik, databindból sosem elég. 😦
  28. }

A dallam viszonylag egyszerű. Laponként végignézzük a GridViewt és lap váltásonként DataBindolunk. Az élet szép. Így tudjuk frissíteni a DataKeys tulajdonság értékeit is.
Ha egyszer megtaláltuk a keresett sort, beállítjuk a SelectedIndex és a PersistedDataKey tulajdonságokat és DataBindolunk mivel már úgy belejöttünk. Ezt az utolsó DataBind kifejezést az istennek sem értem meg, szerintem mennie kéne nélküle is. Ennek ellenére ha kikommentezem a sort akkor szép HttpUnhandleExceptiont kapok.

 

Morzel

ui.:
Felhívás: Ha bárki tud értelmesebb, egyszerűbb, szebben kinéző, folyamatos DataBind() mentes megoldást, kérem ne tartsa vissza!

Written by Morzel

július 25, 2011 at 11:57 de.

Asp.Net, C# kategória

Session és Viewstate változók kezelése

leave a comment »

 

 

I. Session változók

A minap olvastam a CodeProject hírlevélben egy érdekes cikket, arról szólt hogy hogyan érdemes kikerülni azt a problémát, hogy a Session változókra string jelzőkkel hivatkozunk (tulajdonképpen mind a session, mind a ViewState kuls-érték párokat tartalmaz). A kulcs-érték párokkal stringes kulcs esetében tipikusan a legnagyobb probléma a folyamatos elgépelés és természetesen az, hogy ezen hibák futás időben derülnek ki.

Maga a cikkíró azt az elvet vallja, hogy enumokat hoz létre és a felsorolás elemeire való hivatkozással oldja meg az intellisense használatának kérdését. Érdekességként talán érdemes lehet elolvasni ezt is, bár véleményem szerint szörnyű megoldás. Egyszerűen nem tetszik. Persze ízlések és pofonok…

 

Az alternatívák között viszont a 4. (amit mellesleg az olvasók is a legjobb alternatívának értékeltek, egész pofás megközelítés:

 

1.: Készítsünk egy saját típust:

  1. public class SessionVariable<T>
  2. {
  3.     private string VariableName { get; set; }
  4.     private System.Web.SessionState.HttpSessionState Session { get; set; }
  5.     public T Value
  6.     {
  7.         get
  8.         {
  9.             object sessionValue = this.Session[this.VariableName];
  10.             if (sessionValue == null)
  11.             {
  12.                 sessionValue = default(T);
  13.             }
  14.             return (T)sessionValue;
  15.         }
  16.         set
  17.         {
  18.             this.Session[this.VariableName] = value;
  19.         }
  20.     }
  21.     public SessionVariable(string variableName,
  22.         System.Web.SessionState.HttpSessionState session)
  23.     {
  24.         this.VariableName = variableName;
  25.         this.Session = session;
  26.     }
  27. }

Ebben eddig nincs semmi extra. Készítünk egy típust amiben hivatkozunk az aktuális Sessionre és eltárolunk egy nevet a változónknak. A típus a Sessionból a VariableName alapján kezeli az adatainkat. Van egy konstruktorunk is, erre végülis az inicializáló kifejezések megléte óta már csak megszokásból van szükségünk.

 

2.: Kell egy helper class amiben inicializáljuk a változókat:

  1. public class SessionHelper
  2. {
  3.     public static void InitializeSessionVariables(object instance,
  4.         System.Web.SessionState.HttpSessionState session, string prefix)
  5.     {
  6.         foreach (var property in instance.GetType().GetProperties(
  7.             BindingFlags.Public | BindingFlags.Instance))
  8.         {
  9.             if (property.PropertyType.IsGenericType &&
  10.                 property.PropertyType.GetGenericTypeDefinition() ==
  11.                 typeof(SessionVariable<>))
  12.             {
  13.                 property.SetValue(instance, Activator.CreateInstance(
  14.                     property.PropertyType, prefix + property.Name, session),
  15.                     new object[] { });
  16.             }
  17.         }
  18.     }
  19. }

Itt látható a dolog hátránya. Sajnos a GetProperties metódus csak a publikus propertiket adja vissza nekünk. Így ez annyit jelent hogy ha propertit készítünk akkor annak publicnak kell lennie (Természetesen ha létrehozunk kézzel egy változót akkor használhatjuk a konstruktort is az inicializálásra).

 

3.: Az oldalunkon el kell helyeznünk egy inicializálási utasítást az OnInitben:

  1. using System;
  2.  
  3. namespace ServerVariables.Code
  4. {
  5.     public class BasePage : System.Web.UI.Page
  6.     {
  7.         protected override void OnInit(EventArgs e)
  8.         {
  9.             SessionHelper.InitializeSessionVariables(this, Session, this.Page.ToString());
  10.             
  11.          
  12.             base.OnInit(e);
  13.         }
  14.     }
  15. }

Jó szokáshoz híven erre készítek egy ősosztályt és majd ebből származtatom az oldalaimat. Így megúszom azt, hogy minden egyes oldalon bajlódnom kell a hívásokkal.

 

4.: Már csak egy oldalra van szükségünk ahova kipakolhatunk néhány változót:

  1. using System;
  2. using ServerVariables.Code;
  3.  
  4. namespace ServerVariables
  5. {
  6.     public partial class _Default : BasePage
  7.     {
  8.         public SessionVariable<string> SessionHello { get; set; }   // Mindenképpen public legyen
  9.         
  10.  
  11.         protected void Page_Load(object sender, EventArgs e)
  12.         {
  13.             SessionVariable<string> s = new SessionVariable<string>("s", this.Session); // itt már nem fontos a public láthatóság.
  14.             s.Value = "hmmm…";
  15.             
  16.  
  17.             SessionHello.Value = "Hello World";
  18.             
  19.             Response.Write(SessionHello.Value);
  20.         }
  21.     }
  22. }

Van változónk, a Sessionben van letárolva és sehol se látunk szöveges hivatkozást, csökkent az esély arra, hogy el fogjuk gépelni.

Ami hátrány, hogy ha a property nem public akkor bizony a változónk értéke null lesz és bármilyen Value tulajdonságra hivatkozva NullReferenceExceptiont kapunk.

Megjegyzendő hogy egy egyszerű code snippet segítségével gyakorlatilag elérhetjük hogy mindig ott legyen a public láthatósági módosító. Ezzel még tovább csökkenthetjük a félre gépelések valószínűségét.

 

II. ViewState változók

Felbuzdulva a Session változókon, úgy gondoltam hogy nekiállok ViewState változókat is kezelni. Nagy különbség nincsen, csak át kell nevezni pár dolgot.

1.: A ViewState használatához szükséges típus és annak a helper osztálya:

  1. public class ViewStateVariable<T>
  2. {
  3.     private string VariableName { get; set; }
  4.     private System.Web.UI.StateBag ViewState { get; set; }
  5.  
  6.     public T Value
  7.     {
  8.         get
  9.         {
  10.             object viewStateValue = this.ViewState[this.VariableName];
  11.             if (null == viewStateValue)
  12.             {
  13.                 viewStateValue = default(T);
  14.             }
  15.  
  16.             return (T)viewStateValue;
  17.         }
  18.         set
  19.         {
  20.             this.ViewState[this.VariableName] = value;
  21.         }
  22.     }
  23.  
  24.     public ViewStateVariable(string variableName, System.Web.UI.StateBag viewState)
  25.     {
  26.         this.VariableName = variableName;
  27.         this.ViewState = viewState;
  28.     }
  29. }
  30.  
  31. public class ViewStateHelper
  32. {
  33.     public static void InitializeViewStateVariables(object instance,
  34.         System.Web.UI.StateBag viewState, string prefix)
  35.     {
  36.         foreach (var property in instance.GetType().GetProperties(
  37.             BindingFlags.Public | BindingFlags.Instance))
  38.         {
  39.             if (property.PropertyType.IsGenericType &&
  40.                 property.PropertyType.GetGenericTypeDefinition() ==
  41.                 typeof(ViewStateVariable<>))
  42.             {
  43.                 property.SetValue(instance, Activator.CreateInstance(
  44.                     property.PropertyType, prefix + property.Name, viewState),
  45.                     new object[] { });
  46.             }
  47.         }
  48.     }
  49. }

 

2.: Ősosztály a Page-nek:

  1. using System;
  2.  
  3. namespace ServerVariables.Code
  4. {
  5.     public class BasePage : System.Web.UI.Page
  6.     {
  7.         protected override void OnInit(EventArgs e)
  8.         {
  9.             
  10.             ViewStateHelper.InitializeViewStateVariables(this, ViewState, this.Page.ToString());
  11.          
  12.             base.OnInit(e);
  13.         }
  14.     }
  15. }

 

5.: A változók használata megegyezik a Session változóinkkal. Az előnyök és hátrányok szintén ugyanazok.:

  1. using System;
  2. using ServerVariables.Code;
  3.  
  4. namespace ServerVariables
  5. {
  6.     public partial class _Default : BasePage
  7.     {
  8.         public SessionVariable<string> SessionHello { get; set; }   // Mindenképpen public legyen
  9.         public ViewStateVariable<int> ViewStateTheLifeUniverityAndEverything { get; set; }  // Mindenképpen public legyen
  10.  
  11.         protected void Page_Load(object sender, EventArgs e)
  12.         {
  13.             SessionVariable<string> s = new SessionVariable<string>("s", this.Session);
  14.             s.Value = "hmmm…";
  15.             ViewStateVariable<int> i = new ViewStateVariable<int>("i", this.ViewState);
  16.             i.Value = 42;
  17.  
  18.  
  19.             SessionHello.Value = "Hello World – ";
  20.             ViewStateTheLifeUniverityAndEverything.Value = 42;
  21.  
  22.  
  23.  
  24.             Response.Write(SessionHello.Value + ViewStateTheLifeUniverityAndEverything.Value.ToString());
  25.         }
  26.     }
  27. }

 

 

A teljes projekt természetesen letölthető a skydriveról.

 

Morzel

Written by Morzel

június 29, 2011 at 10:33 de.

Asp.Net, C# kategória

Kódolási tanácsok

5 hozzászólás

 

Íme néhány, szerintem hasznos tanács amit talán érdemes megfogadni forráskódjaink gépelése során:

 

1.: Egyenlőség vizsgálat skalár értékkel:

  1. bool valami = true;
  2. if (valami = false)
  3.     return null;

 

Az, hogy elírás, nyilvánvaló. Ez sajnos bár warningot dob, lefordul. Ha az ember kicsit belegondol, egy-egy ilyen elírás hatalmas hibákhoz vezethet. Viszont ha megfordítjuk az egyenlőség vizsgálatot, akkor skalárnak nem adhatunk értéket, fordítási hiba. Így lett a futtatási hibából fordítási hiba:

  1. bool valami = true;
  2. if (false = valami)
  3.     return null;

 

2.: Blokk zárójelezés ciklusoknál, elágazásoknál:

Ciklusok, elágazások feje után akkor is tegyünk blokk zárójeleket ha csak egy utasításunk van. A legtöbb nyelv megengedi hogy ne tegyünk blokkot a fej után, de mindenképpen hasznos. Elég gyakran fordul elő hogy utólag oda kell biggyeszteni egy-két-három vagy sok utasítást és mivel emberek vagyunk, vagy eszünkbe jut ez, vagy nem:

  1. if (true == valami)
  2.     return null;

 

Ez így tökéletesen helyes. Eszünkbe jut hogy egy kiírást is tenni kellene ide:

  1. if (true == valami)
  2.     Console.WriteLine("valami");
  3.     return null;

 

Ez is helyes. Szintaktikailag. Az már más kérdés hogy most teljesen mást csinál a kódunk mint amit szeretnénk. Ha egyből bezárójeleztünk volna, most nem lenne probléma:

  1. if (true == valami)
  2. {
  3.     Console.WriteLine("valami");
  4.     return null;
  5. }

 

Számomra a tanulság az, hogy ha egyből kiteszem a blokk zárójeleket akkor utólag biztosan nem fogom elfelejteni őket.

 

3.: Pontosvessző a ciklusok, elágazások feje után:

Nem egyszer tanítottam már diákokat/hallgatókat a programozás alapjaira, és gyakori típushiba hogy valami úton-módon (pl. esti fáradság a sör mellett) sikerül egy pontosvesszőt tenni a ciklusok, elágazások feje után, közvetlenül az utasítások blokkja előtt:

  1. while (true == valami);
  2. {
  3.     Console.WriteLine("valami");
  4.     return null;
  5. }

 

Mint látható, a probléma egyértelmű. Cefet nehéz odafigyelni rá. Volt olyan kód, ahol már az egész algoritmust átnéztem a tanítvánnyal, mire megtaláltam. Ha azt vesszük észre hogy egy ciklus egyszer sem fut le akkor érdemes jobban odapislantani. Az ember szeme egyszerűen átsiklik felette. Programozók vagyunk, az üzleti logikára, az algoritmusra, a tartalomra figyelünk elsődlegesen. Nem az elgépelésekre.

Felderítése blokk zárójelek nélkül viszonylag egyszerű, minden IDE esetében van automatikus kód formázás, ilyenkor hamar kibukik:

Elírás, automatikus kódformázás előtt:

  1. while (true == valami);
  2.     Console.WriteLine("valami");

Elírás, automatikus kódformázás után:

  1. while (true == valami) ;
  2. Console.WriteLine("valami");

 

Mint látható, innentől kezdve már a kódra messziről ránézve látszik hogy hibás.

Persze blokk zárójelek használata esetében, a kód formázó meghagyja kódunkat a fenti, első formában. Ilyenkor ezzel sokat nem kezdhetünk, szemünket gubbasztjuk, debuggerezünk, keressük a hibát a szokásos módokon. Mindenesetre jó fejben tartani hogy ilyen is történhet.

 

4.: Automatikus formázás:

Időnként fórumokon megjelenik egy-egy okos emberke aki azt mondja hogy notepadben kell fejleszteni. Hát nekik egészségükre, aki nem látja be hogy egy jó IDE mennyi terhet vehet le az ember válláról, az szenvedjen csak egy sima szövegszerkesztővel.

A népszerűbb fejlesztői környezetek mindegyike képes automatikus kódformázásra. Ez mindössze annyit jelent hogy a programunk szerkezete alapján a tabokat szépen helyre teszi nekünk a fejlesztői környezet, így átláthatóbb lesz a kód. Visual Studio alatt az automatikus formázást a CTRL-K –> CTRL-F billenytűkombinációkkal használhatjuk. Itt megjegyezném hogy ha a formázás parancs kiadásának hatására nem történik semmi akkor vagy helyes a kódunk formázása, vagy olyan szintaktikai hiba van, ami ellehetetleníti (pl. kevesebb záró zárójel mint nyitó).

Forráskód automatikus formázás nélkül:

  1. while (true == valami)
  2. Console.WriteLine("valami");
  3. if (true == valami)
  4. Console.WriteLine("hello");
  5. {
  6. {
  7. }{
  8.  
  9. }}

Forráskód automatikus formázás után:

  1. while (true == valami)
  2.     Console.WriteLine("valami");
  3. if (true == valami)
  4.     Console.WriteLine("hello");
  5. {
  6.     {
  7.     }
  8.     {
  9.  
  10.     }
  11. }

 

 

5.: Típus megjelölése a kifejezésekben is.

Hasznos ha a kifejezések típusát nem fejből rakjuk össze amikor ránézünk hanem már eleve jelezzük a forráskódban azt. Persze, nyilván könnyen ki lehet számolni de sose felejtsük el a drága időt amikor hirtelen valamit meg kell keresnünk a fél éve írt kódunkba (én tipikusan arra sem emlékszem hogy tegnap pontosan mit kódoltam).

Nézzük ezt az egyszerű kódot:

  1. float a = 5;
  2. Console.WriteLine(a / 2);

 

Az eredmény 2.5, mint ahogy azt vártuk. Azonban ha csak a második sört nézzük akkor tippelnünk kell az eredmény típusára. Ezt lehet elősegíteni az alábbi módon:

  1. float a = 5;
  2. Console.WriteLine(a / 2.0);

 

Az eredmény megegyezik az előzővel. Egyetlen különbség hogy már a második sorra ránézve is meg tudjuk mondani az eredmény típusát (az tuti hogy lebegőpontos lesz).

Így persze még az előzőnél is szebb:

  1. float a = 5.0f;
  2. Console.WriteLine(a / 2.0f);

 

Hozzátenném ha a deklaráció mondjuk nem a művelet sorában lenne hanem mondjuk 10 sorral vagy 3 metódus hívással arrébb, akkor inkább így írnám:

  1. float a = 5.0f;
  2.  
  3.  
  4. Console.WriteLine(a / 2.0f);    //float

 

Megjegyzendő az ökölszabály: bármilyen kódot is írunk, minimálisra kell csökkenteni az időt amit majd szükség esetén a későbbi értelmezéssel töltünk el. Az iskolai példáknál még nem lehet különösebb gond, később amikor a főnök rákérdez valamire a két hónappal ezelőtt csináltakból, az információra nem egy óra múlva hanem tegnapra lesz szüksége. Szóval minél inkább olvasható a kódunk (és értelmezhető), annál hatékonyabbak leszünk a visszafejtésében amikor már mi is elfelejtettük hogy mit is akartunk csinálni.

 

6.: Kivétel biztos programozás:

Ez egy egyszerű szabály. Törekedjünk arra, hogy olyan kódot írjunk ami nem dobhat kivételt (vagy csak ellenőrzöttet). Arra gondolom nem kell kitérni hogy minden kivétel kezelve legyen, ezt mindenhol beleverik az emberbe. Viszont az is egy nézőpont lehet hogy minimalizáljuk a lehetséges kivételek számát.

Egy egyszerű példa, bekérünk egy egész számot és a user inputot int-é alakítjuk:

  1. try
  2. {
  3.     string input = Console.ReadLine();
  4.  
  5.     int i = int.Parse(input);
  6. }
  7. catch (Exception exc)
  8. {
  9.     Console.WriteLine("Hibás input!");
  10. }

 

Az iskolapélda után nézzük meg kivételek nélkül:

  1. string input = Console.ReadLine();
  2.  
  3. int i = -1;
  4.  
  5.  
  6. if (!int.TryParse(input, out i))
  7.     Console.WriteLine("Hibás input!");

Az eredmény ugyanaz, viszont nincs kivétel.

 

 

A fenti tanácsok betartása sokszor több gépelést jelent mint amennyit a lusta emberek megengednek maguknak. Cserébe viszont a forráskód könnyen átlátható, könnyen javítható és könnyen hiba kereshető lesz.

 

Morzel

Written by Morzel

június 6, 2011 at 9:41 de.

C#, Fejlesztéshez kategória

Asp.Net–dinamikus TreeView

leave a comment »

 

Az internet tele van olyan példákkal hogy hogyan lehet feltölteni egy TreeView-t dinamikusan, kliens oldalon. Ez legtöbbször valamilyen 3rd party js library képében történik meg, pl. jQuery használatával.

A probléma akkor kezdődik ha később szerver oldalon dolgozni is szeretnénk a fánkkal vagy ha az oldalon van millió kontrollunk és természetesen partial és full-page postbackek sora történik. Ilyenkor azért már jól el lehet szórakozni azzal hogy a kliens oldali változásokat szerver oldalon is észleljük. Ez üzleti alkalmazások esetében általában nem éri meg a fáradozást.

Viszont a másik oldal hogy ha rendelkezünk egy olyan TreeView-al aminek mondjuk van pár ezer eleme akkor nem feltétlenül szeretnénk azt a pár ezer elemet betölteni az oldal első betöltésekor. Meg különben se szeretnénk ha az egész betöltődne, az esetek nagy részében a felhasználó úgysem szeretné látni az egész faszerkezetet egyszerre.

A fentiek alapján nézzük meg mi szükséges ahhoz, hogy egy Asp.Net-es, szerver oldali TreeView controlt dinamikusan töltsünk fel:

  • Először is kell egy példa adathalmaz(Jelen esetben az ingyenesen letölthető AdventureWorks adatbázis ProductCategory táblája lesz az).
  • Kell egy módszer arra, hogy meghatározzuk hogy egy adott csomópontnak vannak-e gyermekei és ha igen, akkor mik azok.
  • Kell egy TreeView Smile.
  • Az oldal első betöltésekor meg kell adnunk a TrevView első szintjének elemeit. Az elemeknek meg kell adni az OnTreeNodePopulate eseménykezelőjét abban az esetben ha vannak gyermekei. Az Expanded tulajdonságot false-ra kell állítani ugyanis a TreeNodePopulate akkor fut le amikor kinyitunk egy csomópontot. Ha nem állítjuk át ezt az értéket az alapértelmezett true-ról akkor az első betöltésekor szépen végig fog menni a az algoritmus a fa összes szintjén egyenként hiszen minden egyes csomópontra meg fog hívódni a TreeNodePopulate eseménykezelő.
  • Az előző lépés ismétlése minden TreeNodePopulate lefutásakor.

 

A ProductCategory tábla számunkra fontos tartalma a következő:

ProductCategoriesTable

A Name-re azért van szükségünk hogy ki tudjunk írni valamit. A gyökérelemeknél a ParentProductCategoryID értéke NULL, a többinél meg a szülő elem ProductCategoryID-ja.

Készítsünk egy metódust ami lekérdezi az adott szülőhöz tartozó gyermek rekordokat:

public static List<ProductCategory> GetCategoriesByParentID(int? parentID)
{
    using (AdventureWorksLT2008Entities ctx = new AdventureWorksLT2008Entities())
    {
        return ctx.ProductCategory.Where(cat => cat.ParentProductCategoryID == parentID || (null == parentID && cat.ParentProductCategoryID == null)).ToList();
    }
}

 

Adjunk hozzá egy TreeView-t az oldalunkhoz:

<asp:TreeView runat=”server” ID=”tvCategories”
    OnTreeNodePopulate=”tvCategoris_TreeNodePopulate”></asp:TreeView>

 

Adjuk hozzá a szülő csomópontokat a TreeView-hoz az oldal első betöltésekor:

protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
                AddChildNodes(tvCategories.Nodes, null);
        }

 

Készítsük el az eseménykezelőt a TreeNodePopulate eseményre:

protected void tvCategoris_TreeNodePopulate(object sender, TreeNodeEventArgs e)
        {
            if (e.Node.ChildNodes.Count == 0)
            {
                AddChildNodes(e.Node.ChildNodes, e.Node);
            }
        }

 

Készítsük el az AddChidlNodes metódust:

private void AddChildNodes(TreeNodeCollection nodes, TreeNode node)
        {
            List<ProductCategory> categories = null;
            int parentID = -1;
            
            if (null != node && int.TryParse(node.Value, out parentID))
                categories = CategoryDAO.GetCategoriesByParentID(parentID);
            else
                categories = CategoryDAO.GetCategoriesByParentID(null);

            foreach (var cat in categories)
            {
                TreeNode newNode = new TreeNode();
                newNode.Value = cat.ProductCategoryID.ToString();
                newNode.Text = cat.Name;
                newNode.PopulateOnDemand = null == node;
                newNode.Expanded = false;

                nodes.Add(newNode);
            }
        }

Látható hogy abban az esetben ha gyökérelemeket töltünk fel akkor null lesz a parentID értéke. Ez mondjuk tervezés kérdése, én személy szerint inkább egy ütköző értékkel (-1) láttam volna el. Ekkor sok kódolást meg lehetne úszni (a lekérdező metódusban külön null vizsgálat, ebben a metódusban kétszer kellett leírnom a lekérdező metódus meghívását és ki tudja hogy mi lenne még egy éles programban…).

A PopulateOnDemand értékét aszerint kell meghatároznunk hogy levélelem-e az adott csomópontunk. Ha igen, akkor ezt állíthatjuk false-ra. Jelen esetben azért használtam ezt a logikát mert csak két szintű a fa.

Az expanded értékét a már említett okok miatt false-ra kell állítani.

 

A teljes projekt letölthető a SkyDriveról.

Ha készen vagyunk, nyissunk egy sört Smile.

Written by Morzel

március 5, 2011 at 10:17 de.

Asp.Net, C# kategória

Asp.net–ViewState tárolása Sessionben

leave a comment »

Legnagyobb sávszélesség gyilkosunk egy renderelt html oldalon a viewstate. A viewstate szolgál arra hogy két postback között oldalunk megőrizze szerver controljaink állapotát.
A viewstate a renderelt html-ben egy html input mezőben található meg, melynek típusa hidden. Ez mindössze annyit jelent hogy bár a böngészőben a felhasználó számára nem látható, ettől függetlenül igenis benne van a html forrásunkban és minden egyes postback esetében megutaztatjuk hálózatunkon, akár szeretnénk, akár nem.
A viewstate egy egyszerű login oldal esetében így néz ki:
<input type=”hidden” name=”__VIEWSTATE” id=”__VIEWSTATE” value=”/wEPDwUJNzU5MTcwMDUxD2QWAgIDD2QWCAIDDxYEHgNzcmMFDXN0YXJ0bGFwLmh0bWweBmhlaWdodAUFMjUwcHhkAgUPDxYCHgtOYXZpZ2F0ZVVybAUqaHR0cDovL3BhcGlydXN6LnVuaWRlYi5odS9QYXBpcnVzejI/bGR0PTU0ZGQCBw8WBB8AZR8BBQMwcHhkAgkPDxYCHwIFLGh0dHA6Ly9wYXBpcnVzei51bmlkZWIuaHUvV2ViQXBwVGVzenQ/bGR0PTU0ZGRk2YpyJF6zq4pyHWkXjxV8m57kdk4=” />

Ez már sok fölösleges adatnak tűnik pedig az adott formon mindössze egy login kontrol, néhány label és egy-két linkbutton található. Ez ennél a kicsiny példánál nem zavaró ez a mennyiség de egy nagyobbacska treeview esetében a viewstate mérete akár 1 Mb-nyi is lehet, határ a controllok számától és tartalmától függően a csillagos ég.
Asp.net 2.0-tól kezdve lehetőségünk van  a viestate sessionben történő tárolására. A sessionben való tároláshoz a következőeket kell tenni:
  • Készíteni kell egy basepage-et és minden oldalunkat ebből kell származtatnunk. Ennél ki kell cserélnünk az alapértelmezet PageStatePersistert SessioPageStatePersisterre.
  • A web.configban meg kell adni hogy a ControlState-et is sessionben szeretnénk tárolni.
  • A web.confogban beállítani a sessionPageState historySize-ot. Ez beállítja hogy a lapok előzményeiből mennyinek az állapotát szeretnénk letárolni a Sessionben. A history tárolása tulajdonképpen egy veremben történik, ennek alapértelmezett mérete 9. Amikor ezen a méreten túllépünk, akkor az utolsó elem lesz felülírva. Ha olyat tapasztalunk hogy a postbackek között eltűnik kontrollok értéke akkor nem árt növelni a historySize méretet.

 

Saját basepage készítése SessionBasePagePersisterrel:

– Adjunk új osztályt a porjektünkhöz, ezt származtassuk a System.Web.UI.Page-ből:

BasePage : System.Web.UI.Page

– Írjuk felül a PageStatePersister tulajdonságot a SessionPageStatePersisterrel:

// ViewState a sessionban
private PageStatePersister _persister;
protected override PageStatePersister PageStatePersister
{
    get
    {
        if (null == _persister)
            _persister = new SessionPageStatePersister(this);

        return _persister;
    }
}
// ViewState a sessionban vége

– A projektünkben található oldalakat ebből a  BasePage-ből kell származtatni:

public partial class _Default : BasePage

ControlState a web.configban:

– Adjuk hozzá a web.config system.web szekciójához a következőt:

<browserCaps>
  <case>
     RequiresControlStateInSession=true
  </case>
</browserCaps>

SessionPageState historySize:

– Adjuk hozzá a web.config system.web szekciójához a következőt:

<sessionPageState historySize=40/>

Written by Morzel

február 25, 2011 at 11:14 de.

Asp.Net, C# kategória