<ig:WcfListScheduleDataConnector
Name="WcfDataConnector"
EndpointConfigurationName="MyEndpointConfiguration"
PollingInterval="00:00:05"
PollingMode="Detailed"/>
The WcfListScheduleDataConnector is a non-visual element that uses a WCF service to provide schedule data to XamScheduleDataManager. It is based on the ListScheduleDataConnector and creates view model objects ( resources, calendars and activities) that are provided to the schedule data manager.
In XAML:
<ig:WcfListScheduleDataConnector
Name="WcfDataConnector"
EndpointConfigurationName="MyEndpointConfiguration"
PollingInterval="00:00:05"
PollingMode="Detailed"/>
In Visual Basic:
Dim dataConnector = New WcfListScheduleDataConnector()
dataConnector.EndpointConfigurationName = "MyEndpointConfiguration"
dataConnector.PollingInterval = TimeSpan.FromSeconds(5)
dataConnector.PollingMode = WcfSchedulePollingMode.Detailed
In C#:
var dataConnector = new WcfListScheduleDataConnector();
dataConnector.EndpointConfigurationName = "MyEndpointConfiguration";
dataConnector.PollingInterval = TimeSpan.FromSeconds(5);
dataConnector.PollingMode = WcfSchedulePollingMode.Detailed;
In the sample code above, the specified EndpointConfigurationName is defined in the project’s app.config file as an <endpoint> element.
The WCF list connector should be used when the schedule user interface cannot easily access the data on demand and it is not practical to send all schedule data in a database from the server to the client. The WCF list connector communicates with a service that accepts IEnumerable collections on a server and sends back only the schedule data that is requested by the client.
The application containing the user interface is the client side of the WCF communication. Using the WCF list connector on the client is as simple as setting the XamScheduleDataManager. DataConnector property to an instance of WcfListScheduleDataConnector and telling that connector the location of the remote WCF service.
In XAML:
<ig:XamScheduleDataManager CurrentUserId="jsmith">
<ig:XamScheduleDataManager.DataConnector>
<ig:WcfListScheduleDataConnector
EndpointConfigurationName="MyEndpointConfiguration" />
</ig:XamScheduleDataManager.DataConnector>
</ig:XamScheduleDataManager>
In Visual Basic:
Dim dataConnector = New WcfListScheduleDataConnector()
dataConnector.EndpointConfigurationName = "MyEndpointConfiguration"
Dim dataManager = New XamScheduleDataManager()
dataManager.DataConnector = dataConnector
In C#:
var dataConnector = new WcfListScheduleDataConnector();
dataConnector.EndpointConfigurationName = "MyEndpointConfiguration";
var dataManager = new XamScheduleDataManager();
dataManager.DataConnector = dataConnector;
The remote WCF service can be specified in two different ways. A service endpoint can be defined in a configuration file or the remote address and binding can be specified directly on the connector.
Probably the easiest way to define the location of a remote WCF service and use it in multiple clients in an application is to define an endpoint in the configuration file. The endpoint contains all information necessary to connect to the remote WCF service: the remote address and the binding used to communicate between the client and service. The following is a sample app.config file from a WPF project using a WCF list connector:
In XAML:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<customBinding>
<binding name="MyCustomBinding">
<binaryMessageEncoding />
<httpTransport maxReceivedMessageSize="2147483647"/>
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="http://localhost:39959/MyScheduleDataService.svc"
binding="customBinding" bindingConfiguration="MyCustomBinding"
contract="WcfListConnectorServiceWpf.IWcfListConnectorService"
name="MyEndpointConfiguration" />
</client>
</system.serviceModel>
</configuration>
Notice that the address in the example above is set to localhost. This configuration file is used while debugging both the client and service. When the service is published and the client is released, the address should contains the full domain name so the client can connect to the service from a different machine.
Also notice that the <endpoint> element in the configuration file requires a contract attribute to be specified. This specifies the WCF contract implemented by the remote service.
When using WPF on the client, this contract specification will be:
WcfListConnectorServiceWpf.IWcfListConnectorService
To use an endpoint on a WCF list connector, just set the name of the endpoint configuration on the EndpointConfigurationName property of the WcfListScheduleDataConnector instance.
When an endpoint is used from a configuration file, the remote service address can also be specified on the WCF list connector. Specifying the remote address will cause the connector to ignore the address attribute of the <endpoint> element. In fact, if the remote address is specified, the endpoint’s address can be set to an empty string. The remote address is specified by setting the RemoteAddress property of the WcfListScheduleDataConnector instance. This property can be set to an instance of string, System.Uri, or System.ServiceModel.EndpointAddress.
The other way to specify the remote WCF service is by setting the remote address and binding directly on the WcfListScheduleDataConnector instance. These can be set using the RemoteAddress and RemoteBinding properties, respectively.
In XAML:
xmlns:ig=http://schemas.infragistics.com/xaml
xmlns:channels=
"clr-namespace:System.ServiceModel.Channels;assembly=System.ServiceModel"
<ig:WcfListScheduleDataConnector
RemoteAddress="http://localhost:39959/MyScheduleDataService.svc">
<ig:WcfListScheduleDataConnector.RemoteBinding>
<channels:CustomBinding>
<channels:CustomBinding.Elements>
<channels:BinaryMessageEncodingBindingElement />
<channels:HttpTransportBindingElement
MaxReceivedMessageSize="2147483647" />
</channels:CustomBinding.Elements>
</channels:CustomBinding>
</ig:WcfListScheduleDataConnector.RemoteBinding>
</ig:WcfListScheduleDataConnector>
In Visual Basic:
Dim dataConnector = New WcfListScheduleDataConnector()
dataConnector.RemoteAddress =_
"http://localhost:39959/MyScheduleDataService.svc"
Dim customBinding = New CustomBinding()
customBinding.Elements.Add(New BinaryMessageEncodingBindingElement())
Dim httpTransportBindingElement = New HttpTransportBindingElement()
httpTransportBindingElement.MaxReceivedMessageSize = 2147483647
customBinding.Elements.Add(httpTransportBindingElement)
dataConnector.RemoteBinding = customBinding
In C#:
var dataConnector = new WcfListScheduleDataConnector();
dataConnector.RemoteAddress =
"http://localhost:39959/MyScheduleDataService.svc";
var customBinding = new CustomBinding();
customBinding.Elements.Add(new BinaryMessageEncodingBindingElement());
var httpTransportBindingElement = new HttpTransportBindingElement();
httpTransportBindingElement.MaxReceivedMessageSize = 2147483647;
customBinding.Elements.Add(httpTransportBindingElement);
dataConnector.RemoteBinding = customBinding;
The WCF list connector can poll for changes on the server and update the client accordingly. This is necessary when a change is made to the data by another client or by the server itself. For polling to work correctly, the data items specified on the server should implement the INotifyPropertyChanged interface. If any items have properties changes on the server, the WCF service will see that a change has occurred and it will send that change to the client the next time it polls for changes. However, polling can also be more efficient if the collections containing the data items implement either INotifyCollectionChanged or IBindingList. When one of these interfaces is implemented, the service is able to keep a list of detailed level changes and the order in which they appeared. When a client polls for changes, it can get a list of exactly what changes have occurred since the last polling. There are three types of polling which can be done and they can be controlled by setting the PollingMode property of the WcfListScheduleDataConnector:
None – No polling occurs. The client does not get notified of changes on the server and may display data that is out of sync with the server data.
RequeryOnAnyChange – Any change detected on the server causes the client to ask the server for all the data about the current visible days again.
Detailed – This is the default PollingMode value. When the proper interfaces are implemented, data item level changes are sent to the client in a list. This prevents the client from having to ask for all the data again. However, if too many changes have occurred on the server since the client’s last polling and the returned list would be too large, the client falls back to the RequeryOnAnyChange mode for that one polling. Also, if the collections specified on the WCF service don’t implement INotifyCollectionChanged or IBindingList, the client always uses RequeryOnAnyChange polling. And if the INotifyPropertyChanged interface is also not specified on the data items, polling will take place, but the server will never report any changes.
In addition to controlling the mode, the WCF list connector also allows for control over how often the client polls the service for changes. The default interval is 30 seconds, but this can easily be changed by setting the PollingInterval property on the WcfListScheduleDataConnector instance to a valid TimeSpan.
The WCF service is the server side of the WCF communication. It has references to the schedule data and provides subsets of that data to the clients on demand. The WCF service can be set up by using one of the two default WCF schedule data services. They are WcfListConnectorServiceSingle and WcfListConnectorServiceMulti.
The WcfListConnectorServiceSingle is a service instance that only gets created once and all remote calls from clients are processed on the same thread on the server. This allows for set-up to only be done on the service once, which helps with performance. However, if too many clients try to connect to the service at one time, there may be a bottleneck which can slow down all clients trying to connect. For this reason, the WcfListConnectorServiceSingle should only be used when it is known that only a small number of clients will be connecting to the service at any one time.
The WcfListConnectorServiceSingle defines item source and property mappings for each collection type managed by the ListScheduleDataConnector. The following derived class sets a few of these item sources so clients can modify appointments:
In Visual Basic:
Imports System.ComponentModel
Imports Infragistics.Services.Schedules
Imports Infragistics.Controls.Schedules.Services
Public Class MyScheduleDataService
Inherits WcfListConnectorServiceSingle
Private appointments As BindingList(Of Appointment)
Private resources As BindingList(Of Resource)
Private resourceCalendars As BindingList(Of ResourceCalendar)
Public Sub New()
Me.resources = New BindingList(Of Resource)()
Dim resource As New Resource()
resource.Id = "jsmith"
resource.PrimaryCalendarId = "Cal1"
Me.resources.Add(resource)
Me.resourceCalendars =_
New BindingList(Of ResourceCalendar)()
Dim resourceCalendar As New ResourceCalendar()
resourceCalendar.OwningResourceId = "jsmith"
resourceCalendar.Id = "Cal1"
resourceCalendar.Name = "Primary Calendar"
Me.resourceCalendars.Add(resourceCalendar)
Me.appointments = New BindingList(Of Appointment)()
Me.ResourceItemsSource = Me.resources
Me.ResourcePropertyMappings =_
New ResourcePropertyMappingCollection()
Me.ResourcePropertyMappings.UseDefaultMappings = True
Me.ResourceCalendarItemsSource = Me.resourceCalendars
Me.ResourceCalendarPropertyMappings =_
New ResourceCalendarPropertyMappingCollection()
Me.ResourceCalendarPropertyMappings.UseDefaultMappings = True
Me.AppointmentItemsSource = Me.appointments
Me.AppointmentPropertyMappings =_
New AppointmentPropertyMappingCollection()
Me.AppointmentPropertyMappings.UseDefaultMappings = True
End Sub
End Class
In C#:
using System.ComponentModel;
using Infragistics.Services.Schedules;
using Infragistics.Controls.Schedules.Services;
public class MyScheduleDataService : WcfListConnectorServiceSingle
{
private BindingList<Appointment> appointments;
private BindingList<Resource> resources;
private BindingList<ResourceCalendar> resourceCalendars;
public MyScheduleDataService()
{
this.resources = new BindingList<Resource>();
Resource resource = new Resource();
resource.Id = "jsmith";
resource.PrimaryCalendarId = "Cal1";
this.resources.Add(resource);
this.resourceCalendars = new BindingList<ResourceCalendar>();
ResourceCalendar resourceCalendar = new ResourceCalendar();
resourceCalendar.OwningResourceId = "jsmith";
resourceCalendar.Id = "Cal1";
resourceCalendar.Name = "Primary Calendar";
this.resourceCalendars.Add(resourceCalendar);
this.appointments = new BindingList<Appointment>();
this.ResourceItemsSource = this.resources;
this.ResourcePropertyMappings =
new ResourcePropertyMappingCollection();
this.ResourcePropertyMappings.UseDefaultMappings = true;
this.ResourceCalendarItemsSource = this.resourceCalendars;
this.ResourceCalendarPropertyMappings =
new ResourceCalendarPropertyMappingCollection();
this.ResourceCalendarPropertyMappings.UseDefaultMappings = true;
this.AppointmentItemsSource = this.appointments;
this.AppointmentPropertyMappings =
new AppointmentPropertyMappingCollection();
this.AppointmentPropertyMappings.UseDefaultMappings = true;
}
}
The other type of service that can be used is the WcfListConnectorServiceMulti. This is an abstract class so a derived instance must be created. A new instance of the WcfListConnectorServiceMulti-derived service is created for each remote call that is received from a client and the remote calls are processed on different threads. This prevents the bottleneck which could occur when using the WcfListConnectorServiceSingle with too many clients. However, because a new instance of this service type must be created with each remote call, the overhead of initial set-up and connecting to the provided item sources is incurred with each remote call. So if it is known that only a small number of clients will be connecting at any time, the WcfListConnectorServiceSingle should be used instead.
To reduce the amount of overhead on each remote call, the WcfListConnectorServiceMulti does not require that all items sources and property mappings be initialized on each service instance. Instead, the item sources and property mappings are requested on demand. That is why the WcfListConnectorServiceMulti defines two abstract methods: GetItemSource and InitializePropertyMappings. The following derived class implements these methods so clients can modify appointments:
In Visual Basic:
Imports Infragistics.Services.Schedules
Imports Infragistics.Controls.Schedules.Services
Public Class MySQLScheduleDataService
Inherits WcfListConnectorServiceMulti
Private dataContext As ScheduleSQLDataContext
Protected Overrides Function GetItemSource(listManagerType As _
ItemSourceType) As IEnumerable
Select Case listManagerType
Case ItemSourceType.Appointment
Return Me.dataContext.AppointmentDBs
Case ItemSourceType.Resource
Return Me.dataContext.ResourceDBs
Case ItemSourceType.ResourceCalendar
Return Me.dataContext.ResourceCalendarDBs
End Select
Return Nothing
End Function
Protected Overrides Sub InitializePropertyMappings(listManagerType _
As ItemSourceType, mappings As Object)
Select Case listManagerType
Case ItemSourceType.Appointment
Dim appointmentMappings As AppointmentPropertyMappingCollection =_
DirectCast(mappings, AppointmentPropertyMappingCollection)
appointmentMappings.UseDefaultMappings = True
Exit Select
Case ItemSourceType.Resource
Dim resourceMappings As ResourcePropertyMappingCollection =_
DirectCast(mappings, ResourcePropertyMappingCollection)
resourceMappings.UseDefaultMappings = True
Exit Select
Case ItemSourceType.ResourceCalendar
Dim resourceCalendarMappings As ResourceCalendarPropertyMappingCollection =_
DirectCast(mappings, ResourceCalendarPropertyMappingCollection)
resourceCalendarMappings.UseDefaultMappings = True
Exit Select
End Select
End Sub
Protected Overrides Sub OnRemoteCallReceived(context As CallContext)
Me.dataContext = New ScheduleSQLDataContext()
MyBase.OnRemoteCallReceived(context)
End Sub
Protected Overrides Sub OnRemoteCallProcessed(result As CallResult)
MyBase.OnRemoteCallProcessed(result)
Me.dataContext.Dispose()
Me.dataContext = Nothing
End Sub
End Class
In C#:
using Infragistics.Services.Schedules;
using Infragistics.Controls.Schedules.Services;
public class MySQLScheduleDataService : WcfListConnectorServiceMulti
{
private ScheduleSQLDataContext dataContext;
protected override IEnumerable GetItemSource(
ItemSourceType listManagerType)
{
switch (listManagerType)
{
case ItemSourceType.Appointment:
return this.dataContext.AppointmentDBs;
case ItemSourceType.Resource:
return this.dataContext.ResourceDBs;
case ItemSourceType.ResourceCalendar:
return this.dataContext.ResourceCalendarDBs;
}
return null;
}
protected override void InitializePropertyMappings(
ItemSourceType listManagerType, object mappings)
{
switch (listManagerType)
{
case ItemSourceType.Appointment:
AppointmentPropertyMappingCollection appointmentMappings =
(AppointmentPropertyMappingCollection)mappings;
appointmentMappings.UseDefaultMappings = true;
break;
case ItemSourceType.Resource:
ResourcePropertyMappingCollection resourceMappings =
(ResourcePropertyMappingCollection)mappings;
resourceMappings.UseDefaultMappings = true;
break;
case ItemSourceType.ResourceCalendar:
ResourceCalendarPropertyMappingCollection resourceCalendarMappings =
(ResourceCalendarPropertyMappingCollection)mappings;
resourceCalendarMappings.UseDefaultMappings = true;
break;
}
}
protected override void OnRemoteCallReceived(CallContext context)
{
this.dataContext = new ScheduleSQLDataContext();
base.OnRemoteCallReceived(context);
}
protected override void OnRemoteCallProcessed(CallResult result)
{
base.OnRemoteCallProcessed(result);
this.dataContext.Dispose();
this.dataContext = null;
}
}
In this example, the derived class also overrides OnRemoteCallReceived, which gets called for all remote calls received by the service, and OnRemoteCallProcessed, which gets called just before the results are returned to the client that made the remote call, even if an error has occurred while processing the remote call. It uses these methods to set up and tear down a connection to an SQL database. The ScheduleSQLDataContext class used in this example was auto-generated by Visual Studio when a “LINQ to SQL Classes” item was added to the service project.
Once the service class is properly defined, the WCF service must be exposed so that clients can connect to it. This can be done in the configuration file of the service assembly. The following configuration file exposes the MyScheduleDataService class defined in the WcfListConnectorServiceSingle section above.
In XAML:
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults=" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<bindings>
<customBinding>
<binding name="MyServerCustomBinding">
<binaryMessageEncoding>
<readerQuotas maxStringContentLength="2147483647" />
</binaryMessageEncoding>
<httpTransport authenticationScheme="Anonymous" />
</binding>
</customBinding>
</bindings>
<services>
<service name="MyScheduleDataService">
<endpoint
address=""
binding="customBinding"
bindingConfiguration="MyServerCustomBinding"
contract="Infragistics.Services.Schedules.IWcfListConnectorService" />
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
</configuration>
This configuration file is similar to the one required on the client. However, this configuration file defines a service behavior and a service. The name attribute of the element should be the full type name of the service being exposed. Notice that this configuration file also defines a custom binding, as the client configuration did. It is important that the bindings on the client and server match. It is also important that the contract attribute of the first endpoint be set to:
Infragistics.Services.Schedules.IWcfListConnectorService
The WcfListScheduleDataConnector has a SecurityToken property that can be set to any object. This can be anything from a string containing the hash of a user password to a complex object. When any remote call is made by the client, the object specified on the SecurityToken has its ToString() method called on it, and that value is sent to the server for validation. So, if multiple pieces of information were needed to validate the client, the object specified on the SecurityToken could provide custom XML data in its ToString() implementation. The service can then parse that data to ensure the client is allowed to make the remote call. If no SecurityToken is specified, a null string will be sent to the service for validation.
To validate the security token on the service side, the creator of the service must handle the ValidateSecurityToken event or override the OnValidateSecurityToken method. The event arguments contain the string representation of the security token from the client. If the security token is invalid, an exception should be thrown in the event handler or method override. The remote call will not be processed and the exception information will be sent back to the client. Note that the ValidateSecurityToken event is fired and the OnValidateSecurityToken method is called even when no security token is specified.