Underlätta explicit databindning
Förord
Problembeskrivning Flera av de exempel som ligger ute på nätet visar hur man kan databinda mot en datakontroll i asp.net. Ofta visar de hur man binder ett dataset eller en datatable mot en datakontroll (och då ofta en gridview). I och med .net 2.0 kom en lite trevligare datakontroll: Object Datasource och en del utvecklare började mer och mer skapa klasser som representerade sin problemdomän. För problemdomänen skapas klasser som motsvarar delar i applikationen. En klass för t ex ett meddelande, en klass som representerar en användare mm. Man databinder sedan ofta listor med sina objektinstanser från klasserna mot olika datakontroller. Här uppstår då en lätt ”konflikt”. Man kanske använder den inbyggda eval (alt. Bind) metoden. Att eval:uera ett databundet objekt tar dock kraft/prestanda. Förutom att eval är prestandaineffektivt så anger man egenskapsnamnet med en stränglitteral Eval(”namnpåegenskap”).Innehåll
»»
»
»
»
Problembeskrivning
fortsättning - Mer info om varför eval är prestandaineffektivt finns under sektionen ”Minimize Calls to DataBinder.Eval" på
Problemen med hårdkodade strängar:
Problemen är att man kan stava fel, eller om egenskapsnamn ändras så kommer man inte upptäcka det.
Det går inte lika lätt att automatiskt förändra namnet på egenskapen till alla refererade ställen i koden när referensen är gjord som en sträng. Man kanske inte upptäcker felet förrän man kör applikationen och testar aktuell sida! (eller använder enhetstestning eller motsv.)
För mer info om att göra sig av med hårdkodade strängar se
Tidiga fel är bra fel. Om man kan visa på fel innan kompilation så är det i mitt tycke en bra sak.
Vad gör man då istället för att använda eval? Jo man cast:ar om objektet och säger eg. till applikationen att jag vet att det objekt du databinder mot är av typen T, t.ex. en instans från en egengjord klass med namnet Message.
Hur gör man det?
I t.ex. en datakontroll
<%# DirectCast(container.DataItem, Message).Titel %>
För mer olika exempel se:
det finns till och med en artikel här på Pellesoft om det sedan innan på:
Detta förfarande att cast:a kan dock bli tröttsamt att behöva skriva, och dessutom se (i mitt tycke) fult ut.
Lösningar
Jag har testat lite olika lösningar, bl a att skapa datakontroller vars Container.DataItem är starkt typat med att man kan skriva likn. nedan om dess Item är av Message typ.
<%# container.DataItem.Titel %>
Det blev lite finare, men ack att behöva skapa egna datakontroller för varje tänkbar klass!
Jag funderade vidare och tänkte göra en generell tex repeater av typ T, som returnerar ett item av Typ T. Detta skulle ev. fungera, men man behöver motsvarande för varje typ av datakontroll man vill använda sig av. Dessutom är inte ens container.DataItem.Titel särskilt ”sexigt”.
En lättare lösning blir då att börja se den bundna instansen som vad den eg. är och istället betrakta den som ett annat objekt. Med hjälp av Extension methods kommer här en förklaring till hur man skulle kunna förenkla vardagen med explicit databindning. För att få följande att fungera krävs det att man använder version 3.5 av .Net-ramverket.
Om man i VB skapar en Modul i sitt webbprojekt tex med namnet WebDataBindingHelper
Importerar en namnrymd: Imports System.Runtime.CompilerServices
Och skriver t ex:
_
Public Function AsMessage(ByVal container As System.Web.UI.IDataItemContainer) As Message
If container Is Nothing Then Return Nothing
Return DirectCast(container.DataItem, Message)
End Function
Så har vi i och med detta i ett slag refaktorerat bort behovet att göra en explicit typomvandlig från datakontrollen.
IDataItemContainer är ett trevligt interface som gör att det går oavsett om det är en Repeater eller en Gridview eller en Listview att behandla deras container likvärdigt.
Mer info om interfacet på:
Nu kan man istället skriva nedan i sin datakontroll:
<%# container.AsMessage.Titel %>
Semantiskt mycket finare och dessutom renare i och med mindre kod :D .
Varför går nu detta?
Jo extension methods utökar existerande klasser. Metoden AsMessage utökar/läggs på de objektinstanser av klasser som implementerat interfacet IDataItemContainer och gör att de nu har en pålagd funktion med namnet AsMessage.
Vad man behöver göra nu är alltså att göra extension methods för alla sina klasser vars objektinstanser man vill kunna databinda (som alternativ kan man ju alltid koppla på någon kodgenererare som gör en metod för varje åt en).
Vore det inte smidigare att kunna göra detta generellt?
En DirectCast kan ju cast:a till allt möjligt... Jo det går!
Låt oss nu göra just det genom att använda kraften av Generiska Typer. I modulen kan man nu skriva:
_
Public Function [As](Of T)(ByVal container As System.Web.UI.IDataItemContainer) As T
If container Is Nothing Then Return Nothing
Return DirectCast(container.DataItem, T)
End Function
Vad ovanstående kod gör är att möjliggöra en Container att typomvandla till vad som helst (förutsatt att man databundit med aktuell datatyp).
Har man databundit mot en lista av integer, strängar eller en komplex typ som t ex lista av instanser av klassen Message kan man nu göra det i sin kontroll enligt nedan:
<%# container.As(Of Message).Titel %>
<%# container.As(Of User).Username%> osv.
För tidigare version av .Net
Vad gör man om man har ett äldre projekt t ex i .net 2.0?Ja då kan man inte använda extension methods, men man kan ju fortfarande underlätta databindning.
I sin modul kan man ju alltid skriva samma kod utan att dekorera med extension-attributet och då använda den koden enligt nedan:
<%# AsMessage(container).Titel %>
Reflektioner
Det kan argumenteras om det egentligen är containern och inte dess dataitem som borde ha metoden på sig. För dem som vill kan man då antingen använda hela Container.DataItem alternativt kanske försöka injicera så datakontrollerna exponerar DataItem direkt?Vill man av någon anledning kunna typomvandla i vb.net m h a CType på databundet innehåll kan man ju göra det med t.ex. två olika metoder. En för typomvandling och en för DirectCast. Ex:
As: som returnerar CType(container.DataItem, T)
AsExactly: som returnerar DirectCast(container.DataItem, T)
Mer info om skillnaderna dem emellan på
Ytterligare resurser
Introduktion till Generics *
.NET Introducing Generics in the CLR and Jason Clark (c#)
*
Använda generiska metoder
*
Extension Methods (Visual Basic)
*
*
Pelle Johansson
Verkligen väl skriven artikel och ett intressant ämne. Stort tack!
Johan Normén
hej, Jag hade ju dock inte gjort så... men VB alltså hur fult är inte det språket... hehe kunde inte låta bli... men det är inte vackert. Har du tittat på ASP.Net MVC lite trevligare modell för hantera rednering av vyer. Ang din kod hade jag nog (om jag måste casta) kört på: <%# DirectCast(container.DataItem, Message).Titel %> I C# är ju koden snyggare så det är väl mkt där det faller med vb. Och då kan jag förstå lite din idé bakom detta. Om man bortser från lösningen så var det en intrerssant artikel som kan ge andra idéer för läsarna... Kul. Mvh Johan