How to: Minify require.js Scripts in ASP.net

Ohne Frage, das Bundling in ASP.net ist eine feine Sache – so lange Scripts zusammengefasst werden können, die immer benötigt werden und nicht dynamisch per require.js bei Bedarf nachgeladen werden.

Für diese Scripts gibt es 2 Möglichkeiten.

  1. Die Scripts werden entweder direkt durch einen Script-Minifier gejagt und ausgeliefert – dafür gibt es diverse Tools, die entweder auf die Änderung eines Scripts reagieren oder bei einem Build ausgeführt werden
  2. Die Scripts werden zur Laufzeit optimiert

Ich möchte hier auf die 2. Möglichkeit eingehen und zeige, wie Scripts zur Laufzeit – unter Verwendung von WebGrease – minimiert werden können.

WebGrease installieren

WebGrease kann über nuget eingebunden werden: Install-Package WebGrease

Der Minifier

Um die Ausgabe an den Client verändern zu können, muss der „Filter“ der Response auf ein Objekt gesetzt werden, dass die Arbeit anstelle des Default-Filters übernimmt.

Response.Filter:
Summary: 	Gets or sets a wrapping filter object that is used to modify the 
		HTTP entity body before transmission.
Returns:	The System.IO.Stream object that acts as the output filter.

Konkret bedeutete das, dass ein Objekt benötigt wird, das von Stream ableitet, um es dem Filter zuzuweisen. Diesem Objekt wird der Output-Stream, auf den das minimierte Script geschrieben werden soll:

public class JavascriptRequestMinifier : MemoryStream {
	private readonly Stream _stream;
	
	///<summary>Erzeugt eine Instanz des Objekts</summary>
	///<param name="stream">Der Output-Stream</param>
	public JavascriptRequestMinifier(Stream stream) {
		this._stream = stream;
	}
	//TODO: Write + Flush implementieren
}

Als nächstes muss die Methode ‚Write‘ überschrieben werden, um das Schreiben auf den Output-Stream abzufangen.
Das Framework beschreibt den Stream blockweise, was anfangs dazu führte, dass bei größeren Scripts nicht das Ganze optimiert und ausgegeben wurde, sondern nur ein Teil davon. Abhilfe schafft ein StringBuilder, um den Output zu sammeln und ihn komplett in die Ausgabe zu schreiben, wenn der Stream geschlossen wird: 

readonly StringBuilder _codeBuilder = new StringBuilder();

public override void Write(byte[] buffer, int offset, int count) {
	// string aus buffer extrahieren und _codeBuilder zuweisen, falls das script 
	// länger ist
	_codeBuilder.Append(Encoding.UTF8.GetString(buffer));
}

Abschließend muss die Flush-Methode überschrieben werden, um die gesammelten Daten minimiert an den Client zu senden:

public override void Flush() {
	Minifier m = new Minifier();
	string code = m.MinifyJavaScript(
		this._codeBuilder.ToString(), 
		new CodeSettings {
			PreserveImportantComments = false,
			PreserveFunctionNames = true,
			MinifyCode = true
		}
	);
	this._stream.Write(
		Encoding.UTF8.GetBytes(code), 0, Encoding.UTF8.GetByteCount(code));
	this._stream.Flush();
}

Den Filter setzen

Der letzte Schritt, um das Script minimiert auszugeben ist, den Filter zuzuweisen. Die richtige Stelle ist hier in der Global.asax der Event ‚PostReleaseRequestState‘

// Occurs when ASP.NET has completed executing all request event handlers and the
// request state data has been stored.

An dieser Stelle muss geprüft werden, ob es sich bei dem ContentType um ein Javascript handelt, dass optimiert werden soll.

Ich habe require.js dahingehend konfiguriert, dass dem Script ein QueryString-Parameter ‚min-require=1‘ angefügt wird. Enthält die Query der Url des Request diesen String, wird eine Instanz des Minifiers erzeugt und dem den Response.Filter zugewiesen:

protected void Application_PostReleaseRequestState(Object sender, EventArgs e){
	if (this.Request.Url.Query.Contains("min-require=1")) {
		this.Response.Filter = 
			new JavascriptRequestMinifier(this.Response.Filter);
	}
}

Damit werden alle Scripts, die dynamisch geladen werden, minimiert an den Client übertragen.

C# Code während Laufzeit kompilieren mit Roslyn

Dieser Artikel wird bis zur Fertigstellung regelmäßig aktualisiert. Aktuell dient er als Gedankenstütze und „Brain Backup“ für mich…

Gelegentlich kommt es vor, dass man vor die Aufgabe gestellt wird, Code erst zur Laufzeit zu kompilieren und auszuführen. Dabei gilt es, sich neben der gestellten Aufgabe, auch um Sicherheit bzw. Berechtigungen zu kümmern. Dazu sollte man den Code in einer eigenen AppDomain mit entsprechenden Einstellungen ausführen – darauf möchte ich hier allerdings erstmal nicht eingehen, hier geht es erstmal um das Ausführen dynamischen Codes.

Was ist Roslyn?

Roslyn ist eine Scripting-API die es ermöglicht, Code zur Laufzeit auszuführen. Mit Roslyn ist es nicht mehr notwendig, Assemblies zu kompilieren und diese im Speicher oder in einem Verzeichnis abzulegen.

Die Roslyn-Assemblies

Roslyn benötigt natürlich einen Satz von Assemblies. Diese selbst zusammenzusuchen stellte sich als recht aufwendig heraus. Glücklicherweise kann man alles, was man für die Ausführung benötigt, über den PackageManager installieren: Install-Package Roslyn

Damit sind alle benötigten Assemblies im Projekt und es kann los gehen!

IIS Express und Visual Studio – Externen Zugriff erlauben

hierGelegentlich kann es nützlich sein, einen Kollegen auf seinen lokalen IIS-Express zu lassen, während die WebApp im Debug läuft – oder man will sich einfach mal mit einem mobilen Device anschauen, ob das ganze Projekt auch noch auf einem kleinen Display gut aussieht und mit Touch auch wirklich so läuft, wie man sich das gedacht hat…

Über die Einstellungen in Visual Studio kann man das nicht konfigurieren und auch in der UI des IIS-Express wurde ich nicht fündig.

Nachdem ich Bing bemüht habe, klappt es dann doch relativ simpel!

Ich setze voraus, dass die WebApplication bereits über den IISExpress genutzt wird (Project-Settings -> Web -> „Use Local IIS Web Server“). Die Project Url in meinem Fall lautet „http://localhost:7000/digoso-web&#8220;

Zunächst einmal muss die Kommandozeile mit Admin-Rechten gestartet werden, um Konfigurationen über die NetworkShell auszuführen:

netsh http add urlacl url=http://*:7000/ user=Everyone

Damit ebnen wir den Weg für den Zugriff von extern auf unseren IIS (Info zu add urlacl)

Im nächsten Schritt müssen wir manuell in die Konfigurationsdatein ‚applicationhost.config‘ eingreifen. Das Config-File findet sich in aller Regel im Verzeichnis

%HomeDrive%\Documents\IISExpress\config

Im Node „sites“ sollte ein Site-Child liegen, dem als Name -Attribut der Name des Projekts zugewiesen ist. Dieser Node muss editiert werden!

Unter dem Bindings-Node findet man die Information, wie auf die URL zugegriffen werden darf.

<binding protocol=http bindingInformation=*:7000:localhost />

Die Standard-Einstellung stellt sicher, dass nur von localhost der Aufruf erlaubt ist.

Ersetzt man nun ‚localhost‘ gegen den Namen des Computers (z.B. digoso-pc)

<binding protocol=http bindingInformation=*:7000:digoso-pc />

kann man auf den IIS via „http://digoso-pc:7000/digoso-web&#8220; zugreifen und man muss (leider) die Start-Url des IIS so umbiegen, dass sie den Namen des Computers enthält – was allerdings unschön ist, wenn man im Team arbeitet und jeder Computer anders heißt…

Trägt man statt dem PC-Namen einfach ein * ein, kann jeder im Netzwerk auf den IIS mit der URL zugreifen und es müssen keine Änderungen an der Startseite gemacht werden.

<site name="digoso.WebApplication-Site" id="1">
    <application 
        path="/" 
        applicationPool="Clr4IntegratedAppPool">
        <virtualDirectory 
            path="/" 
            physicalPath="\\...\My Web Sites\digoso.WebApplication-Site1" />
    </application>
    <application 
        path="/digoso-web" applicationPool="Clr4IntegratedAppPool">
    <virtualDirectory path="/" 
        physicalPath="......\digoso.WebApplication" />
    </application>
    <bindings>
        <binding protocol="http" bindingInformation="*:7000:*" />
    </bindings>
</site>

 

Javascript-Minifier beim Response – ohne Bundles

ASP.net bringt von Haus aus ja bekanntlich die Funktionalität mit, viele Javascripts zu einem Bundle zusammenzufassen und diese minimiert an den Client zu übertragen.

Was jedoch, wenn man Scripts hat, die dynamisch – z.B. mit require.js nach – nachgeladen werden?

Die eine Möglichkeit ist, (1)diese nach der Erstellung direkt zu minimieren – eine andere wäre, (2)das Script dynamisch zur Laufzeit zu optimieren und an den Client zu geben.

Zweiteres möchte ich in diesem Post näher Beleuchten.

Um Javascript zur Laufzeit zu minimieren, bedarf es einem Eingriff in der global.asax. Hier kann man sich den PostReleaseRequestState-Event zunutze machen.

Die Beschreibung zu diesem Event lautet:

Occurs when ASP.NET has completed executing all request event handlers and the request state data has been stored.

Und einer Helfer-Klasse, die das minimieren des Scripts übernimmt. Diese nimmt den OutputStream entgegen und minimiert mit Hilfe des Minifier-Objekts aus dem Namespace Microsoft.Ajax.Utilities die Ausgabe:

public class JavascriptRequestMinifier : MemoryStream{
private readonly Stream _stream;

public JavascriptRequestMinifier(Stream stream){
this._stream = stream;
}

public override void Write(byte[] buffer, int offset, int count){
string code = Encoding.UTF8.GetString(buffer);
Minifier m = new Minifier();
code = m.MinifyJavaScript(code, new CodeSettings {
EvalTreatment = EvalTreatment.MakeImmediateSafe,
PreserveImportantComments = false
});

this._stream.Write(
Encoding.UTF8.GetBytes(code),
offset,
Encoding.UTF8.GetByteCount(code));
}
}

In der Global.asax wird nun noch der PostReleaseRequestState-Event behandelt und geprüft, ob es sich um ein Javascript handelt – ist das der Fall, tritt der JavascriptRequestMinifier in Aktion:

protected void Application_PostReleaseRequestState(
Object sender, EventArgs e) {

if (this.Response.ContentType.Equals(„application/javascript“)) {
this.Response.Filter = new
JavascriptRequestMinifier(this.Response.Filter);
}
}

ACHTUNG: An der Stelle wird der Minifier immer dann verwendet, wenn es sich um eine Response handelt, dessen ContentType „application/javascript“ ist.

Das heißt, auch bereits minimierte Scripts werden noch einmal versucht, zu minimieren – was natürlich gelegentlich auch klappt, und das ein oder andere Byte zusätzlich gespart werden kann!

Um das zu verhindern, könnte man ggfs. prüfen, ob der angefragte Dateiname auf .min.js endet oder zu minimierende Scripts immer mit QueryString-Argumenten aufrufen und diese dann zu prüfen…

 

Komplexe Objekte & Arrays mit jquery $.post an einen ASP.net MVC-Controller senden

Das Szenario

Ich wollte ein komplexes Javascript-Objekt auf dem Client erzeugen und per $.post an den Server schicken. Ein einfaches Aufbauen des Objekts hat so lange funktioniert, bis ich ein Array mit weiteren Objekten erzeugt habe.

So bin ich gestartet

Mein Model auf der Serverseite sieht ungefähr so aus:

public class DataModel {
    public string Id { get; set; }
    public List<Data> Items { get; set; }
}
public class Data{
    public string[] Key { get; set; }
    public int Value { get; set; }
}

Auf dem Client habe ich dafür folgendes Aufgebaut

var data = {
    id: 'foo',
    Items: [{ Key: [ 'a', 'b' ], Value: 666 },
            { Key: [ 'c', 'd' ], Value: 999 }]
}

Nachdem ich das losgeschickt hatte, war DataModel.Id gesetzt, Items blieb NULL…

Die Lösung

jQuery ist manchmal ein…nicht ganz so nettes Ding! Ich habe viel hin und her probiert, wie ich das Model auf dem Client zusammenbauen muss, damit etwas vernünftiges auf dem Server ankommt. Dabei habe ich mich an die Notation erinnert die notwendig ist, wenn man die Daten per Form-Serialisierung versenden möchte.

Hier erhalten die Felder im name-Attribute den ‚Pfad‘, an den der Wert am Ende landen soll. Konkret heißt das, dass ein Feld, dessen Wert in DataModel.Items[0].Value stehen soll, den „name“ Items[0].Value besitzen muss.Für den Key wäre der „name“ Items[0].Key[0], Items[0].Key[1], …

Und genau SO muss dann auch der Name des Wertes im ViewModel aussehen:

var data = {
    id: 'foo' 
}
// soweit alles beim alten, jetzt das NEUE
data["Items[0].Value"] = 666;
data["Items[0].Key[0]"] = 'a';
data["Items[0].Key[1]"] = 'b';

data["Items[1].Value"] = 999;
data["Items[1].Key[0]"] = 'c';
data["Items[1].Key[1]"] = 'd';

Damit kann ich dann komplexe Daten wegschicken…

 

Wenn jemand einen besseren Weg kennt, bitte Posten! Ansonsten: Viel Spass und Erfolg damit!

 

VisualStudio ItemTemplate – kleine Helferlein erzeugen und wiederfinden

Heute mal ein kurzer Ausflug in die Welt von Visual Studio!

Ich hatte immer wieder den Fall, dass ich bestimmte Dateien mit einem definierten Aufbau, immer wieder erzeugen musste. Bisher habe ich die Dateien immer kopiert, soweit geleert, bis sie quasi leer waren und nur noch das Gerüst übrig blieb und sie anschließend meinen Bedürfnissen angepasst.

Auf das kopieren und leeren hatte ich keine Lust mehr, also habe ich mir einige Vorlage – Neudeutsch „Templates“ – gebastelt.

Das funktioniert im Grunde ganz einfach. Gehen wir davon aus, dass man zum Beispiel ein Javascript-Gerüst als Template verfügbar machen will:

  • In Visual Studio FILE – EXPORT TEMPLATE…
  • Item Template anhaken und das Projekt wählen, in dem das oder die Elemente sind, die in das Template aufgenommen werden sollen. NEXT >
  • Es erscheint eine Auflistung aller Dateien, die im Projekt vorhanden sind. Die Dateien, die in das Template aufgenommen werden sollen markieren und NEXT >
  • Nun  kann man Referenzen wählen, die beim auswählen des Templates dem Projekt hinzugefügt werden sollen. NEXT>
  • Abschließend noch einen Namen, eine Beschreibung und ein paar Bildchen angeben und alles mit FINISH abspeichern.

Das ItemTemplate ist jetzt direkt verfügbar. Zum ausprobieren ein Rechtskllick auf einen Ordner -> ADD -> NEW ITEM

Das Template erscheint – in meinem Fall – direkt unter Visual C# (Für VB-Entwickler wahrschleinlich unter Visual Basic). Meiner Meinung nach hat ein Javascript-Template hier nichts zu suchen. Ich hätte es lieber unter „Visual C#/Web“, oder lieber unter „Visual C#/Web/Meine“.

Um das zu erreichen, muss man in das Installationsverzeichnis von VS. Dort findet man einen Ordner „Templates“ und „My Exportet Templates“ (hier steht das exportierte Template).

Im Ordner „Templates“ navigiert man nun zum Unterordner „VISUAL C#“ (oder „VISUAL BASIC“), erstellt hier einen neuen Ordner mit dem Namen „Web“ und erstellt hier einen weiteren Ordner mit dem Namen „Meine“. Abschließend verschiebt man das exportierte Template in diesen Ordner.

Jetzt steht das Template im Hinzufüge-Dialog endlich an der richtigen Stelle!

Windows PC als WLAN-Hotspot freigeben – Virtual WiFi (ab Windows 7)

Ein nützliches aber verstecktes Feature, das Microsoft seinem Betriebssystem ab Windows 7 spendiert ist, dass man einen PC als WLAN-Hotspot einrichten kann. Das kann dann nützlich sein, wenn man z.B. nur ein kabelgebundenes Netzwerk zur Verfügung hat, und sein Windows Phone per WLAN nicht an die Leitung bekommt.

Leider ist diese nützliche Funktion so in den Tiefen versteckt, dass es dafür keine Benutzeroberfläche gibt und man sich – zu ModernUI-Zeiten – mit der Konsole begnügen muss.

Um Virtual WiFi – oder kurz VWifi – zu aktivieren, muss also die Konsole gestartet werden – aber bitte als Administrator, sonst wird der Versuch mit der Meldung quittiert, dass man eben dieser sein muss und die Konsole auch bitte mit den entsprechenden Rechten gestartet werden soll.

So schaltet man VWifi ein:

  • Wechseln in das Verzeichnis c:\windows\system32
  • Eingabe: netsh [Enter]
  • Eingabe: wlan set hostednetwork mode=allow ssid=MEINE_SSID key=MeinWpaKey [ENTER]
  • Meldung, dass das gehostete Netzwerk zugelassen wird, erscheint zusammen mit der Meldung, dass die SSID und der Key geändert wurden
  • Eingabe: wlan start hostednetwork [ENTER]
  • Meldung: Das gehostete Netzwerk wurde gestartet
  • Das Netzwerk sollte jetzt bei der Suche nach einem neuen WLAN zu finden sein.

Um das Netzwerk wieder zu deaktivieren, muss der Befehl wlan stop hostednetwork eingegebenen und mit [ENTER] bestätigt werden. Die Meldung „Das gehosteted Netzwerk wurde beendet“ erscheint.

Viel Spaß damit

ASP.net MVC – Kein Zugriff auf Scripts in der Area

Ein Problem, dass jemanden ein wenig beschäftigen kann, wenn mach versucht ein MVC-Projekt zu strukturieren und alle Dinge, die im Kontext eines Bereichs gültig sind, auch dort zu haben.

In meinem Fall habe ich ein bestehendes MVC-Projekt so umstrukturiert, dass Views, PartialView, Controller, Models und eben auch Scripts, in einem Unterordner einer Area stehen.
Versucht man nun das Script z.B. über Url.Content zu laden, erhält man eine HttpNotFound-Exception. Schuld daran sind – bei mir – 2 Einträge in der Web.config, die im Areas-Ordner hinterlegt ist. Diese steuern, dass man von außen keinen Zugriff auf alle Dateien hat, die unterhalb des Pfads „Areas“ liegen:

<system.web>
<httpHandlers>
<add path=“*“ verb=“*“ type=“System.Web.HttpNotFoundHandler“/>   
</httpHandlers>
</system.web>

<system.webServer>
<handlers>
<remove name=“BlockViewHandler“/>
      <add name=“BlockViewHandler“ path=“*“ verb=“*“ preCondition=“integratedMode“ type=“System.Web.HttpNotFoundHandler“/>
</handlers>
</system.webServer>

Ansich eine gute Sache, schließlich möchte man nicht, dass man Views über die physische URL anspricht. Es verhindert allerdings auch das Laden von Scripts aus diesem und allen Unterverzeichnissen.

Die Lösung ist denkbar einfach:

<system.web>
<httpHandlers>
      <add path=“*.aspx“ verb=“*“ type=“System.Web.HttpNotFoundHandler“/>   
      <add path=“*.ascx“ verb=“*“ type=“System.Web.HttpNotFoundHandler“/>   
</httpHandlers>
</system.web>

<system.webServer>
<handlers>
<remove name=“BlockViewHandler“/>
      <add name=“BlockViewHandler“ path=“*.aspx“ verb=“*“ preCondition=“integratedMode“ type=“System.Web.HttpNotFoundHandler“/>
      <add name=“BlockViewHandler“ path=“*.ascx“ verb=“*“ preCondition=“integratedMode“ type=“System.Web.HttpNotFoundHandler“/>
</handlers>
</system.webServer>

Mit diesen Einstellungen ist nun nur noch der Zugriff auf aspx und ascx-Dateien gesperrt. Scripts können problemlos geladen werden.

Hoffentlich konnte ich dem ein oder anderen ähnliche Probleme ersparen!

Visual Studio: Assembly Version Build und Revision automatisch ändern lassen

Will man eine Assembly kompilierren und weitergeben und wundert sich dabei, dass man immer und immer wieder eine Version 1.0.0.0 weiter gibt, kann man dieses Verhalten ganz einfach steuern.

Dazu genügt eine schnelle Änderung in der Datei „AssemblyInfo.cs“ die im Projekt im Ordner „Properties“ hinterlegt ist.

Scrollt man in dieser Datei ans Ende, findet man 2 Einträge:

  • [assembly: AssemblyVersion(„1.0.0.0“)]
  • [assembly: AssemblyFileVersion(„1.0.0.0“)]

Um bei jedem Build nun die BuildNumber und die Revision automatisch zu aktualisieren, löscht man einfach den 2. Eintrag und ändert für die AssemblyVersion der Wert auf „1.0.*“.

 

ASP.net MVC – ActionResult mundgerecht geliefert / ViewResult to JsonResult

Problemstellung:

Eine Action auf einem MVC-Controller soll je nach Art des Request entweder ein ViewResult oder ein JsonResult zurückliefern. Dabei soll im Accepts-Header geprüft werden, ob ‚application/json‘ explizit angefordert wurde.

Per Default liefert die Action ein ViewResult zurück, bei einem Json-Request soll das Result-Objekt in ein JsonResult-Objekt umgewandelt und das Model aus ViewResult genutzt werden.

Lösung:

Die richtige Stelle, um die Anfrage zu überprüfen, ist der Einstiegspunkt ‚OnActionExecuted‘. Diese kann – und wird für dieses Beispiel – im Controller direkt überschrieben werden.

Natürlich kann man das Ganze auch in ein ActionFilterAttribute auslagern und hier OnActionExecuted überschreiben.

Zunächst wird überprüft, ob der Client im Request angegeben hat, dass er Json erwartet. Diese Information findet man hier:

filterContext.RequestContext.HttpContext.Request.AcceptTypes -> string[]

Für meine Anforderung hat es gereicht, um zu überprüfen, ob der erste Eintrag ‚application/json‘ entspricht und ob das Ergebnis ein ViewResult ist. Ist das der Fall, wird ein neues JsonResult-Objekt erzeugt und das Data-Property auf das Model aus dem ViewResult gesetzt.

Abschließend wird dem filterContext als Result das neu erzeugte Objekt zugewiesen.

string[] acceptTypes = filterContext.RequestContext.HttpContext.Request.AcceptTypes;
System.Web.Mvc.ViewResultBase vr = filterContext.Result as System.Web.Mvc.ViewResultBase;
if (acceptTypes.Length > 0 
    && acceptTypes[0].Equals("application/json") 
    && vr != null){
        filterContext.Result = newJsonResult() { Data = vr.Model };
}

Um es zu testen, habe ich einen Link erstellt, bei dem der click-Event abgefangen wird:

$("a[data-getJson]").live("click", function (evt) {
    $.ajax({  type: "POST",
             url: this.href,
             headers: { 
                 Accept:  "application/json", 
},
              complete: function (data) {
                        alert(data.responseText);
                    }
                });
                evt.preventDefault();
            });