Archive for tag: MVC 3

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.

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

jQuery og ASP.NET MVC 3 validering

Har du en formular i dit MVC3 projekt hvor du ønsker at lave en delvis sideopdatering via AJAX, men har lidt problemer med at få standard valideringstekster fra MVC3 til at fungere, så tjek nedenstående...

Controlleren skal udformes som normalt for postback. Jeg vælger typisk at benytte standard dataannotations, dvs. således:

public class PersonController : Controller 
{
    [Authorize]
    public ActionResult Edit()
    {
        return View(new EditPersonViewModel());
    }

    [Authorize]
    [HttpPost]
    public ActionResult Edit(EditPersonViewModel model)
    {
        if(ModelState.IsValid)
        {
            var person = new Person();
            person.Name = model.Name;
            person.Address = model.Address;
            person.Save();
        }
        return View(model)
    }
}

Min ViewModel ser således ud:

public class EditPersonViewModel
{
    [Required]
    public string Name { get; set; }
    [Required]
    public string Address { get; set; }
}

Min klientkode ser således ud:

$(function() {
    function postPerson($frm) {
        var action = $frm.attr("action"),
            params = $frm.serializeArray();
   
        $("#editPersonContainer").load(action, params);
    }

    $("#editPersonContainer input[type=submit]")
        .live("click", function() {
            postPerson($(this.form));
            return false;
        });
});

Submit-knappen kalder postPerson og returnerer derefter false, for at undgå formularen sendes på normal vis (dvs. som en fuldside postback). Jeg benytter .live for at fastholde eventhandleren til submit-knappen efter formularen genindlæses fra serveren i editPersonContainer-elementet (ellers ville handleren gå tabt, når formularen sendes til serveren). I jQuery v 1.7+ burde man nok benytte den nye on-funktion i stedet for live.

$frm.serializeArray benyttes fordi .load-funktionen forventer et objekt når der er tale om et postback. Så almindelig name/value-strenge kan ikke bruges (det er i det mindste min erfaring).

Selve formularen/view ser således ud:

@model EditPersonViewModel
@{
    Layout = null;
}
<div id="editPersonContainer">
@using(Html.BeginForm("Edit", "Profile"))
{
    @Html.LabelFor(m => m.Name)
    @Html.EditorFor(m => m.Name)
    @Html.ValidationMessageFor(m => m.Name)<br/>

    @Html.LabelFor(m => m.Address)
    @Html.EditorFor(m => m.Address)
    @Html.ValidationMessageFor(m => m.Address)<br/>

    <input type="submit" value="Gem">
}
</div>

Layout sættes til null for at undgå evt. implicitte master layouts bliver renderet.

Med ovenstående på plads, kan jeg indsætte formularen på en given side med:

@Html.Action("Edit", "Person")