Bernhard's profileBernhard Grojer - BlogPhotosBlogLists Tools Help

Blog


    April 09

    Silverlight: DataBinding zu WCF Service - Teil 2/2


    Im letzten Posting haben wir mithilfe von Silverlight und WCF Daten am Client verfügbar gemacht. Außerdem haben wir bereits deklaratives DataBinding verwendet in XAML.

    Nun wollen wir noch dafür sorgen, dass der Client Änderungen in den (Customer) Objekten mitprotokolliert und diese später mittels WCF wieder an den Server gesendet / gespeichert werden können.

    Da wir bereits Objekte gegen unser DataGrid gebunden haben werden die Änderungen auch zurück ins Objekt gespeichert (TWO-WAY DataBinding)

    Unsere CustomerDataSource Klasse muss nun nur noch geänderte “Kunden” mitprotokollieren.

    Dafür können wir den selben Ansatz verwenden, den auch Silverlight/WPF für die akt. des UI verwendet oder auch der LINQ DataContext dies macht.
    Wir hängen uns an das PropertyChanged Event (vom Interface INotifyPropertyChanged) und lassen uns Informieren sobald eine Änderung erfolgt:

     Dictionary<Customer, string> _ChangedObjects = new Dictionary<Customer, string>();
            public void RegisterChangeTracking(Customer c)
            {
                c.PropertyChanged += (sender, e) => 
                    { 
                        var cus = sender as Customer;
                        if (_ChangedObjects.ContainsKey(cus))
                            _ChangedObjects[cus] += ";" + e.PropertyName;
                        else
                            _ChangedObjects.Add(cus, e.PropertyName);
                    };
            }

    Bevor nun ein Objekt in die Observable<T> Liste wandert (und somit im UI dargestellt wird) wird es mithilfe von RegisterChangeTracking T o) registriert.
    Ändert sich nun ein Property in der Klasse wird ein Eintrag im _ChangedObjects Dictionary erstellt.

    private void LoadCustomers()
            {
                CustomerServiceClient client = new CustomerServiceClient();
                client.GetCustomersCompleted += (sender, e) =>
                {
                    foreach (var c in e.Result)
                    {
                        _Customers.Add(c);
                        RegisterChangeTracking(c);
                    }
    
                };
                client.GetCustomersAsync();
            }

    Nun müssen wir nur noch das Dictionary mit den geänderten Daten dem WCF Service übergeben.

    private void SaveCustomers()
            {
                CustomerServiceClient client = new CustomerServiceClient();
                client.SaveCustomersCompleted += (sender, e) =>
                {
                    var b = e.Result;
                };
                client.SaveCustomersAsync(_ChangedObjects);
            }

    Die Methode SaveCustomer wird nun aus der Silverlight-Anwendung aufgerufen (ein Button im UI triggert folgenden Code):

    private CustomerDataSource CustomerDataSource
            {
                get
                {
                    return this.Resources["Customers"] as CustomerDataSource;
                }
            }
    
            private void ButtonSave_Click(object sender, RoutedEventArgs e)
            {
                CustomerDataSource.Save();
            }


    Damit ist die Client-Seite abgeschlossen und wir müssen uns nun noch um das tatsächliche Speichern in der CustomerService Klasse kümmern.

    Hier möchten wir natürlich Datenbankabfragen minimieren und soviel wie möglich mit einem gang zur Datenbank ausführen.
    Außerdem sollen nur die Felder gespeichert werden, die tatsächlich geändert wurden:

    public bool SaveCustomers(Dictionary<Customer, string> Customers)
            {
                using (var DB = new NorthwindDataContext())
                {
                    var query = from c in DB.Customers
                                where Customers.Keys.Select(cus => cus.CustomerID).Contains(c.CustomerID)
                                select c;
    
                    var lst = query.ToList();
                    Type t = typeof(Customer);
                    foreach (var kv in Customers)
                    {
                        var oldCustomer = kv.Key;
                        var newCustomer = lst.Single(cus => cus.CustomerID == oldCustomer.CustomerID);
    
                        foreach (var s in kv.Value.Split(new char[] {';'}, StringSplitOptions.RemoveEmptyEntries))
                        {
                            var propInfo = t.GetProperty(s);
                            var v = propInfo.GetValue(oldCustomer, null);
                            propInfo.SetValue(newCustomer, v,null);
                        }
                    }
    
                    DB.SubmitChanges();
                }
                return true;
            }

    Das fertige Beispiel (selber Link wie im Teil 1 d. Blog-Serie) steht zum Download bereit: SilverlightConsumeWCF 08042009.zip

    April 08

    Silverlight: DataBinding zu WCF Service - Teil 1/2


    In Silverlight kann mittels DataBinding sehr einfach ein WCF-Service konsumiert werden.

    image 

    Mit Hilfe von INotifyCollectionChanged und INotifyPropertyChanged bekommen wir im Bereich DataBinding ausgezeichnete Möglichkeiten Änderungen ins UI zu puplizieren.

    Im Beispiel habe ich die Customer Tabelle der Northwind-Datenbank gewählt und diese über ein WCF Service (BasicHttpBinding mit AspNetCompatibility) bereitgestellt.

    [ServiceContract]
        public interface ICustomerService
        {
            [OperationContract]
            List<Customer> GetCustomers();
    
            [OperationContract]
            bool SaveCustomers(Dictionary<Customer, string> Customers);
        }
    
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
        public class CustomerService : ICustomerService
        {
            #region ICustomerService Members
    
            public List<Customer> GetCustomers()
            {
                using (var DB = new NorthwindDataContext())
                {
                    var query = from c in DB.Customers
                                select c;
                    return query.ToList();
                }
            }
    
            public bool SaveCustomers(Dictionary<Customer, string> Customers)
            {
                TODO: Implement Save
                return true;
            }
    
            #endregion
        }

    Damit dieses Service verfügbar ist muss noch die passende web.config (Service Configuration Editor) erstellt werden (passende .svc Datei nicht vergessen bei WCF Service unter WAS/IIS)

    image

    Dieses Service wird in Silverlight von der Klasse CustomerDataSource asynchron konsumiert. Dies hat natürlich den Vorteil, dass das UI sofort verfügbar ist wärend die Daten noch nachgeladen werden. Und das man von der ersten Sekunde an deklaratives DataBinding benutzen kann.

    Damit die Daten aber tatsächlich nachgeladen werden können brauchen wir zuerst einen Proxy den wir über “Add Service Reference” erzeugen lassen. (Meta-Daten müssen vom Service aktiviert/bereitgestellt werden).

    image

    Sobald der CustomerServiceCleint (generierter Code) nun verfügbar ist können wir eine passende Klasse erzeugen und das Service benutzen.

    public class CustomerDataSource
        {        
            public CustomerDataSource()
            {
                LoadCustomers();
            }
    
            private void LoadCustomers()
            {
                CustomerServiceClient client = new CustomerServiceClient();
                client.GetCustomersCompleted += (sender, e) =>
                {
                    foreach (var c in e.Result)
                    {
                        _Customers.Add(c);
                    }
    
                };
                client.GetCustomersAsync();
            }
    
            ObservableCollection<Customer> _Customers = new ObservableCollection<Customer>();
            public ObservableCollection<Customer> Customers
            {
                get {
                    return _Customers;
                }
    
            }     
        }


    Die Klasse stellt nun eine ObservableCollection<T> (Customers) bereit in Form eines Property. Gegen das wird in XAML deklarativ gebunden.

    Die Klasse CustomerDataSource beginnt beim Erzeugen das CustomerService aufzurufen. Sobald das Ergebnis vom Service da ist, wird die Liste _Customers befüllt. Da es sich hierbei um eine ObservableCollection<T> handelt und diese INotifyCollectionChanged implementiert wird das UI informiert sobald ein neuer Customer in die Liste wandert und DataGrid aktualisiert automatisch.

    In XAML fehlt und zuletzt noch das Binding:

    <UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"  x:Class="SilverlightConsumeWCF.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:SilverlightConsumeWCF">
        <UserControl.Resources>
            <local:CustomerDataSource x:Key="Customers" />
        </UserControl.Resources>    
        <Grid x:Name="LayoutRoot" Background="White">
            <data:DataGrid ItemsSource="{Binding Path=Customers, Source={StaticResource Customers}}">
            </data:DataGrid>
        </Grid>
    </UserControl>

    Download: SilverlightConsumeWCF 08042009.zip

    Im Teil 2 wird noch editieren der Customer-Objekte ermöglicht. Das Service stellt die entsprechende Methodensignatur bereits bereit.

    April 07

    Silverlight 3: NetworkChange (u. DataBinding mit INotifyPropertyChanged)

    In Silverlight 3 gibt es nun eine einfache Möglichkeit um den aktuellen Netzwerkstatus zu erkennen.

    Netzwerkstatus:
    Über die statische Methode NetworkInterface.GetIsNetworkAvailable() bekommt man nun die Information ob die Verbindung im Moment verfügbar ist oder nicht.
    Die Klasse NetworkChange stellt hingegen das Event NetworkAddressChanged bereit, das gefeuert wird sobald sich der Netzwerkstatus ändert.

    DataBinding:
    Über DataBinding können wir nun recht einfach die Funktionalität im UserInterface darstellen.
    Mithilfe von einem Binding von der Klasse zu einer CheckBox auf das Property IsChecked=”{Binding Path=IsOnline, Source={StaticResource State}}” kann nun in XAML an eine Ressource angebunden werden. (in dem Fall wird das Property IsOnline abgegriffen von der Resource mit dem Key State)

    image 

    Somit haben wir im UserInterface (= unser Silverlight UserControl) 0 Zeilen Programmcode.

    Klasse:

    	public class NetworkState : INotifyPropertyChanged
        {
            public NetworkState()
            {
                NetworkChange.NetworkAddressChanged +=
                       (sender, args) => SendNotifyPropertyChanged("IsOnline");
            }
    
            public void SendNotifyPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
    
            public bool IsOnline
            {
                get
                {
                    return NetworkInterface.GetIsNetworkAvailable(); ;
                }
            }
            
            #region INotifyPropertyChanged Members
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            #endregion
        }

    UserControl (XAML):

    <UserControl x:Class="SilverlightNetworkState.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:local="clr-namespace:SilverlightNetworkState"
        Width="400" Height="300">
        <UserControl.Resources>
            <local:NetworkState x:Key="State" />
        </UserControl.Resources>
        <Grid x:Name="LayoutRoot" Background="White">
            <CheckBox Margin="10" IsChecked="{Binding Path=IsOnline, Source={StaticResource State}}" Content="Online" />
        </Grid>
    </UserControl>

    Download: SilverlightNetworkState 07042009.zip