Mar.23

Mar.17

Visual Studio 2003 installeren na Visual Studio 2005

Onlangs had ik het probleem dat ik toch nog Visual Studio 2003 nodig had nadat Visual Studio 2005 al was geinstalleerd. Simpelweg VS 2003 CD in de drive en installeren werkt niet. Na het installeren van de prerequisites blijft de setup zeuren om de installatie van… je raadt het niet… de prerequisites.

De oplossing vond ik op het blog van Mark Fletcher. Het komt er op neer dat je de setup start vanaf de commandline. Dus:

  • Start Menu
  • Uitvoeren
  • “cmd”
  • Navigeer naar de CD-ROM/DVD drive (dus: [cddrive]:)
  • Navigeer naar de directory setup (dus: “cd setup”)
  • setup.exe /NO_BSLN_CHECK
  • Console1.JPG

    Nu wordt de prerequisites check overgeslagen en de setup zoals gewenst uitgevoerd.

    Mar.14

    Generieke database Membership- en RoleProvider

    Tja, handig zo’n generieke datalaag (zie: SQL & Objects in the mix, een Object Relational Mapper (ORM)) maar dan moet je er in de praktijk wel iets mee gaan doen. Welnu, hier een praktijkvoorbeeld voor de toepasbaarheid van de generieke ORM.

    VisitorTracker.pngOp dit moment ben ik bezig met een zogenaamd pagecounter projectje (vrijetijdsbesteding). Niet zo maar een ding, maar een echte tracker die probeert zoveel mogelijk gegevens te verzamelen van een willekeurige bezoeker. Daar binnenkort meer over. Echter naar aanleiding daarvan had ik een beveiligd login gedeelte nodig. Dan komt al gauw de .NET MembershipProvider om de hoek kijken. Ik dacht weet je wat, misschien leuk om eens een MembershipProvider te bouwen op basis van de bovengenoemde OR Mapper.

    De OR Mapper is gebaseerd op het providerfactory model in .NET. Dit betekent praktisch dat je ergens in bijv. een configuratiebestand kan vastleggen welke databaseprovider je wilt gebruiken (System.Data.SqlClient, System.Data.OleDb, System.Data.Odbc etc…) met welke connectionstring en vervolgens kan je met deze gegevens je objecten opslaan en ophalen uit elke willekeurige database, mits je hierin natuurlijk wel even vooraf de tabellen juist definieert.

    De hier beschreven (en te downloaden) membership- en roleprovider hebben als voordeel dat ze niet op een bepaalde databaseprovider of database zijn gebaseerd maar generiek inzetbaar zijn voor elke willekeurige database van MySQL tot SQL Server. Dit is dus duidelijk een verbetering ten opzichte van de membership- en roleprovider beschreven in een eerder blog artikel. De providers zijn bruikbaar met ASP.NET configuratie tool zodat rollen en gebruikers kunnen worden aangemaakt via deze Microsoft interface (Menu: Project > ASP.NET Configuration). Bovendien zijn er in de toolbox een aantal controls te vinden die direct overweg kunnen met deze providers zoals de Login control.

    Het maken van een Provider is een vrij simpele aangelegenheid. Er dient een class te worden gecreeerd die erft van het juiste abstracte providertype. Voor de membershipprovider is dit “MembershipProvider” en de roleprovider is dit “RoleProvider”. Vervolgens dienen simpelweg de abstracte methoden geimplementeerd te worden. In de MSDN documentatie is de benodigde informatie te vinden over de te implementeren methoden.

    Om de providers te kunnen gebruiken dient de web.config van de ASP.NET 2.0 applicatie als volgt te worden gedefinieerd (voorbeeld):

    <configuration>
    <system.web>
    <membership defaultProvider="ORMMembershipProvider">
          <providers>
            <clear/>
            <add name="ORMMembershipProvider" type="snoei.net.web.ORMMembershipProvider" ConnectionString="DRIVER={MySQL ODBC 3.51 Driver};SERVER=localhost;DATABASE=visitortracker;USER=root;PASSWORD=root;" Provider="System.Data.Odbc" ApplicationName="VisitorTracker"/>
          </providers>
        </membership>
        <roleManager enabled="true" defaultProvider="ORMRoleProvider">
          <providers>
            <clear/>
            <add name="ORMRoleProvider" type="snoei.net.web.ORMRoleProvider" ConnectionString="DRIVER={MySQL ODBC 3.51 Driver};SERVER=localhost;DATABASE=visitortracker;USER=root;PASSWORD=root;" Provider="System.Data.Odbc" ApplicationName="VisitorTracker"/>
          </providers>
        </roleManager>

    Onderaan dit artikel vindt je een link waarmee de source en binaries van de membership- en roleprovider kunnen worden gedownload. In de zip zitten de volgende onderdelen:

  • snoei.net.utilities.data.orm – De laatste versie van de OR Mapper
  • snoei.net.web – De assembly die zowel de membership- als roleprovider bevat.
  • web.config – Voorbeeld configuratiebestand voor het configureren van de beide providers.
  • Sources – Uiteraard de source code.
    (Voor de duidelijkheid snoei.net.web heeft dus een referentie nodig naar snoei.net.utilities.data.orm)
    De providers zijn enkel getest met MySQL maar zouden theoretisch uit de voeten moeten kunnen met elk soort database die wordt ondersteund door .NET. Mocht je problemen constateren dan hoor ik dat graag (en natuurlijk ook als je het fantastisch vindt werken ;-))

    Download RoleEnMembershipProvider.zip, Download MySQL tabelstructuur

  • Feb.12

    Developer Days 2007

    dd20071.jpg Zoals elk jaar ook dit jaar weer de Developer Days. De Developer Days 2007 gaan plaatsvinden op woensdag 13 en donderdag 14 juni in de RAI in Amsterdam. Het evenement wordt gecombineerd met het ReMix event, een evenement dat speciaal bedoeld is voor web developers en designers.

    Meer info kunnen je vinden op het blog van Arie Leeuwesteijn (Microsoft Developer Advisor).

    Feb.10

    SQL & Objects in the mix, een Object Relational Mapper (ORM)

    Object Relational Mapping, als je geen idee hebt wat dat inhoudt lees dan door. In onderstaande artikeltje wil ik ingaan op een OR Mappertje dat ik onlangs zelf heb gebouwd. Ik ben echt heel benieuwd wat jij als lezer van de oplossing vindt. Dus… REAGEER!

    Wat is een ORM?

    Object Relational Mapping is kort weg gezegd een manier om objecten via een transformatieslag in een database op te slaan (manipuleren) en weer op te halen. De presentatie van het object binnen een database is dan een “gewoon” record met velden en waarden. Wordt via de mapper een record opgehaald dan vertaald dat zich in een object binnen je programmacode.

    Aangezien een ORM juist het transformatiestuk voor zijn rekening neemt houdt dit in dat men nauwelijks nog SQL statements hoeft te schrijven voor de select, insert, update en delete acties. Men kan dus tegen een OR Mapper zeggen Save(Object); en het object wordt direct opgeslagen. Dit gebeurd weliswaar onder water middels automatisch gegenereerde inserts of updates, maar daar merk je als ORM gebruiker maar weinig van. Zie een OR Mapper dus als een soort generieke datalaag.

    Een voorbeeld van een veelgebruikt (open source) OR Mapper is NHibernate.

    Het belangrijkste waar het om draait binnen ORM software is het configureren van transformatiegedrag. Dit is uiteraard ook het meest tijdrovende gedeelte van het opzetten van een goed functionerende ORM datalaag. Al kost het minder tijd dan het schrijven van elk specifiek SQL statement voor alle mogelijke acties zoals in “old-fashion” code, maar toch…

    Uitgangspunten

    NHibernate bijvoorbeeld (maar corrigeer me als ik het mis heb) eist voor elke entity (data-class) een Xml-bestand met transformatieparameters. Dat is jammer, in de meeste gevallen hebben entities een standaard transformatiegedrag waarbij eigenlijk geen configuratie nodig zou moeten is. Het optuigen van een Xml-bestand vindt ik dan zonde van de tijd en een onnodige ballast (extra bestanden). Dat wilde ik dus niet en heb nu zelf iets gebouwd (in +/- 8 uur) wat voldoet aan voor mij belangrijke specificaties.

    Dat NHibernate op bovengenoemde manier is ingericht is overigens geen verwijt. NHibernate is eigenlijk het zusje van Hibernate wat van origine een Java ORM pakket is. NHibernate is dus niet volledig gebaseerd op de mogelijkheden van het .NET framework.

    Veelzijdig
    Het kunnen communiceren via zo veel mogelijk technologieen met zoveel mogelijk databases. Dus ondersteuning voor OleDb, Odbc, Sql(Client) en als databronnen, flat-files, SQL Server, Access, MySQL, SQL Compact Edition etc…

    Simpel en accuraat
    De transformatie moet volledig via code te regelen zijn en in standaardsituaties nauwelijks extra code vereisen. Alles wat in code te regelen is, is later namelijk ook weer in een configuratiebestand te stoppen. De keuze is aan de ontwikkelaar.

    Flexibel en krachtig
    Ik wil gebruik kunnen blijven maken van SQL waar ik dat nodig vindt. SQL blijft tot nu toe (LINQ buiten beschouwing gelaten) de meeste krachtige zoekmethode binnen een database (welke anders?).

    Caching
    Het mappen van objecten van en naar DataTables in plaats van een DBMS is natuurlijk een handige feature. Bijvoorbeeld bij het bouwen van offline-clients of simpelweg het willen cachen van gegevens om de belasting van een DBMS te beperken.

    De mapper die hieronder wordt beschreven en ter download wordt aangeboden voldoet aan bovenstaande voorwaarden.

    De oplossing

    De basis van de ORM zijn de klassen DataTableMapper en DbMapper. DataTableMapper klasse maakt het mogelijk objecten objecten van en naar een DataTable te mappen. De DbMapper maakt gebruik van de DataTableMapper en verzorgt het mappen van objecten van en naar een DBMS (DataBase Management Systeem kortweg database). Beidde klassen zijn generics en hun gedrag kan dan ook exact worden aangepast op de te mappen klassen. Aan elk wordt de te mappen klasse meegegeven.

    Veelzijdig
    Het geheel is gebaseerd op de System.Data.Common namespace ofwel de DbProviderFactory. Dit zijn een verzameling klassen die het mogelijk maken data access objecten te creeeren onafhankelijk van de gekozen provider (System.Data.OleDb, System.Data.SqlClient, System.Data.Odbc etc…).

    Een voorbeeld class die we gaan gebruiken.

    Simpel en accuraat
    Om te beginnen met de ORM moet er voor de te mappen class een mapper worden aangemaakt.

    public DbMapper(String Provider, String ConnectionString, String PrimaryTableName);

    public static DbMapper PersonMapper = new DbMapper("System.Data.SqlClient", ConnectionString, "tblPerson_per");

    Bovenstaande code creeert een mapper voor de Person klasse. De persoon wordt opgeslagen via de SQL Server provider naar een de ConnectionString opgegeven database. De betreffende tabel heet “tblPerson_per” maar kan ook achterwegen worden gelaten als de tabel dezelfde naam heeft als de class.
    In dit voorbeeld is er vanuit gegaan dat de primary key van de genoemde tabel automatisch kan worden achterhaald (dat is in bijna alle gevallen zo). Verder wordt er vanuit gegaan dat de propertynamen van Person exact overeenkomen met de veldnamen in de tblPerson_per tabel. Wil je van deze voorwaarden afwijken dan kun je een andere constructor gebruiken waar deze gegevens op maat aan door kunt geven.

    Het ophalen van een lijst met alle in de tabel beschikbare personen (List) kan met de volgende code:
    PersonMapper.GetAll("SELECT * FROM tblPerson_per");
    Je kunt je voorstellen dat elke variatie mogelijk is.

    Wil je manipulaties uitvoeren zoals, delete, update en insert dan kan dat met de volgende methoden:

  • Delete(Person p) – verwijderd een persoon uit de database
  • Insert(Person p) – voegt een persoon toe in de database
  • Update(Person p) – werkt een persoon bij in de database
  • Save(Person p) – voert een insert of update uit in de database afhankelijk of het object al aanwezig is.
  • Flexibel en krachtig
    Voor de manipulaties worden standaard commands gebruikt deze zijn via properties van de betreffende mapper benaderbaar en aan te passen naar eigen wens. Hiervoor heb zijn er de volgende properties beschikbaar:

  • UpdateCommand – Wordt gebruikt voor het bijwerken van een record.
  • InsertCommand – Wordt gebruikt voor het toevoegen van een record.
  • DeleteCommand – Wordt gebruikt voor het verwijderen van een record.
  • ExistsCommand – Wordt gebruikt bij het bepalen (in de Save methode) of een Insert of Update moet worden uitgevoerd.
  • Naast het aanpassen van de commands is het mogelijk om voor of na het uitvoeren van een command via events extra acties te laten uitvoeren. Hiervoor is de op dat moment actieve de transaction en connection beschikbaar.

    Naast deze functies zijn er nog een aantal factory methoden beschikbaar zoals het aanmaken van een connection, command, dataadapter etc… Deze methoden retourneren het juiste type op basis van het opgegeven providertype. Is het providertype “System.Data.SqlClient” dan zal de factorymethod een SqlConnection, SqlCommand of SqlDataAdapter retourneren. Verder is er nog een factory method waarmee op basis van de propertyname een parameter kan worden gecreeerd die grotendeels automatisch wordt voorzien van de juiste gegevens (ColumnSource, DbType, PlaceHolder, etc…). Afhankelijk van de provider wordt vastgesteld wat bijvoorbeeld de Sql statement parameter placeholder moet zijn “?” of “@parametername”.

    Caching
    Om caching te bewerkstelligen moet er een DataTableMapper voor de Person class worden aangemaakt. Dit gaat alsvolgt:
    DataTableMapper PersonCache = PersonMapper.GetDataTableMapper(string.Format("SELECT * FROM {0}", PersonMapper.PrimaryTable));
    Dit resulteerd in een class waaraan Person object mee kunnen worden gemanipuleerd op dezelfde manier als de DbMapper. Er is echter 1 verschil. De manipulaties worden in een onderliggende DataTable opgeslagen. Functies als Save, Delete, Update, Insert etc.. zijn beschikbaar. De GetAll method werkt in vergelijking tot de DbMapper.GetAll method met een RowFilter (zoals bij een dataview) in plaats van een Sql statement. Uiteindelijk kan na alle manipulaties de inhoud van de DataTableMapper PersonCache worden opgeslagen in de database via de PersonMapper (DbMapper) met de methode PersonMapper.CommitDataTableMapper(PersonCache);.

    Conclusie

    Persoonlijk denk ik dat dit een redelijke elegante oplossing is voor het object versus database probleem. LINQ (Microsoft) gaat in iedergeval deels op een soorgelijke manier de strijd met deze 2 werelden proberen te beslechten. Van LINQ is echter nog steeds geen definitieve release beschikbaar. De gekozen oplossing is gebaseerd op praktijkervaring met ASP.NET applicaties maar nog niet getest met praktijksituaties (Wie meld zich aan?). Ik ben dus zeer benieuwd of deze ORM in de praktijk een lang leven beschoren is.

    Hieronder vindt je de source code van de ORM inclusief een demo console applicatie. LET OP: .NET Framework 2.0 is vereist (Visual Studio 2005).

    Have Fun!

    Downloads

  • OR Mapper & Demo Applicatie
  • Jan.30

    Objecten & objectstructuren opslaan als bestand

    In dit artikeltje wil ik je laten zien hoe je gemakkelijk door middel van 2 methoden objectgegevens binair in een file op kunt slaan en weer op kunt halen. Er zijn heel wat artikelen aan serialization gewijdt. Deze gaan veel dieper als dit artikeltje. In dit geval gaat het erom hoe serialiseren en deserialiseren we op een simpele, snelle en correcte manier.

    Het opslaan van een object of een objectstructuur als bestand is al mogelijk sinds de eerste versie van het .NET framework. Met een duur woord heet dit serialization (opslaan) en deserialization (ophalen/laden). Het opslaan als bestand is 1 vorm maar je kunt bijvoorbeeld de gegevens ook in een MemoryStream of bijv. de Application State van een ASP.NET applicatie opslaan. Verder is de vorm waarmee de gegevens worden opgeslagen te bepalen. Twee van de mogelijkheden zijn binair of in XML formaat. Dit laatste heeft wat nadelen aangezien niet alle gegevens van een object kunnen worden opgeslagen.

    Voorwaarden
    Een voorwaarde om een object op te kunnen slaan is dat de betreffende class voorzien is van het attribuut [Serializable]. Dus…

    [Serializable] public class AClass
    {
    ...
    }

    Op het moment dat een instantie (object) van deze class wordt geserializeerd. Dan zullen ook alle gerelateerde velden (properties) worden geserializeerd. Stel…

    [Serializable] public class Employee
    {
    public Address HomeAddress;
    ...
    }

    public class Address
    {
    public string Street;
    public string HouseNr;
    public string Zip;
    }

    We hebben dus een werknemer (employee) met daaraan gekoppeld een thuis adres (HomeAddress). Op het moment dat we Employee serializeren zal ook het HomeAddress worden geserializeerd. In dit voorbeeld zal dit echter een exception opleveren aangezien de Address class niet het serializable attribuut bevat.


    [Serializable] public class Address
    {
    ...

    Bovenstaande code is dus wel OK. Het is overigens ook mogelijk velden expliciet niet mee te laten serializeren. Dit kan worden geregeld door het NonSerialized attribuut te zetten op het specifieke veld.


    [Serializable] public class Employee
    {
    [NonSerialized] public Address HomeAddress;
    ...
    }

    In bovenstaande geval hoeft de Address class dus ook het attribuut serializable niet te bevatten.

    Verder is het van belang in de gaten te houden dat als een object of objectstructuur is geserializeerd en een class in de objectstructuur wordt daarna aangepast, de geserializeerde gegevens niet meer kunnen worden opgehaald. De binaire gegevens kunnen dan niet meer gemapped worden op het aangepaste type.

    Wanneer?
    Wanneer is serializatie handig om te gebruiken? Stel je hebt een WinForms applicatie geschreven waarbij je ingevoerde gegevens in een soort projectbestand op wilt slaan. Hiervoor kun je prima binaire serializatie gebruiken.

    Ook kan het handig zijn bij webapplicaties. Zo is het bijvoorbeeld mogelijk via SQL Management Objects (SMO), alle mogelijke gegevens van een SQL database (tabellen, velden, primary keys, foreign keys, indexen etc…) op te halen dit proces vergt echter flink wat tijd. Je zou in dat geval een objectstructuur kunnen creeeren bij het ophalen van deze gegevens die overeenkomt met de databaseindeling. Deze structuur vervolgens serializeren naar een bestand en deze vervolgens bij elke request inlezen om gegevens over de database te verkrijgen. Dit kan natuurlijk alleen als de database qua structuur niet wijzigt.

    Verder heeft binair opslaan (en dan niet perse als bestand) in sommige gevallen voordelen boven het vasthouden van een referentie naar een object of objectstructuur. Je kunt hiermee objecten of objectstructuren dupliceren.

    De code
    Ter zake. Hieronder vindt je de code waarmee een object of objectstructuur kan worden opgeslagen op schijf en weer opgehaald. Het mooie is dat bij het ophalen niet noodzakelijk is te weten welk objecttype er terug wordt gegeven.

    LET OP: Via de printversie (links boven en onder) kun je de code kopieren.

    De code kan dus alsvolgt worden gebruikt:

    //Opslaan
    Employee emp = new Employee()
    emp.Name = "Ton Snoei";
    ...
    SerializeObject(emp,@"c:test.bin");

    //ophalen
    Employee x = (Employee) DeSerializeObject(@"c:test.bin");

    Jan.09

    .NET Internationalisatie (Globalization) en een library met internationale gegevens ter download

    Zoals elke gemiddelde .NETter weet is binnen het .NET framework een stuk opgenomen wat localisatie van software vergemakkelijkt, de System.Globalization namespace. De voornaamste klassen binnen deze namespace zijn CultureInfo, RegionInfo en sinds .NET 2.0 CultureAndRegionInfoBuilder de meest belangrijke.

    CultureInfo
    Van de CultureInfo klasse kan een object worden geinstantieerd door een CultureCode mee te geven. Een culture is eigenlijk een combinatie van taal + land. Bijvoorbeeld: Frans – Belgie (fr-BE) , Nederlands – Belgie (nl-BE). De landcode of eigenlijk regioncode wordt volgens ISO 3166-1 met hoofdletters geschreven de taalcode (ISO 639) wordt lowercase geschreven. Dit samen maakt een cultuur. Een cultuur verteld vervolgens iets over de formatting van datum/tijd en numerieke waarden. Een .NET culture wordt ook wel ‘locale’ genoemd in de “unmanaged” volksmond.

    RegionInfo
    De RegionInfo klasse kan worden gebruikt voor het ophalen van gegevens van een land. Ook bij de constructie van een RegionInfo instantie dient een RegionCode te worden meegegeven. Het gaat hier om de 2-karakters lange ISO3166-1 code. Vervolgens heb je dan toegang tot de NativeName (naam in de taal van het land/region), EnglishName en niet te vergeten de Currency (Valuta).

    De hele system.globalization namespace zit goed in elkaar en kan je heel wat programmeerwerk uit handen nemen bij het bouwen van internationale applicaties. Echter, CultureInfo en RegionInfo ondersteunen niet alle landen en cultures, maar alleen de belangrijke. Hoe deze schifting plaats heeft gevonden is mij niet duidelijk. Ik zou zeggen wie daar meer over weten, sta op en reageer! ;-). Het leek mij zelf leuk om informatie te hebben over alle landen, valuta, talen etc.. Ik ben via het web op zoek gegaan naar aan internationalisatie gerelateerde gegevens. Dit heeft geresulteerd en een verzameling data die je hieronder kunt downloaden

    Internationalisatie Library
    Dit library bevat heel veel gegevens over een land. Het is verzameld via allerlei verschillende webpagina’s en samengevoegd tot 1 geheel. De gegevens zijn dus niet 100% betrouwbaar en ik neem wat dat betreft dan ook geen enkele verantwoordelijkheid op me. (Geleden schade.. etc etc kun je dan ook niet op mij verhalen 😉 ) Ik heb trouwens eerlijk gezegd geen idee of op welk van deze gegevens een copyright rust. Mocht er wat dit betreft bezwaren zijn dan hoor ik dat graag. Ik zal in iedergeval waar mogelijk de noodzakelijk stappen ondernemen om te zorgen dat rechthebbende geen verdere schade zullen leiden.

    ..ok, nu ter zake.

    Het library bevat de volgende tabellen:

  • Continent – Continenten
  • Region – Landen
  • Culture – Taal – Land combinaties (Locales)
  • Currency – Valuta
  • Language – Talen
  • TimeZone – Tijdzones
  • TimeZoneNames – Tijdzone namen
  • RegionCurrency – Koppeling land-valuta
  • RegionIP – Koppeling land-internet IP adres
  • RegionTimeZone – Koppeling land-tijdzone
  • RegionLanguage – Koppeling land-taal
  • RegionName – Namen van een land inde verschillende talen
  • PrimaryAdministrativeDivision – Primaire onderverdeling van een land (bijv. Provincie)
  • PrimaryAdministrativeDivisionPopulation – Bevolkingsaantallen van een primaire administratieve divisie per jaar
  • SecondaryAdministrativeDivision – Secundaire onderverdeling van een land (bijv. Gemeente)
  • SecondaryAdministrativeDivisionPopulation – Bevolkingsaantallen van een secundaire administratieve divisie per jaar
  • De regio (=land) tabel bevat belangrijk informatie zoals:

  • Volledige benaming
  • Verkorte benaming
  • Engelse benaming
  • Hoofdstad (Capital)
  • Top Level Domain
  • Dialing Code
  • National Dialing Code
  • Primary Administrative Division Name
  • Secundary Administrative Division Name
  • Zomertijd (Day Light Savings) (ja/nee)
  • Voertuigidentificatiecode
  • Voor elk land zijn er in de RegionName tabel de namen van een land in gemiddeld +/- 50 verschillende talen opgenomen. Mocht je hier vierkantjes als karakters tegenkomen dan heb je waarschijnlijk de chinese karakterset niet geinstalleerd.

    Kortom, een bak informatie heb je interesse dan kun je de zaak hier downloaden in zowel xml, mdb (MS Access 2000) als sdf (SQL 2005 Everywhere Edition) formaat. Mocht je toevoegingen hebben wees dan zo vriendelijk deze ook met ons allen te delen!!

    (Bevolkingsaantallen per land zijn bijvoorbeeld nog niet aanwezig en deze kunnen niet zonder meer worden afgeleid uit de primaire administratieve divisies. Bijvoorbeeld in landen als die van het midden-oosten leven veel nomaden leven die in geen enkele divisie zijn ingedeeld.)

    Download opties

  • GlobalizationXML.zip (XML bestanden)
  • GlobalizationMDB.zip (MS Access database)
  • GlobalizationSDF.zip (SQL Server 2005 Compact Edition / Everywhere Edition database)
  • Vlaggen

  • Flags.zip
  • Meer weten over Globalization in .NET:
    .NET Magazine Maart 2006

    Nov.25

    .NET framework 3.0 is er

    Al is het naar mijn idee met wat stille trom. Het nieuwe .NET framework voorziet o.a. in de nieuwe query taal LINQ waarmee op een uniforme wijze door allerlei data kan worden gezocht (bijv. objecten). Verder is het mogelijk applicaties te ontwikkelen met het WinFX framework, dit behelst Windows Presentation Foundation (voorheen Avalon), Windows Commucation Foundation en Windows Workflow Foundation. Microsoft voorziet hiervoor in een SDK en extensions voor Visual Studio 2005.

    Op de site van Microsoft vindt je de informatie en downloads.

    Oct.28

    Designtime support voor non-visual ASP.NET controls

    Een non-visual control zoals bijvoorbeeld een objectdatasource is alsvolgt zichtbaar in designtime.
    ObjectDataSourceDM.jpg
    Dit soort gedrag is niet standaard voor non-visual controls. In onderstaande artikeltje wil ik uitleggen hoe je een custom non-visual control een eigen look geeft in design-time.

    Een non-visual control maak je alsvolgt. We creeren een class en overerven van WebControl. Overerven van Control is beter maar dan is in design-time de control niet direct zichtbaar in de toolbox. Het is aan te raden na het vervolledigen van de control deze uiteindelijk gewoon te laten overerven van Control.

    Als deze control op een ASP.NET pagina wordt geplaatst is hij echter niet zichtbaar. Nu is de (@runtime) non-visual control tijdens design-time ook non-visual en dat is nu net het gedrag dat aangepast zou moeten worden. De truuk is gebruik te maken van een designer. Een designer beschrijft o.a. het gedrag van een control in design-time.

    Een simpele designer voor bovengenoemde control kan er alsvolgt uit zien:

        public class nvcontroldesigner : System.Web.UI.Design.ControlDesigner
        {
            public override string GetDesignTimeHtml()
            {
                System.Web.UI.Control c = (System.Web.UI.Control)Component;

                string html = "<div style="padding:2px; background-color: ButtonFace;color:ButtonText; " +
                    "border-style:outset; border-width:1px; font: 8pt 'Tahoma';">" +
                    "<b>" + c.GetType().Name + "</b> - " + c.ID;

                html += "</div>";

                return html;
            }        
        }

    Voeg het volgende attribuut toe boven de nvcontrol class definitie:

    [Designer(typeof(nvcontroldesigner))]

    En voila:
    nvcontrol1.jpg

    Download de code nvcontrol.zip.

    Sep.19

    ASP.NET Page refresh afvangen

    Bij het bouwen van ASP.NET webapplicaties is het gebruik van de browser “back”-knop niet aan te raden. In het gunstigste geval treden er geen problemen op maar in andere gevallen zijn de problemen die op kunnen treden vervelend. Neem bijvoorbeeld het dubbel toevoegen van gegevens. Een gebruiker is niet zomaar ander gedrag aan te leren dus zal je als programmeur een oplossing moeten zoeken voor dit probleem. Er bestaan oplossingen voor dit probleem en 1 daarvan is de volgende.

    Wat gebeurt er eigenlijk.

    1. Een gebruiker opent een pagina in de webbrowser, laten we zeggen inschrijven.aspx. Op deze pagina kunnen gegevens worden opgeslagen. (Roundtrip 1)
    2. De gebruiker voert zijn persoonsgegevens in en drukt op de opslaan knop. De pagina wordt gepost naar de server. Op de server pagina wordt het click event van de opslaan knop afgehandeld. De gegevens worden met behulp van een insert query in de database weggeschreven. Vervolgens wordt een specifieke view op de inschrijven pagina getoond waar bij de gebruiker wordt geinformeerd over het feit dat de gegevens succesvol zijn opgeslagen. Daarbij wordt een link getoond waarmee de gebruiker terug kan keren naar de homepage, home.aspx. (Roundtrip 2)
    3. De gebruiker klikt op de link en de homepage wordt getoond. (Roundtrip 3)

    Roundtrips.jpg
    Maar..
    De gebruiker bedenkt zich, hij heeft de tekst niet goed gelezen en kiest de back button.

    en dan beginnen de problemen…

    De browser zal een post uitvoeren met de gegevens die zijn ingevoerd op bij inschrijving. Het click event opslaan wordt opnieuw uitgevoerd en de gegevens worden middels insert opnieuw in de database geschreven. 🙁 .. dat willen we natuurlijk niet. Het zou mooi zijn als op dat moment duidelijk is dat een refresh wordt uitgevoerd. Ofwel een request dat al een keer eerder is uitgevoerd door de gebruiker.

    Als eerste dient er een base page te worden aangemaakt die een property IsRefreshed heeft. Met behulp van deze property kunnen we in alle overerfde pages gebruik maken van deze property. De logica die in de base page wordt geimplementeerd is vrij eenvoudig. Bij elke request van de gebruiker maken we een ticket aan. Dit ticket behelst een uniek gegenereerd nummer in dit geval heb ik gekozen voor een Guid, deze zou ten alle tijden uniek moeten zijn.
    Roundtrips2.jpg

    Wat is een GUID
    Voor degenen die niet weten hoe een Guid er uit ziet, hier een voorbeeld:
    {56CCB843-F919-4bdf-AC18-823710BEDB3F}
    Middels de methode Guid.NewGuid() kan men het .NET framework een nieuwe Guid laten genereren.

    Bij response wordt het ticket in de viewstate van de pagina die de gebruiker voorgeschoteld krijgt opgeslagen. Daarnaast wordt de het ticket ook opgeslagen in de gebruikerspecifieke sessie. Deze acties worden uitgevoerd in het PreRender event van de page. Bij postback (request) door de gebruiker (opslaan inschrijving) wordt via de viewstate van de page het ticket teruggegeven. Bij het oproepen van de IsRefreshed property vlag wordt gecontroleerd of het laatste uitgegeven ticket, vastgelegd in de sessie overeenkomt met het ticket in de viewstate van de page. Wordt er dus een oude request uitgevoerd dan zal ook een oud ticket worden teruggegeven en zal dus niet overeenkomen met het laatste ticket in de sessie waarmee dus bekend is dat de page een refresh heeft gehad. Als er een gewone reguliere postback plaatsvind zal het ticket van de page overeenkomen met het ticket in de sessie en dus herkend worden als een niet-refresh.
    Roundtrips3.jpg
    Of een pagina gerefreshed is wordt overigens alleen gecontroleerd bij een postback. Bij postbacks treden namelijk de eerder genoemde problemen op. De gebruiker moet wel in staat zijn diverse pagina’s meerdere keren te bezoeken waarbij het niet relevant is of deze is gerefreshed.

    De code:

    Wil je gebruik maken van deze oplossing dan is het enige wat er moet gebeuren dat je webpagina’s niet laten overerven van Page maar van bovenstaande RefreshSupportPage.