By now, those of you who know me personally or through the forums and blogs know that I love Silverlight. It had me at hello. A rich client side user experience, cross platform*, tiny runtime. What's not to love? In this set of posts, I want to show you some of the things that you would need, to develop a client side applet for your next big product.
For this, we are going to develop and deploy a bug tracking app. "Bugger" and we'll use XPO for Silverlight to connect to our data. If you have not read the Silverlight and Data Access using XPO I suggest you do so as it covers the basics. The article is written for DXperience v8.2, and in v8.3, a few things have been changed. For now, the only thing we need to worry about is the name change of the data acess assemblies.
· DevExpress.Data.v8.2.SL was renamed to DevExpress.AgData.v8.3
· DevExpress.Xpo.v8.2.SL was renamed to DevExpress.AgXpo.v8.3
Getting Started
We'll start by creating a new ASP.NET project and add a Silverlight-enabled WCF Service to it. The web service will expose the XPO DataStore that our client will use.
[ServiceContract(Namespace = ""), AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[XmlSerializerFormat]
public class Gateway {
static DevExpress.Xpo.DB.IDataStore s_dataStore;
static Gateway() {
try {
string connectionString = DevExpress.Xpo.DB.AccessConnectionProviderMultiUserThreadSafe.GetConnectionString(
"C:\\Bugger.mdb", "Admin", string.Empty);
s_dataStore = DevExpress.Xpo.XpoDefault.GetConnectionProvider(
connectionString,
DevExpress.Xpo.DB.AutoCreateOption.DatabaseAndSchema);
}
catch (Exception e) {
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
[OperationContract]
public DevExpress.Xpo.DB.AutoCreateOption GetAutoCreateOption() {
return s_dataStore.AutoCreateOption;
}
[OperationContract]
public DevExpress.Xpo.DB.SelectedData SelectData(DevExpress.Xpo.DB.SelectStatement[] selects) {
if (selects != null && selects.Length > 0 && selects[0].TableName == "XPObjectType") {
return new DevExpress.Xpo.DB.SelectedData(new DevExpress.Xpo.DB.SelectStatementResult());
}
return s_dataStore.SelectData(selects);
}
[OperationContract]
public DevExpress.Xpo.DB.UpdateSchemaResult UpdateSchema(bool dontCreateIfFirstTableNotExist,
DevExpress.Xpo.DB.DBTable[] tables) {
try {
return s_dataStore.UpdateSchema(dontCreateIfFirstTableNotExist, tables);
}
catch (Exception e) {
System.Diagnostics.Debug.WriteLine(e.Message);
return DevExpress.Xpo.DB.UpdateSchemaResult.FirstTableNotExists;
}
}
[OperationContract]
public DevExpress.Xpo.DB.ModificationResult ModifyData(DevExpress.Xpo.DB.ModificationStatement[] statements) {
try {
return s_dataStore.ModifyData(statements);
}
catch (Exception e) {
System.Diagnostics.Debug.WriteLine(e.Message);
return null;
}
}
}
We will need to add references to DevExpress.Data.v8.3 and DevExpress.Xpo.v8.3 assemblies.
Client Side
On the client side, we will need to add a service reference to this Gateway web service. Instead of using the wizard however, we'll do this manually by adding a generic client proxy implementation (Gateway.cs) to our Silverlight project, downloadable here http://tv.devexpress.com/content/XPO/Gateway/Gateway.zip. We'll update the ServiceReferences.ClientConfig so that our IGateway contract is registered properly.
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_Gateway" maxBufferSize="65536"
maxReceivedMessageSize="65536">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:50344/Gateway.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_Gateway" contract="DevExpress.Xpo.Xtras.IGateway"
name="BasicHttpBinding_Gateway" />
</client>
</system.serviceModel>
</configuration>
Now we are ready to design our data model and start running some queries. Our data model is rather simple here. We have only one table Issues that we want to query, add to and display on the screen.
public enum Status {
Active,
Closed,
}
public enum Importance {
Normal,
High,
Low,
}
[OptimisticLocking(false)]
public class Issue : PersistentBase {
public Issue(Session session)
: base(session) {
}
Guid _id;
[Key(AutoGenerate = true)]
public Guid ID {
get { return _id; }
set { SetPropertyValue("ID", ref _id, value); }
}
string _subject;
public string Subject {
get { return _subject; }
set { SetPropertyValue("Subject", ref _subject, value); }
}
string _owner;
public string Owner {
get { return _owner; }
set { SetPropertyValue("Owner", ref _owner, value); }
}
Status _status;
public Status Status {
get { return _status; }
set { SetPropertyValue("Status", ref _status, value); }
}
Importance _importance;
public Importance Importance {
get { return _importance; }
set { SetPropertyValue("Importance", ref _importance, value); }
}
[NonPersistent]
public Visibility HighImportance {
get {
if (Importance == Importance.High) {
return Visibility.Visible;
}
else {
return Visibility.Collapsed;
}
}
}
[NonPersistent]
public Visibility LowImportance {
get {
if (Importance == Importance.Low) {
return Visibility.Visible;
}
else {
return Visibility.Collapsed;
}
}
}
}
Notice the additional NonPersistent helper properties. They will come in handy when displaying data inside the AgDataGrid.
Since we dont have any data entry forms yet and we need some mock data. Let's add some fake issues. Remember any work done againt XPO must be done asynchronously.
DevExpress.Xpo.Xtras.Gateway _dataStore;
..
this._dataStore = new DevExpress.Xpo.Xtras.Gateway(this.Dispatcher);
..
ThreadPool.QueueUserWorkItem((state) => {
try {
using (UnitOfWork unitOfWork = new UnitOfWork(new SimpleDataLayer(this._dataStore))) {
var issue = new Issue(unitOfWork);
issue.Owner = "Joe Smith";
issue.Status = Status.Active;
issue.Subject = "New Issue";
unitOfWork.Save(issue);
unitOfWork.CommitChanges();
};
}
catch (Exception e) {
System.Diagnostics.Debug.WriteLine(e.Message);
}
});
Displaying the Data
Download the free AgDataGrid and AgMenu suites here http://devexpress.com/Products/NET/Controls/Silverlight/Menu/ we'll use them a lot. Add a reference to the AgDataGrid assembly to your project and add a name space alias to it inside your page.xaml. In this case AgDataGridEx is a derived class from the standard AgDataGrid and the namespace alias is our project itself.
public class AgDataGridEx : AgDataGrid {
public AgDataGridEx() {
this.DefaultStyleKey = typeof(AgDataGridEx);
}
}
<bugger:AgDataGridEx Grid.Row="1" x:Name="issuesDataGrid" ColumnsAutoWidth="True" ShowGroupPanel="Visible" SelectionMode="None">
<bugger:AgDataGridEx.Resources>
<DataTemplate x:Key="ImportanceCellTemplateEx">
<Grid HorizontalAlignment="Center" Margin="0,0,2,0">
<Image Source="ImportanceRed.png" Stretch="None" Visibility="{Binding CellValue}"></Image>
</Grid>
</DataTemplate>
</bugger:AgDataGridEx.Resources>
<bugger:AgDataGridEx.Columns>
<bugger:AgDataGridColumnEx x:Name="issuesColumnImportance" FieldName="HighImportance" FixedWidth="True" Width="22"
MinColumnWidth="22" MaxColumnWidth="22" AllowResizing="False" AllowGrouping="False"