Ressourcen in CustomControl Assembly einbetten und benutzen
Einführung
Bei einem etwas umfangreicheren CustomControl kommt man schnell an die Grenzen.
Es werden zusätzlich zu reinen Textausgaben, vielfach auch Ressourcen gebraucht,
diese können in Form von Text sein, wie z.B. Javascript oder CSS, aber auch binäre Daten, wie z.B. Bilder oder ZIP Dateien.
Seit ASP.NET 2.0 gibt es die Möglichkeit, Ressourcen relativ einfach einzubinden, und zugleich in die Assembly mit zu kompilieren.
Das heisst, die DLL für das Control enthält zugleich die Ressourcen die es benutzt.
Das Deployment wird dadurch klarer und einfacher, die Ressourcen sind in der DLL gekapselt und können nicht einfach so ausgetauscht werden.
Somit kann auch das Problem vermieden werden, eine andere Version der Ressource mitzuliefern.
In ASP.NET 2.0 gibt es eine spezielle Url (WebResource.axd), die von der Engine genutzt wird, um dieses Feature umzusetzen.
Über diese Url und zwei automatisch zugewiesenen Parametern (Assembly Key / DateTime of last Assembly write) kann die Ressource abgerufen und benutzt werden.
Das ist vor allen Dingen für CSS und Javascript sehr interessant.
Anstelle von Javascript Code der im Code eingebettet und per Page.ClientScript.RegisterClientScriptBlock() registriert wird, legen wir in unserem Projekt eine normale Javascript Datei an.
Diese Datei wird als eingebettete Ressource markiert und dann per Code als externe Url in Empfang genommen.
Die Idee für das Beispielprojekt habe ich mir über zwei Ecken von Michal geholt, und den Javascript Code 1:1 übernommen.
Es wird ein CustomControl implementiert, das sich selber bei Bedarf vergrössert und als Textarea gerendert wird.
Damit haben wir nicht eine unnötig grosse Textarea auf der Seite, und den ganzen Text immer im Blickfeld.
Einbettung und Registrierung der Ressource in die Assembly
Eine Ressource (z.B. Javascript oder CSS) kann direkt als Datei in das CustomControl Projekt eingefügt werden.
Im Beispielprojekt erstellen wir eine Javascript Datei im Solution Explorer und markieren diese als eingebettete Ressource.
Dies bewirkt dass die Ressource beim kompilieren in die resultierende DLL eingebettet wird.

Die Ressource muss in der AssemblyInfo.cs registriert werden, damit wir über den Webresource.axd HTTP Handler darauf zugreifen können.
Unter "Properties" im Solution Explorer und dem betreffenden Projekt, liegt die AssemblyInfo.cs, in dieser müssen wir ein zusätzliches Attribut nach folgender Konvention hinzufügen.
[assembly: WebResource("<Root Namespace>.<Dateipfad>", "<ContentType>")]
Im Falle des Beispielprojekts ergibt sich folgendes Attribut:
[assembly: WebResource("t4m.Controls.AutoResizingTextarea.js", "text/javascript")]
Update:
In VB.NET gibt es die Option einen "Rootnamespace" bzw. "Stamm Namespace" anzugeben, oder auch nicht.
Wenn dieser leer ist, darf _nur_ der Dateiname der Ressource angegeben werden.
Andernfalls [Stamm Namespace].[Dateiname]
VB.NET kann nicht mit Pfaden umgehen, daher müssen alle Ressourcen jeweils einen anderen Dateinamen in derselben Assembly haben,
ansonsten wird die Assembly nicht erstellt.
Zitat aus (CodeProject Artikel [Kommentar]):
c# way:
[Default namespace].[PathToTheResource].[ResourceName]
vb.net way:
If you have empty namespace: [ResourceName] Nothing more nothing less!!!
You have a given root namespace: [Default namespace].[ResourceName] No path at all!!!
So vb.net doesn't (cannot?) use the path, this means: if you have two resource with same name in different place in your solution the project won't build!
Eine Angabe in VB.NET kann in der Controlklasse erfolgen, sowie auch in der AssemblyInfo.vb.
Beides funktioniert, allerdings wäre der Weg über die AssemblyInfo.vb wohl sauberer.
Ein solches Attribut kann folgendermassen aussehen:
<Assembly: WebResource("MeineRessource.js", "text/javascript")>
(Damit die AssemblyInfo.vb sichtbar wird, muss im Solution Explorer die Option "Alle Dateien anzeigen" gewählt werden!)
Benutzung der Ressource im CustomControl
Über die ClientScriptManager Instanz und dessen Methode "GetWebResourceUrl" kann die Url zur Ressource abgerufen werden.
Mit der Methode "RegisterClientScriptInclude" des ClientScriptManagers und der Url wird die Ressource als externes Javascript eingebunden, und ist somit auf der aktuellen Seite verfügbar.
In der überschriebenen Methode "AddAttributesToRender" hängen wir ein "onkeyup" Eventhandler mit einem parameterisierten Funktionsaufruf als Attribut für die Textarea hinzu.
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
string attributeString = String.Empty;
bool addAttributes = true;
if(this._minRows != 0 && this._minCols != 0) {
// Zeilen und Spalten resizen
attributeString = "resizeTextArea(this, " + this._minCols.ToString() + ", "
+ this._minRows.ToString() + ");";
} else if(this._minCols != 0) {
// Nur Zeilen resizen
attributeString = "resizeTextArea(this, "
+ this._minCols.ToString() + ");";
} else {
addAttributes = false;
}
if(addAttributes)
writer.AddAttribute("onkeyup", attributeString);
base.AddAttributesToRender(writer);
}
Im überschriebenen OnPreRender des Controls wird das Include per ClientScriptManager registriert.
Die Methode "RegisterClientScriptInclude" erwartet einen Schlüssel und die Url der Resource.
Die Parameter der "GetWebResourceUrl" Methode sind jeweils der Typ der Assembly und den Ressourcennamen (der selbe wie in AssemblyInfo.cs).
protected override void OnPreRender(EventArgs e)
{
if (this.Page != null)
{
ClientScriptManager manager = this.Page.ClientScript;
manager.RegisterClientScriptInclude("AutoResizingTextArea",
manager.GetWebResourceUrl(this.GetType(),
"t4m.Controls.AutoResizingTextarea.js"));
}
base.OnPreRender(e);
}
Mithilfe der "RegisterClientScriptInclude" Methode wird der Javascript Include Tag nur einmal und inmitten der Seite bzw. genau oberhalb des ersten Controls gerendert.
Das ist ein wenig unschön. Das Include kann aber auch in den Head Tag gerendert werden, jedoch in Eigenregie.
Über die ID und vorangehende Prüfung auf ein Control in der Hierarchie wird vermieden, dass das Include bei mehr als einem Control des gleichen Typs mehrfach gerendert wird.
Diese Überprüfungsarbeit wird uns im oberen Fall mit "RegisterClientScriptInclude" abgenommen, aber die Prüfung auf die ID ist nicht aufwändig und funktioniert.
if (this.Page.FindControl("AutoResizingTextArea") == null)
{
string jsUrl = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), "t4m.Controls.AutoResizingTextarea.js");
HtmlGenericControl link = new HtmlGenericControl("script");
link.ID = "AutoResizingTextArea";
link.Attributes.Add("type", "text/javascript");
link.Attributes.Add("src", jsUrl);
this.Page.Header.Controls.Add(link);
}
Die Ausgabe des Javascript Includes sieht dann z.B. so aus:
</script src="http://www.aspnetzone.de/WebResource.axd?d=mKz7Vb-Q8_bkDUZyl5AYY8EyVDVf_p6szFgppsuGhxxVgB7LsNffB2soCdnTCu7p8Y2KMjUlABwuU4ycvynCQw2&t=633201523485135647" type="text/javascript">
Quellen und weiterführende Links:
Beispielprojekt:
Bearbeitung / Korrekturen:
26.06.08 - Update für VB.NET