Monday, June 23, 2008

Creating a sketch application in Silverlight - Part 2, Saving the sketch on the server

In the second part of this tutorial we're going to pick up where we left off. So we can open the SketchApplicationPart1 solution from the first part of the tutorial (or you can download the source of the second part of this one at the end of this tutorial).
Now we're going to create a way to save the drawing to the server using SharpZipLib in Silverlight, to zip the XML file before sending it to a webservice.

1. Add Click eventhandler for the Save button


and a Helper class for creating the XML.
First we need an eventhandler for the Save button. This is added in the Page_Loaded event of the Page.xaml.cs:
SaveButton.Click += new RoutedEventHandler(SaveButton_Click);


In the Lib Folder, which I created, I add a class "XMLHelpers.cs".
This class only contains a single method to create the XML file. I used LINQ (both to objects and XML) to iterate through the StrokeCollection and create the XML. The class returns an XElement object.
Add the following method "CreateXElementsFromStrokeCollection" to the class:

public static XElement CreateXElementsFromStrokeCollection(StrokeCollection DrawingStrokeCollection)
{
var xmlElements =
new XElement("Strokes", DrawingStrokeCollection
.Select(stroke =>
new XElement("Stroke",
new XElement("Color",
new XAttribute("A", stroke.DrawingAttributes.Color.A),
new XAttribute("R", stroke.DrawingAttributes.Color.R),
new XAttribute("G", stroke.DrawingAttributes.Color.G),
new XAttribute("B", stroke.DrawingAttributes.Color.B)
),
new XElement("Points", stroke.StylusPoints
.Select(point =>
new XElement("Point",
new XAttribute("X", point.X),
new XAttribute("Y", point.Y),
new XAttribute("PressureFactor", point.PressureFactor)
)
)
),
new XElement("Width", stroke.DrawingAttributes.Width),
new XElement("Height", stroke.DrawingAttributes.Height)
)
)
);

return xmlElements;
}


This is an example of the xml the function returns:






13
13







13
13



Looks nice with a minimal amount of effort thanks to LINQ.
By the way, don't forget to add a reference to System.Xml.Linq and add both the using's for System.Linq and System.Xml.Linq.

2. Create a webservice in your WebAppication Project

Name it SaveDrawingService.asmx.
In this tutorial I used an ASMX webservice, but it's almost just as easy to implement a WCF service.
This webservice should contain 2 methods, one for saving a new drawing and one for saving a already saved drawing.
These methods are uncomplicated because all we have to do here is to save the byte[] that is sent from the Silverlight application to the server:

[WebMethod]
public Guid SaveNewDrawing(byte[] zippedFileBytes)
{
Guid drawingID = Guid.NewGuid();
DirectoryInfo drawingFolder = new DirectoryInfo(Server.MapPath("~/App_Data/Drawings/"));
if (!drawingFolder.Exists)
drawingFolder.Create();
FileInfo fi = new FileInfo(Server.MapPath("~/App_Data/Drawings/" + drawingID.ToString() + ".zip"));
if (fi.Exists)
fi.Delete();

FileStream fileStream = new FileStream(Server.MapPath("~/App_Data/Drawings/" + drawingID.ToString() + ".zip"), FileMode.OpenOrCreate);

fileStream.Write(zippedFileBytes, 0, zippedFileBytes.Length);
fileStream.Flush();
fileStream.Close();
return drawingID;
}

[WebMethod]
public bool SaveEditedDrawing(Guid DrawingID, byte[] zippedFileBytes)
{
FileInfo fi = new FileInfo(Server.MapPath("~/App_Data/Drawings/" + DrawingID.ToString() + ".zip"));
if (fi.Exists)
fi.Delete();

FileStream fileStream = new FileStream(Server.MapPath("~/App_Data/Drawings/" + DrawingID.ToString() + ".zip"), FileMode.OpenOrCreate);

fileStream.Write(zippedFileBytes, 0, zippedFileBytes.Length);
fileStream.Flush();
fileStream.Close();
return true;
}


3. Add a Service Reference to the ASMX webservice


Before doing this, always try if the ASMX webservice opens in the browser by right-clicking the asmx file of the webservice in VS2008 and clicking "View in browser".
If it opens correctly, it can be added to the Silverlight project using the namespace SaveDrawingService.

4. Add the SharpZipLib library to your Silverlight project


The version I rebuilt is added in the sourcecode, so you can add the project, or compile a DLL and reference that.

5. Implement the save functionality in Silverlight (zipping and sending)


First, add the System.IO and System.Text namespaces in the using section of the Page.xaml.cs. Next, create a private field "_currentDrawing" of type Guid to save the guid of the drawing (this is returned by the webservice).
Now, the following code needs to be used for the click event:
void SaveButton_Click(object sender, RoutedEventArgs e)
{
SaveDrawingService.SaveDrawingServiceSoapClient wsclient = new SaveDrawingService.SaveDrawingServiceSoapClient();

// A memory stream to write to
using (MemoryStream zippedMemoryStream = new MemoryStream())
{
// A ZIP stream
using (ZipOutputStream zipOutputStream = new ZipOutputStream(zippedMemoryStream))
{
// Highest compression rating
zipOutputStream.SetLevel(9);

byte[] buffer;

// The string to use as file content
using (MemoryStream file = new MemoryStream(Encoding.UTF8.GetBytes(Lib.XMLHelpers.CreateXElementsFromStrokeCollection(inkpresenter.Strokes).ToString())))
{
buffer = new byte[file.Length];
file.Read(buffer, 0, buffer.Length);
}

// Write the data to the ZIP file
ZipEntry entry = new ZipEntry("drawing.xml");
zipOutputStream.PutNextEntry(entry);
zipOutputStream.Write(buffer, 0, buffer.Length);

// Finish the ZIP file
zipOutputStream.Finish();
}
if (_currentDrawing == Guid.Empty)
{
wsclient.SaveNewDrawingCompleted += new EventHandler(wsclient_SaveNewDrawingCompleted);
wsclient.SaveNewDrawingAsync(zippedMemoryStream.ToArray());
}
else
{
wsclient.SaveEditedDrawingCompleted += new EventHandler(wsclient_SaveEditedDrawingCompleted);
wsclient.SaveEditedDrawingAsync(this._currentDrawing, zippedMemoryStream.ToArray());
}
}
}

void wsclient_SaveEditedDrawingCompleted(object sender, SaveDrawingService.SaveEditedDrawingCompletedEventArgs e)
{
//Add functionality to show user drawing is saved
}

void wsclient_SaveNewDrawingCompleted(object sender, SaveDrawingService.SaveNewDrawingCompletedEventArgs e)
{
this._currentDrawing = e.Result;
//Add functionality to show user drawing is saved
}


An explanation of what happens here: First, an instance of the WebserviceSoapClient is created. Next, a new memorystream is created for writing and a ZipOutputStream is created for zipping the "XML string" returned by the XElement which is returned by the CreateXElementsFromStrokeCollection method.
A zipentry "drawing.xml" is created for the actual drawing in the zipfile. The byte array created by the zippedMemoryStream.ToArray() is then sent asynchronously to the server.

I'll call it a day for now. If I get around to it, I'll create a third part to add some more functionality to the sketching application.

Click here for the source

3 comments:

  1. Good article.. please tell me how you rendered your code in that neat way? I've seen it on loads of blogs

    ReplyDelete
  2. Very helpful thank u.
    but plz i used the first part of ur article in order to save an ink to xml but i found that when i redraw it, it saves only the last point of the ink.
    could u plz help me with that?!!!

    ReplyDelete