programming4us
programming4us
WEBSITE

Exchanging XML Messages over HTTP

9/14/2010 4:31:19 PM
1. Problem

Your Silverlight application needs to exchange POX messages with an HTTP endpoint.

2. Solution

Use the HttpWebRequest/HttpWebResponse pair of types in System.Net to exchange POX messages with an HTTP endpoint.

3. How It Works

POX-style message exchange can be an attractive alternative to the more structured SOAP-based message exchange. It does not impose any of the specific format requirements of SOAP, and there is much more freedom regarding how messages are structured. Consequently, it requires fewer infrastructural requirements, benefits from more implementation options, and can be consumed by almost any XML-aware runtime environment.

The downside of such loose-format messaging, however, is that very often, client frameworks do not have the luxury of tool-based assistance like Visual Studio's service proxy-generation features. Also, client APIs that consume such services are somewhat lower level—in most cases, they implement some sort of request/response mechanism over HTTP, with support for HTTP-related features, like choice of verbs or Multipurpose Internet Mail Extensions (MIME) types.

3.1. Using HttpWebRequest/HttpWebResponse in Silverlight

The HttpWebRequest/HttpWebResponse types implement an API that allows Silverlight clients to send requests and receive responses from HTTP endpoints in an asynchronous fashion.

HttpWebRequest and HttpWebResponse are abstract classes and hence are not directly constructable. To use the API, you start by invoking the static Create()HttpWebRequest type, supplying the URL of the endpoint you wish to interact with. What is returned to you is an instance of WebRequest—the base class for HttpWebRequest. You have the option of setting the desired HTTP verb to use through the HttpWebRequest.Method property—HttpWebRequestMethod property on a newly created web request is GET. You really only need to set it if you are going to use POST. method on the supports GET and POST. The default value of the

You also have the option of setting the MIME type using the ContentType property.

3.1.1. Using GET

The GET verb is typically used to request a web resource from an endpoint. The request is represented as the URI of the resource, with optional additional query string parameters. You invoke a GET request using the BeginGetResponse() method on the WebRequest instance. Pass a delegate of the form AsyncResult around a handler that you implement. This handler gets called back when the async request operation completes. In the handler, call EndGetResponse() to access any response information returned in the form of a WebResponse instance. You can then call WebResponse.GetResponseStream() to access the returned content.

3.1.2. Using POST

If you need to submit content back to an HTTP endpoint for processing, and you want to include the data in the body of the request, you must use the POST verb. To POST content, you need to write the content to be posted into the request stream. To do this, first call BeginGetRequestStream(), again passing in an AsyncResultEndGetRequestStream() to acquire a stream to the request's body, and write the content you intend to POST to that stream. Then, call BeginGetResponse() using the same pattern outlined earlier. delegate. In the handler, call

3.1.3. Handling Asynchronous Invocation

The methods discussed here follow an asynchronous invocation pattern. The BeginGetResponse() and BeginGetRequestStream() methods dispatch the execution to a randomly allocated background thread, returning control to your main application thread right away. The AsyncResult handlers that you pass in as callbacks are invoked on these background threads. If you want to access any parts of your object model created on the main thread—such as the controls on the page or any types that you instantiate elsewhere in your code—from one of these handlers, you cannot do it in the normal fashion, because doing so causes a cross-thread access violation. You need to first switch context to the thread that owns the object you are trying to access. To do this, you must use a type called Dispatcher.

The Dispatcher type is designed to manage work items for a specific thread. More specifically, in this context, a Dispatcher exposes methods that allow you to execute a piece of code in the context of the thread that owns the Dispatcher. The DependencyObject type, and hence all derived types, exposes a DispatcherPage itself. instance, which is associated with the thread that creates the type. One of the easiest instances you can get hold of is exposed on the

To use the Dispatcher, use the static BeginInvoke() function, passing in a delegate to the method that you want to execute on the Dispatcher's thread, regardless of which thread it is called from. Dispatcher ensures a proper thread-context switch to execute the targeted method on its owning thread. For instance, if you want to access some element on the Page from a background thread, you use the Page's Dispatcher as described.

NOTE

Although we chose POX messages as the first example of demonstrating this API, the types are a general-purpose means of HTTP communication from Silverlight. You can exchange other kinds of information over HTTP using these as well.

3.2. Configuring WCF to Use Non-SOAP Endpoints

Although the Silverlight techniques demonstrated in this API can be used with any HTTP endpoint that accepts and responds with POX messages, we have chosen to implement the POX/HTTP endpoint using WCF.

WCF by default uses SOAP-based message exchange, but it also enables a web programming model that allows non-SOAP endpoints over HTTP to be exposed from WCF services. This allows REST-style services to use formats like POX or JSON to exchange messages with clients.

To enable web-style, URI-based invocation of operations on these services, apply one of the WebGetAttribute or WebInvokeAttribute types found in System.ServiceModel.Web to the operations. The WebGetAttribute mandates use of the HTTP GET verb to acquire a resource; hence the only way to pass in parameters to such an operation is through query string parameters on the client that are mapped by the WCF runtime to parameters in the operation. As an example, here is the declaration of a GET-style operation:

[OperationContract]
[WebGet()]
Information GetSomeInformation(int Param);

You can invoke this operation by sending an HTTP GET request to an URI endpoint, formatted like so:

http://someserver/someservice.svc/GetSomeInformation?Param=50

WebInvokeAttribute defaults to the use of the POST verb but can also be specified to accept the PUT or DELETE verb. If you are using POST, the message body is expected to be in the POST body content, whereas you can continue to use query-string style parameters with a verb like PUT. However, keep in mind that Silverlight only allows the use of POST, not PUT or DELETE.

In addition to using these attributes to decorate your WCF operations, you also need to specify the appropriate binding and behavior. To use POX messaging over HTTP, you must use WebHttpBinding for the endpoint. Here is a snippet from a WCF config file that shows this:

<endpoint address="" binding="webHttpBinding" contract="IProductManager" />

4. The Code

To illustrate the concept, change the WCF service to use POX messages over HTTP, and implement the client using the HttpWebRequest/HttpWebResponse API.

Listing 1 shows the service contract for the WCF service adapted for POX exchange over HTTP.

Listing 1. Service contract for the POX Service in ServiceContract.cs
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Xml;

namespace Recipe7_2.ProductsDataPOXService
{
[ServiceContract]
public interface IProductManager
{
[OperationContract]
[XmlSerializerFormat()]
[WebGet()]
XmlDocument GetProductHeaders();

[OperationContract]
[XmlSerializerFormat()]
[WebInvoke()]
void UpdateProductHeaders(XmlDocument Updates);

[OperationContract]
[XmlSerializerFormat()]
[WebGet()]
XmlDocument GetProductDetail(ushort ProductId);

[OperationContract]
[XmlSerializerFormat()]
[WebInvoke()]
void UpdateProductDetail(XmlDocument Update);
}
}


POX messages are just blocks of well-formed XML. Consequently, you use the System.Xml.XmlDocument type to represent the messages being exchanged. Because XmlDocument does not have the WCF DataContractAttribute applied to it, WCF cannot use the default data-contract serialization to serialize these messages. So, you also apply System.ServiceModel.XmlSerializerFormatAttribute() to the service operations to use XML serialization.

Listing 2 shows the implementation of the GetProductHeaders() and the UpdateProductHeaders() operations.

Listing 2. Service implementation for POX service in ProductManager.cs
using System.IO;
using System.Linq;
using System.ServiceModel.Activation;

using System.Xml;
using System.Xml.Linq;

namespace Recipe7_2.ProductsDataPOXService
{
[AspNetCompatibilityRequirements(
RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class ProductManager : IProductManager
{
public XmlDocument GetProductHeaders()
{
//open the local data file
StreamReader stmrdrProductData =
new StreamReader(
new FileStream(
HttpContext.Current.Request.MapPath("App_Data/XML/Products.xml"),
FileMode.Open));
//create and load an XmlDocument
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(stmrdrProductData.ReadToEnd());
stmrdrProductData.Close();

//return the document
HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
return xDoc;
}

public void UpdateProductHeaders(XmlDocument Updates)
{
//load the XmlDocument containing the updates into a LINQ XDocument
XDocument xDocProductUpdates = XDocument.Parse(Updates.OuterXml);
//load the local data file
StreamReader stmrdrProductData =
new StreamReader(
new FileStream(
HttpContext.Current.Request.MapPath("App_Data/XML/Products.xml"),
FileMode.Open));
XDocument xDocProducts = XDocument.Load(stmrdrProductData);
stmrdrProductData.Close();
//for each of the updated records, find the matching record in the local data
//using a LINQ query
//and update the appropriate fields
foreach (XElement elemProdUpdate in xDocProductUpdates.Root.Elements())
{

XElement elemTarget =
(from elemProduct in xDocProducts.Root.Elements()
where elemProduct.Attribute("ProductId").Value ==
elemProdUpdate.Attribute("ProductId").Value
select elemProduct).ToList()[0];
if (elemTarget.Attribute("Name") != null)
elemTarget.Attribute("Name").
SetValue(elemProdUpdate.Attribute("Name").Value);
if (elemTarget.Attribute("ListPrice") != null)
elemTarget.Attribute("ListPrice").
SetValue(elemProdUpdate.Attribute("ListPrice").Value);
if (elemTarget.Attribute("SellEndDate") != null)
elemTarget.Attribute("SellEndDate").
SetValue(elemProdUpdate.Attribute("SellEndDate").Value);
if (elemTarget.Attribute("SellStartDate") != null)
elemTarget.Attribute("SellStartDate").
SetValue(elemProdUpdate.Attribute("SellStartDate").Value);
}
//save the changes
StreamWriter stmwrtrProductData =
new StreamWriter(
new FileStream(
HttpContext.Current.Request.MapPath("App_Data/XML/Products.xml"),
FileMode.Truncate));
xDocProducts.Save(stmwrtrProductData);
stmwrtrProductData.Close();
}

public XmlDocument GetProductDetail(ushort ProductId)
{
StreamReader stmrdrProductData =
new StreamReader(
new FileStream(
HttpContext.Current.Request.MapPath("App_Data/XML/Products.xml"),
FileMode.Open));
XDocument xDocProducts = XDocument.Load(stmrdrProductData);
XDocument xDocProdDetail = new XDocument(
(from xElem in xDocProducts.Root.Elements()
where xElem.Attribute("ProductId").Value == ProductId.ToString()
select xElem).ToList()[0]);

XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(xDocProdDetail.ToString());
stmrdrProductData.Close();



HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
return xDoc;
}
public void UpdateProductDetail(XmlDocument Update)
{
XDocument xDocProductUpdates = XDocument.Parse(Update.OuterXml);
XElement elemProdUpdate = xDocProductUpdates.Root;
StreamReader stmrdrProductData =
new StreamReader(
new FileStream(
HttpContext.Current.Request.MapPath("App_Data/XML/Products.xml"),
FileMode.Open));

XDocument xDocProducts = XDocument.Load(stmrdrProductData);
stmrdrProductData.Close();

XElement elemTarget =
(from elemProduct in xDocProducts.Root.Elements()
where elemProduct.Attribute("ProductId").Value ==
elemProdUpdate.Attribute("ProductId").Value
select elemProduct).ToList()[0];

if (elemTarget.Attribute("Class") != null)
elemTarget.Attribute("Class").
SetValue(elemProdUpdate.Attribute("Class").Value);
if (elemTarget.Attribute("Color") != null)
elemTarget.Attribute("Color").
SetValue(elemProdUpdate.Attribute("Color").Value);
if (elemTarget.Attribute("DaysToManufacture") != null)
elemTarget.Attribute("DaysToManufacture").
SetValue(elemProdUpdate.Attribute("DaysToManufacture").Value);
if (elemTarget.Attribute("DiscontinuedDate") != null)
elemTarget.Attribute("DiscontinuedDate").
SetValue(elemProdUpdate.Attribute("DiscontinuedDate").Value);
if (elemTarget.Attribute("FinishedGoodsFlag") != null)
elemTarget.Attribute("FinishedGoodsFlag").
SetValue(elemProdUpdate.Attribute("FinishedGoodsFlag").Value);
if (elemTarget.Attribute("MakeFlag") != null)
elemTarget.Attribute("MakeFlag").
SetValue(elemProdUpdate.Attribute("MakeFlag").Value);
if (elemTarget.Attribute("ProductLine") != null)
elemTarget.Attribute("ProductLine").
SetValue(elemProdUpdate.Attribute("ProductLine").Value);
if (elemTarget.Attribute("ProductNumber") != null)
elemTarget.Attribute("ProductNumber").

SetValue(elemProdUpdate.Attribute("ProductNumber").Value);
if (elemTarget.Attribute("ReorderPoint") != null)
elemTarget.Attribute("ReorderPoint").
SetValue(elemProdUpdate.Attribute("ReorderPoint").Value);
if (elemTarget.Attribute("SafetyStockLevel") != null)
elemTarget.Attribute("SafetyStockLevel").
SetValue(elemProdUpdate.Attribute("SafetyStockLevel").Value);
if (elemTarget.Attribute("StandardCost") != null)
elemTarget.Attribute("StandardCost").
SetValue(elemProdUpdate.Attribute("StandardCost").Value);
if (elemTarget.Attribute("Style") != null)
elemTarget.Attribute("Style").
SetValue(elemProdUpdate.Attribute("Style").Value);

StreamWriter stmwrtrProductData = new StreamWriter(new FileStream(HttpContext.
Current.Request.MapPath("App_Data/XML/Products.xml"), FileMode.Truncate));
xDocProducts.Save(stmwrtrProductData);
stmwrtrProductData.Close();
}
}
}


Because GetProductHeaders() returns a POX message, you open the local data file, load the XML content into an XmlDocument instance, and return the XmlDocument instance. The XmlSerializerFormatAttribute on the operation ensures that the XML content is formatted as it is on the wire.

In UpdateProductHeaders(), you receive the updates as a POX message. You parse the content of the message and load it into an instance of the XDocument type so that it can participate in a LINQ to XML query. You use the query to find the matching records in the local XML data, also loaded in an XDocument, and copy over the updates before you save the local data back to its file store.

The GetProductDetail() and UpdateProductDetail() methods follow the same implementation pattern.

Note the call to SetCacheability() to set the cache policy to NoCache before you return data from the GetProductHeaders() and GetProductDetail() methods. The Silverlight network stack relies on the browser's network stack, and the default behavior has the browser look for the data requested in its own cache first. Setting this in the server response causes the browser to never cache the returned data, so that every time the client calls the service operation, the operation is invoked and current data is returned. This is important for data that can be changed between requests, as in this case with the update operations. For purely lookup data that seldom changes, you may want to leave the browser cache on, and possibly stipulate an expiration. You can refer to more information about controlling the browser-caching policy from the server on MSDN at msdn.microsoft.com/en-us/library/system.web.httpresponse.cache.aspx.

Now, let's look at the client code in the codebehind class. Because the complete code listing is repetitive between the product header- and product detail-related functionality, we list only the code pertaining to the acquiring and updating product headers. You can access the book's sample code to get the full implementation.

Listing 3 shows the product header-related functionality.

Listing 3. Partial listing of the codebehind in MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Xml.Linq;
namespace Recipe7_2.POXProductsDataViewer
{
public partial class MainPage : UserControl
{
private const string ServiceUri =
"http://localhost:9292/ProductsPOXService.svc";
bool InEdit = false;

public MainPage()
{
InitializeComponent();

RequestProductHeaders();
}


private List<ProductHeader> DeserializeProductHeaders(string HeaderXml)
{
//load into a LINQ to XML Xdocument
XDocument xDocProducts = XDocument.Parse(HeaderXml);
//for each Product Xelement, project a new ProductHeader
List<ProductHeader> ProductList =
(from elemProduct in xDocProducts.Root.Elements()
select new ProductHeader
{
Name = elemProduct.Attribute("Name") != null ?
elemProduct.Attribute("Name").Value : null,
ListPrice = elemProduct.Attribute("ListPrice") != null ?
new decimal?(
Convert.ToDecimal(elemProduct.Attribute("ListPrice").
Value)) : null,
ProductId = elemProduct.Attribute("ProductId") != null ?
new ushort?(Convert.ToUInt16(elemProduct.Attribute("ProductId").
Value)) : null,
SellEndDate = elemProduct.Attribute("SellEndDate") != null ?

elemProduct.Attribute("SellEndDate").Value : null,
SellStartDate = elemProduct.Attribute("SellStartDate") != null ?
elemProduct.Attribute("SellStartDate").Value : null

}).ToList();
//return the list
return ProductList;
}

private void RequestProductHeaders()
{
//create and initialize an HttpWebRequest
WebRequest webReq = HttpWebRequest.Create(
new Uri(string.Format("{0}/GetProductHeaders", ServiceUri)));

//GET a response, passing in OnProductHeadersReceived
//as the completion callback, and the WebRequest as state
webReq.BeginGetResponse(
new AsyncCallback(OnProductHeadersReceived), webReq);
}

private void OnProductHeadersReceived(IAsyncResult target)
{
//reacquire the WebRequest from the passed in state
WebRequest webReq = target.AsyncState as WebRequest;
//get the WebResponse
WebResponse webResp = webReq.EndGetResponse(target);

//get the response stream, and wrap in a StreamReader for reading as text
StreamReader stmReader = new StreamReader(webResp.GetResponseStream());
//read the incoming POX into a string
string ProductHeadersXml = stmReader.ReadToEnd();
stmReader.Close();

//use the Dispatcher to switch context to the main thread
//deserialize the POX into a Product Header collection,
//and bind to the DataGrid
Dispatcher.BeginInvoke(new Action(delegate
{
ProductHeaderDataGrid.ItemsSource =
DeserializeProductHeaders(ProductHeadersXml);
}), null);

}
private void UpdateProductHeaders()

{
//create and initialize an HttpWebRequest
WebRequest webReq = HttpWebRequest.Create(
new Uri(string.Format("{0}/UpdateProductHeaders", ServiceUri)));
//set the VERB to POST
webReq.Method = "POST";
//set the MIME type to send POX
webReq.ContentType = "text/xml";
//begin acquiring the request stream
webReq.BeginGetRequestStream(
new AsyncCallback(OnProdHdrUpdReqStreamAcquired), webReq);
}

private void OnProdHdrUpdReqStreamAcquired(IAsyncResult target)
{
//get the passed in WebRequest
HttpWebRequest webReq = target.AsyncState as HttpWebRequest;
//get the request stream, wrap in a writer
StreamWriter stmUpdates =
new StreamWriter(webReq.EndGetRequestStream(target));
Dispatcher.BeginInvoke(new Action(delegate
{
//select all the updated records
List<ProductHeader> AllItems =
ProductHeaderDataGrid.ItemsSource as List<ProductHeader>;
List<ProductHeader> UpdateList = new List<ProductHeader>
(
from Prod in AllItems
where Prod.Dirty == true
select Prod
);

//use LINQ to XML to transform to XML
XElement Products = new XElement("Products",
from Prod in UpdateList
select new XElement("Product",
new XAttribute("Name", Prod.Name),
new XAttribute("ListPrice", Prod.ListPrice),
new XAttribute("ProductId", Prod.ProductId),
new XAttribute("SellEndDate", Prod.SellEndDate),
new XAttribute("SellStartDate", Prod.SellStartDate)));

//write the XML into the request stream
Products.Save(stmUpdates);
stmUpdates.Close();

//start acquiring the response
webReq.BeginGetResponse(
new AsyncCallback(OnProdHdrsUpdateCompleted), webReq);
}));

}

private void OnProdHdrsUpdateCompleted(IAsyncResult target)
{
HttpWebRequest webResp = target.AsyncState as HttpWebRequest;
HttpWebResponse resp =
webResp.EndGetResponse(target) as HttpWebResponse;
//if response is OK, refresh the grid to
//show that the changes actually happened on the server

if (resp.StatusCode == HttpStatusCode.OK)
RequestProductHeaders();
}
void ProductHeaderDataGrid_SelectionChanged(object sender, EventArgs e)
{
if (ProductHeaderDataGrid.SelectedItem != null)
{

//invoke the GetProductDetails() service operation,
//using the ProductId of the currently selected ProductHeader
RequestProductDetail(
(ProductHeaderDataGrid.SelectedItem
as ProductHeader).ProductId.Value);
}
}
void ProductHeaderDataGrid_CurrentCellChanged(object sender,
EventArgs e)
{
//changing the dirty flag on a cell edit for the ProductHeader data grid
if (InEdit && (sender as DataGrid).SelectedItem != null)
{
((sender as DataGrid).SelectedItem as ProductHeader).Dirty = true;
InEdit = false;
}
}
private void ProductHeaderDataGrid_BeginningEdit(object sender,
DataGridBeginningEditEventArgs e)
{
InEdit = true;
}

void Click_Btn_SendHeaderUpdates(object Sender, RoutedEventArgs e)
{
UpdateProductHeaders();
}
void Click_Btn_SendDetailUpdate(object Sender, RoutedEventArgs e)
{
UpdateProductDetail();
}

//Product detail functionality omitted –
//please refer to sample code for full listing
}
}

In the RequestProductHeaders() method, you create the HttpWebRequest and submit it asynchronously using BeginGetResponse(). Note the passing of the WebRequest instance as the state parameter to BeginGetResponse(). On completion of the async call, when the supplied callback handler OnProductHeadersReceived() is called back, you need access to the WebRequest instance in order to complete the call by calling EndGetResponse() on it. Passing it in as the state parameter provides access to it in a thread-safe way, inside the handler executing on a background thread.

In OnProductHeadersReceived(), you obtain the WebRequest from the IAsyncResult.AsyncState parameter and then obtain the WebResponse using the EndGetResponse() method on the WebRequest. Open the response stream using WebResponse.GetResponseStream(), read the POX message from that stream, and bind the data to the ProductHeaderDataGrid after deserializing it into a suitable collection of ProductHeaders using DeserializeProductHeaders(). DeserializeProductHeaders() uses a LINQ to XML query to transform the POX message to an instance of List<ProductHeader>.

To send updates back to the service, you use the UpdateProductHeaders() method. Set the Method property of the request to POST, with the MIME type appropriately set to text/XML. Then, asynchronously acquire the request stream with a call to BeginGetRequestStream().

When BeginGetRequestStream() is completed, the OnProdHdrUpdReqStreamAcquired() callback occurs on a background thread. In the handler, switch thread context back to the main thread using Dispatcher.Invoke(). In the delegate passed to Invoke(), filter out the updated records and transform the records to XML using LINQ to XML, and then serialize the resulting XML to the request stream. After closing the stream, submit the POST calling BeginGetResponse(). After the POST completes, you have the ability to check the StatusCode property to decide on your course of action. If the code is HttpStatusCode.OK, refresh the data from the server by calling RequestProductDetail() again. The only other possible value is HttpStatusCode.NotFound, which indicates a problem with the service call and can be used to display a suitable error message.

Other  
 
PS4 game trailer XBox One game trailer
WiiU game trailer 3ds game trailer
Top 10 Video Game
-   F1 2015 [PS4/XOne/PC] Features Trailer
-   Act of Aggression [PC] Pre-Order Trailer
-   Sword Coast Legends [PC] Campaign Creation E3 2015 Trailer
-   Sword Coast Legends [PC] Campaign Creation E3 2015 Dungeon Run Trailer
-   Naruto Shippuden: Ultimate Ninja Storm 4 Trailer
-   Danganronpa Another Episode: Ultra Despair Girls Trailer 2
-   Project X Zone 2 Trailer
-   Poly Bridge Early Access Trailer
-   Rodea The Sky Soldier Trailer
-   CABAL 2 Launch Trailer
-   The Smurfs Trailer
-   Act of Aggression Pre-Order Trailer
-   Project X Zone 2 [3DS] Trailer
-   Minecraft: Story Mode Debut Trailer
-   Minecraft: Story Mode Reveal Trailer at Minecon 2015
Game of War | Kate Upton Commercial
programming4us
 
 
programming4us