June 2009 - Posts

Jag läste just en intressant bloggpost om användandet av olika JavaScript-ramverk, eller snarare Ajax-ramverk.

javascript

Man kan enkelt se att ASP.NET Ajax var det absolut största 2007, men att det sedan har blivit omsprunget av jQuery som har växt explosionsartat.

Det jag själv tolkar statistiken som är att många kom igång ordentligt med Ajax när ASP.NET Ajax först släpptes, då det gjorde det busenkelt att få in avancerade JavaScript på sin sida. Efter att folk har kommit igång med det så har de sedan vågat sig på andra bibliotek och har sett vilka otroliga funktioner jQuery har och har gått över dit. Att jQuery ingår i ASP.NET MVC lär inte heller bidra till att jQuery stannar där det är nu.

Jag själv använder jQuery i i stort sett alla webbprojekt jag arbetar med, då det gör det så enkelt att modifiera DOM:en och göra asynkrona anrop.

Olika bibliotek:

Vilket/vilka bibliotek använder du, och varför?

I Visual Studio 2008 SP 1 med .NET 3.5 SP 1 så kom möjligheten att skapa upp modeller baserade på en befintlig databas med hjälp av Entity Framework. I Visual Studio 2010 och .NET 4.0 kommer vi även att kunna gå åt andra hållet, det vill säga att vi kommer att kunna generera databasen utifrån modellen.

Redan i Visual Studio 2008 så kunde vi skapa upp en tom modell när vi lade till en Entity Data Model, där vi även kunde lägga till entiteter, relationer med mera, men sen tog det stopp. Det vi har nu är möjligheten att gå vidare med denna modell och genom att högerklicka på den välja att generera en databas baserad på modellen. I Beta 1 av Visual Studio 2010 så kan vi bara generera ett SQL-script som vi sedan kan köra mot databasen, men i senare versioner så kommer det att ske mer transparent.

För att testa den här funktionaliteten så kan vi skapa upp ett Console-project där vi lägger till en Entity Data Model och väljer att skapa en tom modell. Sedan drar vi in en entitet som heter ”Customer”. Vi kan här se att den redan har en property vid namn ”Id”, vilket är en primärnyckel. Vi låter denna vara och väljer istället att högerklicka på entiteten, väljer Add och sedan Scalar Type. Vi låter den heta FirstName, och skapar sedan en likadan för LastName. Sedan markerar vi dessa två, högerklickar och väljer ”Refactor into New Complex Type”. Just Complex Type är en typ som kan ha flera fält. Vi kallar denna för ”Name”. Vi skapar sedan en ny Complex Type på samma vis, men kallar den för ”Adress” och låter den ha fälten ”Street”, ”ZipCode” och ”Country”. Till syvende och sist så skapar vi en enkel typ som heter ”Age”.

Det bör nu se ut i stil med det här:

1

Kikar vi i Model Browser-fönstret (om det inte syns så välj View -> Other Windows -> Entity Data Model Browser) så kan vi se det här:

2

Här kan vi få en enkel, men bra överblick över våra typer i modellen, och kan enkelt ändra egenskaper för de enskilda objekten.

När vi nu har skapat upp modellen så ska vi generera en databas baserad på den. Till att börja med så högerklickar vi någonstans i modelldesignern och väljer ”Generate Database Script from Model”. Här får vi välja en befintlig databas, alternativt skapa upp en ny. Jag kommer i exemplet att använda en nyskapad SQL Server 2008-databas, men det kan lika gärna vara en SQL Server Express-databas.

När vi har valt databas så klickar vi vidare. I nästa steg så genereras ett SQL-script, vilket vi kommer att använda vid genererandet av databasen. Nu klickar vi på Finish och sedan Yes i rutan som dyker upp. Vi får nu upp vår genererade SQL i ett nytt fönster. Här högerklickar vi och väljer ”Execute SQL” och markerar databasen i dropdown-listan. Nu bör databasen vara skapad, vilket vi kan se om vi öppnar upp den i Server Explorer:

3

Vi kan här se att vår entitet har skapats upp, samt att våra komplexa typer har fått ett namn likt ”NamnetPåKomplexaTypen_NamnPåPropertyn”. Vi kan nu lägga till lite dummy-data i stil med:

4

Och nu till det roliga, nämligen kodningen!

Vi börjar med att skapa en instans av CustomersContainer, vilket är containern för vår Customer-klass. Sedan loopar vi igenom all data i dess CustomerSet.

Här kan vi se att våra komplexa typer faktiskt har skapats på ett sätt som får dem att se ut som om de vore främmande nycklar i databasen:

5

Den färdiga koden för att skriva ut namn och gata för alla användare kan se ut så här:

using System;
 
namespace GenerateDatabase
{
    class Program
    {
        static void Main(string[] args)
        {
            CustomersContainer customers = new CustomersContainer();
            foreach (Customer customer in customers.CustomerSet)
            {
                Console.WriteLine("Name:\t{0} {1}", customer.Name.FirstName, customer.Name.LastName);
                Console.WriteLine("Street:\t{0}", customer.Address.Street);
                Console.WriteLine("------------------------------");
            }
 
            Console.ReadKey();
        }
    }
}

Om vi sedan kör vår applikation så får vi det här:

6

Vi har här alltså skapat upp en databas baserad på vår modell, utan att själva behöva göra något själva direkt mot databasen. Goda nyheter för de som inte orkar sitta och skriva långa SQL-satser dagarna i ända för att få till tabellerna korrekt. :-)

En nyhet i ASP.NET 3.5 SP 1 var stödet för ASP.NET Routing (System.Web.Routing). Det används som standard i ASP.NET MVC, men går även att använda med Web Forms. I ASP.NET 3.5 är det ganska krångligt att implementera det då inte all funktionalitet för det finns implementerad som standard, utan man är tvungen att skriva egna RouteHandlers, eventuella Expression Builders m.m.

I ASP.NET 4.0 så blir det dock mycket enklare. Nyheter här är bl.a. PageRouteHandler och Expression Builders för ASP.NET MVC. Jag ska visa ett exempel där vi med väldigt lite kod kan implementera stöd för Routing i en Web Forms-baserad webbsida med hjälp av ASP.NET 4.0.

Jag förutsätter att du har en viss kunskap om ASP.NET Routing, genom exempelvis ASP.NET MVC innan du läser vidare, annars kan det bli svårt att förstå exakt vad som händer.

Det första vi behöver är en Web Application baserad på ASP.NET 4.0. Till denna behövs en Global.asax.

Nästa steg är att lägga till några nycklar i web.config.

Under httpModules så behöver vi en referens till UrlRoutingModule, vilken går igenom alla angivna routes och ser om någon av dem passar in på URL:en som har angivits:

<add name="RoutingModule" type="System.Web.Routing.UrlRoutingModule"/>

Nästa steg är att under Compilation lägga till två ExpressionBuilders som vi kommer att använda oss utav:

<expressionBuilders>
    <add expressionPrefix="RouteUrl" type="System.Web.Compilation.RouteUrlExpressionBuilder"/>
    <add expressionPrefix="RouteValue" type="System.Web.Compilation.RouteValueExpressionBuilder" />
</expressionBuilders>

RouteUrl gör det möjligt att generera en URL anpassad för en viss Route I vår RouteTable. RouteValue ger funktionalitet för att direkt på sidan kunna skriva ut ett värde som har kommit genom vår Route.

För att kunna specifiera vilka Routes vi vill använda och lägga till dem i vår RouteTable så behöver vi detta i Global.asax:

using System;
using System.Web.Routing;
 
namespace Routing
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.Add("UsersPaging", new Route("users/{page}", new PageRouteHandler("~/Default.aspx")));
        }
    }
}

Här skapar vi upp en Route som har syftet att visa en lista på användare, samt ge stöd för paging. Den pekas om till Default.aspx, men skulle självklart kunna leda till vilken sida som helst och ha hur många parametrar vi än vill ha.

Nu har vi allt vi behöver och kan utan problem surfa till http://localhost/sidan/users/4 för att komma till sida 4 i listningen.

För att kunna generera en länk till users/4 automatiskt baserat på hur Routen är specifierad så kan vi använda RouteUrl-Expression Buildern (ber om ursäkt för svengelskan):

<asp:HyperLink runat="server" NavigateUrl="<%$ RouteUrl:RouteName=UsersPaging, page=4 %>"
        Text="Lista användare, sidan 4" />

Länken ovan pekar till Routen med namn “UsersPaging” och skickar med parametern “page” som har fått ett värde satt till “4”. Om vi kör sidan så kan vi se att den här länken har genererats:

<a href="/users/4">Lista användare, sidan 4</a>

Det vi vill göra nu är att visa alla användare på sidan 4 om ett värde är satt i page-parametern. För det så kan vi ha något i stil med:

<asp:Panel ID="pnlUsers" runat="server">
    <h1>
        Användare, sidan
        <asp:Literal runat="server" Text="<%$ RouteValue:page%>" />
    </h1>
    <ul>
        <li>Användare 1</li>
        <li>Användare 2</li>
        <li>Användare 3</li>
        <li>Användare 4</li>
        <li>Användare 5</li>
    </ul>
</asp:Panel>

Här använder vi den andra Expression Buildern, RouteValue, för att visa värdet för page-parametern direct på sidan.

För att kunna dölja panelen när värdet inte har angivits så kan vi från Code Behind hämta RouteDatan:

using System;
 
namespace Routing
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            string page = RouteData.Values["page"] as string;
            pnlUsers.Visible = !String.IsNullOrEmpty(page);
        }
    }
}

Här hämtar vi programmatiskt i Code Behind page-parametern och visar bara panelen om den har ett värde.

När man arbetar med JavaScript och Web Forms-baserade ASP.NET-sidor så uppstår ofta problem när man skall plocka ut ett element som har genererats genom en kontroll. Problemet beror på att id:t som genereras hos klienten ser ut på ett sätt som säkerställer att det alltid är unikt.

För att kunna använda JavaScript för att anropa ett element vars id genereras på serversidan så brukar man göra något i stil med det här:

document.getElementById(’<%=Kontrollen.ClientId%>’);

Det kräver dock att man har koden på samma sida som kontrollen, och fungerar således inte i js-filer, där den hör hemma.

En nyhet i ASP.NET 4.0 är att man själv kan avgöra vilket id som skall genereras hos klienten. Genom en ny property vid namn ”ClientIDMode” så kan man ange fyra olika värden – Inherit, Legacy, Predictable samt Static.

För att kunna visa hur de olika värdena fungerar så kommer jag att använda den här sidan för att skapa upp element:

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ClientId._Default" %>
<%@ Register Src="Controls.ascx" TagName="Controls" TagPrefix="uc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>ClientId Test</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:Panel ID="pnlItems" runat="server">
        <uc1:Controls ID="Controls1" Text="Testar" runat="server" />
        <br />
        <asp:GridView ID="gvNames" ClientIDMode="Legacy" runat="server" AutoGenerateColumns="false" CellPadding="4"
            ForeColor="#333333" GridLines="None">
            <AlternatingRowStyle BackColor="White" ForeColor="#284775" />
            <Columns>
                <asp:TemplateField HeaderText="Name">
                    <ItemTemplate>
                        <uc1:Controls ID="Controls1" Text='<%#Eval("Value") %>' runat="server" />
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
            <EditRowStyle BackColor="#999999" />
            <FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
            <HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
            <PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
            <RowStyle BackColor="#F7F6F3" ForeColor="#333333" />
            <SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
        </asp:GridView>
    </asp:Panel>
    </form>
</body>
</html>

Default.aspx.cs

using System;
using System.Collections.Generic;
 
namespace ClientId
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Dictionary<int, string> users = new Dictionary<int, string>();
            users.Add(100, "Mikael");
            users.Add(200, "Steve");
            users.Add(300, "Bill");
 
            gvNames.DataSource = users;
            gvNames.DataBind();
        }
    }
}

Controls.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Controls.ascx.cs" Inherits="ClientId.Controls" %>
<asp:Label ID="lblName" runat="server" />

Controls.ascx.cs

using System;
using System.Web.UI;
 
namespace ClientId
{
    public partial class Controls : UserControl
    {
        public string Text { get; set; }
 
        protected void Page_Load(object sender, EventArgs e)
        {
            lblName.Text = Text;
        }
    }
}

Det som visas är dels en fristående Controls-kontroll, samt även en GridView som använder kontrollen för alla namn som binds till den. Anledningen till att det är så många kontroller i exemplet är för att kunna visa på ett bra sätt hur de genererade elementen ser ut.

Inherit

Inherit betyder att kontrollen ärver värdet från kontrollen innan. Om vi har Legacy i en kontroll (t.ex. PlaceHolder) och Inherit på kontrollerna under (vilket är standard), så betyder det att kontrollerna i PlaceHolder-kontrollen också har Legacy. Om inget anges på någon kontroll så ärvs det automatiskt från Page.

Legacy

För att få allt att fungera som standard så använder vi Legacy. Det här är standard på alla sidor, och därmed alla kontroller i dessa. Det här genererar alla id:n precis som i .NET 1.0-3.5.

Resultat:

<div id="Div1">
    <span id="Controls1_lblName">Testar</span>
    <br />
    <div>
    <table cellspacing="0" cellpadding="4" border="0" id="Table1" style="color:#333333;border-collapse:collapse;">
        <tr style="color:White;background-color:#5D7B9D;font-weight:bold;">
            <th scope="col">Name</th>
        </tr><tr style="color:#333333;background-color:#F7F6F3;">
            <td>
                    <span id="gvNames_ctl02_Controls1_lblName">Mikael</span>
                </td>
        </tr><tr style="color:#284775;background-color:White;">
            <td>
                    <span id="gvNames_ctl03_Controls1_lblName">Steve</span>
                </td>
        </tr><tr style="color:#333333;background-color:#F7F6F3;">
            <td>
                    <span id="gvNames_ctl04_Controls1_lblName">Bill</span>
                </td>
        </tr>
    </table>
</div>

Det kan här bli svårt att veta hur id:t skall hämtas med JavaScript då det är helt beroende på kontrollerna utanför. Om man byter namn på en kontroll, alternativt flyttar runt dem så blir det ett annorlunda id.

Predictable

När vi använder kontroller som visar upp en samling av data, som t.ex. Repeater eller GridView så bör man använda Predictable eller Legacy. Just Predictable lägger automatiskt till en siffra på slutet av alla id:n för att säkerställa att vi inte får flera element med samma id.

Resultat:

<div id="Div1">
    <span id="Controls1_lblName">Testar</span>
    <br />
    <div>
        <table cellspacing="0" cellpadding="4" border="0" id="Table1" style="color:#333333;border-collapse:collapse;">
            <tr style="color:White;background-color:#5D7B9D;font-weight:bold;">
                <th scope="col">Name</th>
            </tr><tr style="color:#333333;background-color:#F7F6F3;">
                <td>
                        <span id="gvNames_Controls1_0_lblName_0">Mikael</span>
                    </td>
            </tr><tr style="color:#284775;background-color:White;">
                <td>
                        <span id="gvNames_Controls1_1_lblName_1">Steve</span>
                    </td>
            </tr><tr style="color:#333333;background-color:#F7F6F3;">
                <td>
                        <span id="gvNames_Controls1_2_lblName_2">Bill</span>
                    </td>
            </tr>
        </table>
    </div>
</div>

Här kan vi se en liten skillnad. Här försöker ASP.NET generera id:n som görs unika genom att lägga till en unik siffra efter kontrollens id. Det underlättar en del då vi kan styra namnen bättre. Det går även att utöka detta för att få ännu mer kontroll, men mer om det senare.

Static

Om vi vill använda exakt det id:t som vi satte på kontrollen så kan vi använda static. Om kontrollens id är ”MinKontroll” på servern, så blir det även exakt det på klienten. Det här kan dock leda till att flera kontroller får samma id, vilket kräver at utvecklaren är uppmärksam.

Resultat:

<div id="Div1">
    <span id="lblName">Testar</span>
    <br />
    <div>
        <table cellspacing="0" cellpadding="4" border="0" id="Table1" style="color:#333333;border-collapse:collapse;">
            <tr style="color:White;background-color:#5D7B9D;font-weight:bold;">
                <th scope="col">Name</th>
            </tr><tr style="color:#333333;background-color:#F7F6F3;">
                <td>
                        <span id="lblName">Mikael</span>
                    </td>
            </tr><tr style="color:#284775;background-color:White;">
                <td>
                        <span id="lblName">Steve</span>
                    </td>
            </tr><tr style="color:#333333;background-color:#F7F6F3;">
                <td>
                        <span id="lblName">Bill</span>
                    </td>
            </tr>
        </table>
    </div>
</div>

Här kan vi se att alla id:n får samma värde. Det här leder till att koden inte validerar och att vi inte kan använda getElementById(). Det bör absolut inte användas i kontroller som visar upp repeterande kontroller, utan bara enskilda element.

Mer kontroll över id:n i databundna kontroller

För att få mer kontroll över id:n i databundna kontroller som GridView så finns det en ny property som kan användas om vi har satt ClientIDMode till Predictable. Som jag visade i exemplet tidigare så används dels ett prefix i form av kontrollens id, samt även ett suffix i form av en siffra. Med hjälp av en property vid namn ”ClientIDRowSuffix” så kan vi ändra suffixet till ett eget värde.

Vi kan här använda ett eller flera fält från vår datakälla. Om vi istället för att ha 1, 2, 3 osv som suffix vill ha ”personensId_personensNamn” så kan vi sätta propertyn i vår GridView till ”Key,Value” vilket är våra två properties som vi har bundit.

Resultat blir då:

<table cellspacing="0" cellpadding="4" border="0" id="Table1" style="color:#333333;border-collapse:collapse;">
    <tr style="color:White;background-color:#5D7B9D;font-weight:bold;">
        <th scope="col">Name</th>
    </tr><tr style="color:#333333;background-color:#F7F6F3;">
        <td>
                <span id="gvNames_Controls1_100_Mikael_lblName_100_Mikael">Mikael</span>
            </td>
    </tr><tr style="color:#284775;background-color:White;">
        <td>
                <span id="gvNames_Controls1_200_Steve_lblName_200_Steve">Steve</span>
            </td>
    </tr><tr style="color:#333333;background-color:#F7F6F3;">
        <td>
                <span id="gvNames_Controls1_300_Bill_lblName_300_Bill">Bill</span>
            </td>
    </tr>
</table>

Vi har här fått t.ex. ”100_Mikael” som suffix istället för ”1”. Anledningen till att det kommer på två ställen beror på att vi inkluderar en user control med en label som visar namnet. Hade vi skrivit ut namnet direkt på sidan så hade vi bara fått upp det en gång.

More Posts