Søg på DotNyt:
Denne blog er flyttet til www.nielsbrinch.com


lørdag den 12. april 2008

Trådning i ASP.NET

skrevet af Niels Brinch

Ja, der står ASP.NET. Trådning i .NET er rigtig let, men trådning i ASP.NET, det vil sige, på et website, er ikke helt så lige til at gøre på en god og pålidelig måde. Ikke fordi sproget eller miljøet gør det specielt besværligt, men fordi et website simpelt hen ikke er beregnet på det.

Anvendelse
Jeg har udviklet en funktion på et website hvor man kan hente oplysninger fra en web-adresse man indtaster. Oplysningerne står på siden efter indtastning og der skal også vises behandlede billeder fra web-adressen. Behandlingen af billeder tager meget længere tid end resten, hvorfor jeg vil gøre dette i en tråd, så brugeren kan se de første resultater med det samme. Et halvt minut senere er billederne klar og så vises de også på sitet.

1. UpdatePanel
Første ingrediens som skal bruges for at ovenstående kan lade sig gøre, er et UpdatePanel. Det er et panel som kommer med ASP.NET Ajax. Det er indbygget i Visual Studio 2008 men kan også hentes til Visual Studio 2005. I det panel kan placeres elementer som skal opdateres uden siden genindlæses. Her placerer jeg en Repeater som indeholder en skabelon til de billeder som lidt senere skal vises.

2. Timer
En timer er også en Ajax-kontrol. Den reagerer efter et bestemt interval. F.eks. hvert femte sekund. I dette tilfælde får det den effekt at siden holder øje med om tråden er færdig hvert femte sekund. Jeg anbefaler et minimum på 2 sekunder, da serveren jo skal kontaktes hver gang.

<asp:Timer Interval="3000" ID="imageTimer" runat="server" OnTick="imageTimer_Tick">
</asp:Timer>

3. Trigger
For at sikre mit UpdatePanel bliver opdateret, tilføjes en trigger til det.

Jeg har nu følgende:

<asp:UpdatePanel ID="imageUpdatePanel" runat="server" UpdateMode="Conditional">
  <Triggers>
    <asp:AsyncPostBackTrigger ControlID="imageTimer" EventName="Tick" />
  </Triggers>
  <ContentTemplate>
    -- her er min Repeater som indeholder billeder.
  </ContentTemplate>
</asp:UpdatePanel>

Bemærk UpdateMode="Conditional". Det betyder at panelet kun opdateres som følge af de triggere som er angivet.

4. Guid i ViewState
Da flere brugere kan anvende systemet på samme tid, skal vi have en mulighed for at vide hvilke tråde der hører til hvilke sider. Det gøres simpelt ved at skabe en guid og lægge den i ViewState. Som vist her:

protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ViewState["guid"] = Guid.NewGuid().ToString();
}
}

5. Start af tråden
Ved start af tråden er det vigtigt at medsende ovennævnte guid så tråden senere kan informere korrekt om at den er færdig. Ved start af tråden skal også medsendes 'HttpContext.Current', eftersom tråden skal anvende oplysninger fra requestet. Navntlig skal den indbyggede Cache anvendes.


public static void BeginProcessImages(List<string> imageUrls, string guid)
{
ThreadParameter param = new ThreadParameter();
param.ImageUrls = imageUrls;
param.Guid = guid;
param.Context = HttpContext.Current;

ParameterizedThreadStart ts =
new ParameterizedThreadStart(ThreadedProcessImages);
Thread t = new Thread(ts);
t.Start(param);
}

private static void ThreadedProcessImages(object objParam)
{
ThreadParameter param = objParam as ThreadParameter;

string guid = param.Guid;
List<string> imageUrls = param.ImageUrls;
List<string> processedImageUrls = new List<string>();
Cache cache = param.Context.Cache;
    // Udførelse af arbejde her
}

6. Tråden færdigmelder sin opgave
Når tråden er færdig med sit arbejde, tilføjes resultatet af arbejdet til cachen med 'guid' som nøgle. Sådan:


cache.Add(guid, processedImageUrls, null, DateTime.Now.AddMinutes(10),
                Cache.NoSlidingExpiration, CacheItemPriority.High, null);

Desuden kan man f.eks. løbende opdatere en post under guid+"status" hvis man give mulighed for at brugergrænsefladen kan vise oplysninger om hvor langt arbejdet er undervejs. F.eks. i form af en progress bar.


7. Brugergrænsefladen opdateres
Til sidst skal vi kigge tilbage på brugergrænsefladen. Timeren vi tilføjede til brugergrænsefladen havde en event som hed Tick, som var bundet op til metoden "imageTimer_Tick". Det er i denne metode det kontrolleres om tråden er færdig. Når tråden er færdig, opdateres brugergrænsefladen med resultatet.


Herunder er hele metode. Bemærk at når tråden er færdig, bliver timeren sat til Enabled = false så den ikke længere udfører eventen.


protected void imageTimer_Tick(object sender, EventArgs e)
{
string guid = ViewState["guid"] as string;
if (Cache[guid] == null)
{
if (Cache[guid + "status"] != null)
{
string status = Cache[guid + "status"] as string;

lblWaitForUpdate.Text = "Please wait. " + status;
}
else
{
lblWaitForUpdate.Text += ".";
}
}
else
{
imageTimer.Enabled = false;

List<string> imageUrls = (List<string>)Cache[guid];
Cache.Remove(guid);

lblWaitForUpdate.Visible = false;
repImages.DataSource = imageUrls;
repImages.DataBind();
repImages.Visible = true;

btnSubmit.Enabled = true;
}
}

 


Ovenstående er én måde at lave en pålidelig trådning i en ASP.NET applikation med anvendelse af almindelige trygge komponenter og metoder. Fortæl gerne om andre måder at gøre det samme på i kommentarerne til dette indlæg.

0 kommentarer

søndag den 6. april 2008

Introduktion til LINQ

skrevet af Niels Brinch


Jeg har været på et 2-timers kursus i LINQ og er efterfølgende begyndt at bygge en lille webapplikation som anvender LINQ. I dette indlæg vil jeg gerne dele de første erfaringer med andre.


Hvad skal jeg bruge LINQ til?

Det er et redskab til lettere at tilgå data i din database. Der er normalt en masse kedeligt manuelt arbejde når man skal tilgå en database. Det kan undgås med LINQ.

Hvis du gerne vil have en liste med dine bruger-objekter, kan du skrive følgende:

List<User> users = 
(from u in db.Users
where u.IsDeleted == false
orderby u.CreatedDate descending
select u).ToList();

Ovenstående skrives med intellisense og det hele. Kolonnerne i din database er typestærke! Så det kan ikke diskuteres at det er både nemt og hurtigt.


Hvordan kommer jeg i gang?


1. Åbn Visual Studio 2008.
2. Tilføj en .dbml-fil til projektet. (Den Item hedder "LINQ to SQL Classes")
3. Åbn din SQL Server 2005 database gennem Server Explorer og træk alle tabellerne over i den åbne .dbml-fil.


Nu er du i gang, klar til at bruge LINQ i din applikation. Det fungerer bedst hvis dine tabeller og kolonner er godt navngivet og på engelsk - men det er ikke et krav.



Hvordan bruger jeg det?


Hvis du kaldte din .dbml-fil for "DB", skal du lave en instans af "DBDataContext" for at få adgang til din database.


DBDataContext db = new DBDataContext();


Den kan også tage en SqlConnection eller en connectionString. Det er nok vejen frem, da den ellers bare benytter den database du åbnede i Server Explorer.


Herefter kan du anvende db-objektet til at skrive queries som den øverst i dette indlæg.



Indsæt nyt element


Desuden er det enormt let at indsætte nye elementer i databasen. Det kan gøres på to måder:


1. Tilføj objektet til den liste du har hentet med din query, og kør db.SubmitChanges();
2. Tilføj objektet til db.Users.InsertOnSubmit(user), og kør db.SubmitChanges();



Hent elementer med paging


Der er et væld af andre muligheder i LINQ queries, men det er lidt for meget til at dække det i dette indlæg. Men herunder er et eksempel på, at man kan man skrive paging direkte ind i en LINQ query.


List<User> users = 
(from u in db.Users
where u.IsDeleted == false
orderby u.CreatedDate descending
select u).Skip(40).Take(10).ToList();

(lad mig vide hvis der er compile-fejl i ovenstående - jeg har ikke en compiler ved hånden pt.)



Problemer


Da jeg startede med LINQ regnede jeg selvfølgelig med at det var perfekt. Det er det ikke. Jeg er allerede blevet opmærksom på en række problemer, og der er sikkert flere.


Performance
Performance KAN være lige så god som hvis man selv skriver sql til databasen, fordi LINQ bliver faktisk oversat til rigtig sql inden det anvendes. Men man skal vide hvad man gør! Man kan sagtens arrangere sin LINQ på en måde som virker, men som bliver katastrofal for performance. I de tilfælde er man nødt til at vende tilbage til almindelig SQL.


Dynamisk indhold af queries
I klassisk sql kan jeg let justere sorteringen ved ganske enkelt at udskifte det kolonne-navn jeg vil sortere på i min tekst-baserede query. Det er let, men det er ikke let i LINQ. I dette indlæg, som jeg har oprettet på Experts Exchange er det også tydeligt at de work-arounds der findes til dette, ikke er særligt gode.


Mange-til-mange relationer
LINQ understøtter ganske enkelt ikke mange-til-mange relationer. I de tilfælde hvor man skal bruge det, er man nødt til at skrive sin SQL i hånden. Også her findes nogle work-arounds, men dem er der enorme performance-omkostninger ved.



Understøttelse af SQL


Ovenstående problemer med LINQ gør faktisk ikke så meget! Man kan stadig få stor udnytte af LINQ i de situationer hvor man er nødt til at skrive din egen SQL. Det er nemlig muligt at eksekvere en query og direkte få en typestærk liste af objekter ud som resultat.


string query = @"
SELECT Users.UserName, Users.CreatedDate, Users.IsDeleted ...
FROM Users
"


List<User> users = db.ExecuteQuery(typeof(User), query);

Nemmere kan det ikke være. Og hvad mere er - ovenstående genvej er performance-mæssigt forsvarligt.



Konklusion


LINQ er uperfekt men stadig så tidsbesparende, både på kort og langt sigt, at jeg klart vil anbefale at anvende det i nye projekter.

0 kommentarer


 
Til forsiden

Niels Brinch

- Seneste indlæg