Jarda Jirava

Vývojář a architekt řešení postavených na technologii .net framework. Zabývám se jak vývojem webových aplikací za pomoci asp.net, tak také desktopových aplikací winform. Při návrhu řešení a samotném vývoji pak využívám dlouholetých zkušeností se zpracováním obchodní logiky a pravidel aplikací získaných z vývoje komerčních aplikací pro finanční a bankovní instituce.

Microsoft MVP

Microsoft MVP - Client App dev

Poslední příspěvky

22
Jul

Entity framework a IN klauzule

V konferenci o .net na builder.cz se objevil dotaz, jak sestavit SQL dotaz pomocí Entity frameworku, který by vygeneroval na výstupu omezující podmínku IN.

Jelikož v Linq to SQL je toto poměrně triviální řešení a je možné použít extenzní metodu Contains, neváhal jsem a autorovi potvrdil, že obdobně to bude i v případě Entity frameworku. Jenže pak ve mě začal hlodat červík nedůvěřivosti, vždyť to autor dotazu měl správně, tak proč to nejde. Až jsem přišel na to, že v EFv1 nelze použít Contains metodu tak, aby se vygenerovala IN klauzule.

Samozřejmě existuje řešení, kdy je možné provést celý SQL dotaz a až následně v paměti aplikovat spojení, které zajistí omezující podmínku. Je to však řešení nepříliš praktické.

Zkusil jsem tedy chvilku bádat a tady je řešení. Jedná se o to, že klauzule IN je možné reprezentovat také jako spojení jednotlivých hodnot operátorem OR. Pro lepší možnost použití je pak vytvořena extenzní metoda s názvem In a přebírající dva parametry.

public static class EFExtensions {

    private static Expression<Func<TEntity, bool>> GetIn<TEntity, TValue>(
 Expression<Func<TEntity, TValue>> propertySelector,
 IEnumerable<TValue> values) {
        var property = propertySelector.Parameters.Single();
        if ((values == null) || (!values.Any()))
            return e => false;

        var parts = values.Select(value => Expression.Equal(
 propertySelector.Body, 
 Expression.Constant(value, typeof(TValue))));
        var body = parts.Aggregate(Expression.Or);

        return Expression.Lambda<Func<TEntity, bool>>(body, property);
    } 

    public static IQueryable<TE> In<TE, TV>(this IQueryable<TE> source, Expression<Func<TE, TV>> propertySelector, 
params TV[] values) {
        return source.Where(GetIn(propertySelector, values));
    }

    public static IQueryable<TE> In<TE, TV>(this IQueryable<TE> source, Expression<Func<TE, TV>> propertySelector, 
IEnumerable<TV> values) {
        return source.Where(GetIn(propertySelector, values));
    } 
}

Použití této extenzní metody je pak velice jednoduché a demonstruje ji následující případ:

var ids = new int[] {1, 2, 3};
var data = db.TestTable.In(e => e.IntValues, ids).OrderBy(e => e.StringValues);
Publikováno pod: builder.cz , code snippet
25
May

WPF Binding bez codebehind

Celkem zajímavý dotaz padl v konferenci o .net na serveru builder.cz. V krátkosti se jednalo o změnu datového zdroje nabindovaného na ListView při změně vybrané položky jiného ListView.

Varianta codebehind

V dotazu bylo poukazováno na obsluhu události SelectionChanged, ve které chtěl tazatel řešit změnu bindování na jiný deklarovaný zdroj ve Window.Resources, konkrétně různě naplněný XmlDataProvider.

Samozřejmě by toto provázání bylo taktéž možné, ale z vlastní zkušenosti a vývoje wpf aplikací jsem zjistil, že používání codebehind souboru není většinou nutné a je možné vše deklarovat pomocí XAML. Codebehind je tak ve větší míře využíván jen při tvorbě vlastních Control.

Varianta bez codebehind

Tazateli jsem tedy zaslal odkaz na příspěvek [Selecting the Detail Level to View at Runtime in WPF] publikovaný na CodeProject od Josh Smith, který se věnoval bindování různých deklarovaných template dle uživatelského výběru. Principiálně tedy velice podobný problém. Martin to tak neviděl a tak jsem se rozhodl konkrétní případ vytvořit a publikovat. Jelikož je však třeba bližšího komentáře, ponechal jsem si odpověď jako krátké povídání.

Postup vytvoření aplikace splňující zadání

Začal jsem s čistým projektem typu WPF aplikace. První na řadě tak byla deklarace datových zdrojů – XmlDataProviderů a naplnění daty.

Deklarace datových zdrojů

<XmlDataProvider x:Key="products" XPath="root/datas">
            <x:XData>
                <root xmlns="">
                    <datas>
                        <data id="a1">a</data>
                        <data id="a2">b</data>
                        <data id="a3">c</data>
                    </datas>
                </root>
            </x:XData>
        </XmlDataProvider>
        <XmlDataProvider x:Key="a1" XPath="root/datas">
            <x:XData>
                <root xmlns="">
                    <datas>
                    <data>1aa</data>
                    <data>1bb</data>
                    <data>1cc</data>
                    </datas>
                </root>
            </x:XData>
        </XmlDataProvider>
        <XmlDataProvider x:Key="a2" XPath="root/datas">
            <x:XData>
                <root xmlns="">
                    <datas>
                    <data>2aa</data>
                    <data>2bb</data>
                    <data>2cc</data>
                    </datas>
                </root>
            </x:XData>
        </XmlDataProvider>
        <XmlDataProvider x:Key="a3" XPath="root/datas">
            <x:XData>
                <root xmlns="">
                    <datas>
                    <data>3aa</data>
                    <data>3bb</data>
                    <data>3cc</data>
                    </datas>
                </root>
            </x:XData>
        </XmlDataProvider>

Deklaraci datových zdrojů a přiřazení jim příslušných identifíkátorů jsem vložil do Window.Resources elementu tak, aby tyto zdroje byly dostupné v celém objektu okna. Tady musím zmínit, že názvy datových zdrojů, které budou měněny po výběru jsem definoval shodné s hlavním zdrojem dat. Toto samozřejmě není nutné, jde jen o to, mít jasně definovaný převodní můstek pro výběr správného zdroje.

Vzhled aplikace

Následovala deklarace aplikace, zvolil jsem StackPanel, do kterého jsem postupně přidával jednotlivé prvky. Nesmí chybět nějaký nadpisek aplikace, abychom neměli jen holé okno. Následuje pak deklarace prvního ListBoxu – jakožto jednodušší varianty (předka) pro ListView. Tento ListBox je nabindován na hlavní datový zdroj. Zároveň definuje vlastní DataTemplate, který se stará o vizuální zobrazení dat v prvku.

        <ListBox Width="400" Height="200" Background="Honeydew" x:Name="prod">
            <ListBox.ItemsSource>
                <Binding Source="{StaticResource products}" XPath="*" />
            </ListBox.ItemsSource>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock FontSize="12" Foreground="Red">
                      <TextBlock.Text>
                        <Binding XPath="." />
                      </TextBlock.Text>
                    </TextBlock>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

V tomto případě jsem použil expandovaný zápis pro binding i když by se samozřejmě dal použít i inline styl. Druhý deklarovaný ListBox je jen o něco málo složitější, ale ukrývá v sobě hlavní sílu WPF a to MultiBinding a využití IValueConverter resp. IMultiValueConverter.

IMultiValueConverter – srdce bindingu pro řešení zadání

Dříve než sem napíšu i deklaraci druhého ListBoxu, musím se zmínit o srdci celého řešení a to je vytvoření konverteru, který zajistí vyhledání datového zdroje v Resources a jeho navrácení. Pro vyhledání jakéhokoliv pojmenovaného zdroje je možné použít metodu FindResource, případně její rozšířenou variantu TryFindResource lišící se pouze v tom, že nevyvolá Exception v případě, že daný klíč není nalezen v Resources. Pro získání daného zdroje pak potřebujeme získat objekt, který tento zdroj vlastní v tomto případě objekt Window. Zároveň potřebujeme mít k dispozici i vybranou hodnotu prvního ListBoxu.

Jelikož potřebujeme dvě hodnoty, není možné využít jednoduchý IValueConverter, ale musíme použít IMultiValueConverter, který se liší pouze tím, že přebírá do metody Convert pole hodnot.

Jak tedy vypadá kód našeho konverteru:

    public class ResourceKeyConverter: IMultiValueConverter {

        #region Implementation of IMultiValueConverter

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
            var fe = values[1] as FrameworkElement;
            var val = values[0] as XmlNode;
            if ((fe != null) && (val != null)) {
                var resource = fe.TryFindResource(val.Attributes["id"].Value) as XmlDataProvider;
                return resource;
            }
            return null;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
            throw new NotImplementedException();
        }

        #endregion
    }

Jak je vidět, je to velice jednoduché, samozřejmě je třeba si domyslet ještě i další kontroly, které by bylo vhodné provést.

Binding ListBoxu dle výběru

Nyní se tedy můžeme dostat k deklaraci ListBoxu, který bude obsahovat měněná data z různých datových zdrojů. Jedná se o stejný ListBox, který je definován v prvním případě, rozdílem je pouze zmíněný MultiBinding, který předává konverteru vybranou hodnotu prvního ListBoxu a zároveň i objekt, který drží Resources, tedy v tomto případě pojmenované hlavní okno.

        <ListBox Width="400" Background="AntiqueWhite" ItemsSource="{Binding XPath=*}">
            <ListBox.DataContext>
                <MultiBinding Converter="{StaticResource rsKey}">
                    <MultiBinding.Bindings>
                        <Binding ElementName="prod" Path="SelectedItem" />
                        <Binding RelativeSource="{RelativeSource  AncestorType={x:Type Window}}" />
                    </MultiBinding.Bindings>
                </MultiBinding>
            </ListBox.DataContext>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock FontSize="12" Foreground="Red">
                      <TextBlock.Text>
                        <Binding XPath="." />
                      </TextBlock.Text>
                    </TextBlock>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

Důležitým krokem v tomto případě je, deklarace a registrace našeho konverteru do zdrojů tak, aby byl k dispozici pro použití v deklaracích u bindingu. To provedeme rovněž v elementu Window.Resources a specifikujeme pro něj klíč. Taktéž je nutné deklarovat namespace, ve kterém se náš konverter nachází.

<WpfBinding:ResourceKeyConverter x:Key="rsKey" />

Stačí jen spustit

Jsme u posledního kroku a to je spuštění aplikace, pokud jsme postupovali správně, dostaneme požadovaný výsledek. Z předchozího povídání je vidět, že pro toto zadání není nutné používat codebehind a je možné téměř všechnu logiku deklarovat za pomoci XAML a vhodně napsaného konverteru, který může být znovupoužit.

Navržené řešení nemusí být optimální a pouze reflektuje dané zadání. Osobně bych se k danému problému snažil postavit v jiném duchu a využil v hojné míře návrhového vzoru Model-View-ViewModel, který používám při tvorbě WPF aplikací. Na druhou stranu se mi toto řešení jeví jako vhodnější, než obsluhovat události jednotlivých prvků v codebehing a snažit se na ně reagovat.

Projekt ukázkové aplikace ke stažení:

Publikováno pod: builder.cz , code snippet , .net technology , wpf
10
Jul

IQueryable do DataView

Prý nikde není k dispozici popis nebo dokonce kód pro vytvoření DataView z dotazu provedeného pomocí Linq to SQL. Tak jsem se jeden takový pokusil sestavit, byť by si určitě zasloužil ještě nějaké ty úpravy a vylepšení.

DataView ToDataView<T>(this IQueryable<T> query) where T: class

Tak toto je předpis dané extension metody, kterou je možné použít pro vytvoření DataView z dotazu. Celá metoda je velice jednoduchá a jistě ji pochopí každý, alespoń maličko zkušený vývojář. Pro ty ostatní jen malé připomenutí, jak celá metoda pracuje a provádí se převod. Nejdříve však samožná metoda.

using System.Data;
using System.Reflection;
using System.Collections;

public static class DataExtensions {
    public static DataView ToDataView<T>(this IQueryable<T> query) where T: class {
        DataTable dt = new DataTable();
        Type type = typeof(T);
        PropertyInfo[] pis = type.GetProperties();
        foreach (var item in pis) {
            if (!(typeof(ICollection).IsAssignableFrom(item.PropertyType))) {
                DataColumn dc = new DataColumn(item.Name, item.PropertyType);
                dt.Columns.Add(dc);
            }            
        }
        foreach (var item in query) {
            DataRow row = dt.NewRow();
            foreach (var pi in pis) {
                if (!(typeof(ICollection).IsAssignableFrom(pi.PropertyType))) {
                    row[pi.Name] = pi.GetValue(item, null);
                }
            }
            dt.Rows.Add(row);
        }
        return new DataView(dt);
    }
}

Nejdříve je třeba zajistit vytvoření odpovídajících sloupečků. To se provede iterováním přes všechny viditelné vlastnosti třídy s tím, že vynechávám ze seznamu kolekce (EntitySety). Pro jistotu jsem však použil jen rozhraní ICollection, tak aby byly vynechány případné dodatečně definované kolekce.

Následně se již vytvářejí řádky a pomoci Reflection se plní jednotlivé řádky hodnotamy z dotazu.

Posledním krokem je poté již jen samotné vrácení DataView na výstupu z metody.

Publikováno pod: code snippet , Linq , .net technology
21
Feb

RE: ApplicationContext ještě jednou a lépe

Nakonec uveřejňuji následující text do samostatného příspěvku, trošku více jsem se rozepsal na otázku položenou v komentářích k příspěvku ApplicationContext ještě jednou a lépe. A také proto, že autor dotazu na sebe nezanechal kontakt(?) a tak jej nemohu s odpovědí dát lépe vědět.

Skrytí formuláře při startu aplikace

Ač trošku pozdě přicházím s odpovědí, přeci. Property Visible se nastavuje uvnitř vykonávání metody Application.Run() a to vždy na hodnotu true. Tudíž není možné spustit žádný formulář jako neviditelný. Podotýkám, že se stále bavíme o formuláři, nikoliv jen o zobrazení NotifyIcon.

Není však třeba zoufat, i toto má řešení, byť nevede přes ApplicationContext (nebo jsem jej jen nenalezl). V čem vlastně spočívá problém? Ten je v tom, že formulář při všemožném volání metody Hide() na malou chvilku problikne na obrazovce a to i v okamžiku, kdy se budeme snažit přepsat metodu OnShow.

Řešením tak je umístit formulář v události Load na pozice mimo obrazovku a v metodě OnShow jej následně skrýt. Samozřejmě nesmíme zapomenout na to, jej při následném požadavku na zobrazení napozicovat na určené místo. Např. při doubleclick na NotifyIcon. Osobně se mi líbí myšlenka napozicování formuláře na hodnoty

Left = int.MinValue;
Top = int.MinValue;

Snad tyto informace pomohou a dostatečně jsem zodpověděl položenou otázku.

Publikováno pod: code snippet , .net technology
24
Jan

ApplicationContext ještě jednou a lépe

Udělat chybu je celkem lidské, že to však dopadne takto jsem tedy vůbec nepředpokládal. Když jsem nedávno psal o splash screenu a jak jej zobrazit za pomoci třídy ApplicationContext vůbec jsem netušil, jaký to bude mít dopad a že něco není v pořádku.

Chyba není na vašem přijímači

To jediné mě malinko uklidňuje, že chyba není ani na vašem a kupodivu ani na mém přijímači. Tedy samozřejmě v přeneseném slova smyslu (je třeba si za tím představit napsaný kód). A o jakou vlastně chybu se jedná? Při použití SaveFileDialogu a pokusu o přepsání již existujícího souboru by se měl objevit dialog zda chceme soubor přepsat. Místo toho však aplikace vyvolá vyjímku Cannot access a disposed object named "FormSplash". S výpisem tohoto Stack trace:

-----[Core exception]--------------------
at System.Windows.Forms.Control.CreateHandle()
at System.Windows.Forms.Form.CreateHandle()
at System.Windows.Forms.Control.get_Handle()
at System.Windows.Forms.ThreadWindows.Callback(IntPtr hWnd, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.EnumThreadWindows(Int32 dwThreadId, EnumThreadWindowsCallback lpfn, HandleRef lParam)
at System.Windows.Forms.ThreadWindows..ctor(Control parent, Boolean onlyWinForms)
at System.Windows.Forms.ThreadContext.DisableWindowsForModalLoop(Boolean onlyWinForms)
at System.Windows.Forms.ThreadContext.BeginModalMessageLoop()
at System.Windows.Forms.Application.BeginModalMessageLoop()
at System.Windows.Forms.MessageBox.ShowCore(IWin32Window owner, String text, String caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton, MessageBoxOptions options)
at System.Windows.Forms.MessageBox.Show(String text, String caption, MessageBoxButtons buttons, MessageBoxIcon icon)
at System.Windows.Forms.FileDialog.MessageBoxWithFocusRestore(String message, String caption, MessageBoxButtons buttons, MessageBoxIcon icon)
at System.Windows.Forms.SaveFileDialog.PromptFileOverwrite(String fileName)
at System.Windows.Forms.SaveFileDialog.PromptUserIfAppropriate(String fileName)

To je, zjednodušeně řečeno, způsobené tím, že smyčka zpráv se vytvořila nad formulářem FormSplash a přestože došlo následně k přiřazení hlavního formuláře do property MainForm ve třídě ApplicationContext v přepsané metodě OnMainFormClosed, smyčka zpráv se nepředala a zůstala nad již zavřeným formulářem.

Finální řešení

Mohli bychom samozřejmě provádět ještě šílenější obezličky než za chvíli zmíněné řešení, ale proč. Určitě je možné zobrazovat Splash screen v obsluze události Load hlavního formuláře, nebo vymyslet ještě obskurnější řešení, většinou však tato řešení budou poměrně hodně svázána s konkrétním hlavním formulářem a nebude tak možné mít jednu obecnou třídu pro zobrazení Splash screenu.

Navržené řešení je triviální, leč ne úplně čisté a děkovat za něj mohu dobrému pomocníkovi Reflectoru. Je totiž nutné přenastavit interní proměnnou currentForm ve třídě ThreadContext, která je interní třídou v ApplicationContext.

Část kódu, který je tedy přítomen v metodě OnMainFormClosed vypadá následovně a prosím všechny ty, kteří použili mnou navržené řešení, o opravu v jejich kódu:

_main = new FormMain();
Type application = typeof(Application);
Type threadContext = application.GetNestedType("ThreadContext", BindingFlags.NonPublic);
object current = threadContext.InvokeMember("FromCurrent", BindingFlags.Static | BindingFlags. NonPublic | BindingFlags.InvokeMethod, null, null, new object[0]);
FieldInfo currentForm = threadContext.GetField("currentForm", BindingFlags.NonPublic | BindingFlags.Instance);
currentForm.SetValue(current, _main);
this.MainForm = _main;
this.MainForm.Show();

Proč považuji toto řešení za ne úplně čisté je celkem jasné, je využívána reflection, což by samo o sobě vadit nemělo, avšak je nutné uvést textově odkazy na získání interních proměnných, jejichž název se může v příští verzi změnit.

Po provedení této úpravy by se aplikace měla chovat tak jak očekáváme a tak jak jsme ji napsali.

Publikováno pod: code snippet , .net technology
10
Nov

Splash screen a ApplicationContext

Vidět reakci ihned po spuštění programu, je přesně to, po čem touží snad každý uživatel aplikace. Vznikají tak různé splash screeny, které informují uživatele co se právě s aplikací děje. Pro tvůrce aplikace je to něco navíc, co dává ke své aplikaci a přitom by se žádná aplikace neměla bez této úvodní obrazovky objevit. Ano, ano, existují výjimky, ale jestliže aplikace je komplexnější a při startu je potřeba provést náročnější inicializaci, je splash screen nejvhodnějším řešením.

Způsobů jak vytvořit tento úvodní formulář určitě existuje několik, já se chci věnovat jednomu z nich, který považuji za velice elegantní a přímočarý. Určitě však nečekejte, že zde objevíte nádherný formulář, mě zajímalo spíše to, co se skrývá tak říkajíc pod kapotou a jak aplikaci vhodně inicializovat.

Použití ApplicationContext třídy

Jednou z možností jak zahájit zpracování smyčky zpráv je vložení instance třídy ApplicationContext jako parametru do metody Application.Run(). Takto vytvořená instance třídy musí obsahovat odkaz na formulář, který bude sloužit jako context. Při zavření tohoto formuláře poté dojde k ukončení aplikace. To ovšem nemusí platit v okamžiku, kdy je přepsána metoda OnMainFormClosed() a vlastnost MainForm je nahrazena za jinou instanci formuláře, který v ten okamžik převezme context aplikace.

Právě tohoto způsobu můžeme využít k vytvoření splash screenu, inicializaci aplikace a zobrazení hlavního okna aplikace.

Inspirace

Teď určitě někteří mohou namítnout, že takových příkladů je možné najít na internetu dostatek. Což je pravda, ale přesto uvedu své řešení, i z toho důvodu, že jsem na stejné nenarazil a většinou všem takovým příkladům scházelo to hlavní, úvodní inicializace aplikace. Můžete tak najít řešení, která používají timer, vykreslují krásné splash screeny a podobně.

Ač by určitě nebyl problém takovou inicializaci doplnit, jsou zde začátečníci, kterým takovýto návod přijde jistě vhod.

Co budeme potřebovat

Určitě to bude nějaký pěkně vyvedený formulář, který bude sloužit jako splash screen. Jakým způsobem bude takový formulář vypadat nechám na vkusu každého, co však bude zajímavější je jeho jedna případně dvě public metody, které budou aktualizovat stav inicializace a informovat uživatele. Dále to bude formulář hlavní, se kterým bude uživatel aplikace pracovat, ten je v plné režii vývojáře. A v neposlední řadě je to třída, ktérou pojmenujeme třeba SplashApplicationContext a jenž je poděděná od třídy ApplicationContext.

SplashApplicationContext

Tato třída provádí vše co budeme potřebovat k zobrazení splasch screenu a následnému zobrazení hlavního okna aplikace. Zároveň se postará o to, že dojde k potřebné inicializaci aplikace (dynamicky nahrávané moduly, připojení ke vzdálenému serveru a natažení úvodních dat, atd.) a to v samostatném vlákně. Po dokončení inicializace, které může trvat proměnlivě dlouhou dobu, pak dojde k zobrazení hlavního formuláře.

Co je důležité si uvědomit je, že inicializace běží v samostatném vlákně aplikace a tak veškerá volání nad prvky formuláře nebo i formulářem samotným musí být kontrolována a vyvolána z vlákna hlavního (UI).

Třída SplashApplicationContext tak může vypadat následovně:

public class SplashApplicationContext : ApplicationContext
{
  private FormSplash _splash;
  private FormMain _main;
  public SplashApplicationContext () {
    _splash = new FormSplash();
    this.MainForm = _splash;
    // Thread.CurrentThread.Name = "mainThreadUI"; abychom snáze identifikovali v jakém vlákně se nacházíme
    Thread thread = new Thread(new ThreadStart(initialize));
    // thread.Name = "initializeThread";
    thread.Start(); // spustíme inicializaci
  }
}

Po provedení konstruktoru se nastartuje inicializace aplikace v samostatném vlákně, ihned poté dochází k vložení instance contextu do smyčky aplikace - Application.Run(new SplashApplicationContext()) - a zobrazení úvodního formuláře.

V metodě initialize() dochází jednak k inicializaci aplikace a také k aktualizaci stavu, který informuje uživatele o prováděných krocích.

private void initialize() {
  _splash.PerformStep("Nahrání uživatelského profilu");
  // zde je nahrání uživatelského profilu
  _splash.PerformStep("Nahrání doplňkových modulů");
  // nahrajeme nutné doplňkové moduly
  splashClose();
}

Po provedení inicializace, na samém jejím konci dochází k volání zavření formuláře. Proč není volána přímo metoda Close() je bystřejším již jasné, nejsme totiž v hlavním vlákně aplikace a tak bychom se mohli dočkat výjimky. Metoda splashClose(), stejně tak jako metoda PerformStep() tak využívají vlastnosti InvokeRequired a případně vyvolají samu sebe.

private void splashClose() {
  if (_splash.InvokeRequired) {
    _splash.Invoke(new MethodInvoker(splashClose));
  } else {
    _splash.Close();
  }
}

Ještě dříve než skončíme a budeme moci naši skvělou aplikaci spustit a kochat se úvodním formulářem, musíme přepsat metodu OnMainFormClosed().

protected override void OnMainFormClosed(object sender, EventArgs e) {
  if (sender is FormSplash) {
    _main = new FormMain();
    this.MainForm = _main;
    this.MainForm.Show();
  } else {
    base.OnMainFormClosed (sender, e);
  }
}

V metodě nejdříve otestujeme, zda dochází k zavření formuláře představujícího náš splash screen, pokud je tomu tak, instancujeme hlavní formulář a přiřadíme ho proměnné MainForm a formulář zobrazíme, tím získá/převezme tento hlavní formulář kontext aplikace. V opačném případě dojde k ukončení aplikace.

Máme hotovo

Tím máme úkol splněn a můžeme aplikaci s dobrým pocitem předat uživatelům, ti pak budou aplikaci vnímat o něco lépe.

Publikováno pod: builder.cz , code snippet , .net technology
27
Oct

Odhalujeme .net - Deserializace - řešení

Aneb, jak se také nedočkat správné odpovědi. Zřejmě jsem zvolil velice záludnou otázku a přestože si ji přečetlo jistě více jak 70 lidí, žádný se nepokusil o její zodpovězení.

Nebudu tedy více napínat všechny ty, kteří čekají na správné řešení. Problémem, na který jsem narazil, bylo zpracovávání tzv. bílých znaků, whitespacehandling. Jak už jsem se zmínil, bylo potřeba serializovat a zpětně deserializovat malé konfigurační objekty a mezi jednotlivými vlastnostmi se objevila i samotná mezera.

Ta se ovšem při použití vstupního parametru typu StringReader nepřevedla zpět. Tudíž bylo nutné explicitně vytvořit XmlTextReader a teprve ten použít pro deserializaci xml řetězce. Pokud se podíváte blíže na implementaci metody Deserialize() např. pomocí Reflectoru, zjistíte, že se interně vytváří právě XmlTextReader a nastavuje se mu property WhitespaceHandling. A právě v nastavení této property ležel ten hlavní důvod a úskalí celého řešení.

Takže cena pro tuto chvíli zůstává pro mě.

Publikováno pod: code snippet , .net technology
10
Oct

Přístup k datům v .net 2.0

V předchozích příspěvcích, kde jsem v praktických ukázkách používal přístup k datům, jsem se zmiňoval o správné konfiguraci datového spojení. V této oblasti došlo k poměrně významným změnám oproti předchozím verzím .net frameworku.

Obecný přístup k datům

Pro obecný přístup k datům v předchozích verzích .net frameworku bylo nutné používat interface. Vznikla proto řada obecných řešení, jedním z nich byla i komplexní Enterprise library, případně několik dalších, většinou uvedených na CodeProject.com, která se snažila odstínit konkrétní implementaci a přístup k datové vrstvě a rozhodování ponechat na konfiguraci. Taková řešení byla dobrá, převážně založená na design pattern a to především konkrétně na vzoru Abstract factory, případně Factory metod. Jak už to však bývá, bylo zde jedno ale a to konkrétně generování výjimek. Tento neduh nespočíval ani tak v samotné implementaci, ale již v .net frameworku. A pokud se nepoužilo některé více komplexní řešení, které by jednotlivé výjimky zapouzdřilo, bylo velice obtížné takové Exception obecně odchytit. Jednotlivé výjimky totiž byly závislé na implementaci a tak nebylo možné odchytit např. DbException, která neexistovala, ale musela se zachytávat přímo SqlException, OleDbException atd.

S příchodem .net 2.0 se mnohé změnilo a to k dobrému.

Je dobře známé, že architektura .net frameworku využívá a je postavena na návrhových vzorech. A i pro práci s datovými zdroji bylo těchto vzorů hojně využito. Tím prvním, se kterým se setkáte je tak Provider pattern, který byl použit pro získání příslušné Abstract factory třídy, konkrétně potomka DbProviderFactory.

Získání DbProviderFactory

Abychom mohli přistupovat k datům v abstraktní rovině, musíme také používat abstraktní objekty jejichž konkrétní implementaci získáme až za běhu programu pomocí konfigurace. Tímto "tvůrcem", který nám zprostředkuje potřebné objekty je právě DbProviderFactory třída, kterou dostaneme při volání statické metody DbProviderFactories.GetFactory(). Po získání instance tak můžeme od této instance žádat voláním příslušných metod o vytvoření potomka pro DbConnection, DbCommand, DbCommandBuilder, DbDataAdapter nebo DbParameter.

Třída DbProviderFactories má pak ještě druhou statickou metodu nazvanou GetFactoryClasses() o které se ještě zmíním.

DbConnection, DbCommand a také DbException

Nejen zmíněné objekty tvoří abstrakci - Bridge - pro přístup k datům. I další objekty umožňující obecný přístup k datům, které je možné získat pomocí volání metod nad získanou instancí objektu DbProviderFactory, a jenž jsem zde neuvedl, jsou umístěny v System.Data.Common namespace. V tomto namespace jsou vedeny také výčty, jenž se mohou s těmito objekty použít, např. výčet datových typů.

Určitě bych neměl opomenout zmínit, že existuje také obecná databázová výjimka DbException a tak se přístup k datům může stát téměř univerzálním.

Dříve než se podívám na to, jak to celé tedy správně nakonfigurovat, neopomenu ještě jedno malé upozornění. Týká se parametrů, které se předávají do příkazů. Při vytváření parametrů se totiž nepřidává žádný prefix a tak tato operace je na vývojáři, který musí vědět, jaký symbol prefixu pro daný databázový stroj použít. Tímto se tak úroveň abstrakce malinko snižuje a musí být zvoleno vhodné řešení pro tvorbu parametrů.

Použití ConnectionStringSettings a .config soubor

K vytvoření správné instance DbProviderFactory potřebujeme znát provider name. Jeho přesný název je uveden u každé definice příslušné Factory v souboru machine.config a to v atributu invariant. Tento název poté použijeme v konfiguraci connectionstringu v atributu providerName, abychom jej následně předaly do volání metody GetFactory().

Sekce connectionStrings tak může vypadat následovně:


 

Samotný prováděcí kód pro získání příslušné faktory a vytvoření connection bude následující:

ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings["MainConn"];
DbProviderFactory factory = DbProviderFactories.GetFactory(settings.ProviderName);
using(DbConnection connection = factory.CreateConnection()) {
}

na základě výše uvedeného nastavení vytvoří spojení na SQL Server databázi

K jakým datovým zdrojům tedy máme přístup ihned po instalaci .net frameworku? Jsou to následující implementace:

  • Sql server - System.Data.SqlClient
  • Oracle - System.Data.OracleClient
  • OleDb - System.Data.OleDb
  • Odbc - System.Data.Odbc
  • Sql server CE - Microsoft.SqlServerCe.Client

jež jsou definovány v souboru machine.config. Samozřejmě nám nic nebrání v tom, napsat si vlastní Factory, která bude zprostředkovávat - tvořit - objekty pro přístup i k jiným databázím, např. FireBird nebo MySQL. Seznam všech dostupných implementací je k dispozici též za běhu programu po volání statické metody DbProviderFactories.GetFactoryClasses().

Měníme datové úložiště

Chceme-li tedy následně změnit datové úložiště, jediným zásehem tak může být záměna připojovacího řetězce a změna hodnoty v atributu providerName, která bude reflektovat nově používanou databázi a s tím nově používanou implementaci k tvorbě objektů zajišťujících přístup k danému datovému zdroji.

Na samotný závěr je třeba uvést, že z důvodu použití abstrakce se přichází o specifické možnosti, které jsou dostupné pro daný databázový stroj. I zde tedy platí, že každé plus má své minus.

10
Oct

Odhalujeme .net - Deserializace

O deserializaci je toho možné napsat velice mnoho. Jsou však situace, kdy si člověk říká, že má vše pod kontrolou a ono ejhle, poměrně triviální věc a najednou je vývojář v rozpacích. Něco takového se mi přihodilo poměrně nedávno, kdy jsem řešil poměrně zajímavý projekt na zpracování a převod datových struktur. Zjednodušeně řečeno, takové menší SSIS.

Deserializace ze stringu

V uvedeném řešení existovalo několik malých tříd, které sloužily jako konfigurace pro jednotlivé tasky a jež měl možnost uživatel přes vizuální prostředí editovat. Hodnoty z těchto tříd potom byly serializovány a uloženy do databáze k danému tasku. To je pro tento případ nepodstatné. Co je však podstatnější, že bylo nutné zpětně tyto třídy obnovit z takto uložených řetězců a předat do aplikace. Ano, to co jsem zde popsal bylo uschováno a bylo použito vzoru Memento. Ale i ten musí být nějakým způsobem implementován.

Toto je útržek kódu pro deserializaci

XmlSerializer xs = new XmlSerializer(creationType);
StringReader reader = new StringReader(setting);
object result = xs.Deserialize(reader);

který byl použit v prvotní fázy a jenž byl funkční. Jistě namítnete, že nevidíte nic neobvyklého a že tímto způsobem také provádíte deserializaci.

Nakonec je z toho hádanka pro Vás

Teď by možná mohla následovat otázka, co je na této části kódu špatně a v jaké situaci se nedočkáme očekávaného výsledku? Pominu to, že může dojít ke změně interface serializovaného objektu, což se nestalo. Úspěšně však zapracovalo interní testování.

Znáte tedy odpověď? Buďte prvním kdo se o ní podělí.

Publikováno pod: code snippet , .net technology
2
Oct

Odhalujeme FlowLayoutPanel a TableLayoutPanel

Tentokrát se ve svém odhalování .NETu zaměřím na dva nové panely v .NET 2.0 a to konkrétně na FlowLayoutPanel a TableLayoutPanel. V obou případech se jedná o nevizuální prvky, které slouží jako container pro umístění dalších winform kontrolů na formulář.

TableLayoutPanel

Jestliže se zabýváte vedle tvorby winform aplikací také vývojem web aplikací určitě znáte tabulkový layout. A právě pro tvorbu tabulkového layoutu je určen TableLayoutPanel, jak ostatně již vyplývá z jeho názvu. Můžete tak do panelu přidávat řádky nebo sloupečky, a jednotlivé buňky seskupovat. Každá buňka pak slouží k vložení jednoho jediného kontrolu.

I mě se tento způsob umístění prvků na formulář líbil a našel jsem pro něj uplatnění ve své aplikaci, ale malinko mě překvapil. A než jsem stačil nalézt chybu, uběhla nějaká ta hodinka. Co se vlastně stalo? Jak už to tak bývá, nejdříve jsem si s daným panelem hrál a přidával a odebíral sloupčky a řádky. Poté co jsem si řekl, že znám možnosti designera, smazal jsem všechny řádky a začal prvky vkládat v runtime. Účelem mého snažení totiž bylo vkládání jednotlivých kontrolů na základě definované a načtené konfigurace při běhu aplikace. Jaké však bylo mé překvapení, když se zobrazilo nejdříve prázdné místo a vložené kontroly se nacházely až někde hluboko pod okrajem formuláře.

Ano, přestože jsem vymazal sloupčky i řádky pomocí grafického designeru. Tyto zůstaly zachovány v inicializaci formuláře/panelu. Než mě však napadlo se podívat na počet vytvořených řádků, uvažoval jsem o úplně jiných chybách ... chjo. A tak ponaučení pro příště, překontrolovat i formular.Designer.cs soubor, ne vždy se designeru povede vše odstranit.

FlowLayoutPanel

O tomto prvku již jenom krátce. Jelikož také slouží jako kontainer pro další kontroly a to tím způsobem, že umožňuje jejich pozicování za sebe a to jedním z několika možných způsobů jejich toku. Opět je zde analogie z web aplikací a tentokráte pro beztabulkový layout.

Následující, je ale spíše pro oba dnes zmíněné panely. Je známé, že pokud umístíme na formulář nebo usercontrol některé prvky (většinou se jedná o složitější prvky jako je StripControl) nelze je v poděděných formulářích nebo usercontrolech editovat a to ani po nastavení jejich visibility na public nebo protected.

Já jsem tuto funkcionalitu však potřeboval využít a tak jsem se pokusil poodhalit opět něco z .netu a výsledek se dostavil. Vytvořil jsem si nový kontrol, který byl poděděn z FlowLayoutPanelu a přidal mu atribut, konkrétně DesignerAtribut pro Panel.

[Designer("System.Windows.Forms.Design.PanelDesigner, System.Design")]

S touto úpravou jsem pak měl zpřístupněn panel i ve zděděných formulářích a mohl tak vesele designovat dataentry formuláře. A po přečtení tohoto příspěvku to dokážete i vy.

Zamyšlení k vylepšení

Ač nejsem velkým příznivcem naklikávání aplikací, občas se rychlý návrh formuláře pro editaci záznamu hodí a k tomuto účelu má Visual Studio 2005 poměrně pěkný designer. Co bych však uvítal je možnost zvolit si, že chci takto vygenerovaný formulář umístit právě do TableLayoutPanelu. Přeci jen mi přijde, že v něm jsou prvky lépe pozicovány a celý formulář pak vypadá úhledněji.

Publikováno pod: code snippet , .net technology
22
Sep

Jednoduchý web sql editor

Vytvořil jsem si jednoduchý sql editor. Záměrně zmiňuji, že se jedná o jednoduchý webový sql editor, který umí pouze to základní, co jsem požadoval. A z jakého důvodu jsem se rozhodl o něm napsat? Myslím si totiž, že podobný editor může být pro někoho stejně užitečný jako je pro mě.

SqlExpress na webhostingu

Tím hlavním důvodem je to, že pokud stejně jako já máte hosting na kterém je provozován SQL EXPRESS 2005 a používáte jej pomocí AttachDBFilename není dostupný žádný nástroj, jak se k databázi připojit. A to mě dovedlo k tomu, že jsem napsal tento velice jednoduchý web sql editor.

V rozbalovacím seznamu je možné vybraz jednu z dostupných databází nakonfigurovaných pomocí connectionStrings. Do připraveného textového pole je pak možné vkládat jakékoli sql dotazy (SELECT, UPDATE atd.) Není problémem vložit i několik SELECT dotazů za sebe a ty spustit společně. Výsledek se pak zobrazí ve formě resultsetů uspořádaných do tabulek.

Jednotlivé příkazy jsou uchovány v historii a je tak možné se k nim jednoduše dostat a provést jejich spuštění znovu.

Architektura

Architektura této aplikace je velice jednoduchá. Jelikož jsem si chtěl vyzkoušet AJAX Pro, použil jsem k přenosu dat z/na server výhod AJAX technologie. Tím se mi také podařilo částečně zmenšit objem přenášených dat, neboť se přenáší skutečně jen data a nikoli i jejich reprezentace.

Jelikož jsem potřeboval zajistit výpis dat až na klientovi (tedy v prohlížeči) jako rozumným řešením mi přišlo zvolit XSL transformaci. Ta na základě definice XSD vytvoří tabulku a vypíše data. Díky tomuto řešení jsem se tak poměrně elegantně vypořádal se zpracováním NULL hodnot, a určitě by bylo možné i lépe formátovat tabulku na základě datových typů - tato funkcionalita však implementována zatím není. Malým mínusem, který mi však nevadí je provázanost na IE a instancování msxml2.domdocument, pokud někdo ví, jak toto vytvořit univerzálně má možnost nechat řešení v komentářích.

Vzhled aplikace prodává, o tom jsem již psal, a tak jsem se rozhodl osladit i tuto aplikaci pověstnou třešničku na dortu a použít nifty corners od Alessandro Fulciniti. (Díky) Nic Vám však nebrání v tom, změnit si vzhled dle vlastních potřeb.

Zabezpečení

Určitě by bylo velice nemilé, pokud by k takovéto aplikaci měl přístup každý návštěvník. Samozřejmě by bylo možné řídit přístup pomocí oprávnění v souboru web.config, avšak to je nedostatečné v případě použití aplikace jakou je např. Community server, na níž je provozován tento blog. Proto je přístup na stránky řízen pomocí konfigurovatelného security managera. V případě, že potřebujete řídit specificky přístup k editoru, postačí implementovat rozhraní IEditorAuthorize z assembly JiravaNet.Data.SqlEditor.Authorize a napsat vlastní implementaci. V sekci authorizeManager souboru web.config poté tuto implementaci uvést a nakonfigurovat. Přístup k editoru má v mém případě pouze CS administrátor a při přístupu z localhost není autorizace prováděna vůbec.

Rozšíření

Jestliže se Vám bude zdát, že by tento jednoduchý web sql editor mohl umět více, nebojte se svoje náměty zamechat v komentářích, pokud budu mít chvilku, pokusím se je implementovat a rozšířit editor o další důležité schopnosti. Zdrojové kódy, byť na nich není nic převratného, mohu poskytnout na vyžádání.

Zdroje

Web SQL editor ke stažení.

Update

Samozřejmě je možné s tímto editorem použít i jakoukoli jinou databázi, nejen SQL EXPRESS 2005, která bude správně konfigurována.

20
Sep

Uchování ViewState mimo asp.net stránku

O tom, že protokol http je bezstavový je jeho vlastnost se kterou se musí vypořádat nejeden vývojář webových aplikací. Microsoft přišel v asp.net s tím, používat pro uchování stavu požadavků mezi jednotlivými requesty klienta, ViewState. ViewState je ve výchozím nastavení webového projektu povolen a jeho uchování se děje na základě vloženého input prvku typu hidden do renderované HTML stránky.

Tento způsob uchování ViewState není jediný možný a již ve verzi asp.net 1.x byla možnost jej za pomoci přepsání dvou metod změnit. ASP.NET 2.0 však jde ještě dál a poskytuje přímo abstraktní třídu PageStatePersister, ve které stačí přepsat dvě metody pro uložení a načtení ViewState a ControlState a stav uložit dle návrhu aplikace. V tomto článečku ukáži, jak takový stav uložit do databáze.

Pro napsání tohoto příspěvku, s poměrně triviálním kódem, jsem se rozhodl z toho důvodu, že se opět rozbouřily vody kolem asp.net a jeho renderování výstupu HTML na klienta. Nejsem si jist, zda to rozpoutal Borber s uveřejněním své bakalářské práce, na kterou reagoval Jakub Vrána. Nebo to bylo až poté, co se DGX pozastavil nad výroky Marcuse, a kdy jsem se do debaty vložil také osobně?

Hnedka na úvod musím říci, že ViewState využívám, snažím se však o to, aby byl využívám účelně a jeho velikost zbytečně nebobtnala. Proto je vždy důležité zvolit správné UI kontroly a v případech kdy to jde, zakázat ViewState zcela. Jelikož píši z převážné většiny intranetové aplikace, nebráním se ViewState využívat. Avšak přišlo mi velice neefektivní jej přenášet při každém požadavku na klienta a zase zpět. Také proto jsem zvolil uchování stavu na serveru a to přímo v databázi - přeci jen v aplikacích je těch dotazů do db několik a jeden dotaz navíc je téměř zanedbatelný.

Vlastní implementace

Důležité je říci stránce, aby používala náš vlastní persister. V případě, že používáte vlastní poděděnou třídu Page jako výchozí pro všechny ostatní, což z vlastní zkušenosti doporučuji, je postačující přepsat property PageStatePersister

protected override PageStatePersister PageStatePersister {
 get {
  return new DbPagePersister(Page, "connectionStringKey");;
 }
}

Jestliže nemáte stránky navrženy výše uvedeným způsobem, nemusíte ještě zoufat. Můžete využít PageAdapter, a obdobně zde přepsat metodu GetStatePersister(), následně pomocí souboru .browser tento adaptér pro generování stránek přiřadit.

Dříve však než můžeme zaregistrovat vlastní persister, musíme si jej vytvořit. Vytvoříme tedy novou třídu a podědíme ji od abstraktní třídy PageStatePersister. Zde přepíšeme dvě metody pro uložení Save() a načtení Load() stavu. Předpokládám, že každý umí uložit data do databáze a tak tento krok přeskočím, nicméně je v přiložené ukázce implementován. Co je však důležité, jakým způsobem bude identifikována stránka, abychom mohli načíst její stav zpět. To je zajištěno pomocí registrace hidden pole a přiřazení unikátního řetězce Guid. Tento je jako jediný odeslán na výstup stránky a při zobrazení zdrojového kódu jej zde naleznete.

Pod čarou

Pokud se podíváte na výsledný zdrojový kód, možná vás překvapí, že se zde vyskytuje hidden pole nazvané __VIEWSTATE avšak je prázdné. Toto pole se mi však nepodařilo využít pro uložení jednoznačného identifikátoru, stejně tak se mi nepodařilo pole odstranit z finálního výstupu. Tím ovšem neříkám, že to ve výsledku nelze, ale o tom až příště.

Zdroje

Ukázka ke stažení.

Publikováno pod: code snippet , .net technology