Powershell til opsætning af udviklingsserver

I forbindelse med at jeg har en udviklingsserver liggende på Azure, har jeg behov for få automatiseret opsætningen af en FTP-server, med hvad deraf følger, herunder åbne porte i firewall, samt oprettelse af en ny bruger, der skal knyttes til en gruppe på serveren. 

Man kan knytte startuptasks til konfigurationen af et Azure projekt (i ServiceDefinition.csdef under /ServiceDefinition/WebRole/Startup). Disse vil blive kørt når serveren starter og man kan bla. benytte batchfiler og powershell-scripts.

Derfor har jeg kastet mig over powershell som metoden til at få disse opgaver udført, men jeg har et lille problem: Jeg har kun ganske lidt kendskab til PS, så det er lidt af en opgave at få støvet alle de forskellige elementer op!

Der er visse ting man skal forholde sig til, dels er der funktionen af startup tasks i Azure, dels er der funktionsmåden og de sikkerhedslåse der er indbygget i PS pr. default. I først omgang vil jeg prøve at koncentrere mig om, at få PS scriptet til at gøre de (fleste af de) ting jeg gerne vil opnå. Pt. har jeg flg. konkrete opgaver:

  • Oprette en ny bruger der kan køre FTP
  • Knytte denne bruger til en gruppe som kan benytte FTP-serveren
  • Installere FTP serveren
  • Aktivere FTP-servicen for default websitet i IIS, herunder
    • ændre portbindingerne for port 21 til FTP i stedet for HTTP
    • oprette tilladelser for brugere til at koble på ftp-serveren
  • Åbne for porte i firewall'en, således der kan køres passiv FTP mod serveren.

Mht. punkt 1, så er jeg kommet frem til flg.:

$computer = [ADSI]"WinNT://."
$usr = $computer.Create("User", "ftpbruger")
$usr.setpassword("password")
$usr.put("fullname", "FTP Bruger")
#Don't expire user + user can't change password
$usr.UserFlags[0] = $usr.UserFlags[0] -bor 0x10040
$usr.setinfo()

Mht. punkt 2, så er jeg foreløbig kommet frem til flg:

$grp = [ADSI]"WinNT://$env:computername/Administrators"
$grp.Add("WinNt://$env:computername/ftpbruger") 

dog har jeg ikke set dette fungere endnu (selvom jeg har set eksemplet flere steder), så det er noget jeg lige skal have testet lidt mere på...

Mht. punkt 3, så burde dette løfte denne opgave:

Import-Module ServerManager
Add-WindowsFeature -Name Web-Ftp-Server,Web-Ftp-Service 

Derefter er jeg lidt blank, men jeg søger videre. Indtil videre, så er det jo halvdelen af selve opsætningsopgaven der er løst, så det skal nok blive godt. 

Dernæst kommer udfordringen så med at få sat startuptasks op i mit Azure-projekt - men den tid den glæde :-)

Jeg forventer at opdatere denne artikel med løsninger på de sidste trin, når jeg finder ud af hvordan de skal løses. Indtil da smid en kommentar, hvor du har løsninger på disse eller hvis du har rettelser til de 3 første trin jeg har illustreret ovenfor.

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 :-)

Serialisering og deserialisering af objekter

Hvis man skal gemme objekter udenfor hukommelsen gøres det typisk ved at serialisere objektets data til et permanent lager. Typisk sker det til en database, men man kan også serialisere til andre formater, f.eks. XML.

Givet flg. objekt:

[Serializable]
public class Person
{
   [XmlAttribute]
   public int Id { get; set; }
   public string Fornavn { get; set; }
   public string Efternavn { get; set; }
}

kan dette serialiseres til XML (og placeres i en MemoryStream) således:

var p = 
   new Person { 
      Id = 1, 
      Fornavn = "tester", 
      Efternavn = "serialisering" 
   };
var ms = new MemoryStream();
var x = new XmlSerializer(p.GetType());
x.Serialize(ms, p);

Herefter ligger objektets data i MemoryStream-objektet ms og kan benyttes derfra, f.eks. gemme til en fil eller sende data et andet sted hen.

Vi kan deserialisere fra denne MemoryStream således:

ms.Position = 0;
var person = (Person)x.Deserialize(ms);

Objektet person skulle nu indeholde de samme data som objektet der blev serialiseret tidligere.

Der er mange muligheder for at styre serialiseringsprocessen. Dels med atributter på objektet der skal serialiseres, dels kan man helt overtage kontrollen med at serialisere. Dette er dog materiale til en anden artikel, men umiddelbart skal man så implementere ISerializable-interfacet og selv styre lagring og indlæsning af data i det serialiserede format.

Effektiv skrivning af XML i .NET

Skal man logge data i et let tilgængeligt format, kan man med fordel benytte sig af XML. Det er let at arbejde med også udenfor systemet og det er let at udvide med flere informationer, hvis man skulle have behov for dette.

Dette kan gøres således:

public void AppendToLog(string text)
{
   using (FileStream fs = File.Open("logentries.xml", 
                                    FileMode.Append, 
                                    FileAccess.Write, 
                                    FileShare.Read)) 
   {
      XmlTextWriter writer = new XmlTextWriter(fs, Encoding.UTF8);
      writer.WriteElementString("entry", "", text);
      writer.WriteWhitespace("\n");
      writer.Close();
   }
}

Dette resulterer imidlertid kun i en liste af XMLNodes i en fil og munder derfor ud i et ugyldigt XML-dokument. Dette kan dog reddes ved at lave en anden XML-fil, som linker logentries.xml ind og gør det til en del af den nye XML-fil. Dette gøres således:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!DOCTYPE root [
  <!ENTITY frags SYSTEM "logentries.xml">
]>
<root>
&frags;
</root>

Nu kan denne XML-fil åbnes i en XML-viewer (f.eks. Internet Explorer eller XmlDocument-objektet) og danne grundlag for visning af XML-loggen (logentries.xml) øverst.

Et hurtigt brødkrummespor i Umbraco

Efter at have opgraderet til Umbraco 4.7.2 er jeg gået igang med Razor-delen af Umbraco og det er jo indtil videre en fornøjelse (sammenlignet med XSLT/Macro-helvedet). Jeg har ellers ikke haft det store imod at arbejde med XSLT, men hvis det kan blive så meget lettere, så kom bare med det!

Jeg ved det er et simpelt problem, men nedenstående kode smidt ind i en skabelon (inline!) giver et brødkrummespor til den aktuelle side.

<umbraco:Macro runat="server" language="cshtml">                                                       
  @foreach(var ancestor in Model.AncestorsOrSelf()) {
    if(!ancestor.umbracoNaviHide) {
      if(ancestor != Model) {
        <a  href="@ancestor.Url">@ancestor.Name</a>
        @Html.Raw("&gt;")
      }
      else {
        @ancestor.Name
      }
    }
  }
</umbraco:Macro>

Så bliver det vist ikke ret meget simplere! Der er taget højde for at, at visse sider i hierarkiet kan være skjult for menuer og andet. Desuden er den aktuelle side ikke renderet som et link.

Jeg kommer til at bruge mere tid på dette! Nice!

Distribueret caching

I forbindelse med udarbejdelsen af et større system, har skalerbarhed været en bekymring. Systemet ligger i Azure og kan som sådan skalere ud uden de store problemer. Det er altså ikke antallet af servere (CPU-kraft) der er problemet, men derimod adgang til data. Mange forspørgsler går igennem databasen og dermed er der stor risiko for at databasen bliver flaskehalsen i dette setup.

Caching er naturligvis den oplagte løsning, men der er dog nogle udfordringer med den indbyggede cache i ASP.NET, da denne ikke får mig hele vejen i mål. Dette er naturligvis fordi den cache er lokal på hver server og dermed kun rigtig brugbar til data der eksklusivt skal læses og som ikke ændrer sig særlig ofte. Data der skal opdateres vedligeholdes bedst i en centraliseret cache, dvs. distribueret cache. Sådan en findes der heldigvis også i Windows Azure AppFabric og den kan afhjælpe dette problem. Dette er dog en lidt anden caching-model end den indbyggede Cache, da en distribueret cache stiller mig over for nogle nye udfordringer.

Jeg har, i forbindelse med min research på emnet, fundet frem til en artikel, der omhandler caching og de faldgruber man skal være opmærksom på, når man benytter en distribueret cache. Nogle af de, for mig, mest fremtrædende pointer jeg tog med mig fra artiklen var følgende:

Caching sker af kopier af objektet. I modsætning til en lokal cache, hvor det er en reference til objektet der caches, er det en kopi af et objekt der caches i en distribueret cache. Dette skyldes at cachen typisk ligger out of process og ofte på en helt anden fysisk maskine.

Samtidighed er en udfordring. At der arbejdes med kopier af objekter kan bla. betyde at opdateringer af et objekts egenskaber, kan risikere at ske fra flere fronter på samme tid og dermed give nogle konsistensproblemer, hvis man ikke er forsigtig (objektet i cachen skal låses fra læsning til tilbageskrivning).

Caching af store objekter kan sløve systemet. Da der sker en serialisering/deserialisering af objektet når det skrives til/læses fra cachen, kan der være store CPU-omkostninger forbundet med at læse og skrive data til en cache. Derfor bør man cache komplekse objekter og objektstrukturer med stor omtanke.

Artiklen "Ten Caching Mistakes That Will Break Your App" beskriver dette og flere andre aspekter vedr. distribueret caching i dybden.

Jeg vil i øvrigt opfordre til at kigge på nogle af de andre artikler af samme forfatter. Der er noget god viden at hente angående optimering af ASP.NET løsninger.

Samme højde på elementer

Jeg sidder ofte og irriterer mig over at jeg ikke kan få CSS-reglen height:100% til at fungere (og det er helt klart fordi jeg ikke ved nok om CSS!). Derfor har jeg lige nappet et eksempel på jQuery plugin-intrositet og tilpasset den lidt, så jeg kan køre den på et sæt elementer og tilpasse højden på alle disse elementer til det højste.

Det ser nogenlunde således ud:

(function ($) {
  $.fn.alignHeight = function() {
    var max = 0;

    this.each(function() {
      max = Math.max(max, $(this).height());
    });

    this.height(max);

    // returner this for chainability
    return this;
  };
})(jQuery);

Det kan så kaldes nogenlunde således:

$(function() {
  $(".venstre-menu, .indhold").alignHeight();
});

Hvilket så gerne skulle gøre begge elementer lige høje (og fylde lige så meget som det højste element).

Graciøs tilbagefald på websider

Når man laver websider er det en god idé at tænke på, hvilke behov og forudsætninger slutbrugeren har. Herunder hvilken opsætning brugeren ser din side med (javascript til/fra, stylesheets til/fra, browser type og - version osv.).

Målet er er så mange som muligt kan se indholdet på siden.

Mange sider er styret og drevet med en høj grad af scripting, således funktioner kun er tilgængelige, hvis scripting er slået til. Der kan være mange årsager til at man ønsker denne afhængighed af scripting, men man bør som udgangspunkt sikre sig at relevant indhold også kan ses og bruges uden scripting slået til.

Dette kan opnås ved at sætte siden op uden scripting i første omgang og derefter så tilføje de effekter man måtte have behov for efterfølgende. Med jQuery er det overkommeligt at tilføje f.eks. effekter, som kræver scripting når siden loader. Dette vil jeg demonstrere nedenfor.

Eksemplet implementerer en ramme med info under et billede. Når scripting er slået til skjules info som udgangspunkt og bliver i stedet til en slideUp når musen føres over.

<style type="text/css">
   .boks {
   }
   .info {
   }
   .info.dynamic { bottom: 0px; position: absolute; }
</style>
<script>
   $(function() {
$(".boks .info").addClass("dynamic"); $(".boks").hover( function() { $(".info", this).slideUp(); }, function() { $(".info", this).slideDown(); }); }); </script>

Følgende html afspejler html:

<div class="boks">
   <div class="basis">
      <img src="billede.jpg"/>
   </div>

   <div class="info">
      <p>
         Slideup info eller fast, hvis scripting
         ikke understøttes.
      </p>
   </div>
</div>

Som det kan ses, sørger scriptet for at koble stylen dynamic på det element der skal have speciel effekt, når script understøttes. Hvis scipting ikke er aktiveret vises info blot under billedet.

Tanker vedr grænseflader på klasser i C#

Jeg kom forleden til at se på en klasses interface og reflekterede i den forbindelse lidt over hvor logisk/intuitivt det interface var. Min konklusion var, at der var plads til forbedring...

Klassen tilbød noget i stil med dette:

public class Member {
   public Member(Guid id) { ... }
   public static Member GetByEmail(string email) { ... }
}

Hvor konstruktøren indlæser et Member på grundlag af det id der medsendes. Dette virker da også tilforladeligt, men ved nærmere eftertanke, synes jeg det virker lidt ulogisk. Observer flg. eksempel på brugen af denne klasse:

var member = new Member(memberId);

Skulle man læse ovenstående linje, ville det se ud til at man oprettede et nyt Member-objekt med et givet id. Dette er dog ikke tilfældet, idet man som sagt, henter et eksisterende Member-objekt med det givne id.

Jeg ville derfor vælge at lave endnu en statisk metode til at indlæse et Member-objekt med, dvs. noget i stil med flg.:

public class Member {
   public Member() { ... }
   public static Member Get(Guid id) { ... }
   public static Member GetByEmail(string email) { ... }
}

Nu ville flg. linje rent faktisk oprette et nyt Member-objekt:

var member = new Member();

og flg. linje ville afspejle hvad der rent faktisk foregik:

var member = Member.Get(memberId);

Dette er blot en lille overvejelse, men jeg synes det gør koden lettere at læse, fordi koden hjælper med at formidle hvad der rent faktisk foregår.

Man kunne evt. overveje om disse statiske metoder var bedre implementeret på en factory-klasse, som instance-metoder, men det er en diskussion som også involverer andre overvejelser, f.eks. om det kan betale sig i det aktuelle projekt og om der er andre behov som retfærdiggør at der skal oprettes en helt ny klasse, herunder løsere binding til Member-klassen. En løsere binding vil dog også kræve en abstraktion, f.eks. i form af et interface...

Server to server kommunikation i .NET

I forbindelse med at jeg skulle lave en løsning der integrerer med Facebook har jeg haft behov for at kommunikere med facebook fra serveren. I den forbindelse har jeg fået sammensat flg. simple klasse til at indpakke husholdningen forbundet med at sende og modtage en datastrøm til hhv. fra facebook.

public class HttpComm
{
   public virtual string GetData(string getAddr)
   {
      // Udfør et kald til den angivne adresse og returner svaret
      try
      {
         var request = (HttpWebRequest)WebRequest.Create(getAddr);
         request.Credentials = CredentialCache.DefaultCredentials;
         var response = (HttpWebResponse)request.GetResponse();
         var responseStream = response.GetResponseStream();
         var readStream = new StreamReader(responseStream);

         string status = readStream.ReadToEnd();
         response.Close();
         readStream.Close();

         return status;
      }
      catch(Exception x)
      {
         ErrorLog.Write(x);
      }

      return "";
   }

   public virtual string PostData(string addr, string data)
   {
      byte[] postData = Encoding.UTF8.GetBytes(data);

      try
      {
         HttpContext ctx = HttpContext.Current;
         if(ctx != null)
         {
            string host = ctx.Request.Url.Host;
            if(ctx.Request.Url.Port != 80)
               host += ":" + ctx.Request.Url.Port.ToString();

            var request = (HttpWebRequest)WebRequest.Create(addr);
            request.Credentials = CredentialCache.DefaultCredentials;
            request.Method = "POST";
            request.UserAgent = 
                       "Mozilla/4.0+"
                     + "(compatible;+MSIE+8.0;+Windows+NT+6.0)";
            request.Referer = ""; // "http://" + host + "/";
            request.ContentType = 
                       "application/x-www-form-urlencoded";
            request.ContentLength = postData.Length;
            request.TransferEncoding = "UTF-8";

            Stream reqStream = request.GetRequestStream();
            reqStream.Write(postData, 0, postData.Length);
            reqStream.Close();

            var response = (HttpWebResponse)request.GetResponse();
            Stream responseStream = response.GetResponseStream();
            var readStream = new StreamReader(responseStream);

            string status = readStream.ReadToEnd();
            response.Close();
            readStream.Close();

            return status;
         }
         else
            return "";
      }
      catch(Exception x)
      {
         ErrorLog.Write(x);
         throw;
      }
   }
}

 

Så kan jeg kalde HttpComm.GetData og HttpComm.PostData for at sende og modtage data med protokollerne GET og POST.