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…

 

Advertisements

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!

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 – Nach Login NICHT auf die ReturnUrl

Wieder einmal ein Problem dessen Lösung nach einem „BrainBackup“
schreit: Man verlässt einen geschlossenen Mitgliederbereich (blabla.bla/member.aspx)
über das ASP-Steuerelement und kommt auf die Login-Seite bei der die URL dann
so ausschaut: /login.aspx?returnUrl=member.aspx

Kommt man jetzt auf die Idee sich neu anzumelden – und
man nutzt dazu das Login-Control, wird man danach wieder auf member.aspx
geleitet. Will man das genau nicht, muss man den Event abfangen und folgendes
erledigen:

 

// Cookie setzen

FormsAuthentication.SetAuthCookie(username, true);

// Auf die Seite der Wahl weiterleiten (In dem Fall ist sie
als Default-Url in der web.config angegeben)

Response.Redirect(FormsAuthentication.DefaultUrl);

 

 

Schon ist man wie gewünscht nicht da, wo die ASP-Maschinerie
einen hinleiten will, sondern dort, wo man selbst hinleiten will

Generic handler (ashx) für die Anzeige von Dateien über https

Möchte man eine Datei über einen genric handler (*.ashx) an
den Browser schicken, ist das meist recht simpel

 

HttpContext.Current.Response.Clear();

HttpContext.Current.Response.ContentType
= "application/pdf";

HttpContext.Current.Response.OutputStream.Write(pdfStream.ToArray(),
0,  Convert.ToInt32(objMemoryStream.Length));

HttpContext.Current.Response.Flush();

HttpContext.Current.Response.End();

 

Das ganze funktioniert auch – solange man kein
SSL-Zertifikat dahinter klemmpt und die Daten über https anspricht. Die Lösung:

 

Response.AddHeader("Accept-Header", pdfStream.Length.ToString());

 

How-To: Disable caching in Temporary Internet Files

Was tun wenn man Daten im Browser anzeigen möchte
die lokal nicht gecacht werden sollen? Mit diesen Einstellungen funktioniert
das ganz gut, ich gebe aber KEINE Garantie darauf das es
immer und überall funktioniert – jeder Browser kann das für sich
handhaben wie er will. Mit den Standardeinstellungen funktioniert das
allerdings und – was auch noch zur Sicherheit beiträgt – der Ordner
„Temporary Internet Files“ hängt einmal im Verzeichnis des
Benutzers UND ist zusätzlich so so geschützt, dass er, auch wenn die Option „versteckte
und Systemdateien anzeigen“ angehakt ist, nicht sichtbar ist.

 

context.Response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));

context.Response.Cache.SetValidUntilExpires(false);

context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);

context.Response.Cache.SetCacheability(HttpCacheability.NoCache);

context.Response.Cache.SetNoStore();

context.Response.AddHeader("Pragma", "no-cache");

GridView – Dynamische Template-Fields

Ich denke es wird mal wieder Zeit einen Teil
meines neu angeeigneten Wissens an einem Ort zu sichern, von dem ich
weiß/denke, dass ich diese Dinge auch wieder finde wenn ich sie benötige.

 

Mein Thema heute: Wie füge ich einem
GridView dynamisch Spalten zu – inklusive DataBinding!

 

Zunächst muss ein (oder mehrere, je nach
Art der Aufgabe) eigenes Template angelegt werden das die Arbeit für uns beim
DataBind übernimmt. Dieses Template muss das Interface ITemplate und die
dazugehörige Methode InstantiateIn beinhalten – InstatiateIn wird aufgerufen
wenn das übergeordnete Control die ChildControls initialisiert. InstantiateIn
wird das Container-Objekt übergeben dem unser eigenes Template hinzugefügt
werden soll. In meinem Beispiel verwende ich ein Label um einen einfachen
String aus einem BusinessObject mit diversen Properties (Name, Vorname, Adresse)
und einem weiteren Objekt – erreichbar über „BusinessObject.More.[Whatever(string)]“.

Um die Daten an das Label binden zu
können müssen wir den DataBidning-Event des Labels behandeln. In dem Ereignis
bekommen wir – über „object sender“ Zugriff auf den Container
und dem dazugehörigen DataItem das das entsprechende BusinessObject darstellt.
Mit Eval(refereceToDataItem, Argument) wird der Wert aus dem BusinessObject
gezogen und dem Label angefügt.

 

public class MyTemplateField
: ITemplate

{

        public string Argument { get;
set; }

 

        public MyTemplateField(string arg)

        {

            this.Argument = arg;

        }

 

        #region ITemplate
Members

 

        public void
InstantiateIn(Control container)

        {

            Label label = new Label();

           
label.DataBinding += new EventHandler(label_DataBinding);

 

            container.Controls.Add(label);

        }

 

        void label_DataBinding(object
sender, EventArgs e)

        {

            IDataItemContainer dataItemContainer = (IDataItemContainer)((Control)sender).NamingContainer;

 

            object result = DataBinder.Eval(dataItemContainer.DataItem,
this.Argument);

            Label theSender = sender as
Label;

 

            if (result == null)

                result = "Keine Daten";

 

            theSender.Text =
result.ToString();

        }

    }

 

    public
partial class SuggestionTextBox : System.Web.UI.Page

    {

        protected void
Page_Load(object sender, EventArgs e)

        {

            List<BusinessObjectBase>
businessObjectList =DAL.GetBusinessObjects(“dummy”);

 

            TemplateField tf = new
TemplateField();

            tf.ItemTemplate
= new MyTemplateField(“Name”);

            tf.HeaderText = "Name";

 

           
gv.Columns.Add(tf);

 

            tf = new TemplateField();

tf.ItemTemplate
= new MyTemplateField(“More.Whatever”);

tf.HeaderText
= "ChildProperty";

 

           
gv.Columns.Add(tf);

 

            gv.DataSource =
boList;

            gv.DataBind();

        }

   
}