2007-07-27

AutoEventWireup=true

Om du på en Web User Control eller sida skriver AutoEventWireup=True kommer metoder som t.ex. Page_Init() och Page_Load() att automatiskt hakas på sidans Init- resp. Load-event.

Detta är bra ibland, dumt ibland. Smidigt för man slipper haka på eventen själv. Dumt därför att det är något som görs i runtime (tar därmed tid) och kan leda till att event anropas flera gånger.

Exempel: Event-hanterare som anropas två gånger.

protected void Page_Init()
{
Page.Load += Page_Load;
}
protected void Page_Load(object sender, EventArgs e)
{
//This code will execute twice
}

I ovanstående exempel kommer Page_Load att anropas två gånger. En gång eftersom du manuellt hakat på metoden på Load-eventet. Ytterligare en gång därför att AutoEventWireup=true och metoden heter som den gör vilket gör att den automatiskt kommer att hakas på Load-eventet.


AutoEventWireup fungerar  för TemplateControl vilket både Page och Web User Controls ärver av.


Du kan, om du vill dock inte nödvändigt, börja med att läsa K Scott Allens bloggpost om vad AutoEventWireup gör. Det nedan är en fördjupning på ämnet. :)


SupportAutoEvents


Vad händer egentligen när AutoEventWireup=true? En TemplateControl har en property som heter SupportAutoEvents. Denna är true per default (och ändras bara till false av parsern/control buildern som bygger upp kontrollen om man sätter AutoEventWireup=false).


HookUpAutomaticHandlers


TemplateControl.HookUpAutomaticHandlers (vilken anropas under kontrollens OnInit bland annat) kommer om AutoEventWireup=true att försöka hitta följande metoder:



  • Om kontrollen är Page:

    • Page_PreInit
    • Page_PreLoad
    • Page_LoadComplete
    • Page_PreRenderComplete
    • Page_InitComplete
    • Page_SaveStateComplete

  • För alla TemplateControls:

    • Page_Init
    • Page_Load
    • Page_DataBind
    • Page_PreRender
    • Page_Unload
    • Page_Error
    • Page_AbortTransaction eller om den inte finns; OnTransactionAbort
    • Page_CommitTransaction eller om den inte finns; OnTransactionCommit

Detta görs i metoden TemplateControl.GetDelegateInformationWithNoAssert().


32 försök


Som K Scott Allen skriver kommer den för varje metodnamn att försöka två gånger. Först försöker den hitta en EventHandler med det namnet, dvs. en metod med signaturen:

void EventHandler(object sender, EventArgs e)

Exempel: Metod som är en EventHandler

void Page_PreInit(object sender, EventArgs e)

Om det misslyckas kommer den försöka att hitta en VoidMethod, dvs. en metod med signaturen:

void VoidMethod()

Exempel: Metod som är en VoidMethod

void Page_PreInit()

Så om du inte har angivet någon metod alls som matchar något av kriterierna ovan i t.ex. en sida så kommer den ändå att leta efter 32 metoder (och inte 28 som K Scott Allen skrev).


Resultatet av matchningen kommer att lagras i en cache, och kommer därför inte att anropas varje gång. Men det kommer att anropas åtminstone en gång. :)


Haka metoden på eventet


För alla metoder som matchar kriterierna kommer TemplateControl.HookUpAutomaticHandlers() att haka metoden på motsvarande event. Detta kommer dock inte att göras om metoden redan har hakats på eventet eftersom HookUpAutomaticHandlers letar igenom alla event-hanterare för eventet och om den hittar metoden där så struntar den i att lägga till metoden igen.


Men i exemplet ovan så blev ju Page_Load() påhakat två gånger på samma event?
Jepp, men det var för att när vi i Page_Init hakade på eventet så hade HookUpAutomaticHandlers redan gjort det. HookUpAutomaticHandlers anropas ju i OnInit som körs före vår Page_Init. Om vi istället hakar på vårt event före sidans OnInit och därmed före HookUpAutomaticHandlers anropats så kommer Page_Load bara att anropas en gång.


Exempel: Page_Load anropas bara en gång.

public partial class _Default : System.Web.UI.Page
{
public _Default()
{
Page.Load += Page_Load;
}

protected void Page_Load(object sender, EventArgs e)
{
//This code will execute once
}
}

Slutsats: AutoEventWireup=false


Överväg att slå om AutoEventWireup till false (den är true per default) och haka själv på de event du vill använda. Du får mer kontroll över när saker anropas och du slipper den overhead som TemplateControl.HookUpAutomaticHandlers medför.


Microsoft själva rekommenderar att man slår av det när hög prestanda är viktigt.


Mer om AutoEventWireup på MSDN


http://msdn2.microsoft.com/en-us/library/system.web.configuration.pagessection.autoeventwireup.aspx
http://support.microsoft.com/default.aspx/kb/324151

2007-07-10

Vad Asp.Net-utvecklare bör kunna om JavaScript

K. Scott Allen har skrivit en utmärkt artikel om "What ASP.NET Developers Should Know About JavaScript"

Obligatorisk läsning om man ska koda lite mer avancerad JavaScript på klientsidan.

  • Varje objekt i JavaScript är ett dictionary
  • Vare funktion i JavaScript är även ett objekt
  • Varje objekt har en prototyp.

Förvirrad? Läs artikeln!

Utöka en befintlig AjaxControlToolkit Extender

I en tidigare bloggpost visade jag hur man skapar en AjaxControlToolkitExtender från början. Den här gången tänkte jag visa hur man utökar en befintlig extender med lite ytterligare funktionalitet. Det kan vara bra att läsa den tidigare bloggposten först.

HoverAddsCssClassExtender

Extendern HoverAddsCssClassExtender hakas liksom alla extendrar på en befinlig kontroll på sidan, t.ex. en Panel. När muspekaren förs över kontrollen kommer dess class-property (som innehåller css-klassnamn) att utökas med ytterligare ett css-klassnamn. När musen lämnar kontrollen återställs värdet. En klassisk onmouseover-, eller hover-effekt alltså. Traditionellt så brukar man byta css-klassnamnet mot något helt annat, men det är smartare att lägga på ytterligare en regel.

Ett litet exempel kan nog förtydliga vad som händer och hur den används:

<asp:Panel runat="server" ID="Panel1" CssClass="squared">
Hover me!
</asp:Panel>
<
ajaxExtender:HoverAddsCssClassExtender runat="server"
ID="HoverAddsCssClassExtender1" TargetControlID="Panel1" HoverCssClassName="hover" />

På klienten kommer Panel1 att vara en div vars class-property är satt till "squared". När musen förs över Panel1 kommer dess class-property att sättas till "squared hover", dvs. värdet från HoverCssClassName läggs till sist, för att återställas till "squared" när musen försvinner.


HoverExtender


Hur får man då till det här enklast? Jo det finns en extender som heter HoverExtender som följer med AjaxControlToolkit och som är lite odokumenterad. Den ligger som grund till DropDownExtender och HoverMenuExtender men den fungerar alldeles utmärkt att använda fristående. Det den gör att den kör ett av användaren specificerat script på mouseover och ett annat script på mouseout. Man kan även specificera att det ska dröja ett tag innan någon av scripten körs. Detta kan t.ex. utnyttjas till att tillåta att muspekaren passerar över kontrollen utan att scriptet körs (eller lämnar kontrollen som hastigast utan att scriptet körs direkt). Bara genom att låta muspekaren dröja kvar ett tag körs scriptet.


Vi kommer att låta vår extender ärva funktionalitet av HoverExtender. Men bara på klientsidan.


Skapa HoverAddsCssClassExtender


Nog pratat. Låt oss börja koda. Jag kommer inte att gå in i detalj hur du sätter upp projektet utan utgå ifrån att du har ett projekt där du kan skapa extendern. Mer information om hur du sätter upp ett sånt projekt finns i den tidigare bloggposten. Namnge projektet HoverAddsCssClass


Ny AjaxControlToolkitExtender


Du måste inte skapa Ajax Control Project utan kan skapa en extender i ett vanligt klassbibliotek. För att skapa en ny extender i ett befintligt projekt: Högerklicka på projektet och välj Add > New Item... och välj ASP.NET AJAX Extender Control längst ner under My Templates. Ange HoverAddsCssClass som namn. Se till att du har en referens till AjaxControlToolkit.


Nedan kommer jag förutsätta att du skapat ett nytt projekt som heter HoverAddsCssClass med tre filer. HoverAddsCssClassBehavior.js, HoverAddsCssClassDesigner.cs och HoverAddsCssClassExtender.cs. Det första man ALLTID ska göra är att se till att js-filen har Build Action=Embedded Resource (finns under properties för den filen).


Radera designern


Vi kommer inte att behöva någon egen designer så ta bort HoverAddsCssClassDesigner.cs ur projektet. Default-designern duger utmärkt. (Designern används i design-läget inne i Visual Studio). Gå samtidigt in och radera hänvisningen till den i HoverAddsCssClassExtender.cs.

namespace HoverAddsCssClass
{
[Designer(typeof(HoverAddsCssClassDesigner))]
[ClientScriptResource("HoverAddsCssClass.HoverAddsCssClassBehavior",
"HoverAddsCssClass.HoverAddsCssClassBehavior.js")]
[TargetControlType(typeof(Control))]
public class HoverAddsCssClassExtender : ExtenderControlBase
{
// TODO: Add your property accessors here.

Ärv av HoverExtender – men bara på klientsidan


Normalt ärver en extender (som används på server-sidan) från ExtenderControlBase som syns i koden ovan, men den kan bytas ut mot vilken extender som helst i AjaxControlToolkit (och alla andra klasser som implementerar IExtenderControl). Då vi vill återanvända funktionalitet som HoverExtender har skulle vi kunna byta ut arvet mot HoverExtender och få en massa saker gratis. Men eftersom vi inte vill exponera alla de properties som HoverExtender har så struntar vi i arvet. Istället lägger vi till de properties vi vill ha.


På klientsidan däremot vill vi utnyttja HoverBehavior (som är klientdelen av HoverExtender). Även här används arv. Längst ner i filen HoverAddsCssClassBehavior.js hittar du:

HoverAddsCssClass.HoverAddsCssClassBehavior.registerClass(
'HoverAddsCssClass.HoverAddsCssClassBehavior', AjaxControlToolkit.BehaviorBase);

Klassen HoverAddsCssClass.HoverAddsCssClassBehavior registreras och den ärver av AjaxControlToolkit.BehaviorBase, men det finns inget som säger att den måste ärva från den klassen. Vi ändrar det till att vara HoverBehavior istället

HoverAddsCssClass.HoverAddsCssClassBehavior.registerClass(
'HoverAddsCssClass.HoverAddsCssClassBehavior', AjaxControlToolkit.HoverBehavior);

Vi måste även se till att koden för HoverBehavior laddas ner till klienten. Det gör vi med hjälp av attributet RequiredScript på serversidans extender.

[ClientScriptResource("HoverAddsCssClass.HoverAddsCssClassBehavior", 
"HoverAddsCssClass.HoverAddsCssClassBehavior.js")]
[TargetControlType(typeof(Control))]
[RequiredScript(typeof(HoverExtender))]
public class HoverAddsCssClassExtender : Hover
{


Vi säger med RequiredScript-raden ovan att vi vill få ner alla script-filer som HoverExtender använder.


HoverCssClassName – Property


Vi behöver en property HoverCssClassName så att man kan ange css-klassnamnet som ska läggas till då musen förs över elementet. Vi börjar i extendern med att lägga till propertyn. Ta samtidigt bort MyProperty som redan ligger i filen.

[ExtenderControlProperty()]
[DefaultValue("")]
public string HoverCssClassName
{
get
{
return GetPropertyValue("HoverCssClassName", String.Empty);
}
set
{
SetPropertyValue("HoverCssClassName", value);
}
}


Över till klientsidans behavior-kod.

HoverAddsCssClass.HoverAddsCssClassBehavior = function(element) {
HoverAddsCssClass.HoverAddsCssClassBehavior.initializeBase(this, [element]);

this._hoverCssClassName = "";
}
...
dispose : function() {
// TODO: add your cleanup code here

HoverAddsCssClass.HoverAddsCssClassBehavior.
callBaseMethod(this, 'dispose');
},

get_HoverCssClassName : function() {
return this._hoverCssClassName;
},
set_HoverCssClassName : function(value) {
this._hoverCssClassName = value;
this.raisePropertyChanged('HoverCssClassName');
},


Hämta och spara värdet i en lokalt deklarerad variabel.


Hantera mouseover


HoverBehavior, klientsidans behavior som vi ärver av, har två properties hoverScript och unhoverScript, på vilka man anger javascriptkod i form av en strängar. Denna kod kommer att tolkas och köras vid mouseover resp. mouseout (och även focus resp. blur). Det här utnyttjar vi till att köra två av våra egna metoder för att lägga till och ta bort css-klassnamnet.


Eftersom vi ärver av HoverBehavior har vi tillgång till de metoder som används för att sätta dess properties. På samma sätt som vår HoverCssClassName har en metod set_HoverCssClassName för att ange värdet har hoverScript och unhoverScript de två metoderna set_hoverScript och set_unhoverScript. Så vi anropar dem från vår initialize-kod:

initialize : function() {
HoverAddsCssClass.HoverAddsCssClassBehavior.
callBaseMethod(this, 'initialize');

this.set_hoverScript("this._addCss()");
this.set_unhoverScript("this._removeCss()");
},

och vi lägger till de två metoder som initialize hänvisar till:

_addCss : function()
{
var targetElement=this.get_element();
if(targetElement && !Sys.UI.DomElement.containsCssClass(targetElement,this._hoverCssClassName))
{
Sys.UI.DomElement.addCssClass(targetElement,this._hoverCssClassName);
}
},

_removeCss : function()
{
var targetElement=this.get_element();
if(targetElement && Sys.UI.DomElement.containsCssClass(targetElement,this._hoverCssClassName))
{
Sys.UI.DomElement.removeCssClass(targetElement,this._hoverCssClassName);
}
}

I _addCss() börjar vi med att plocka ut det element som extendern hakades på. Om ett sånt element fanns (ska alltid finnas, men bäst att kolla ändå för att slippa fel om den ändå inte skulle finnas) och dess css-klassnamnsträng inte redan innehöll vår _hoverCssClassName lägg till det till css-klassnamnet.


_removeCss() fungerar på liknande sätt. Om vi har ett target element och dess css-klassnamnsträng innehåller _hoverCssClassName, ta bort det.


Vi nyttjar här tre funktioner som Asp.Net Ajax för med sig:



I och med det är allt klart.


Testsida


Läs den tidigare bloggposten för information om hur man skapar en testsajt. I Default.aspx knackar du in följande:

<head id="Head1" runat="server">
<title>Untitled Page</title>
<style type="text/css">
.squared
{
width:200px;
height:200px;
border: dashed 4px black;
background-color: #eee;
text-align:center;
}

.hover
{
background-color: #228;
color:white;
}
</style>

</head>
<
body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:Panel runat="server" ID="Panel1" CssClass="squared">
Hover me!
</asp:Panel>
<myExtenders:HoverAddsCssClassExtender runat="server" ID="HoverAddsCssClassExtender1" TargetControlID="Panel1" HoverCssClassName="hover" />
</form>
</
body>
</
html>

Sen är det bara att trycka F5 och testa.


Utöka med HoverDelay & UnhoverDelay


HoverBehavior har ytterligare properties som vi enkelt kan utnyttja: hoverDelay och unhoverDelay. Genom att sätta dessa specificerar vi hur lång tid i millisekunder man måste befinna oss över elementet innan hoverScript körs, resp. hur länge vi måste befinna oss utanför för att unhoverScript ska köras. Att lägga till stöd för dessa två värden är enkelt. På klientsidan ärver vi ju av HoverBehavior så där är dessa properties redan exponerade. Det räcker således med att göra det i serversidans extender.

[ExtenderControlProperty]
[DefaultValue(0)]
[ClientPropertyName("hoverDelay")]
public int HoverDelay
{
get
{
return GetPropertyValue("HoverDelay", 0);
}
set
{
SetPropertyValue("HoverDelay", value);
}
}

[ExtenderControlProperty()]
[ClientPropertyName("unhoverDelay")]
[DefaultValue(0)]
public int UnhoverDelay
{
get
{
return GetPropertyValue("UnhoverDelay", 0);
}
set
{
SetPropertyValue("UnhoverDelay", value);
}
}

Composition istället för arv


I det här exemplet använde vi arv på klientsidan för att få den funktionalitet vi behövde. Ett annat sätt är använda composition, vilket innebär att att man istället för att ärva in önskat beteende skapar en privat HoverBehavior inne i vår behavior (och då ärver man från AjaxControlToolkit.BehaviorBase som vanligt). Man får då själv se till att förändringar av mina properties slår igenom även på HoverBehavior.


När och varför man ska välja arv eller composition ligger utanför denna genomgång och även hur man skapar med Composition. Är du nyfiken på hur man gör kolla in t.ex. HoverMenuBehavior i AjaxControlToolkit som använder composition för PopupBehavior och HoverBehavior.


Ladda ner projektet


Ladda ner projektet