Podcast om ASP, ASP.NET og PHP

13. februar 2010

For nogle dage siden deltog jeg i en podcast primært omhandlede ASP, ASP.NET og PHP. Podcasten er et slags interview mellem Daniel Mellgaard Frost, Developer Evangelist i Microsoft Danmark, samt altså undertegnende og meget kort fortalt får du i den lidt historie om min baggrund, lidt om min nutid samt nogle tanker fx omkring religionskrig mellem, valg af og hjælpemuligheder i forskellige sprog/teknologier.

Jeg har endnu ikke turde høre podcasten igennem selv da jeg ser det som værende med stor fare for at sidde tilbage med en enormt nedtryk følelse efter at have hørt ens egne fejl såsom skæv accent, talebøffer, tekniske ups'ere etc. En enkelt ting jeg dog er rimelig overbevist om er, at jeg får det gjort til en dårlig ting, at man har så stort et udvalg af løsningsmodeller i ASP.NET i forhold til mit "gamle" sprog ASP - det er selvfølgelig ikke hensigten, for mange muligheder kan give en stor frihed! Det jeg forsøger at sige med det er, at de mange måder at kunne løse den samme udfordring på i mine øjne er med til at gøre det sværere dels at komme i gang med og dels at få hjælp til ASP.NET.

Men lyt selv her; Frosts Podcast Show #16 – Rundt om ASP, ASP.NET og PHP med Kim Larsen

ASP, Privat, ASP.NET, .NET , , ,

Korrekt visning af æøå - character encoding

31. juli 2009

I en tidligere artikel her på siden forsøgte jeg at komme med et opråb om korrekt brug af DOCTYPE og valid kode da det så ud til, at mange havde deres egen definition af, hvordan HTML kode kunne struktureres - og lige som om at det ser ud til at problemer med DOCTYPE og invalid HTML er lidt et "mode-problem" i øjeblikket lader det også til, at mange har problemer med deres encoding (tegnsæt) selvom dette også er en udfordring, der har eksisteret så længe man har skrevet web - så nu må vi hellere forsøge at rettet op på det.

Hvad består fejlen i og hvornår

Kort fortalt handler problemet med encoding om, at hvis man ikke har styr på encodingen i sin applikation og fx kører med flere forskellige i de forskellige lag i applikationen, så er der stor risiko for problemer med æøå og andre special karakterer på sin hjemmeside. Problemet omhandler ikke kun dynamiske applikationer - også simple statiske html-sider er ramt. Værst af alt, så er der modsat valid HTML-kodning reelt meget få ting at holde styr på her, men heldigvis er løsningerne langt hen ad vejen også rimelig simple.

Selvom det ikke bliver til meget kode, vil jeg i artiklen tage udgangspunkt i ASP.NET bare for at have et fast holdepunkt, men udfordringerne med korrekt tegnsæt kan lige så vel ramme klassisk ASP, PHP og altså selv simpel HTML.

Valg af encoding

Der findes ikke et endeligt svar på hvilken encoding man skal gå efter - men jeg kan ikke komme på en grund til ikke at vælge UTF-8 (Unicode Transformation Format 8-bit) hvorimod der er flere grunde til ikke at vælge det i Danmark nok mest kendte og brugte, ISO-8859-1, da det fx må betragtes som værende forældet da flere JavaScript-funktioner er udgået til fordel for tilsvarende Unicode-funktioner. Så selvom ISO-8859-1 flere steder anses som dét man benytter på hjemmesider i Danmark og vesteuropa så er min mening, at UTF-8 er det eneste rigtige valg i dag.

Encoding på filen

Det første sted at starte med det korrekte tegnsæt er allerede når vi laver vores filer, og det er nok her de fleste går fejl da de færreste er opmærksom på, at det har noget at sige og måske endda slet ikke er opmærksom på, at filer kan overhovedet gemmes med forskellig encoding. Visual Studio opretter som udgangspunkt filer i UTF-8, så i bund og grund behøver vi ikke bekymre os om det store her efter vores valg af netop UTF-8, men vil vi gerne være sikre kan vi åbne vores fil og klikke på Advanced Save Options i File-menuen for at se filens nuværende format og eventuelt gemme den med en ny encoding. Notepad er er også et godt lille program til at se filens encoding i, nemlig ved at klikke på Gem som og se hvilket format der står under Kodning - det "farlige" ved Notepad er, at de fleste vil opleve, at den som standard gemmer nye filer i ANSI og ikke UTF-8, og genkender Notepad ikke formatet på den fil man åbner vil det derfor også være ANSI, der gemmes i hvis man gemmer filen igen og dermed overskrive man det eksisterende format.


Encoding i HTML koden

Dette punkt er det nok de færreste der er i tvivl om så det bliver meget kort - i HTML kan vi benytte meta til at sende informationer om vores dokument til klienten, og en af de informationer vi bør sende med er Content-Type;

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

Encoding på data(basen)

Langt de fleste applikationer i dag er dynamisk opbygget ud fra data i et lager - mest oplagt selvfølgelig en database, som er det jeg vil tage udgangspunkt i selvom det bliver relativt kort da korrekt database-opsætning kan være en videnskab i sig selv - og her er encoding altså bestemt heller ikke uden betydning.

Præcis hvordan encodingen, og dataene generelt, håndteres her er lidt forskelligt fra den ene database til den anden så sæt dig godt ind i den database du arbejder med. Taler vi MySQL kan man sætte tegnsættet direkte på databasen og i MSSQL kan lidt af styringen ligge i valg af datatype, hvor nchar, nvarchar og ntext til unicode (hvilket reelt vil sige, at den valgte collation kun styrer sortering og ikke tegnsæt) og char, nvarchar og text til ikke-unicode (som altså styres af collation så æøå kun kan gemmes ved korrekt valg af collation), men her er det også værd at overveje performance ind i valget da unicode datatyperne er tungere at arbejde.

Men ellers ligger det vigtigste tiltag omkring encoding-opsætningen i valget af ens collation - collations handler ikke kun om encoding men også om sortering og sammenligning af data - et simpelt eksempel på dette kunne være en numeriske collation, der sorterer 1 før 2 eller en karakter collation, der bestemmer om hvorvidt a er det samme som á eller ej, men det er ikke desto mindre vigtigt at tage stilling til.

Encoding fra serveren

Det sidste sted det kan gå galt er i forhold til hvilken encoding serveren sender dokumentet afsted i - og her ved jeg at flere webhoteller vælger at sender ISO-8859-1 som standard da det stadig er det format langt de fleste vælger og dette betyder altså, at vi har noget at skal tage forhold for med vores valg af UTF-8. Heldigvis er det ret simpel at styre da vi i vores web-config kan tilføje følgende til at overskrive webhotellets valg;

<system.web>
  <
globalization responseEncoding="utf-8" requestEncoding="utf-8" fileEncoding="utf-8" />
</system.web>


Tilsvarende muligheder har vi selvfølgelig til rådighed i fx PHP gennem .htaccess.

Vil du se hvad serveren sender af sted sammenlignet med din meta kan du gå ind på http://validator.w3.org/, vælge More Options og sætte hak i Verbose Output før du validerer dit dokument - er der uoverenstemmelser mellem dokumentets og serverens format vil du her få besked om det.

Ikke flere problemer med æøå

Nu har vi endelig fået styr på hvilke 4 flaskehalse der er for at få æøå vist korrekt på vores hjemmeside - så dem ser vi selvfølgelig ikke flere af fremover :)

(X)HTML, ASP, ASP.NET

FileSystemObject - FSO

24. oktober 2008
Der er ofte behov for at få adgang til filer på serveren fx i forbindelse med manipulering af filer, mapper og drev - og her er FileSystemObject (FSO) værktøjet vi har behov. Med FSO har vi blandt andet mulighed for at oprette, rette, slette samt læse, slette og skrive til og fra filer og mapper. Jeg vil ikke komme ind på egentlige scenarier hvor FSO er det rigtige valg men derimod gå direkte til nogle eksempler.

Vis alle drev

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Sætter en objekt med samlingen af drev
Set DRIVES = FSO.Drives
'Looper drev igennem
For Each DRIVE In DRIVES
    'Udskriver drev-bogstavet
    Response.Write DRIVE.DriveLetter & "<br>"
Next
Set DRIVES = Nothing
Set
FSO = Nothing
%>

Vis oplysninger om et drev

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Definering af hvilket drev vi vil kigge på
Set DRIVE = FSO.GetDrive("c:")
'Vi omdøber lige drevtypen fra et tal til et forståeligt navn
Select Case DRIVE.DriveType
   
Case 0: DriveType = "Ukendt"
   
Case 1: DriveType = "Flytbart medie"
   
Case 2: DriveType = "Fastsiddende"
   
Case 3: DriveType = "Netværksdrev"
   
Case 4: DriveType = "CD-ROM"
   
Case 5: DriveType = "RAM-disk"
End Select
'Og så udskriver vi informationerne
Response.Write "Drev bogstav: " & DRIVE.DriveLetter & "<br />"
Response.Write "Drev type: " & DriveType & "<br />"
Response.Write "Ledig plads i KB på drev: " & FormatNumber(DRIVE.AvailableSpace / 1024, 0) & "<br />"
Response.Write "Plads i alt i KB på drev: " & FormatNumber(DRIVE.TotalSize / 1024, 0) & "<br />"
Response.Write "Drev serienummer: " & DRIVE.SerialNumber & "<br />"
Response.Write "Drev delingsnavn: " & DRIVE.ShareName & "<br />"
Set DRIVE = Nothing
Set
FSO = Nothing
%>

Opret en mappe

<%
'Oprettelse af selve FSO-objektet, der gør alt dette muligt
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Oprettelse af mappen
FSO.CreateFolder("c:\dinmappe")
'Og selvfølgelig husker vi at lukke vores FSO-objekt igen
Set FSO = Nothing
%>

Kopier en mappe

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Kopiering af mappe
FSO.CopyFolder "c:\dingamlemappe\", "c:\dinnyemappe\"
Set FSO = Nothing
%>

Flyt en mappe 

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Flytning af mappe
FSO.MoveFolder "c:\dingamlemappe\", "c:\dinnyemappe\"
Set FSO = Nothing
%>

Slet en mappe

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Sletning af mappen
FSO.DeleteFolder("c:\dinmappe")
Set FSO = Nothing
%>

Undersøg om mappe eksisterer

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Tjek om mappe findes
If FSO.FolderExists("c:\dinmappe\") Then
   
'udfør handling hvis mappen findes
   
Response.Write "Mappen findes"
Else
   
'udfør anden handling hvis mappen ikke findes
   
Response.Write "Mappen findes ikke"
End If
Set
FSO = Nothing
%>

Vis alle filer i en mappe

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Definering af hvilken mappe vi vil kigge i
Set FOLDER = FSO.GetFolder("c:\dinmappe\")
'udskriv filer i mappen
For Each FILE In FOLDER.Files
    Response.Write FILE.Name &
"<br />"
Next
'Igen husker vi at destruere alle de objekter være laver
Set FILE = Nothing
Set
FSO = Nothing
%>

Vis alle mapper i en mappe

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Definering af hvilken mappe vi vil kigge i
Set FOLDER = FSO.GetFolder("c:\dinmappe\")
'udskriv mapper i mappen
For Each SUBFOLDER In FOLDER.SubFolders
    Response.Write SubFolder.Name &
"<br>"
Next
Set FOLDER = Nothing
Set
FSO = Nothing
%>

Vis oplysninger om en mappe

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Definering af hvilken mappe vi vil kigge på
Set FOLDER = FSO.GetFolder("c:\dinmappe\")
'Udskriv mappens oplysninger
Response.Write
"Mappens navn: " & FOLDER.Name & "<br />"
Response.Write "Mappens type: " & FOLDER.Type & "<br />"
Response.Write "Mappens størrelse i bytes: " & FormatNumber(FOLDER.Size, 0) & "<br />"
Response.Write "Mappen er oprettet: " & FOLDER.DateCreated & "<br />"
Response.Write "Mappen er sidst redigeret: " & FOLDER.DateLastModified & "<br />"
Response.Write "Mappen er sidst tilgået: " & FOLDER.DateLastAccessed
Set FOLDER = Nothing
Set FSO = Nothing
%>

Opret en fil
Bemærk, at det selvfølgelig ikke behøver at være .txt-filer vi arbejder med - det kunne lige så godt være fx .xml eller .html.

<%
Set
FSO = Server.CreateObject("Scripting.FileSystemObject")
'Oprettelse af fil
FSO.CreateTextFile("c:\dinfil.txt")
Set FSO = Nothing
%>

Kopier en fil

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Kopiering af fil
FSO.CopyFile "c:\dingamlemappe\dinfil.txt", "c:\dinnyemappe\dinfil.txt"
Set FSO = Nothing
%>

Flyt en fil

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Flytning af fil
FSO.MoveFile "c:\dingamlemappe\dinfil.txt", "c:\dinnyemappe\dinfil.txt"
Set FSO = Nothing
%>

Slet en fil

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Sletning af fil
FSO.DeleteFile("c:\dinfil.txt")
Set FSO = Nothing
%>

Undersøg om fil eksisterer

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Tjek om fil findes
If FSO.FileExists("c:\dinmappe\dinfil.txt") Then
    'Udfør handling hvis filen findes
    Response.Write "Fil findes"
Else
   
'Udfør anden handling hvis filen ikke findes
   
Response.Write "Fil findes ikke"
End If
Set
FSO = Nothing
%>

Vis oplysninger om en fil

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Definering af hvilken fil vi vil kigge på
Set FILE = FSO.GetFile("c:\dinmappe\dinfil.txt")
Response.Write
"Filens navn: " & FILE.Name & "<br />"
Response.Write "Filens type: " & FILE.Type & "<br />"
Response.Write "Filens størrelse: " & FormatNumber(FILE.Size, 0) & " bytes<br />"
Response.Write "Filen er oprettet: " & FILE.DateCreated & "<br />"
Response.Write "Filen er sidst redigeret: " & FILE.DateLastModified & "<br />"
Response.Write "Filen er sidst tilg†et: " & FILE.DateLastAccessed
Set FILE = Nothing
Set FSO = Nothing
%>

Skriv enkelt linie til fil

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Definering af hvilken fil vi vil skrive til
Set FILE = FSO.OpenTextFile("c:\dinmappe\dinfil.txt", 2)
FILE.WriteLine(
"Din linie tekst")
FILE.Close
Set FILE = Nothing
Set FSO = Nothing
%>

Skriv flere linier til en fil

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Definering af hvilken fil vi vil skrive i
Set FILE = FSO.OpenTextFile("c:\dinmappe\dinfil.txt", 2)
FILE.Write(
"Din første linie" & VbCrLf & "Din anden linie")
FILE.Close
Set FILE = Nothing
Set FSO = Nothing
%>

Læs linie for linie fra en fil

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Definering af hvilken fil vi vil læse fra
Set FILE = FSO.OpenTextFile("c:\dinmappe\dinfil.txt", 1)
'Loop linierne igennem
Do While Not FILE.AtEndOfStream
'Udskriv hver enkelt linie
Response.Write FILE.Readline & "<br />"
Loop
FILE.Close
Set
FILE = Nothing
Set
FSO = Nothing
%>

Læs hele indholdet fra en fil

<%
Set FSO = Server.CreateObject("Scripting.FileSystemObject")
'Definering af hvilken fil vi vil skrive i
Set FILE = FSO.OpenTextFile("c:\dinmappe\dinfil.txt", 1)
'Udskriv alt indhold fra filen
Response.Write FILE.ReadAll
FILE.Close
Set FILE = Nothing
Set
FSO = Nothing
%>

Ovenstående eksempler er kun nogle relativt simple eksempler og de peger alle direkte på c: drevet hvilket (forhåbentlig) ikke er tilladt at skrive til eller læse fra på et webhotel - ligger du på et webhotel vil du kunne drage stor fordel af at benytte Server.MapPath i forbindelse med angivelse af mappe- og drev placeringer. Nærmer information omkring FSO kan findes på MSDN (http://msdn.microsoft.com/en-us/library/6kxy1a51(VS.85).aspx).

ASP , ,

SQL injections - mere end bare et pling

14. juli 2008

Den seneste tid har jeg rundt omkring i diverse fora og nyhedssites set en masse tråde, flere end hvad jeg husker er normalt og derfor denne artikel, omkring sites hvis udseende eller indhold pludselig er blevet ændret - og i mange tilfælde skælder man ud på fx webhotellet fordi man mener at der her er et sikkerhedshul. I langt de fleste tilfælde skyldes det dog formentlig bare dårlig kode; deres sites er nemlig åben for SQL injections.

SQL Injections er et spørgsmål om, at ens applikation er åben over for, at udefrakommende personer med triste hensigter kan udnytte huller i ens kode til at eksekvere utiltænkte databasekald - hvilket kan føre til usikre loginsystemer eller i værste fald direkte manipulering af data i databasen. Før jeg går videre kan du kigge på dette eksempel;

Det normale

Den mest normale opfattelse er, at SQL injection udelukkende handler om at sikre sine databasekald mod bestemte tegn, der kommer fra brugerinput på en side - har vi fx en (usikker) loginformular til et adminsystemt, hvor der skal angives brugernavn og password kunne vores SQL-sætning se sådan ud i ASP;

SQL =
"SELECT id FROM [users] WHERE [username] = '" & Request.Form("txtusername") & "' AND [password] = '" & Request.Form("txtpassword") & "'"

Denne formular ville kunne udnyttes ved fx at skrive ' OR 1=1; -- i txtusername-formfeltet og det ville resultere i dette databasekald;

SELECT id FROM [users] WHERE [username] = '' OR 1=1; --' AND [password] = 'xxx'

I dette tilfælde vil alt efter -- bliver ignoreret, tilbage står kun sammenligningen at username skal være lig '' eller at 1 skal være lig 1 og da det jo er en korrekt sammenligning har vi lige udnyttet muligheden for SQL injection og logget os ind med fri adgang til adminsystemet.

Sikringen af vores loginformular er - i hvert fald ifølge de fleste artikler om emnet - ret lige til da vi bare skal sikre os imod pling (') da det er dette tegn der laver al balladen. Dette kan være gøre vha en simpel replace i vores SQL;

SQL = "SELECT id FROM [users] WHERE [username] = '" & Replace(Request.Form("txtusername"),"'","''") & "' AND [password] = '" & Replace(Request.Form("txtpassword"),"'","''") & "'"

Nu vil forsøget med at indtaste ' OR 1=1; -- i txtusername-formfeltet resultere i en afvisning af loginforsøget (medmindre der da er en bruger der hedder sådan) da vores replace sikrer at plingen i brugernavnet escapes så den rigtige sammenligning sker.

Men vi mangler stadig noget

Det er somregel her de fleste stopper - men så prøv at se nedenstående eksempel. Først har vi vores SQL;

SQL = "SELECT name, description FROM product WHERE id = " & Request.Querystring("id")

Siden hvor ovenstående SQL benyttes kalder vi så vha;

domain.dk/produkt.asp?id=3

Fint nok, nu får vi produkt nummer 3 frem - men hvad sker der så nu;

domain.dk/produkt.asp?id=3;DELETE * FROM product

Nu får vi rigtig nok stadig produkt nummer 3 vist på siden men vores reele databasekald kommer til at se sådan ud;

SELECT name, description FROM product WHERE id = 3;DELETE * FROM product

Hvilket er et tilladt kald og kommer den igennem slettes alle data fra tabellen produkt umiddelbart efter vores select er udført. Ikke godt.

Løsningen 

Som det kan ses ud fra ovenstående eksempler er det altså nødvendigt at sikre sig imod SQL injections da det ellers kan få fatale konsekvenser overfor hele databasen. En mulighed er at validere alle data inden de bruges til databasekald - dvs fx sikre sig, at variablen fra ens querystring også virkelig er et tal hvis det skal bruges til at trække data ud fra product-tabellen. Men når nu der findes en bedre løsning, parameters, hvorfor så ikke benytte den? Parametre gør, at man ikke behøver at bekymre sig om SQL injection og selvom det ved første øjekast måske virker lidt mindre tilgængeligt end de sædvanlige simple SQL-statements så er de bestemt til at arbejde med og på sigt kan de endda gøre arbejdet lettere da de giver mere gennemsigtighed over for databasen. Der kunne skrives en hel artikel bare om at arbejde med parametre (og det kommer der måske også), men i første omgang vil jeg bare lægge en muligh løsning for vores to cases ovenover.

Eksempel på login

<%
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=E:\test\test.mdb"
Set Cmd = Server.CreateObject("ADODB.Command")
Cmd.ActiveConnection = Conn
Cmd.CommandText =
"SELECT id FROM [users] WHERE [username] = @username AND [password] = @password"
Cmd.Parameters.Append(Cmd.CreateParameter("@username", 200, 1, 50))
Cmd.Parameters.Append(Cmd.CreateParameter(
"@password", 200, 1, 50))
Cmd.Parameters(
"@username") = Request.Form("username")
Cmd.Parameters("@password") = Request.Form("password")
Set login = Cmd.Execute
  'Gør noget med login("id")
login.Close
Set login = Nothing
Set
Cmd = Nothing
Conn.Close
Set Conn = Nothing
%>

Eksempel på querystring

<%
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=E:\test\test.mdb"
Set Cmd = Server.CreateObject("ADODB.Command")
Cmd.ActiveConnection = Conn
Cmd.CommandText =
"SELECT navn FROM [product] WHERE [id] = @id"
Cmd.Parameters.Append(Cmd.CreateParameter("@id", 3, 1, 50, Request.Querystring("id")))
Set product = Cmd.Execute
If Not product.eof Then
 
'Gør noget med product("navn")
End If
product.Close
Set product = Nothing
Set
Cmd = Nothing
Conn.Close
Set Conn = Nothing
%>

Jeg håber at denne artikel har givet en god idé både omkring hvad SQL injections er samt ikke mindst forslag på løsninger.

ASP, ASP.NET , ,

Pæn og brugervenlig URL med ASP

25. juni 2008
A) web-dev.dk/includes/articles.asp?category=45670&id=34802
eller
B) web-dev.dk/ASP/brugervenlig_url

Ja, der er vist ikke den store tvivl om hvilken URL af de to ovenstående der er den mest brugervenlige - både når det kommer til at kunne forstå hvad siden, formentlig, handler om samt muligheden for at huske adressen til senere vil mulighed B give langt flere fordele. Og selvfølgelig skal vi heller ikke glemme søgemaskinerne, for dem vil vi selvfølgelig gerne ligge så godt placeret på som muligt, og med en pæn og forståelig URL har vi gjort en smule mere til at vores side bliver lidt bedre forstået og dermed forhåbentlig også bedre placeret.

I modsætning til PHP og ASP.NET har vi i klassisk ASP desværre ikke de store muligheder for uden videre at gå ind og arbejde med URL'en, altså en URL rewrite. I PHP har vi mod_rewrite og i ASP.NET har vi flere muligheder, bla direkte i vores web.config - men i ASP er der ingenting indbygget og de fleste anbefalinger når man spørger rundt omkring går på at man skal benytte en ISAPI rewriter, altså et tredieparts komponent, der, udover formentlig at koste en del, kræver installation på serveren og det har vi meget sjældent adgang til på vores webhoteller.

Den metode jeg vil vise her kræver en custom 404 fejl-side på vores domæne - administrerer vi selv vores IIS eller har vi adgang gennem et kontrolpanel som fx Plesk kan vi selv gå ind foretage de nødvendige ændringer, ellers er vi desværre stadig afhængig af, at webhotellet vil oprette sådan en side for os, men det er dog heldigvis de fleste seriøse steder der vil gøre dette. Før jeg kommer til noget egentlig kode skal det lige siges, at det er nogle forholdsvis simple eksempler jeg kommer med - koden kan variere meget fx afhængig af hvordan databasen er struktureret så der er ikke et egentlig facit, men forhåbentlig kan eksemplerne lede dig på rette spor.

Kendte sidenavne

Første forsøg viser en struktur, hvor vi kun arbejder med statiske sider i en en-til-en mapning relation (altså én redirect for hver url/asp-fil) i vores fejl-side. Her skal vi ind og ændre i vores kode for hver gang vi opretter en ny asp-fil - så denne kode alene opnår man nok ikke alverden med så se det som et eksempel eller som en mulighed i kombination med noget større.

404.asp

<%
'Her fanger vi den string (fx "404;http://domain.dk:80/kontakt"), der bliver sendt med til vores fejlside og som altså indeholder den originale URL. Vi er kun interesseret i en del af den stringen og om den har en rigtig struktur
splitURL = Split(Request.ServerVariables("QUERY_STRING"), ";")

'Her sætter vi en standard side vi redirecter til hvis vi ikke finder en korrekt side
REDIRECT = "sidenfindesikke.asp"

'Først tjekker vi om der er nogle parametre med overhovedet - altså den del efter ;-tegnet
If UBound(splitURL) > 0 Then
 
'Hvis der er, så tager vi fat i den del vi er interesseret i og deler den op i flere bidder så vi kan arbejde videre med informationerne
 
splitLINK = Split(splitURL(1), "/")
 
'Så tjekker vi igennem for at finde den rigtige side at redirecte til
 
If LCase(splitLINK(3)) = "kontakt" Then
   
'Vores kontakt-side
   
REDIRECT = "contact.asp"
 
ElseIf LCase(splitLINK(3)) = "galleri" Then
   
'Galleri-siden
   
REDIRECT = "gallery.asp"
 
ElseIf LCase(splitLINK(3)) = "omos" Then
   
'Om os
   
REDIRECT = "about.asp"
 
Else
   
'Hvis ingen af ovenstående matcher lander vi på forsiden
   
REDIRECT = "default.asp"
 
End If
End If

'Til sidst redirecter vi uden at URL'en ændrer sig
Server.Transfer("/" & REDIRECT)
%>


Dynamik fra databasen

I dette eksempel forestiller vi os at have en database, der tillader administrator af vores side at oprette hovedsider og undersider i ét niveau under hovedsiderne. Vores menu-struktur kunne fx se således ud og med URL'en angivet parentes;

Velkommen (/velkommen)
Kategorier (/kategorier)
 - SEO (/kategorier/seo)
 - ASP (/kategorier/asp)
 - CSS (/kategorier/css)
Tags (/tags)
 - ASP (/tags/asp)
 - SEO (/tags/seo)
Kontakt os (/kontakt.asp)

Vores fejl-side kunne så håndtere strukturen således.

404.asp

<%
splitURL = Split(Request.ServerVariables("QUERY_STRING"), ";")
REDIRECT = "sidenfindesikke.asp"

If UBound(splitURL) > 0 Then
 
SplittetURL = splitURL(1)
  'Først vil vi sikre os at vores URL aldrig slutter på / da dette kan forvirre vores udregning når vi skal finde antallet af niveauer
 
If Right(SplittetURL, 1) = "/" Then
   
SplittetURL = Left(SplittetURL, Len(SplittetURL) - 1)
  End If
 
splitLINK = Split(SplittetURL, "/")

 
If LCase(splitLINK(3)) = "kontakt" Then
   
'Vores kontakt-side
    REDIRECT = "contact.asp"
  
ElseIf UBound(splitLINK) = 3 Then
   
'Hvis der kun findes ét niveau i vores URL skal vi til vores kategori-side
   
REDIRECT = "category.asp"
 
ElseIf UBound(splitLINK) = 4 Then
   
'Hvis der er to niveauer i vores URL skal vi hen til vores underside
   
REDIRECT = "subpage.asp"
 
End If
End If

Server.Transfer("/" & REDIRECT)
%>

For at have så få database-opslag som muligt laver vi ingenting i vores 404-side udover at viderestille og så lader vi den side vi redirecter til foretage den egentlig validering af om det er en valid URL. Man kunne selvfølgelig sagtens foretage en masse validering op imod databasen i 404-siden men i dette eksempel vil det ikke være nødvendigt.

Nu mangler vi så kun en side der kan vise noget indhold.

category.asp

<!--#include file="constants/dbopen.asp" -->
<%
splitURL = Split(Request.ServerVariables(
"QUERY_STRING"), ";")

'Her sættes en variabel der bestemmer om siden er valid eller ej - som standard er siden ikke valid
SITEERROR = True

If UBound(splitURL) > 0 Then
 
splitLINK = Split(splitURL(1), "/")

 
'Vores SQL hvor vi forsøger at hive de nødvendige værdier ud ud fra parameteren i vores URL
 
SQL = "SELECT id, realname, urlname, description FROM categories WHERE lcase(urlname) = '" & Replace(LCase(splitLINK(3),"'", "''") & "'"
 
Page = Conn.Execute(SQL)

  'Hvis resultatet ikke er tomt gemmer vi værdierne fra databasen i nogle variabler og sætter siden til at være valid
 
If Not Page.eof Then
   
SITEERROR = False
   
ID = Page("id")
   
REALNAME = Page("realname")
    URLNAME = Page(
"urlname")
   
DESCRIPTION = Page("description")
 
End If

 
Page.Close()
  Page = Nothing
End If

'Hvis siden er valid skriver vi vores html
If SITEERROR = False Then
 
'INDHOLD
End If
%>
<!--#include file="constants/dbclose.asp" -->
<%
'Hvis siden ikke er valid viderestiller vi til en fejlside
If SITEERROR = True Then
 
Response.Redirect("/404.asp")
End If
%>

Man kan altså opnå nogle ret avancerede strukturerer i sin fejl-side håndtering, herover altså kun vist nogle ret simple ting, men det giver nogle rigtig gode muligheder for at lave en pæn og læsbar URL for både brugere og søgemaskiner.

Som en lille ekstra-bemærkning skal det også lige tilføjes, at når databasen styrer vores URL vil det være en god idé at lave en URL-kolonne i databasen og hvor indholdet er renset for special-tegn som æøå - det kunne fx give resultatet "roedgroed_med_floede" i stedet for "rødgrød med fløde".

ASP ,

Realtime xml fra asp og database

12. maj 2008

Det sker oftere og oftere at systemer skal udveksle data med hinanden og det sker for det meste i form af XML, men det lader desværre til at mange enten virker bange for at komme i gang med et udvikle noget med XML eller også at det bare er en for stor mundfuld.

Derfor forsøger jeg her at komme med et lille eksempel på hvordan man ved hjælp af ASP kan lave en realtime XML fil med data fra en database. Der er ikke så meget at forklare til nedenstående kode - det er bare at komme i gang, læse de små kommentarer i scriptet og så ellers få det tilrettet til sit eget behov.

xml.asp

<%
Response.Expires = -1
Response.AddHeader
"pragma", "no-cache"
Response.CacheControl = "no-cache"
Response.AddHeader "cache-control", "no-store"
response.Charset = "UTF-8"
Response.ContentType = "text/xml" 'fortæl at dette er et xml dokument

Set xmldoc = Server.CreateObject("Microsoft.XMLDOM") 'opret xml dokument objekt
xmldoc.preserveWhiteSpace = true

Set
rootelement = xmldoc.createElement("images") 'opret root element
xmldoc.appendChild rootelement

'opret forbindelse til databasen
Set Conn = Server.CreateObject("ADODB.Connection")
Conn.Open
"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Server.Mappath("/database.mdb")

SQL =
"SELECT id, billede FROM mintabel" 'vores sql
Set pics = Conn.Execute(SQL)

Do While Not pics.eof 'Loop recordsettet igennem
  Set picelement = xmldoc.createElement("pic") 'opret parent node
 
rootelement.appendChild picelement

  Set idelement = xmldoc.createElement("id") 'opret id element
 
idelement.Text = pics("id") 'sæt værdi fra databasen
 
picelement.appendChild idelement

 
Set imageelement = xmldoc.createElement("image") 'opret image element
 
imageelement.Text = pics("picture") 'sæt værdi fra databasen
 
picelement.appendChild imageelement

  pics.movenext
Loop

'husk at luk recordset
pics.Close
Set pics = Nothing

Set
p = xmldoc.createProcessingInstruction("xml","version='1.0' encoding='UTF-8'")
xmldoc.insertBefore p,xmldoc.childNodes(0)

Response.Write xmldoc.Xml
'udskriv dokumentet

Set xmldoc = Nothing 'husk at luk xml objektet

'husk at luk database forbindelsen
Conn.Close
Set Conn = Nothing
%>

Når denne side bliver kaldt bliver XML filen genereret realtime så vi har hele tiden et opdateret data-ark vi nu kan sende til samarbejdspartnere etc.

ASP ,

Masterpage look-a-like i ASP

8. marts 2008

Selvom ASP efterhånden er ved langsomt at dø hen til fordel for .NET sidder mange stadig og udvikler eller vedligeholder i ASP - det er der for så vidt intet i vejen med, men mange glemmer stadig bare enten at gøre opdateringer let ved at dele siden op i mindre genbrugelig stykker eller overser de mere bløde ting som fx dynamisk title, hvilket søgemaskiner er glade for og også må kunne kategoriseres som brugervenligt.

Genbrugeligheden løses normalt ved Server.Execute, hvor man fx styrer hele siden ud fra en default.asp med querystrings, eller includes, hvor det mest normale er at inkludere fx top, bund og navigation - og det er der for så vidt selvfølgelig intet i vejen med, men de sidste småting kommer i de fleste tilfælde til at mangle ved sådan en løsning. Så hvorfor nøjes når man kan få mere :)

En måde at løse dette på indebærer stadig includes, men det på en måde, der kan give meget mere frie hænder, primært i form af dynamik i retning af styring af title, meta og inkludering af javascripts; Vi laver simpelthen to funktioner vi kan kalde og som indeholder alt det genbrugelige samt kan modtage parametre til at styre dynamikken - en slags skabelon for hele vores side som man måske også kender det fra fx Frontpage eller MasterPages i ASP.NET.

Først opretter vi lige en meget simpel skabelon - layout.asp

<%
Sub startPage(title,keywords,description,js)
 
'Hvis ingen titel er sendt med sætter vi en standard titel
 
If title = "" Then
   
title = "standard title"
 
End If
 
'Hvis ingen keywords er sendt med sætter vi et par standard keywords
 
If keywords = "" Then
   
keywords = "standard keywords"
 
End If
 
'Hvis ingen description er sendt med sætter vi en beskrivelse
 
If description = "" Then
   
description = "standard description"
 
End If
%>
<!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>
 
<title><%=title%></title>
 
<meta name="keywords" content="<%=keywords%>" />
 
<meta name="description" content="<%=description%>" />
 
<link rel="stylesheet" href="stylesheet.css" type="text/css" />
 
<%=js%>
</head>
<
body>

<
div class="content">
<%
End Sub

Sub
endPage()
%>
</div>

</
body>
</
html>
<%
End Sub
%>

Mit layout er, som de fleste nok kan gennemskue, ret simpelt da jeg kun har én div - men ellers ville jeg lægge alt design inde i disse to funktioner, dvs top, bund, navigering osv. Det vigtige her er, at den div/td, der vil komme til at indeholde det egentlige indhold starter i den første funktion, startPage, og slutter i den sidste funktion, endPage.

De skarpe kan nok også se, at min første funktion tager imod parametrene title, keywords, description samt js og disse variabler bliver altså brugt til netop disse 4 ting.

Slutteligt skal vi have implementeret vores skabelon på vores sider - default.asp

<!--#include file="layout.asp" -->
<%
Call startPage("Sidens titel","masterpage, lookalike, cool","Cool eksempel på en MasterPage look-a-like til ASP.","<script type=""text/javascript"" src=""javascript.js""></script>")
%>

Sidens indhold

<%
Call endPage()
%>

Alt vi skal gøre så snart vores skabelon er oprettet er at inkludere den på vores sider og lige før samt lige efter vores egentlig indhold på siden kalder vi de to funktioner fra vores layout.asp.

Hver side kan nu få unik title og meta og vi kan overføre javascripts til at blive placeret korrekt, altså i headeren. Udvidder vi funktionaliteten kan vi fx også komme til at overføre onload parametre til body eller variabler, der bestemmer om elementer i vores skabelon skal vises eller ej.

Jeg håber mit eksempel er til at gennemskue og at det kan bringe nytte rundt omkring - jeg har i hvert fald sparet masser af tid og bragt en masse både dynamik og genbrugelighed ind i løsninger ved at benytte denne metode fremfor de mere gængse, der ikke kan give mig de helt samme resultater.

ASP , ,