





















































When building applications that utilize data, it is important to start with defining what data you are going to collect and how it will be stored once collected. In the last chapter, we created a Silverlight application to post a collection of ink strokes to the server. We are going to expand the inkPresenter control to allow a user to submit additional information.
Most developers would have had experience building business object layers, and with Silverlight we can still make use of these objects, either by using referenced class projects/libraries or by consuming WCF services and utilizing the associated data contracts.
We'll create a business object that can be used by both Silverlight and our ASP.NET application. To accomplish this, we'll create the business object in our ASP.NET application, define it as a data contract, and expose it to Silverlight via our WCF service.
Start Visual Studio and open the CakeORamaData solution. When we created the solution, we originally created a Silverlight application and an ASP.NET web project.
using System;
using System.Runtime.Serialization;
namespace CakeORamaData.Web
{
[DataContract]
public class CustomerCakeIdea
{
[DataMember]
public string CustomerName { get; set; }
[DataMember]
public string PhoneNumber { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public DateTime EventDate { get; set; }
[DataMember]
public StrokeInfo[] Strokes { get; set; }
}
[DataContract]
public class StrokeInfo
{
[DataMember]
public double Width { get; set; }
[DataMember]
public double Height { get; set; }
[DataMember]
public byte[] Color { get; set; }
[DataMember]
public byte[] OutlineColor { get; set; }
[DataMember]
public StylusPointInfo[] Points { get; set; }
}
[DataContract]
public class StylusPointInfo
{
[DataMember]
public double X { get; set; }
[DataMember]
public double Y { get; set; }
}
}
We just added a business object that will be used by our WCF service and our Silverlight application. We added serialization attributes to our class, so that it can be serialized with WCF and consumed by Silverlight.
The [DataContract] and [DataMember] attributes are the serialization attributes that WCF will use when serializing our business object for transmission. WCF provides an opt-in model, meaning that types used with WCF must include these attributes in order to participate in serialization. The [DataContract] attribute is required, however if you wish to, you can use the [DataMember] attribute on any of the properties of the class.
By default, WCF will use the System.Runtime.Serialization.DataContractSerialzer to serialize the DataContract classes into XML. The .NET Framework also provides a NetDataContractSerializer which includes CLR information in the XML or the JsonDataContractSerializer that will convert the object into JavaScript Object Notation (JSON). The WebGet attribute provides an easy way to define which serializer is used.
For more information on these serializers and the WebGet attribute visit the following MSDN web sites:
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractserializer.aspx.
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.netdatacontractserializer.aspx.
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.json.datacontractjsonserializer.aspx.
http://msdn.microsoft.com/en-us/library/system.servicemodel.web.webgetattribute.aspx.
Windows Communication Foundation (WCF) provides a simplified development experience for connected applications using the service oriented programming model. WCF builds upon and improves the web service model by providing flexible channels in which to connect and communicate with a web service. By utilizing these channels developers can expose their services to a wide variety of client applications such as Silverlight, Windows Presentation Foundation and Windows Forms.
Service oriented applications provide a scalable and reusable programming model, allowing applications to expose limited and controlled functionality to a variety of consuming clients such as web sites, enterprise applications, smart clients, and Silverlight applications.
When building WCF applications the service contract is typically defined by an interface decorated with attributes that declare the service and the operations. Using an interface allows the contract to be separated from the implementation and is the standard practice with WCF.
You can read more about Windows Communication Foundation on the MSDN website at: http://msdn.microsoft.com/en-us/netframework/aa663324.aspx.
Now that we have our business object, we need to define a WCF service that can accept the business object and save the data to an XML file.
The standard design practice with WCF is to create an interface that defines the ServiceContract and OperationContracts of the service. The interface is then provided, a default implementation on the server. When the service is exposed through metadata, the interface will be used to define the operations of the service and generate the client classes. The Silverlight-enabled WCF service does not create an interface, just an implementation, it is there as a quick entry point into WCF for developers new to the technology.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace CakeORamaData.Web.Services
{
// NOTE: If you change the interface name "ICakeService" here,
you must also update the reference to "ICakeService" in Web.config.
[ServiceContract]
public interface ICakeService
{
[OperationContract]
void SubmitCakeIdea(CustomerCakeIdea idea);
}
}
using System;
using System.ServiceModel.Activation;
using System.Xml;
namespace CakeORamaData.Web.Services
{
// NOTE: If you change the class name "CakeService" here, you
must also update the reference to "CakeService" in Web.config.
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)]
public class CakeService : ICakeService
{
public void SubmitCakeIdea(CustomerCakeIdea idea)
{
if (idea == null) return;
using (var writer = XmlWriter.Create(String.Format(@"C:
ProjectsCakeORamaCustomerData{0}.xml", idea.CustomerName)))
{
writer.WriteStartDocument();
//<customer>
writer.WriteStartElement("customer");
writer.WriteAttributeString("name", idea.CustomerName);
writer.WriteAttributeString("phone", idea.PhoneNumber);
writer.WriteAttributeString("email", idea.Email);
// <eventDate></eventDate>
writer.WriteStartElement("eventDate");
writer.WriteValue(idea.EventDate);
writer.WriteEndElement();
// <strokes>
writer.WriteStartElement("strokes");
if (idea.Strokes != null && idea.Strokes.Length > 0)
{
foreach (var stroke in idea.Strokes)
{
// <stroke>
writer.WriteStartElement("stroke");
writer.WriteAttributeString("width", stroke.Width.
ToString());
writer.WriteAttributeString("height", stroke.Height.
ToString());
writer.WriteStartElement("color");
writer.WriteAttributeString("a", stroke.Color[0].
ToString());
writer.WriteAttributeString("r", stroke.Color[1].
ToString());
writer.WriteAttributeString("g", stroke.Color[2].
ToString());
writer.WriteAttributeString("b", stroke.Color[3].
ToString());
writer.WriteEndElement();
writer.WriteStartElement("outlineColor");
writer.WriteAttributeString("a", stroke.
OutlineColor[0].ToString());
writer.WriteAttributeString("r", stroke.
OutlineColor[1].ToString());
writer.WriteAttributeString("g", stroke.
OutlineColor[2].ToString());
writer.WriteAttributeString("b", stroke.
OutlineColor[3].ToString());
writer.WriteEndElement();
if (stroke.Points != null && stroke.Points.Length > 0)
{
writer.WriteStartElement("points");
foreach (var point in stroke.Points)
{
writer.WriteStartElement("point");
writer.WriteAttributeString("x", point.
X.ToString());
writer.WriteAttributeString("y", point.
Y.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
}
// </stroke>
writer.WriteEndElement();
}
}
// </strokes>
writer.WriteEndElement();
//</customer>
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
}
}
We added the AspNetCompatibilityRequirements attribute to our CakeService implementation. This attribute is required in order to use a WCF service from within ASP.NET.
One thing to note is that you will need to grant write permission to this directory for the ASP.NET user account when in a production environment.
<bindings>
<customBinding>
<binding name="customBinding0">
<binaryMessageEncoding />
<httpTransport>
<extendedProtectionPolicy policyEnforcement="Never" />
</httpTransport>
</binding>
</customBinding>
</bindings>
<service behaviorConfiguration="CakeORamaData.Web.Services.
CakeServiceBehavior"
name="CakeORamaData.Web.Services.CakeService">
<endpoint address="" binding="wsHttpBinding"
contract="CakeORamaData.Web.Services.ICakeService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IM
etadataExchange" />
</service>
To the following:
<service behaviorConfiguration="CakeORamaData.Web.Services.
CakeServiceBehavior"
name="CakeORamaData.Web.Services.CakeService">
<endpoint address="" binding="customBinding" bindingConfiguratio
n="customBinding0"
contract="CakeORamaData.Web.Services.ICakeService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMeta
dataExchange" />
</service>
<configuration>
<system.serviceModel>
<bindings>
<customBinding>
<binding name="CustomBinding_ICakeService">
<binaryMessageEncoding />
<httpTransport
maxReceivedMessageSize="2147483647" maxBufferSize="2147483647">
<extendedProtectionPolicy policyEnforcemen
t="Never" />
</httpTransport>
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="http://localhost:2268/Services/
CakeService.svc"
binding="customBinding" bindingConfiguration="Cust
omBinding_ICakeService"
contract="Services.ICakeService"
name="CustomBinding_ICakeService" />
</client>
</system.serviceModel>
</configuration>