Néhány változás:
Az EditStateChanged esemény most közli a módosított tulajdonságokat is, esetleg ha szükség lenne loggoláshoz, vagy bármihez. Ez akkor váltódik ki amikor az IEditableObject műveletek használtak akár az EditableObject, akár az EditableViewModelBase származékok esetében.
Van egy új PagingHelper osztály a Helpers mappába, illetve ehhez egy extension az Extensions mappába. Így könnyebb lapozható query-ket írni.
Service
Az egyik legérdekesebb része a dolognak. Mivel egy-egy művelet lehet komplexebb, több input és több outputal, így ezt elvonatkoztatva a konkrét példáktól, generikusabb módszert választottam. Szolgáltatás esetében most tisztázzuk, hogy nem WCF szolgáltatásról, vagy hasonlóról beszélünk, hanem tulajdonképpen a model, mint szolgáltatás rétegről van szó. A történet egyszerű, szolgáltatás műveletek vannak, melyeket interfészek írnak le, pontosan két típus:
Ha nincs visszatérési érték:
interface IServiceAction<in TRequest> : IServiceOperation<TRequest>
Ha kapunk vissza értéket:
interface IServiceFunc<in TRequest, TResponse> : IServiceOperation<TRequest>
Mindegyikhez tartozik egy alap implementáció, melyek neve:
class ServiceActionBase<TService, TRequest> : ServiceOperationBase<TService, TRequest>, IServiceAction<TRequest>
class ServiceFuncBase<TService, TRequest, TResponse> : ServiceOperationBase<TService, TRequest>, IServiceFunc<TRequest, TResponse>
Az implementáció tartalmaz egy szinkronizációs kontextust (ami WPF és SL esetében a dispatcher kontextus lesz alapértelmezésben), illetve magát a tényleges szolgáltatást (TService), ami lehet egy WCF Data Service kontextus, sima WCF szolgáltatás, vagy akár egy generikus lista (List<IPerson>) is teszteléshez, mint adatforrás (szolgáltatás).
Minden szervíz művelet meghatároz egy abstract Execute metódust, aminek a Request lesz a paramétere. Amikor saját műveletet írunk, ezt kell mindig felülírnunk. Mutatok egy példa implementációt WCF Data Service-hez:
Ugye nem akarunk 667 query-t, meg anyám kínját írni minden lapozáshoz, stb. Ezért származtatunk egy service operation-t. Jelenleg én csináltam egy ItemsRequest és egy ItemsResponse osztályt lekérdezésekhez. A ItemsRequest osztály tartalmaz a lapozáshoz szükséges paraméterek megadását, az ItemsResponse pedig az eredményt, illetve az összes tétel számát. Ezalapján csináljunk egy közös őst lekérdezésekhez általában, ami ráadásul a megfelelő felüleletbe is már automatikusan kasztol (Person => IPerson). Ezt a példát megtaláljátok a Tests mappában:
public abstract class DataServiceQueryOperation<TItem, TInterfaceItem> : ServiceFuncBase<ShopContext, ItemsRequest, ItemsResponse<TInterfaceItem>>{ public DataServiceQueryOperation(ShopContext service) : base(service) { } protected abstract DataServiceQuery<TItem> OnExecute(ItemsRequest request); public override void Execute(ItemsRequest request) { try { var query = (DataServiceQuery<TItem>)(OnExecute(request) .AddQueryOption("$inlinecount", "allpages") .Paging(request.Paging.PageIndex, request.Paging.PageSize)); query.BeginExecute((IAsyncResult ar) => { var list = default(IEnumerable<TInterfaceItem>); var error = default(Exception); var response = default(QueryOperationResponse<TItem>); try { response = (QueryOperationResponse<TItem>)query.EndExecute(ar); list = response.Cast<TInterfaceItem>().ToArray(); } catch (Exception ex) { error = ex; } Synchronization.Post(state => { OnCompleted(new ResponseEventArgs<ItemsResponse<TInterfaceItem>>( error, new ItemsResponse<TInterfaceItem>(list, response.TotalCount))); }, null); }, null); } catch (Exception ex) { Synchronization.Post(state => { OnCompleted(new ResponseEventArgs<ItemsResponse<TInterfaceItem>>(ex, null)); }, null); } }}
Mint látható ez a kis osztály segít egy query-t aszinkron futtatni, majd az eredményt a dispatcher kontextusába (vagy az éppen aktuális szinkronizációs kontextusba) vissza postolni. Az $inlinecount segít, hogy egyetlen kéréssel kapjuk vissza az összes tétel számát is egyúttal. Ez alapján egy Person listát lekérdező művelet:
public class PersonQueryOperation : DataServiceQueryOperation<Person, IPerson>{ public PersonQueryOperation(ShopContext service) : base(service) { } protected override DataServiceQuery<Person> OnExecute(ItemsRequest request) { return Service.Person; }}
Jó nem? Természetesen csinálhatunk saját request és response-t is, a lényeg ugyanaz. Egységes módon, műveleteket definiálunk a szolgáltatás modellünkbe, kicsit SOA mintára, amiket különböző adatforrásokhoz implementálhatunk. Maga a szolgáltatás csak a műveletet írja le:
public interface IPersonService{ IServiceFunc<ItemsRequest, ItemsResponse<IPerson>> GerPersonList { get; }}
Azt már különböző implementációk döntik el, honnan jönnek ehhez az adatok. További részletekért nézzétek át a teszt alkalmazást. Megjegyzem az a teszt nem teljes, csak azon kísérletezgetek.
ViewModels
Összevontam néhány dolgot, csak egy RepositoryViewModelBase létezik, viszont van egy új property: Pager. A Pager egy RepositoryPagerViewModel, amely az összes szükséges lapozási szolgáltatás commandokba foglalva a felületen elérhetővé teszi. Fontos látni, ez az implementáció nem függ a IPagedCollectionView-tól, amúgy is az WPF-ben nincs is, hanem bármihez jó. A tesztbe szintén láthattok rá példát, hogyan kell XAML-be a felületbe bekötni. DataPager nem támogatott és nem is kell! A lényeg, hogy kezeli a szerkesztést, stb. Így pl. nem lehet addig lapozni, amíg valami szerkesztési állapotba van, illetve auto frissíti a lapozó parancsokat, gombokat. Lehet állítani az oldal méretet is a felületbe kötve, stb. Alapértelmezésbe felülírhatók az OnPageChanging és OnPageChanged tagok, így akár egyénileg megkadályozható a lapozás. A commandok auto kezelik ezt és a UI szintén frissül eszerint. De lehetőség van az egész Pager cseréjére is, felülírva a virtuális property-t. Egy dologra kell figyelni ha saját pager-t csinálunk, hogy az OnPageChanging és OnPageChanged eseményekre iraktkoztassuk fel (nem kötelező, de pl. az OnPageChanged auto meghívja a Refresh metódust, ami újra lekéri az adatokat a PageIndex, PageSize, ItemCount változáskor).
Jó szórakozást hozzá: