lördag 20 oktober 2012

Typade repeaters i "gamla" ASP.NET


I nya ASP.NET 4.5 går det att använda typade repeaters (video):
(Fungerar i klassiska ASP.NET också, men då får man hacka ganska mycket först).

Tyvärr har man ofta inte möjlighet att köra nya ASP.NET 4.5 med typade repeaters. Och man hinner inte heller använda hacket ovan, vilket medför att man är fast med att använda den tråkiga och *farliga DataBinder.Eval:

* = Du får inget kompileringsfel om du råkar skriva "FullNme" istället för "FullName" och du kan inte trycka F9 / "go to definition" om du vill navigera till den aktuella metoden/propertyn.

Skapade därför en enkel hjälpklass för att nästan uppnå samma sak som en typad repeater. Denna klass fungerar som en wrapper för det objektet man vill arbeta med i sin repeater.

Notera även att ToString()-metoden returnerar en tomsträng. Kommer förklara varför längre ner.

Klassen RepeaterItem kan användas på följande sätt:
PersonListView.aspx.cs - "codebehind"


Fördelen med att använda denna hjälpklass är:

  1.  Man slipper oväntade runtime-fel pga felstavning
  2.  Intellisense i aspx-filen 
  3.  Möjlighet att direkt använda "go to definition" 
  4.  Renare och mer överblickbar kod

PersonListView.aspx  med hjälpklassen RepeaterItem:

Snyggare och mindre risk för felstavningar!
Har använt denna lösning i tidigare projekt, och det har fungerat mycket väl.

Dock kan man ju tycka att man borde kunna skriva nåt sådant här också:
<% Item = (Person)Container.DataItem %> 

Och då få samma resultat utan en extra klass.
Dock har man inte tillgång till Container.DataItem utan att använda "databind taggarna" <%# %> med (#).
Skulle man istället skriva:
<%# Item = (Person)Container.DataItem %>

Hade outputen blivit något i stil med:
Labs.Project.Views.Person
Nils, Jönköping


Labs.Project.Views.Person
Karl, Stockholm


Som ni ser, får man även med: Labs.Project.Views.Person (outputen av ToString vid tilldelningen av Item) eftersom <%# databind taggen %> anropar ToString på objektet.

Av den anledningen görs en override av ToString i RepeaterItem.cs som endast returnerar string.Empty.


 /Nils

fredag 19 oktober 2012

a.b.C cannot be cast to a.b.C


Har i veckan råkat ut för, ett för mig konstigt fel i JBoss. Detta fel uppkommer vid hot-deploy av en war-fil på JBoss 5.1.

Applikationen jag arbetat med är en enkel servlet som autentiserar ett inkommande HTTP-requests med Basic Authentication mot en JBoss login-modul (UsernamePasswordLoginModule).

Nedan ges en enkel översiktsbild av applikationen:

myapp.war
| - WEB-INF
    | - classes
        | - se.labs.myapp
            | -  UserServlet.class
            | -  UserHttpRequest.class
    | - lib
        | - common.jar
        | - custom-loginmodule.jar

Login-modulen ligger i ett annat bibliotek (custom-loginmodule.jar)
Både login-modulen och klasserna i myapp.war har referenser till klassen UserPrincipal i common.jar.


common.jar
| - se
    | - se.labs.common
        | - UserPrincipal.class

Här är koden för UserServlet och UserHttpRequest:



Frid och fröjd!

Allting kompilerar och körs hur bra som helst...Fram till nästa re-deploy av myapp.war.
Själva deployen fungerar fint, och applikationen är fortfarande körbar. Men nästa gång servleten (UserServlet) anropas kastas följande runtime-fel:

[ERROR] [se.labs.myapp.UserServlet] java.lang.ClassCastException: se.labs.common.UserPrincipal cannot be cast to se.labs.common.UserPrincipal

Varför?! 
Var frågan jag ställde mig. Gjorde en snabb sökning på google och konstaterade att fler hade råkat ut för detta fel. Om samma klass laddats av två olika class-loaders, tolkar JVM:en dessa som två helt olika klasser, och dessa kan inte castas från den ena till den andra.

I JBoss sker tydligen en del "magi" vid en hot-deploy. Samtliga klasser i war-filen laddas inte om, bara för att man gör en ny deploy.

Skickade därför in till lite loggning  i UserHttpRequest för att se om klassen UserPrincipal var laddad av olika class-loaders efter hot-deploy:



Fick då se följande utdrag i loggen:

[INFO]  [se.labs.myapp.UserHttpRequest] user.getClass().getClassLoader() = BaseClassLoader@3f1dae99{vfszip:/jboss5/jboss/server/farm/myapp.war/}

[INFO]  [se.labs.myapp.UserHttpRequest] UserPrincipal.class.getClassLoader() = BaseClassLoader@4861c19c{vfszip:/jboss5/jboss/server/farm/myapp.war/}

Olika instanser av samma klassladdare har använts för att skapa objektet UserPrincipal.
Enligt vad jag har förstått, ska man kunna styra klassladdning i JBoss genom konfiguration av t.ex /WEB-INF/jboss-classloading.xml.

Dock fick jag inte detta att bita och efter många försök fick jag helt enkelt släppa konfigurationslösningen vilket gjorde att jag hade följande alternativ kvar:

1.) Deserialisering av objektet.
2.) Använda reflection.
3.) Skriva en egen klassladdare.
4.) Ändra om i projekten och bryta ut commons.jar
5.) Undivka hot-deploy och istället starta om servern vid varje deploy

Alternativ #1, #2, #3 och #5 var egentligen inga alternativ. Det slutade alltså med att alternativ #4 valdes efter att jag läst följande artikel som istället medförde att jag fick checka ut och ändra beroenden mellan de olika projekten och lägga common.jar i JBoss egna /lib katalog.


myapp.war
| - WEB-INF
    | - classes
        | - se.labs.myapp
            | -  UserServlet.class
            | -  UserHttpRequest.class
    | - lib
        | - common.jar
        | - custom-loginmodule.jar


Efter detta tråkiga faktum, kan jag bara säga som såhär:

Jag hade högre förväntningar på JBoss (5.1). Dokumentationen som finns på nätet känns bristfällig och jag hade blivit glad om någon JBoss-guru kunde visa mig en smidigare lösning på problemet.

Dock vill jag alltid se saker och ting positivt!
Jag har fått mer förståelse om JBoss och klassladdning under mina timmar av felsökning.

Mer information och referenser:
http://stackoverflow.com/questions/826319/classcastexception-when-casting-to-the-same-class
http://www.jspwiki.org/wiki/A2AClassCastException
http://www.mastertheboss.com/jboss-configuration/solving-jboss-5-classloading-issues
http://thoughts.inphina.com/2010/09/02/classloading-isolation-issues-with-jboss-5-1/
http://javadevtips.blogspot.se/2011/10/how-to-make-correct-classloading-using.html


/Nils

tisdag 9 oktober 2012

JUnit code template


Den senaste tiden har jag försökt besvara några frågor på stackoverflow.com (har även lagt mina senaste svar till höger på bloggen). Det känns nyttigt att lära sig förstå vad andra frågar efter, samt försöka formulera ett svar så att personen som ställde frågan förstår vad man menar. Inte alltid det enklaste... :)

Har också labbat lite med Eclipse, Maven och github. Skriver troligen några rader om detta i ett senare inlägg.

Ikväll tänkte jag endast dela med mig av min JUnit code template som är ganska bra att ha till hands om man snabbt vill komma igång och skriva ett unit-test utan att behöva importera alla nödvändiga bibliotek.


Efter att ovanstående template är importerad är det bara att skapa en ny klass, slänga bort den automatgenererade koden och skriva "test" följt av CTRL + Space.

/Nils