Posts tonen met het label C#. Alle posts tonen
Posts tonen met het label C#. Alle posts tonen

21 september 2012

ASP.NET validatie in VS 2012 werkt niet meer?

Als je gewend bent om met de validatiecontrols te werken in ASP.NET, zou je wel eens voor verrassingen kunnen komen te staan. Beschouw de volgende snippet gecreëerd in een “New Empty Website” met VS 2012:

<form id="form1" runat="server">
<div>
    <asp:Label Text="Vul iets in: " runat="server" />
    <asp:TextBox ID="InputTextBox" runat="server"></asp:TextBox>
    <asp:RequiredFieldValidator ID="RequiredFieldValidator1"
        runat="server"
        ErrorMessage="U heeft niets ingevuld!"
        ControlToValidate="InputTextBox">
    </asp:RequiredFieldValidator>
    <br />
    <asp:Button ID="OKButton" runat="server" Text="Klik" />
</div>
</form>

Als je deze pagina opent in een browser en op de knop klikt, krijg je volgende foutmelding:

ServerError

Als je echter bestaande websites opent die gemaakt zijn met vroegere edities van Visual Studio, blijkt alles perfect te werken. Hoe komt dit nu?

Wel, zoals je weet gaan de validation controls zowel op de server als op de client (via gegenereerde JavaScript code) de validatie uitvoeren. Als je veel van deze controls op de pagina hebt staan, zal de JavaScript code groter en groter worden om uiteindelijk het laden van de pagina te vertragen. Dit is in feite gelijkaardig aan het ViewState probleem. Daarom is het raadzaam om bij nieuwe sites de validatie uit te besteden aan het JQuery framework. Echter dit is bij een bestaande website niet aanwezig en moet manueel toegevoegd worden.

We stellen drie oplossingen voor om dit probleem te verhelpen:

  1. Nieuwe websites draaien op een vorige versie van het .NET framework
  2. Nieuwe websites draaien op het meest recente .NET framework, maar via klassieke validatie
  3. Nieuwe websites draaien op het nieuwe validatieframework gebaseerd op JQuery

Nieuwe websites draaien op een vorige versie van het .NET framework

Dit is wellicht de eenvoudigste oplossing. Verander het web.config bestand van je website en vermeld 4.0 als versie nummer in plaats van 4.5:

<system.web>
  <compilation debug="true" targetFramework="4.0"/>
  <httpRuntime targetFramework="4.0"/>
</system.web>

Nieuwe websites draaien op het meest recente .NET framework, maar via klassieke validatie

Een nadeel van de vorige oplossing is dat je het meest recente .NET framework negeert. Je kan dus geen gebruik maken van de meeste recente performantieverbeteringen. Daarom is het wellicht beter om het frameworknummer te behouden, maar terug te vallen op de oude validatiemethode. Ook dit kan je instellen in het web.config bestand. Je voegt hiervoor een <appsettings> element toe met de volgende subelementen:

<?xml version="1.0"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5"/>
    <httpRuntime targetFramework="4.5"/>
  </system.web>
  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
    <add key="ValidationSettings:UnobtrusiveValidationMode" value="None" />
  </appSettings>
</configuration>

Nieuwe websites draaien op het nieuwe validatieframework gebaseerd op JQuery

De beste oplossing, maar ook de meeste complexe, is het toevoegen van JQuery aan je website. Dit doen we in een paar stappen:

Stap 1: voeg via “Add New Item” een “Global Application Class” toe. Dit voegt een bestand genaamd Global.asax toe aan je project.

Stap 2: voeg volgende code toe, de methode headers zijn al voor jou aangemaakt:

void Application_Start(object sender, EventArgs e) 
{
    ScriptResourceDefinition jQuery = new ScriptResourceDefinition();
    jQuery.Path = "~/scripts/jquery-1.7.2.min.js";
    jQuery.DebugPath = "~/scripts/jquery-1.7.2.js";
    jQuery.CdnPath = "http://ajax.microsoft.com/ajax/jQuery/jquery-1.7.2.min.js";
    jQuery.CdnDebugPath = "http://ajax.microsoft.com/ajax/jQuery/jquery-1.7.2.js";
    ScriptManager.ScriptResourceMapping.AddDefinition("jquery", jQuery);
}

Deze code registreert het JQuery framework in je applicatie. JQuery wordt lokaal benaderd (in een scripts map) maar in een productieomgeving via de website van Microsoft zelf (via het CdnPath oftwel Content Delivery Network).

Stap 3: download de twee js-bestanden en voeg ze toe aan het project. Je project heeft dan volgende layout:

solution

Verwijder nu eventuele sporen van de vorige twee oplossingen, en run je programma. Je zal vaststellen dat validatie nu opnieuw werkt en dat het JQuery framework gebruikt wordt (rechtsklik: View Source).

Referenties:

2 januari 2012

Code omzetten van C# naar VB

Telerik heeft een online tool om C# naar VB om te zetten (en vice versa). Alhoewel dit zeker geen foutloze omzettingen oplevert (Handles clauses worden niet omgezet, sommige klassen zijn niet gekend e.d.), kan het zeker handig zijn om de hoeveelheid “overtikwerk” de reduceren.

Andere omzetters:

Let op: geen enkele van deze sites kan je programma in zijn geheel, foutloos vertalen. Je zal altijd zelf de code moeten nalezen en verbeteren!

31 oktober 2011

Predikaten met C#

Predikaten (Engels: Predicate) verkorten vaak het programmeerwerk omdat ze het zelf schrijven van herhalingsstructuren overbodig maken. Ze komen handig van pas bij het zoeken. In plaats van zelf lussen te schrijven, gebruik je een ingebouwde zoekmethode zoals Array.Find of Array.FindAll en kan je via predikaten zelf je zoekcriteria “inpluggen”.

Vanuit de logica weet je wellicht dat een predikaat is een bewering is die waar of onwaar is. In C# is een predikaat een speciale methode (een delegate) die true of false oplevert. Het volgende voorbeeld is een predikaat die onderzoekt of een woord een palindroom is: achterstevoren gelezen blijft het woord hetzelfde.

public bool IsPalindroom(string woord)
{
    // omzetten naar karakterarray
    char[] karakters = woord.ToCharArray();
    // omdraaien
    Array.Reverse(karakters);
    // opnieuw naar string omzetten
    string omgekeerd = new string(karakters);
    // vergelijken
    return omgekeerd == woord;
}

Stel nu dat je een lijst van woorden hebt die je wil onderzoeken of het palindromen zijn:

private List<string> woordenLijst = new List<string>();

...

woordenLijst.Add("zeven");
woordenLijst.Add("lepel");
woordenLijst.Add("koekoek");
woordenLijst.Add("racecar");
woordenLijst.Add("racewagen");
woordenLijst.Add("topkookpot");

In plaats van zelf een for-lus te schrijven en één voor één de methode toe te passen, gebruik je de Array.FindAll-methode en voorzie je via het predikaat een zoekcriterium:

List<string> palindromen = woordenLijst.FindAll(IsPalindroom);

MessageBox.Show("gevonden palindromen: " +
                String.Join(", ", palindromen.ToArray()));

De lus is er nu nog wel, Array.FindAll doorloopt alle woorden en evalueert ze via het predikaat, en stopt de resultaten in een nieuwe lijst. De hoeveelheid programmeerwerk is echter afgenomen en je redeneert meer op een declaratieve manier: je denkt meer naar over “wat heb ik nodig (IsPalindroom)?” in plaats van “hoe moet ik dat programmeren (lussen)?”.

19 september 2011

Entiteiten van naam veranderen

Als je een methode, variabele, parameter, … van naam wil veranderen kan je natuurlijk een “search & replace”-actie over je bestanden doen. Vaak is het gemakkelijker om via rechtsklik > Refactor > Rename de naamsverandering door te voeren.

refactor

Omzetten naar gehele getallen

Er zijn meerder methoden om een double getal om te zetten naar een geheel getal. De cast-operator realiseert een getal door afkapping, terwijl Convert.ToInt32 afrondt naar het dichtsbijzijnde geheel getal.

Bijvoorbeeld:

double d = 5.6;
int a = (int)d; // a = 5
int b = Convert.ToInt32(d); // b = 6

Bij de afronding maakt men gebruik van het zogenaamde Bankiersalgoritme. Dit betekent dat, als een getal halfweg ligt tussen twee gehele getallen, je afrondt naar het dichtstbijzijnde even getal. Dus 4.5 wordt 4, en 5.5 wordt 6.

Wil je “gewoon” afronden, dien je te werken met de methode Math.Round waarbij je met een extra parameter instelt welk soort afronding je wenst.

12 september 2011

Drag & Drop in .NET programma’s

Gebaseerd op dit artikel ga ik je het meest eenvoudige programma uit de doeken doen dat drag & drop ondersteunt. De applicatie toont een leeg formulier waarop je één (of meerdere) bestanden kan “droppen”. Bijvoorbeeld vanaf de desktop of de verkenner, zoals hieronder aangegeven.

DNDTest1

Hier sleep je 3 bestanden en 1 directory naar het formulier door eerst met CTRL die items aan te klikken en vervolgens te slepen naar de centrale zone. Als je de muis vervolgens loslaat, verschijnt een bericht met alle bestands- en mapnamen:

DNDTest2

Om te beginnen start je een leeg project. De control die de drop-operaties ontvangt, moet dit toelaten: vandaar de property AllowDrop die je op true zet:

public Form1()
{
    InitializeComponent();

    AllowDrop = true;
}

Nu moet je twee events afhandelen: DragEnter en DragDrop. Het eerste event (DragEnter) wordt afgevuurd zodra de gebruiker met de muis het formulier binnengaat. Hiermee kan je de muiscursorafbeelding wijzigen indien de operatie toegelaten is:

private void Form1_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
        e.Effect = DragDropEffects.All;
    }
    else
    {
        e.Effect = DragDropEffects.None;
    }
}

De if-test (met e.Data.GetDataPresent) controleert of er wel bestanden gedropt worden. Indien dat het geval is, stel je de property e.Effect op de waarde DragDropEffects.All. Hierdoor verschijnt het bekende plusteken bij de muiscursor. In het andere geval is draggen verboden en wordt de muiscursor daaraan aangepast. Sleep bijvoorbeeld de prullenbak eens naar het formulier en je zal merken dat dit niet lukt.

Het tweede event bevat de eigenlijke gegevens (in dit geval de bestandsnamen) en handel je als volgt af:

private void Form1_DragDrop(object sender, DragEventArgs e)
{
    string[] files = (string[])e.Data.GetData(DataFormats.FileDrop, false);

    string message = String.Join("\r\n", files);
    MessageBox.Show(message);
}

Eerst zet je de data om naar een string-array, hiervoor is een cast-operatie noodzakelijk. Vervolgens formatteer je deze array tot één string en toon je die in een berichtvenster.

Download het volledige programma.

7 september 2011

Tekenen op componenten en het Paint-event

Typisch (en voor de eenvoud) wordt het maken van tekeningen in .NET als volgt aangeleerd:

  • Zet een PictureBox control op een Form
  • In een event handler (bv. een Button) maak je een Graphics object aan via deze PictureBox (met de methode CreateGraphics)
  • Teken met dit Graphics object

Dit stukje code illustreert deze aanpak:

private void drawButton_Click(object sender, EventArgs e)
{
    Graphics g = redPictureBox.CreateGraphics();
    Pen pen = new Pen(Color.Black);
    int diameter = redPictureBox.Width - 4;
    g.DrawEllipse(pen, 2, 2, diameter, diameter);
}

Dit ziet er dan zo uit:

badpicturebox

Om beginnende programmeurs snel grafische leuke toepassingen te laten maken is dit perfect, maar toch zijn er een aantal ernstige tekortkomingen:

  1. De PictureBox wordt niet bijgewerkt, bijvoorbeeld bij het minimaliseren en herstellen van de applicatie
  2. Het Graphics object g wordt niet opgeruimd achter de schermen. Volgens de MSDN documentatie moet je ervoor zorgen dat een aanroep van CreateGraphics later gevolgd wordt door een Dispose van het aangemaakte object
  3. Threading issues: de teken-code wordt uitgevoerd in de thread die verantwoordelijk is voor eventhandling. Er is echter een aparte thread die ervoor zorgt dat alle controls netjes getekend en up-to-date worden gehouden. Je mag in feite deze twee zaken niet mengen
  4. Verkeerd gebruik van PictureBox. De klassenaam zegt het zelf: een Picture Box dient om afbeeldingen te tonen en niet om allerhande lijnen en vormen op te tekenen. Hiervoor gebruik je volgens deze referentie beter een Panel.

We gaan nu deze tekortkomingen één voor één wegwerken door ons programma aan te passen.

Up-to-date houden van de PictureBox (of een Component in het algemeen)

We passen het programma als volgt aan:

private bool clicked = false;

public Form2()
{
    InitializeComponent();
}

private void drawButton_Click(object sender, EventArgs e)
{
    clicked = true;
    redPictureBox.Invalidate();
}

private void redPictureBox_Paint(object sender, PaintEventArgs e)
{
    if (clicked)
    {
        Graphics g = e.Graphics;
        Pen pen = new Pen(Color.Black);
        int diameter = redPictureBox.Width - 4;
        g.DrawEllipse(pen, 2, 2, diameter, diameter);
    }
}

Elke component (dus ook een PictureBox) wordt op gezette tijden ververst. Het besturingssysteem vraagt dan om, bijvoorbeeld na het minimaliseren en herstellen van een venster, om de componenten opnieuw te tekenen. Je kan hierop inspelen door het Paint-even af te handelen. In ons voorbeeld doen we dit als volgt:

private void redPictureBox_Paint(object sender, PaintEventArgs e)

Merk op dat we nu niet meer het Graphics object aanmaken via CreateGraphics, maar dat je het Graphics object kan verkrijgen als property van het PaintEventArgs-object e. Meer nog: CreateGraphics werkt niet in deze methode!

In de event handler van de Button geven we een signaal dat de klik heeft plaatsgevonden en dat de cirkel op de PictureBox mag worden getekend. Via Invalidate zorg je ervoor dat het Paint-event zal afgevuurd worden.

Threading issues

Als gevolg van deze aanpassing is er geen “teken”-code die uitgevoerd wordt in de event-thread:

  • Het Paint-event wordt afgehandeld in de thread die verantwoordelijk is voor het up-to-date houden van componenten
  • Invalidate wordt uitgevoerd in de event-thread, maar vraagt (onrechtstreeks) dat het Paint-event wordt afgevuurd zodat de cirkel kan worden getekend.

Panel in plaats van PictureBox

Het is een eenvoudige oefening om het voorbeeld om te bouwen. Verwijder de PictureBox en vervang door een Panel component.

Referenties: