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

code snippet

10
VII

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: .net technology , code snippet , Linq
21
II

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: .net technology , code snippet
24
I

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: .net technology , code snippet
10
XI

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: .net technology , builder.cz , code snippet
27
X

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: .net technology , code snippet
10
X

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.