ListView, ObjectDataSource un Linq
Šodien, papildinot Tfs laika uzskaites moduli, par kura 3. daļu man ir jāatvainojas, ka nav sanācis laika turpināt sēriju, uzskrēju virsū interesantai lietai, par kuru vēlējos pastāstīt.
Tātad laika uzskaites pārskata lapa, kas attēlo pavadīto laiku pie konkrētā darba jeb Work Item, ieguva sev Ajax šarmu un pēc rediģēšanas operācijas sarakstā, tas vairs nepārlādējas nozibsnījot visai lapai, bet gan eleganti pārlādējas, jo ievietots UpdatePanel kontrolē. Pārskatot kodu un pamazām to pucējot uz publicēšanu, nolēmu ar rokām rakstīto asp:Repeater kontroli aizvietot ar asp:ListView.
ListView kontrole pa lielam ir DataGrid un Repeater apvienojums, piedāvājot šablona elastīgumu no Repater kontroles un rediģēšanas iespējas no DataGrid. Ši kontrole ir ērtāka par DataGrid, jo piedāvā lielāku kontroli pār to, kā izskatīsies datu rediģēšanas kontrole klientam, tajā pašā laikā saglabājot visas spēcīgās DataGrid iespējas.
Tātad šoreiz ir stāsts par sekojošiem aktieriem galvenajās lomās: lietotāja saskarnei tiek izmantots ListView kontrole datu attēlošanai sarakstā (Grid style), ObjectDataSource kā datu avots, kurš tiek pievienots pie paštaisīta datu pārvaldnieka jeb vārtejas uz datu avotiem. Zem pārsega slēpjas Linq to Sql tehnloģija, lai iegūtu datus no datu bāzes, kā arī veikti saglabāšanas, rediģēšanas un dzēšanas operācijas.
ObjectDataSource tiek pievienots paštaisītam datu pārvaldniekam viena iemesla dēļ - ja atceraties Tfs laika uzskaites moduļa arhitektūras bildi (sk. šeit), tad datu operācijas tiek veiktas citā servisā, kas nozīmē, ka lietotāja saskarsnē nebūs pieejams DataContext u tāpēc LinqDataSource nav iespējams izmantot. Šim nolūkam ir jāizmanto klasi no DataLayer bibliotēkas, kas nodrošina vārteju uz laika uzskaites datu rediģēšanas servisu.
Demonstrācijas nolūkiem izveidosim sekojošu datu pārvaldnieku:
public List<Customer> GetList()
{
using (NorthwindDataContext ctx = new NorthwindDataContext())
{
return (from c in ctx.Customers
select c).ToList();
}
}
public void Delete(Customer customer)
{
using (NorthwindDataContext ctx = new NorthwindDataContext())
{
ctx.Customers.Attach(customer);
ctx.Customers.DeleteOnSubmit(customer);
ctx.SubmitChanges();
}
}
Šis datu pārvaldnieks nodrošina atlases un dzēšanas operācijas. Demonstrācijas nolūkiem vairāk arī nav nepieciešams.
Pēc tam mūsu lietotāju saskarnes slānī nodefinējam ObjectDataSource:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
SelectMethod="GetList"
TypeName="ObjectDataStoreTest.DataManager"
DataObjectTypeName="ObjectDataStoreTest.Customer"
DeleteMethod="Delete">
</asp:ObjectDataSource>
Tad nodefinējam ListView kontroli. Tā ka ListView kontrole izmanto šablonus, tad operāciju saites (Insert, Update, Delete) mums ir jāveido pašiem tajā vietā un pozīcijā, kurā mēs to vēlamies. Šim nolūkam atliek tikai kontrolei, kas nodrošinās kādu no datu manipulācijas operācijām norādīt CommandName atribūtu.
<asp:Button ID="DeleteButton" runat="server" CommandName="Delete" Text="Delete" />
Palaižot mūsu super-puper aplikāciju iegūstam smuki ģenerētu lietotāja interfeisu:
Šajā demonstrācijā mums interesē kāda no operācijām, šajā piemērā - Delete.
Ja mēs ievietojam Breakpoint Delete() operācijā mūsu datu pārvaldniekā, tad momentā, kad nospiežam Delete pogu, izsaukuma parametrā redzam sekojošu ainu:
Pēc ienākošā Delete operācijas parametra ir skaidrs, ka ListView kontrole visticamāk ģenerē operācijas parametru jeb Customer klases instanci.
Papētot sīkāk ListView kontroli, ieraugām DataKeyNames īpašību, kas nosaka, kuras objekta īpašības kalpo identifikācijai. Loģiski, ka Customer klasei mēs definējam CustomerID, kā DataKeyNames atribūtu:
<asp:ListView ID="ListView1" runat="server"
DataSourceID="ObjectDataSource1"
OnItemDeleting="ListView1_ItemDeleting"
DataKeyNames="CustomerID">
Jaunajā redakcijā situācija izskatās savādāk:
Tātad šaudas ir apstiprinājušās, ListView pats ģenerē operācijas parametra klases instanci, norādot tai DataKeyNames atribūtus pārkopējot tos no instances, kuras kontekstā operācija ir izpildīta.
Šāds piegājiens, mūs īsti neapmierina, jo pielietojot standarta Linq to Sql iespējas:
using (NorthwindDataContext ctx = new NorthwindDataContext())
{
ctx.Customers.Attach(customer);
ctx.Customers.DeleteOnSubmit(customer);
ctx.SubmitChanges();
}
Delete operācijai ir nepieciešams tieši tā pati klases instance, kas tika ielādēta, pretējā gadījumā iegūstam:
Kāds var man lūdzu paskaidrot, kas iepriekš jau ir saskāries ar šāda veida problēmām - es daru kaut ko nepareizi, vai tas ir by-design?
Ir viens variants, kas man ienāca prātā, kā varētu risināt šo nelielo pārpratumu - ieviešot dzēšanas operāciju (vai jebkuru cita veida operāciju) pēc identifikātoriem:
public void DeleteById(Customer customer)
{
using (NorthwindDataContext ctx = new NorthwindDataContext())
{
ctx.Customers.DeleteOnSubmit (
ctx.Customers.Single (c => c.CustomerID.Equals (customer.CustomerID)));
ctx.SubmitChanges();
}
}
Pēc pēdējām modifikācijām izriet, ka objekts, kas tiek nodots no ListView līdz attiecīgās operācijas metodei ir uzskatāms tikai kā DTO (data transfer objekts), kuram nekādas biznesa vērtības nav.
Kopsummā jaunā ListView kontrole ir diezgan pievilcīga, piedāvājot funkcionalitāti, kas līdz šim nebija pieejams - šabloni no Repeater kontroles, rediģēšanas iespējas no DataGrid. Protams, ir nepieciešams daudz maarkup elementus ielikt kodā, lai tas viss strādātu, bet pārlūkošanas un rediģēšanas iespējas tur pat uz vietas - patiešām ir jauka iespēja.
Jauks pievienojums ASP.NET 3.5 kontroļu saimei.
Cerams, ka noderēs!