Archive for tag: MVC

Datastyrede routes i ASP.NET MVC

I disse tider med større og større fokus på SEO-venlighed, kan det være rart, hvis man kan basere sine url'er på data fra eksempelvis sin database.

Man kunne have en produktdatabase, hvor produktet har en unik (url-venlig) streng, som kan anvendes til at finde produktet. I et setup uden nogen specielle regler, kan dette være en udfordring at få gjort stømlinet. Med en routedefinition som denne:

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { 
        controller = "Home", 
        action = "Index", 
        id = UrlParameter.Optional 
    }
);

hvor produkt-controlleren hedder ProduktController, action-metoden til at vise et produkt hedder Vis og produktets urlnavn ligger i id-parameteren, kunne url'en til et produkt se således ud:

/produkt/vis/stor-tallerken-med-sort-kant

Det kan være fint nok ift. SEO, men det ville jo være rart, hvis man kunne slippe for de lidt overflødige controller- og action-tekster, så url'en kom til at se således ud i stedet:

/stor-tallerken-med-sort-kant

Dette kan opnåes med en RouteConstraint og en wildcard-urlparameter. Route-mapping kunne derfor se således ud:

routes.MapRoute(
    name: "Default",
    url: "{id}",
    defaults: new { controller = "Produkt", action = "Vis" },
    constraints: new { id = new ProduktRouteConstraint() }
);

Nu kaldes ProduktRouteConstraint's Match-metode hver gang routing kaldes. Denne Match-metode skal så håndtere et opslag i produktdatabasen for at se, om den angivne id refererer til et eksisterende produkt. Match-metoden returnerer en bool, der indikerer om den aktuelle route skal anvendes eller ej, dvs. om produktet findes i dette tilfælde. En implementering kunne se således ud:

public class ProduktRouteConstraint : IRouteConstraint
{
    public bool Match(
            HttpContextBase httpContext,
            Route route,
            string parameterName,
            RouteValueDictionary values,
            RouteDirection routeDirection
        )
    {
        var svc = new ProduktService();
        var id = values["id"].ToString();
        return svc.FindesProduktMedUrlnavn(id);
    }
}

Match-metoden fyrer op under en produktservice-klasse, der, via metoden FindesProduktMedUrlnavn, kan svare på, om produktet med det angivne navn (fra url'en) findes eller ej.

Hvis url'en ikke er et match, fortsætter routing-systemet videre til næste regel (som det altid har gjort). Derfor skal denne regel helst ikke være den sidste regel i rækken af routing-regler. 

En ting man skal være opmærksom på med denne metode er, at routing-systemet aktiveres hver gang der foretages et request til sitet. Derfor skal routingconstraints helst være så optimerede som muligt, da sitet ellers vil virke langsomt. Det vil med andre ord være en god idé, at overveje caching af grundlaget for disse specielle routingregler, i dette eksempel produktets urlnavne.

Ikke standard 404 handler i ASP.NET MVC 3

Jeg vil, i tilfælde med indhold som ikke eksisterer, gerne returnere en side med statuskoden 404 (af hensyn til SEO), men samtidig gerne have, at siden ikke er en standard 404-side. Dette sker som udgangspunkt ikke i ASP.NET MVC3, da MVC 3 kobler en standard handler på fejlsider. Dette er også helt fint, men i disse særlige tilfælde, hvor jeg gerne selv vil styre 404-siden, er det træls.

Efter lidt søgen rundt på nettet (primært StackOverflow), fandt jeg, i en kommentar til et indlæg, en henvisning til denne egenskab på Response-objektet:

Response.TrySkipIisCustomErrors = true;

Den sørger åbenbart for, at den standard handler som er opsat, ikke aktiveres, når jeg sætte Response.StatusCode = 404.

Dermed kan jeg med følgende kode i min Controller's Action, få vist en ikke standard 404-side (som, i mit tilfælde, ligger i et view med navnet "NotFound"):

public ActionResult View(int id)
{
   Response.Status = "404 - Not Found";
   Response.StatusCode = 404;
   return View("NotFound");
}

Jeg kunne måske endda pakke denne kode ind, så jeg kan kalde den fra forskellige controllers, som så kunne have deres egne specialiserede views til NotFound-håndtering...

Bundling og minification i ASP.NET MVC 3

Har man arbejdet med ASP.NET MVC 4, ved man der findes en bundling og minification mulighed indbygget i frameworket. Dette er umiddelbart nyt ifht. MVC 4 og fandtes ikke som udgangspunkt i MVC 3.

Man kan dog benytte sig af disse features i et MVC 3-projekt, hvis man benytter .NET 4, man skal bare lige selv sætte de grundlæggende ting op i sit projekt (registrering af bundles, inkludering af System.Web.Optimization og kald af bundles i sit layout).

Du kan finde en nærmere forklaring til dette i artiklen om "Using MVC 4 bundling and minification in an MVC 3 project".

Se også

Denne artikel på MSDN er endnu mere uddybende omkring emnet.

"Javascript minification in MVC 3" kommer også ind på, hvordan man kan overstyre bundling- og minificationprocessen, så man kan smide sine egne regler ind i mix'et.

Hvis du arbejder med webforms kan du måske få lidt ud af kigge på, hvordan man kan benytte scriptmanager til at kombinere scriptfiler.

Azure Emulator og SSL

Jeg arbejder på en webapplikation der skal køre i Azure og jeg har dermed også behov for at kunne teste lokalt.

Mit setup er:

  • Visual Studio 2012
  • ASP.NET MVC 3
  • .NET 4.0
  • Windows 8 Pro x64
  • fuld IIS 8

Denne webapp skal køre en del af sitet over SSL, så derfor har jeg fået sat mit udviklingsmiljø op til at benytte SSL, hvilket umiddelbart også fungerer.

Jeg har dog oplevet lidt problemer med, at uploade filer over SSL. Dette kan nok skyldes mange ting, men umiddelbart udmynter det sig i, at jeg får en 404 tilbage, når jeg POST'er filen til en, 100% sikkert, eksisterende action på min controller. Først troede jeg det havde noget med mine routing at gøre, men GET-request til samme action fungerer jo, så jeg har udelukket denne mulighed.

Jeg har endnu ikke fundet en løsning på dette problem, men jeg har fundet et workaround.

Det jeg gør er, i stedet for at køre over den localhost IP, som IDE'en starter min browser på (https://127.0.0.1/), så kører jeg over den localhost IP som IIS manageren peger på, nemlig https://127.255.0.0:444/

Porten kan dog variere, men hvis man lurer i Output-vinduet af VS, når Emulatoren startes (ifm. build and run), skal man bruge det private portnummer, hvilket kan ses i den linje hvor der står

Windows Azure Tools: Warning: Remapping private port 443 to 446 in role...

Når jeg gør det, lader det til at min action bliver aktiveret korrekt og filen bliver uploadet som den skal.

Dynamisk opdatering af resursefiler i ASP.NET

I forbindese med at jeg skal lave et site der understøtter flere sprog, har jeg kastet mig ud i et forsøg på at bruge resursefiler til at styre sprog/tekster.

Dette er jo oplagt, da resurserne er let tilgængelige i koden og i views. Desuden er der en indbygget integration mellem MVC-frameworkets DataAnnotations og resursefilerne, som ligeledes er let tilgængelig.

Udfordringer

Alt er, indtil videre, fint og sprogstyringen kører som sådan smertefrit. Jeg har dog behov for at kunne vedligeholde teksterne via mit administrationssystem og dermed mindst at kunne opdatere eksisterende tekster uden at skulle bygge min applikation og uploade igen.

Uden de store problemer formår jeg, dynamisk, at få indlæst de forskellige sprogversioner af resursefilerne og få dem vist i mit administrationssystem. Men så opstår udfordringerne, idet jeg jo gerne vil gemme mine ændringer i resursefilerne igen. Selvom det også går relativt smertefrit, at gemme mine ændringer i resx-filerne, opstår der i den forbindelse dog lige et lille problem: Mit site holder op med at svare. Frustrerende!

Det viser sig så, at når resx-filen opdateres, så bygger sitet automatisk systemet igen og dermed bliver sitet "lagt ned" i den periode dette sker. I mit tilfælde, hvor jeg kører en Azure Emulator, lader det ikke til at sitet kommer på benene igen overhovedet, men det er ikke noget jeg er helt sikker på (manglende tålmodighed til at vente længe nok).

Denne genstart medfører desuden, at alt hvad der hedder session-data slettes, fordi applikationen genstartes. Dette kan være ret uhensigtsmæssig - med mindre der er opsat distribueret caching af session, eller man slet ikke benytter session-state (som mig) ;-)

En delvis løsning

Det kan tilsyneladende ikke lade sig gøre at undgå denne bygning af koden, med mindre man går på kompromis med nogle af de integrationer til resursefiler, der er rundt omkring i frameworket. Desværre har jeg ikke fundet en løsning der er 100% optimal, men noget der måske kunne bruges er "Updatable Resources", som omgår de File Change Notifications, som medfører af applikationen bygges ved ændringer i f.eks. resx-filerne.

Jeg er klar over, at artiklen er noget gammel (2009), men jeg har ikke kunnet finde noget information, der var mere up to date, ej heller nogle der omhandlede MVC 4, som jeg pt. arbejder i.

Jeg har fået opfattelsen af, at det ikke er muligt at lave sin egen resursefabrik, således den smertefrit integrerer med MVC-frameworkets DataAnnotation, på samme måde, som den indbyggede. Det undrer mig bare, at dette ikke skulle være muligt. Det er muligt, at dette problem ikke eksisterer længere (der er trods alt løbet meget vand under broen siden 2009!). Umiddelbart ville jeg jo forvente, at resurser, ligesom så mange andre elementer i .NET, var pluggable og dermed kunne udskiftes efter behov. Spørgsmålet er bare hvor man skal proppe sine ændringer ind...(?)

Se også

Rick Strahl's gennemgang af en hjemmestrikket ResourceProvider der henter resurserne fra en database:  http://www.west-wind.com/presentations/wwdbResourceProvider/

Min MVC sikkerhedscheckliste

Jeg har flg. chekliste, som jeg bruger ifm. udvilking af systemer der baserer sig på ASP.NET MVC. En del af listens punkter kan også overføres på andre platforme og frameworks.

Listen er en foreløbig liste, dvs. der kan (og vil givetvis) kommer opdateringer til den, men dette er altså hvad jeg har pt.

Autorisation

  • Alle sider (controller actions) der skal være beskyttet, har en Authorize atribut. Alternativt er der opsat et globalt filter, således alle sider er beskyttet.
  • Alle sider der har Authorize-atribut tilknyttet leveres over https (SSL). Der må IKKE være indhold på siden, som kommer fra en "untrusted" kilde. Der må helst ikke være noget indhold overhovedet der leveres over http på disse sider.
  • Loginsiden leveres ligeledes, i sin helhed, over https (dvs. ingen resurser på siden må leveres over http).
  • Cookies der har noget med brugeridentificering eller på anden måde indeholder følsomme data, leveres kun over https (dvs. secure er sat på cookien).
  • Cookies i øvrigt, leveres kun over httpOnly, med mindre der skal arbejdes med cookies på klienten.

XSS (Cross Site Scripting)

  • Alle informationer der vises i sider er encoded (HTML, HTML-atribut, JavaScript, Url med evt. flere).
  • AntiXSS er installeret og benyttes til encoding.
  • INGEN sider tillader HTML-input (uden der er en rigtig god grund til det). Benyt i stedet BB-codes til formatering af indholdet, hvis det er nødvendigt, at brugeren har denne mulighed.
  • Request-validation er slået til for hele sitet.

CSRF (Cross Site Request Forgery)

  • INGEN GET-metoder ændrer på systemets tilstand (dvs. der må kun returneres tilstand ifm. GET-metoder).
  • Alle formularer har AntiForgeryToken og action-metoder der modtager formulardata har ValidateAntiForgeryToken-atributten tilknyttet.

Øvrige

  • CustomErrors i web.config er sat til "On"
  • Alle formularer er beskyttet mod "overposting", dvs. at der sendes flere data med end forventet som modelbinder automatisk fletter ind i modellen på serveren.

Listen er lidt indforstået, men jeg vil se om jeg kan få lavet nogle artikler omkring nogle af emnerne for at kaste lidt mere lys over, hvordan man sikrer sig disse regler overholdes.

Andre resurser

XSS og CSRF beskyttelse i ASP.NET MVC

Jeg sidder pt. og forsøger, at sikre et site mod XSS og CSRF og i den forbindelse er jeg pt. kommet frem til at flg. som minimum bør gøres.

Sikring mod XSS

Her skal man sikre sig at klienten ikke kan injicere skadelig indhold på sitet og dette indbærer, at man konstant (nærmest paranoidt) sørger for at encode sit output. Der er flere forskellige scenarier man skal forholde sig til og nogle af disse er

  1. data der genereres ind i HTML
  2. data der genereres ind i HTML-tag atributter
  3. data der genereres ind i JavaScript

Hver af disse skal håndteres forskelligt og flg. eksempler forsøger at give et bud på, hvordan dette kan gøres. Model indeholder i disse eksempler de tekster der skal genereres ind i siden i et objekt der hedder Translations. Dette er for at signalere, at teksterne kan være variable og derfor ikke kan garanteres værende passende indhold i den givne kontekst.

Ad 1: Dette sker som udgangspunkt, når man bruger @ til at generere output, dvs. at

<strong>@Model.Translations.ImportantText</strong>

som udgangspunkt er HtmlEncoded, fordi @ sørger for dette i MVC 3.

Ad 2: Her skal man gøre sig lidt ekstra umage, men der findes også en helper i frameworket til dette, så data kan indsættes i en atribut således

<a href="/"
    title="@Html.AttributeEncode(Model.Translations.Home)">
        @Model.Translations.Home</a>

Ad 3: Denne løsning ligner lidt atribut-encoding, men formatet er endnu længere

<script>
   var tekst = 
      "@Html.Raw(Ajax.JavaScriptStringEncode(
                    Model.Translations.AlertText))";
   alert(tekst);
</script>

Med dette på plads skulle der være taget hånd om de fleste scenarier mht. encoding. Det skal naturligvis gøres for ALT input der kommer fra en utroværdig kilde - endda også databasen, da den kan gemme på XSS, med mindre man har sørget for at lukke for alt indkommende skadelig indhold på alle kanaler (POSTS, COOKIES, HEADERS osv.).

Håndtering af CSRF

MVC 3 har som standard en atribut til controller actions, som tjekker for om en given forespørgsel indeholder et unikt token og hvis det ikke gør, så fejler forespørgslen helt.

Feltet hedder __RequestVerificationToken og ligger typisk i den formular der sendes med en POST-forespørgsel. Dette felt kan, af MVC frameworket, også nemt genereres og indsættes i formularer, hvor dette måtte være nødvendigt. Dette gøres således

@using(Html.BeginForm())
{
   @Html.AntiForgeryToken()
   <!-- Resten af din fomular her... -->
}

Således er der, i formularen og i en cookie, indsat et unikt token, som serveren kan tjekke på når formularen POSTes tilbage til serveren. Controlleren til formularen kunne så dekoreres således

[ValidateAntiForgeryToken]
public ActionResult Contact(ContactViewModel model)
{
   // do the contactstuff here...
   return View();
} 

Således skulle denne formular være sikret mod at contact-formularen kan sendes fra andre sites end dit eget. Hvis man vil krydre tingene endnu mere, er der også mulighed for at tilføje "salt" til sit token. Dette gøres således begge steder (i formularen og i ValidateAntiForgeryToken-atributten ifm. controller action).

Håndtering af CSRF i et AJAX-scenarium

Hvis man laver meget AJAX på en side, kan det være besværligt at håndtere CSRF, men laver man POST-request flere gange i løbet af sidens levetid, kan dette løses med lidt scripting og én omnipresent formular på siden, der indeholder AntiForgeryToken (som illustreret nedenfor).

@using(Html.BeginForm("", "", FormMethod.Post, 
              new { id = "frmARFTokenForm" }))
{
   @Html.AntiForgeryToken()
} 

I scriptet kan man så aflæse denne og plastre den ind i sin AJAX-POST-forespørgsel. Dette kunne se således ud (her antager jeg at jQuery er inkluderet i siden)

<script>
    $(function() {
       var tokenForm = "#frmARFTokenForm",
           tokenField = "input[name=__RequestVerificationToken]";
  
       $("#sendData").click(function() {
          var contactForm = $("#frmContact");
          var arfToken = $(tokenForm + " " + tokenField).val();

          $.ajax({
             type: "POST",
             url: "/home/contact",
             data: {
                text: $("textarea[name=comment]", contactForm).val(),
                __RequestVerificationToken: arfToken
            },
            success: function(data) {
                // update the page...
            }
         });
      });
   });
</script>

Dette betyder at det er samme token der sendes med alle formularer indtil siden opdateres i browseren, hvorefter frmARFTokenForm opdateres med et nyt token, som så benyttes af de forskellige scripts.

Der er naturligvis basis for en centralisering af denne feature (f.eks. en rutine til at hente ARFToken og en til at (ind)sætte ARFToken-feltet i en given formular), men princippet er i det mindste skitseret...

Oversættelse vha Custom Data Annotations

Arbejder man med internationale web applikationer, er man nok stødt på problemstillingen om, hvordan man oversætter sine sider. Der findes mange metoder, men når nu man er lidt doven og gerne vil undgå for meget arbejde, gælder det jo om at finde den metode som involverer mindst arbejde. Desuden kan man jo lige så godt bruge de metoder som frameworket tilbyder.

I mit arbejde med ASP.NET MVC 3 er jeg stødt på denne metode, der muligvis kunne spare mig for noget kode og kompleksitet andre steder i mit system. Det drejer sig om nedarvning af DataAnnotationsModelMetaDataProvider-klassen, hvor metoden CreateMetadata overstyres, således DisplayName-egenskaben oversættes. Det kunne se nogenlunde således ud: 

public class CustomModelMetadataProvider 
                : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(
        IEnumerable<Attribute> attributes, 
        Type containerType, 
        Func<object> modelAccessor, 
        Type modelType, 
        string propertyName)
    {
        var metaData =  base.CreateMetadata(
            attributes, 
            containerType, 
            modelAccessor, 
            modelType, 
            propertyName);

        metaData.DisplayName = 
            metaData.GetDisplayName().Translate();

        return metaData;
    }
}

Den nye provider skal aktiveres i Global.asax, således den bliver kaldt når dataannotations skal behandles. Dette gøres i Application_Start således:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    ModelMetadataProviders.Current = 
        new CustomModelMetadataProvider();

    RegisterStorageConfiguration();
    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);

    // ... og hvad vi ellers har...
}

Med dette på plads, kan (view)model udstyres med en nøgle til oversættelsen. Det kunne se således ud:

public class Person
{
    [Display(Name="First name")]
    public string FirstName { get; set; }

    [Display(Name="Last name")]
    public string LastName { get; set; }

    public string Address { get; set; }
}

Bemærk at for egenskaber uden Display-annotation vil det være egenskabens navn er benyttes som nøgle til oversættelse. Nu vil Person-objektets data kunne vises i et view således med oversatte ledetekster:

@model Person
@{
    Layout = null;
}

@Html.DisplayForModel()

Selve oversættelsen sker i Translate-funktionen, der i mit tilfælde er en Extension på string-objektet og som slår op i et datalager, hvor hver tekst er oversat til det aktuelle sprog. Hvilket sprog der er tale om, hentes (af Translate extension funktionen) i den aktuelle tråds UI Culture.

MVC3 klient og servervalidering med jquery-validation og validationatributter

Hvis man har arbejdet lidt med ASP.NET MVC3 og formularer, kender man givetvis "validationattributes", der bla. giver let adgang til validering af om et felt er udfyldt eller om værdien ligger indenfor et interval osv. Der findes en håndfuld i MVC3 som man får serveret på et sølvfad.

Hvis du er i tvivl om hvad jeg snakker om, så ser det nogenlunde således ud i modellen:

public class Person
{
   [Required]
   public string Navn { get; set; }

   [Required]
   public string Adresse { get; set; }
}

Formularen kunne se således ud i View:

@model Person

@using(Html.BeginForm())
{
   @Html.LabelFor(m => m.Navn)<br/>
   @Html.EditorFor(m => m.Navn)<br/>
   @Html.ValidationMessageFor(m => m.Navn)<br/>
   
   @Html.LabelFor(m => m.Adresse)<br/>
   @Html.EditorFor(m => m.Adresse)<br/>
   @Html.ValidationMessageFor(m => m.Adresse)<br/>
   <br/>
   <button type="submit">Gem</button>
}

Dette vil, ved postback, resultere i en validering, på serveren, af om Navn og Adresse indeholder noget (som ikke er en tom streng eller mellemrum). Hvis den ikke er gyldig, vises standardfejlbeskeden for Required i de felter som Html.ValidationMessageFor genererer.

Hvis man ønsker at få valideret sin formular på klienten, kan man blot inkludere jQuery basis biblioteket, samt jquery.validate og jquery.validate.unobtrusive i forbindelse med formularen, eller i basis-layoutet, hvis man har mange formularer på sit site. Noget i stil med dette:

<script src="/scripts/jquery-1.7.2.min.js"></script>
<script src="/scripts/jquery.validation.min.js"></script>
<script src="/scripts/jquery.validation.unobtrusive.min.js"></script>

Med disse inkluderet i siden med formularen, skulle valideringen allerede ske inden data sendes til serveren og det er jo ofte en god ting (for brugeren og for din server).

Der findes, som standard, 5 valideringsatributter i MVC, der spiller sammen med jquery.validate. Disse er:

  • Required (som vist tidligere)
  • StringLength
  • Range
  • RegularExpression
  • Compare.

jquery.validate har yderligere 6 i værktøjskassen, nemlig: 

  • Email
  • Url
  • Date
  • Number
  • Digits
  • Creditcard

Disse er dog ikke umiddelbart tilgængelige som server validationatributter, så hvis man vil have mere valideringsfunktionalitet end de 5 første, må man til at strikke sine egne atributter sammen. Det er nu heller ikke så galt endda!

Egne klient/server valideringsatributter

Alle valideringsatributter bør nedarve fra ValidationAttribute, det kommer dem jeg laver i det mindste til. Hvis vi gør det, kan vi lave en ny regel, der validerer om et felt indeholder en emailadresse, med følgende kode:

public class EmailAddressAttribute : ValidationAttribute
{
   private static readonly Regex rxEmail = 
         new Regex("^\\S+@(\\S+\\.)+\\S+$");

   public EmailAddressAttribute()
   {
      ErrorMessage = "Please enter a valid email address.";
   }

   public override bool IsValid(object value)
   {
      if (value == null)
         return false;

      string v = (value as string).Trim();
      return !String.IsNullOrEmpty(v) && rxEmail.IsMatch(v);
   }
}

Med dette kode, har vi en validationattribute vi kan bruge i vores model og dermed få validering af e-mailadresser.

public class Person
{
   [Required]
   public string Navn { get; set; }

   [Required]
   public string Adresse { get; set; }

   [EmailAddress]
   public string Email { get; set; }
}

Som man kan se er der sat validering på det nye Email-felt i Person-klassen. Der er dog ikke klientvalidering på Email-feltet endnu, så det må vi hellere få lagt på.

Dette gøres ved at implementere interfacet IClientValidatable, hvilket kunne gøres nogenlunde således:

public class EmailAddressAttribute : ValidationAttribute
                                   , IClientValidatable
{
   // ... valideringskoden fra før ...

   public IEnumerable<ModelClientValidationRule> 
      GetClientValidationRules(
         ModelMetadata metadata,
         ControllerContext context
      )
   {
      return new List<ModelClientValidationRule> {
         new ModelClientValidationRule {
            ValidationType = "email",
            ErrorMessage = this.ErrorMessage
         }
      };
   }
}

Med denne kode kan vi nu forvente at der sker en validering af Email-feltet på formularen, både på klienten og på serveren (forudsat vi har placeret feltet på vores View efter samme metode som for Navn og Adresse).

Hvis man har særlige valideringsbehov, kan man også udvide jquery.validator med nye valideringsrutiner. Dette vil jeg dog lade vente til en anden artikel.

NB: Beklager den lidt spøjse formatering af koden, men sidelayoutet bød mig at gøre et eller andet for at holde styr på den kodeeksemplerne :-)

Ninject med ASP.NET MVC 3

Jeg er for alvor begyndt at bevæge mig ind i .NET-udvikling og er i den forbindelse igang med at lære en masse nye begreber og metoder, som, for mange metoders vedkommende, ikke har givet så meget mening at arbejde ud fra i et ASP Classic miljø (mest fordi miljøet ikke understøtter værktøjerne til at gøre tingene på den måde).

Jeg tænker på områder som

  • SoC (Separation of Concern)
  • DI (Dependency Injection)
  • DRY (Don't Repeat Yourself)
  • Automatiserede tests

m.fl.

Jeg er klar over at man godt kan udøve ovenstående praksiser i ASP Classic (og jeg har da i nogen grad forsøgt dette gennem tiden), men da måden at inkludere kode på er yderst kluntet i mere komplekse scenarier og muligheden for at benytte objektorientering er begrænset, kræver det i bedste fald en yderst disciplineret udvikler at gennemføre dette. Desuden er muligheden for at isolere koden fra IIS begrænsede i ASP og derfor er unittests af kode som benytter session, response, request osv. besværlige i bedste fald.

Begreberne og metoderne er noget lettere at arbejde med i .NET, som i høj grad understøtter disse. Specielt når man arbejder med ASP.NET MVC.

En af de metoder jeg er blevet glad for er DI. DI tvinger mig til at tænke på tingene i mindre og mere afgrænsede opgaver for at få tingene til at hænge ordentlig sammen. DI øger også testbarheden af koden, hvilket understøtter et andet af ovenstående punkter.

DI kan gøres manuelt, men det kan hurtigt blive træls at skulle instantiere objekter alle de steder hvor man skal injicere funktionalitet, så derfor har jeg været på jagt efter et DI-framework, som kunne hjælpe med dette.

Jeg har fundet Ninject. Det eneste DI-framework jeg har prøvet, men jeg fornemmer det skiller sig ud fra mange andre DI-frameworks i.o.m. det ikke konfigureres i en XML-fil og dermed fjerner evt. fejl som følge at tastefejl i den tekstuelle XML. Det er i stedet konfigureret i kode og dermed bliver bindinger testet på compiletidspunktet, hvilket jo giver en tidligere mulighed for at fange evt. fejl. Dermed ikke sagt at det fjerner kørselsfejl ifm. instantiering, men det er en fejlkilde mindre ifht. konfiguration via XML.

Der findes et hav af extensions til Ninject, herunder til webforms, mvc3 og azure. Jeg har ikke selv arbejdet i meget andet end MVC 3 med ninject (ud over et lille testprojekt til webforms), så jeg har ikke den store erfaring med hvordan det fungerer i andre sammenhænge, men mon ikke det mest er et spørgsmål om opsætning. Resten kører nok ens, uanset projektets form, når først DI-frameworket er initialiseret.

Du kan tjekke ninject ud her: www.ninject.com