Oprettelse af klasser on-the-fly

Denne artikel tager udgangspunkt i funktionen ExecuteGlobal og udnytter at man med den kan køre VBScript-kode fra en streng.

Idéen er helt konkret, at man opbygger en class som gør adgangen til et given sæt oplysninger mere strømlinet, således grænsefladen til det man arbejder med bliver simplere. Dette kunne f.eks. benyttes i forbindelse med post af en formular, hvor værdierne i formularen kunne omsættes til direkte egenskaber på et objekt i stedet for at man skal igennem request.form. Ydermere kunne der lægges standardvalidering ind, således man slap for at tage stilling til om det grundlæggende format var korrekt. Et eksempel på en måde at aflæse formularfelter via sådan en klasse:

if Form.IsValid then
  Response.Write Form.Navn & "<br>"
  Response.Write Form.Adresse & "<br>"
  Response.Write Form.Postnr & " "
  Response.Write Form.Bynavn & "<br>"
else
  Response.Write Form.ValideringsfejlSomListe
  Response.End
end if

I stedet for denne traditionelle måde:

if len(Request.Form("Navn") & "") > 0 and len(Request.Form("Adresse") & "") > 0 then
  Response.Write Request.Form("Navn") & "<br>"
  Response.Write Request.Form("Adresse") & "<br>"
  Response.Write Request.Form("Postnr") & " "
  Response.Write Request.Form("Bynavn") & "<br>"
else
  Response.Write "Der er opstået følgende fejl:<br>"
  Response.Write "<ul>"
  if len(Request.Form("Navn") & "") = 0 then
    Response.Write "<li>Navn skal være udfyldt.</li>"
  end if
  if len(Request.Form("Adresse") & "") = 0 then
    Response.Write "<li>Adresse skal være udfyldt.</li>"
  end if
  Response.Write "</ul>"
  Response.End
end if

Ovenstående eksempel tager stilling til en ultrasimpel validering af felterne på formularen, hvilket objektet Form kan bygges til at varetage. På den måde skal man bare huske at tjekke IsValid på Form-objektet inden man arbejder videre med formulardata. Hvis der opstår fejl kan man aflæse disse fra ValideringsfejlSomListe (der udskriver en uordnet liste med alle fejlbeskeder).

Nu har jeg skitseret hvordan jeg kunne tænke mig objektet skal benyttes. Nu vil resten af artiklen forsøge at skitsere en måde til at definere hvordan klassen skal genereres, samt et konkret forslag til selve genereringen af klassen.

Hvis jeg lige skal give et bud på generering af en klasse på grundlag af en given formular (altså en generisk metode til at skabe en klasse til alle formularer, undtagen dem hvor der sendes filer med), så kunne det være noget i stil med denne:

function getFormClass()
  dim fc, privates, props, inits

  for each fld in Request.Form
    privates = privates & "private m_" & fld & vbCrLf
    props = props & "public property get " & _
             fld & ": " & fld & "=m_" & fld & _
             ": end property" & vbCrLf
    inits = inits & "m_" & fld & "=Request.Form(""" & fld & """)&""""" & vbCrLf
  next

  fc = "class CForm" & vbCrLf
  fc = fc & privates
  fc = fc & "private sub class_initialize()" & vbCrLf
  fc = fc & inits
  fc = fc & "end sub" & vbCrLf
  fc = fc & props
  fc = fc & "end class"

  ExecuteGlobal fc

  set getFormClass = new CForm
end function

Form-objektet kan nu hentes ved at kalde funktionen således:

set Form = getFormClass()

Egenskaberne på Form er nu afhængige af hvilke felter din formular indeholder! Bemærk at alle felter betragtes som strenge.

Ulemperne ved denne version er

  1. at vi ikke kan garantere at et felt rent faktisk findes i Form-objektet, da nogle webkontroller ikke bliver sendt med i et postback, hvis de ikke er valgt.
  2. at hvis der ikke er tale om et postback, så vil formobjektet slet ikke indeholde nogen felter og der vil opstå fejl, hvis man forsøger at aflæse objektets felter.

Ad 1: Dette gælder f.eks. checkbokse og radiobuttons. Det betyder at vi ikke bare kan bruge navnet på disse kontroltyper uden at tjekke om de findes først. I den nuværende udgave har Form-objektet ikke nogen måde (ud over exception-handling), hvorpå den kan signalere om et felt findes eller ej.

Der er flere muligheder for at komme omkring denne udfordring. En måde er at indføre en funktion der kan oplyse om et felt findes eller ej. Det kunne f.eks. gøres således:

if Form.Exists("checkbox1") then
  Response.Write Form.Checkbox1 & "<br>"
end if

Denne metode skulle kun benyttes for de felter, hvor der er risiko for at de ikke eksisterer i Form-objektet.

Ad 2: dette er et stort problem ifht. den almindelige anvendelighed. Derfor bør der findes en løsning på dette. En måde kunne være at definere en struktur med oplysninger om de felter man altid ønskede at have adgang til i Form-objektet og så generere Form-objektet på grundlag af denne struktur. Strukturen kunne i samme ombæring have oplysninger om valideringer for de enkelte felter.

Denne løsning er mere vedligeholdelseskrævende, da man skal tage stilling til hver enkelt formulars indhold og sætte sig ned for at definere en masse omkring formularen. Spørgsmålet er, om man i virkeligheden ikke skal det alligevel og så er det måske ikke så ringe at få lidt struktur på det med sådan en definition...

Jeg har strikket et koncept sammen til validering af formularer i javascript og tænker at noget af det samme kan anvendes på serveren (hvis man ligefrem kunne anvende den samme struktur til at generere klientvalideringen, var det jo optimalt!). En mulig sæt af strukturer kunne derfor være:

class CFormValidering
  private m_arrFelter

  private sub class_initialize()
    m_arrFelter = Array()
  end sub

  public sub tilfoejFelt(feltnavn)
    tilfoejFeltOgValidering(feltnavn, empty, empty, empty)
  end sub

  public sub tilfoejFeltOgValidering(feltnavn, tekst, parametre, validering)
    dim felt: set felt = new CFelt
    with felt
      .felt = feltnavn
      .tekst = tekst
      .parametre = parametre
      .validering = validering
    end with
    redim preserve m_arrFelter(ubound(m_arrFelter) + 1)
    set m_arrFelter(ubound(m_arrFelter)) = objFelt
  end sub

  public property get felter: felter = m_arrFelter: end property
end class

class CFelt
  private m_felt
  private m_tekst
  private m_parametre
  private m_validering

  public property get felt: felt = m_felt: end property
  public property let felt(value): m_felt = value: end property

  public property get tekst: tekst = m_tekst: end property
  public property let tekst(value): m_tekst = value: end property

  public property get parametre: parametre = m_parametre: end property
  public property let parametre: m_parametre = value: end property

  public property get validering: validering = m_validering: end property
  public property let validering: m_validering = value: end property
end class

Nu kan man så oprette en valideringsstruktur ved at gøre følgende: 

dim r, fv: set fv = new CFormValidering

call fv.tilfoejFeltOgValidering("Navn","Navn skal udfyldes", _
     Array(),"requiredFieldValidator")
call fv.tilfoejFeltOgValidering("Adresse","Adresse skal udfyldes", _
     Array(),"requiredFieldValidator")
call fv.tilfoejFelt("Telefonnr")
call fv.tilfoejFeltOgValidering("Email","E-mail skal være udfyldt",Array(),"")
call fv.tilfoejFeltOgValidering("Email","E-mail skal være gyldig", _
     Array("regex","^[\w\.\-]+@[\w\.\-]+\.[\w\-]+$"),"regularexpressionValidator")

Med denne struktur kan der nu genereres et Form-objekt, der indeholder de felter som forventes (uanset om de så findes i Request.Form eller ej!). Vi kan få dette til at køre ved at tilføje følgende funktion (en tilrettede version af funktionen getFormClass) til objektet CFormValidering:

function getServerForm()
  dim fc, privates, props, inits

  privates = "private m_arrErrors" & vbCrLf
  props = "public property get arrErrors: " & _
          arrErrors = m_arrErrors: end property" & vbCrLf
  inits = "m_arrErrors = Array()" & vbCrlf

  for each fld in m_arrFelter
    privates = privates & "private m_" & fld.felt & vbCrLf
    props = props & "public property get " & fld.felt & ": " & _
              fld.felt & "=m_" & fld.felt & ": end property" & vbCrLf
    inits = inits & "m_" & fld.felt & _
            "=Request.Form(""" & fld.felt & """)&""""" & vbCrLf
    if not isEmpty(fld.validering) then
      valids = valids & "Execute ""res1 = " & _
                 fld.validering & "(" & fld.felt & ")""" & vbCrLf
      valids = valids & "if not res1 then" & vbCrLf
      valids = valids & "call addError(""" & fld.tekst & """)" & vbCrLf
      valids = valids & "res = false" & vbCrLf
      valids = valids & "end if" & vbCrLf
    end if
  next

  fc = "class CForm" & vbCrLf
  fc = fc & privates
  fc = fc & "private sub class_initialize()" & vbCrLf
  fc = fc & inits
  fc = fc & "end sub" & vbCrLf
  fc = fc & "public sub addError(tekst)" & vbCrLf
  fc = fc & "redim preserve m_arrErrors(ubound(m_arrErrors)+1)" & vbCrLf
  fc = fc & "m_arrErrors(ubound(m_arrErrors)) = tekst" & vbCrLf
  fc = fc & "end sub" & vbCrLf
  fc = fc & "public function isValid()" & vbCrLf
  fc = fc & "dim res1, res: res = true" & vbCrLf
  fc = fc & valids
  fc = fc & "isValid = res" & vbCrLf
  fc = fc & "end function" & vbCrLf
  fc = fc & "public function getErrorsAsList()" & vbCrLf
  fc = fc & "dim i, el" & vbCrLf
  fc = fc & "for each e in m_arrErrors" & vbCrLf
  fc = fc & "el = el & "" <li>"" & e & ""</li>"" " & vbCrLf
  fc = fc & "next" & vbCrLf
  fc = fc & "getErrorsAsList = "" <ul> "" & el & "" </ul> """ & vbCrLf
  fc = fc & "end function" & vbCrLf
  fc = fc & props
  fc = fc & "end class"

  ExecuteGlobal fc

  set getServerForm = new CForm
end function

Så skulle det være muligt at kalde getServerForm og få en klasse genereret som tillader at man aflæser alle de felter man har defineret og som muliggør validering og aflæsning af evt. fejl. De fornødne valideringsfunktioner skal naturligvis også laves, således de tager to parametre, nemlig array'et med parametre og selve værdien der skal valideres.

set Form = getServerForm()

if Form.isValid then
  Response.Write Form.Navn & "<br>"
  Response.Write Form.Adresse & "<br>"
  Response.Write Form.Telefonnr & "<br>"
  Response.Write Form.Email & "<br>"
else
  Response.Write Form.getErrorsAsList()
end if
Response.End

Et eksempel på en valideringsform er requiredFieldValidator (der kontrollerer om feltet har noget indhold eller om det er tomt).

function requiredFieldValidator(params, val)
  requiredFieldValidator = len(val) > 0
end function

function regularexpressionValidator(params, val)
  if ubound(params) > 0 then
    if params(0) = "regex" then
      dim rx
      set rx = new RegExp
      rx.Global = true
      rx.IgnoreCase = true
      rx.Pattern = params(1)
      regularexpressionValidator = rx.Execute(val).Count > 0
    else
      regularexpressionValidator = true
    end if
  else
    regularexpressionValidator = true
  end if
end function 

 
Sidst opdateret: 04-09-2010 23:40:33
Tilmeld link | Tilføj Link | Tilføj Link | @-begynder
Erklæring om beskyttelse af personlige oplysninger

nope.dk - Danmarks Website Chart