metadevblog

Windows Phone 7 App Development

Posts Tagged ‘WCF

WP7 accessing a Windows Service DataService

leave a comment »

This application took 2 minutes to write…

Now that I have demonstrated how Silverlight can access a DataService that is hosted by a WindowsService the final example is to show that it will work on a WP7.

I created a new Windows Phone application and added a Button and Textbox.

I added a Service Reference to the DataService to the Solution.

Finally I pretty much copied the code from the Silverlight application in the previous post.

using WPService.DataService;
using System.Xml.Linq;

namespace WPService
{
    public partial class MainPage : PhoneApplicationPage
    {
        DataServiceClient dataService = null;

        // Constructor
        public MainPage()
        {
            InitializeComponent();

            dataService = new DataServiceClient();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                dataService.GetDateTimeCompleted += new EventHandler<GetDateTimeCompletedEventArgs>(dataService_GetDateTimeCompleted);
                dataService.GetDateTimeAsync();
            }
            catch (Exception ex)
            {
                textBlock1.Text = string.Format("Error occurred: {0}", ex.Message);
            }
        }

        void dataService_GetDateTimeCompleted(object sender, DataService.GetDateTimeCompletedEventArgs e)
        {
            try
            {
                XElement xResult = (XElement)e.Result;

                string date = xResult.Element("Date").Value;
                string time = xResult.Element("Time").Value;

                textBlock1.Text = string.Format("The date is: {0}\nThe time is: {1}", date, time);

            }
            catch (Exception ex)
            {
                textBlock1.Text = string.Format("Error occurred: {0}", ex.Message);
            }
        }
    }
}

 

I clicked the Button!

image

Says it all!

Advertisements

Written by metadevblog

March 29, 2011 at 10:00 pm

Posted in WCF, WP7

Tagged with , ,

Using Silverlight with a Windows Service – Part 2

with one comment

In order to get the Silverlight version of the application to work the Windows Service has to be modified so that it can provide the necessary Silverlight clientaccesspolicy.xml file or Flash policy file crossdomain.xml from root domain of the service.

This is accomplished by adding a WebService to the WindowService running on the same port as the existing Data Service and using a URL rewriter to provide the correct xml file to the Silverlight application.

First add empty IWebService.cs and WebService.cs classes.

The IWebService.cs should be replaced with this code:

using System.ServiceModel;
using System.IO;
using System.ServiceModel.Web;

namespace ConsoleApplicationWCF
{
    [ServiceContract]
    public interface IWebServer
    {
        [OperationContract, WebGet(UriTemplate = "ClientAccessPolicy.xml")]
        Stream GetClientAccessPolicy();
    }
}

 

Note: You will need to add a reference to System.ServiceModel.Web which can only be added when the dialog is filtered by .NET 4.0 Framework (and not the Client framework).

The key difference between this interface and the one use for the DataService is the WebGet() that has been added to the Operation Contract and it will invoke the GetClientAccessPolicy() method when a GET request matches the template.  The more typical use of this pattern is to respond to more generic request pattern, however it is ideal for handling the Silverlight request.

http://localhost:1234/clientaccesspolicy.xml

The code to handle the request is also very simple:

using System.IO;
using System.ServiceModel.Web;
using System.Reflection;

namespace ConsoleApplicationWCF
{
    public class WebServer : IWebServer
    {
        public const string AssemblyRootPath = "ConsoleApplicationWCF.";

        public Stream GetClientAccessPolicy()
        {
            WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
            return Assembly.GetExecutingAssembly().
               GetManifestResourceStream(AssemblyRootPath + "ClientAccessPolicy.xml");
        }
    }
}

 

The AssemblyRootPath is the namespace for the application.  This should be ConsoleApplicationWCF but can be confirmed by looking at the Default Namespace in the Projects Properties.

Add the following XML as a ClientAccessPolicy.xml file to the project and make sure that the Build Action for the file is set to ‘Embedded Resource’.  When the project is built the xml file will be built into the binary file.

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy>

 

When the method is invoked the file data is returned to the caller.

The final part of the process is to create a web server endpoint to handle this contracted request.

   <behaviors>
    <endpointBehaviors>
      <behavior name="WebServerEndpointBehavior">
        <webHttp />
      </behavior>
    </endpointBehaviors>
    <serviceBehaviors>
      <behavior name="WebServerBehavior">
        <serviceMetadata httpGetEnabled="false" />
        <serviceDebug httpHelpPageEnabled="false" includeExceptionDetailInFaults="true" />
      </behavior>
        ...
    </serviceBehaviors>
   </behaviors>

 

Notice that there is an endpointBehaviour and a serviceBehaviour. 

These two behaviours are used in the definition of the WebServer service:

    <service behaviorConfiguration="WebServerBehavior" name="WebServer">
      <endpoint address="" behaviorConfiguration="WebServerEndpointBehavior"
        binding="webHttpBinding" contract="IWebServer">
        <identity>
          <dns value="localhost" />
        </identity>
      </endpoint>
      <host>
        <baseAddresses>
          <add baseAddress="http://localhost:1234/" />
        </baseAddresses>
      </host>
    </service>

 

Notice that the <service> behaviourConfiguration uses the WebServerBehaviour and the <endpoint> behaviourConfiguration uses the WebServerEndpointBehaviour.  I know that this looks simple but it’s driven me mad while configuring WCF previously – the trick I think is to make sure the names are really reflecting the configuration!

The binding for the WebServer is ‘webHttpBinding’ which will respond to the HTTP GET method that Silverlight makes (and notice that the GetClientAccessPolicy() outgoing content type is ‘text/html’

Here is the full service configuration for both the WebService and the DataService:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <endpointBehaviors>
        <behavior name="WebServerEndpointBehavior">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
      <serviceBehaviors>
        <behavior name="WebServerBehavior">
          <serviceMetadata httpGetEnabled="false"/>
          <serviceDebug httpHelpPageEnabled="false" includeExceptionDetailInFaults="true"/>
        </behavior>
        <behavior name="DataServiceBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="WebServerBehavior" name="ConsoleApplicationWCF.WebServer">
        <endpoint address="" behaviorConfiguration="WebServerEndpointBehavior" 
             binding="webHttpBinding" contract="ConsoleApplicationWCF.IWebServer">
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:1234/"/>
          </baseAddresses>
        </host>
      </service>
      <service behaviorConfiguration="DataServiceBehavior" name="ConsoleApplicationWCF.DataService">
        <endpoint address="" binding="basicHttpBinding" contract="ConsoleApplicationWCF.IDataService">
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:1234/DataService"/>
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

And now when the Silverlight application runs and the button is pressed it will request the client access policy file, validate it and then assuming its all OK will fetch the date and time. Awesome!

image

Written by metadevblog

March 28, 2011 at 10:58 pm

Posted in Silverlight, WCF

Tagged with ,

WCF–Testing the DataService

leave a comment »

The quickest way to test that the Data Service is working is to create a simple Windows Forms based application as this will be able to connect without encountering any of the cross domain issues that a Silverlight application will hit.

Once this test has completed then the WCF service will be changed so that a Silverlight application can be created.

Create a new Windows Forms based application in VS2010.  Its best to develop the application in a separate instance of VS2010 rather than adding new project to the solution because then the DataService can be left running while the client is being developed.

Ensure that the ConsoleApplicationWCF is running and the DataService console window is displayed.

Add a button and a label to the Form1 that will have been created automatically by VS.

In the Project Solution right click on References and select Add Service Reference.  Enter the address of the DataService (e.g. http://localhost:1234/DataService).  Click the Advanced button and tick the ‘Generate asynchronous operations’ checkbox.  Asynchronous operations are slightly more tricky to use but is the only option when using Silverlight so it may as well be implemented now.

Once the service reference has been created (and you can see that the process creates quite a bit of code and support files if you ‘show all files’ in the project Solution explorer) it can be used with relative ease.

One of the really great things about WCF is how much work the Microsoft developers have put into hiding the complexity of what is a very complex process.

The code pretty much writes itself, you only have to remember a couple of names in order to get it working.

The starting point when using the service reference is to create a reference to the service code that has been used, the will be called DataServiceClient.

DataServiceClient dataService = null;

 

Adding this will report an error.  Right clicking on DataServiceClient and selecting Resolve will add the correct using statement to the class (it should be  “using WindowsFormsWCF.DataService”).

Using the event driven asynchronous access pattern that is exposed via the code that has been generated during the creation of the service reference exposes the functionality of WCF.  VS2010 IntelliSense will virtually fill out the code to access the service.  So the button click code should look like this:

        private void button1_Click(object sender, EventArgs e)
        {
            dataService.GetDateTimeCompleted += new EventHandler
                    <GetDateTimeCompletedEventArgs>(dataService_GetDateTimeCompleted);
            dataService.GetDateTimeAsync();
        }


The GetDateTimeCompleted delegate simply needs an appropriate event handler to be added and IntelliSense will event generate the name of the handler “dataService_GetDateTimeCompleted” all that needs to be done is add it to the code.

        void dataService_GetDateTimeCompleted(object sender, DataService.GetDateTimeCompletedEventArgs e)
        {
            try
            {
                XElement xResult = (XElement)e.Result;

                string date = xResult.Element("Date").Value;
                string time = xResult.Element("Time").Value;

                label1.Text = string.Format("The date is: {0}\nThe time is: {1}", date, time);

            }
            catch (Exception ex)
            {
                label1.Text = string.Format("Error occurred: {0}", ex.Message);
            }
        }


The only bit of work that has be hand cranked is to remember that the event handler will have the following parameters:

(object sender, DataService.GetDateTimeCompletedEventArgs e)

The e.Result will be returning an XElement, as this was defined in the original service.  I’m simply grabbing the Date and Time elements from the XML, formatting them and showing the result in the label.

image

The code for the application is:

using System;
using System.Windows.Forms;
using WindowsFormsWCF.DataService;
using System.Xml.Linq;

namespace WindowsFormsWCF
{
    public partial class Form1 : Form
    {
        DataServiceClient dataService = null;

        public Form1()
        {
            InitializeComponent();

            dataService = new DataServiceClient();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            dataService.GetDateTimeCompleted += new EventHandler<GetDateTimeCompletedEventArgs>(dataService_GetDateTimeCompleted);
            dataService.GetDateTimeAsync();
        }

        void dataService_GetDateTimeCompleted(object sender, DataService.GetDateTimeCompletedEventArgs e)
        {
            try
            {
                XElement xResult = (XElement)e.Result;

                string date = xResult.Element("Date").Value;
                string time = xResult.Element("Time").Value;

                label1.Text = string.Format("The date is: {0}\nThe time is: {1}", date, time);

            }
            catch (Exception ex)
            {
                label1.Text = string.Format("Error occurred: {0}", ex.Message);
            }
        }
    }
}

 

Transforming this code to Silverlight is pretty simple.  Create a new Silverlight Application project in VS2010 (remove the checkmark for hosting in a new web site).

In the MainPage.xaml add a Button and a TextBox.  Create the Service Reference and then paste in the following code:

using System;
using System.Windows;
using System.Windows.Controls;
using SilverlightApplicationWCF.DataService;
using System.Xml.Linq;

namespace SilverlightApplicationWCF
{
    public partial class MainPage : UserControl
    {
        DataServiceClient dataService = null;

        public MainPage()
        {
            InitializeComponent();

            dataService = new DataServiceClient();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                dataService.GetDateTimeCompleted += new EventHandler<GetDateTimeCompletedEventArgs>(dataService_GetDateTimeCompleted);
                dataService.GetDateTimeAsync();
            }
            catch (Exception ex)
            {
                textBlock1.Text = string.Format("Error occurred: {0}", ex.Message);
            }
        }

        void dataService_GetDateTimeCompleted(object sender, DataService.GetDateTimeCompletedEventArgs e)
        {
            try
            {
                XElement xResult = (XElement)e.Result;

                string date = xResult.Element("Date").Value;
                string time = xResult.Element("Time").Value;

                textBlock1.Text = string.Format("The date is: {0}\nThe time is: {1}", date, time);

            }
            catch (Exception ex)
            {
                textBlock1.Text = string.Format("Error occurred: {0}", ex.Message);
            }
        }
    }
}


Apart from a few name differences its the same code!

Run the application and you will get a warning about debugging issues when using web services (you can see what’s coming soon…).  Ignore the warning and continue to run the application and while the Silverlight form will display, as soon as you click the button an cross-domain error will be thrown:

image

The next post will show how this is solved quite elegantly.

Written by metadevblog

March 24, 2011 at 11:07 pm

Posted in WCF

Tagged with ,

WCF–Service Behaviours, Bindings and Contracts

leave a comment »

WCF is now a very mature technology and one that I have used a great deal over the last few years, in fact it has been core to most of my recent work both professionally and with personal development projects.

I can’t however be the only one to have struggled with getting the triumvirate of behaviours, bindings and contracts to work together when creating a web service without at least tearing some of my hair out.

I don’t know if there is something different in WCF 4.0 or its just ‘clicked’ for me finally but I managed to create two web services in a hurry last night and get them working by actually understanding the XML.  So while it is still fresh in my mind I’m going to write it down…

One of the pitfalls with using WCF is the number of different parts that need to be created before it can be tested end to end.  There are also some ‘gotchas’ that need to be avoided along the way.

Exposing data via WCF requires a data service.  I typically use XML for both sending and receiving data to/from services, so before any description of setting up WCF can be given it is first of all necessary to create a data service that will provide some information.

The data service is a pre-requisite because WCF binds to a service interface that will be exposed at the service endpoint (an example HTTP service endpoint is http://localhost:1234/DataService which will be described below).

In order to expose the service endpoint the service provider is hosted within a ServiceHost object.  ServiceHost is a core windows technology that is also a core part of IIS.  ServiceHost can be hosted within any persistent application, even a console application, but it provides excellent functionality when used with a Windows Service.

I have used Visual Studio 2010 and .NET 4.0, all the code is in c#.

The first part of this article will cover the creation of the data service provider.  The configuration of the service will follow and then I will create a simple application to access the service.

The next part of this article to follow will add another service so that Silverlight can be used to access the data service.

ConsoleApplicationWCF.sin


Create a new console application in VS2010; make sure you call it ConsoleApplicationWCF.

IDataService.cs


Add a new class called IDataService.cs and then paste this code in (you will probably get some errors reported which can be cleared by adding System.XML.Linq.dll and System.ServiceModel.dll to the projects references)

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

namespace ConsoleApplicationWCF
{
    [ServiceContract]
    public interface IDataService
    {
        [OperationContract]
        XElement GetDateTime();
    }
}

This will create the DataService interface which will be referenced in the WCF service endpoint contract as ConsoleApplicationWCF.IDataService.  Note that it is necessary to include the namespace when identifying the endpoint in the definition.

The [ServiceContract] and [OperationsContract] are code decorations that are used during compilation to build the service interface.  The [ServiceContract] decoration identified IDataService as the contract that will be used in the service definition.  The [OperationContract] will allow the GetDataTime() method to be accessed via the service.

DataService.cs

Add a new class called DataService.cs and then paste in this code to implement the interface.  Notice that the service is returning a Linq XElement.

using System;
using System.Xml.Linq;

namespace ConsoleApplicationWCF
{
    public class DataService : IDataService
    {
        public XElement GetDateTime()
        {
            XElement result = new XElement("Data");
            result.Add(new XElement("Date", DateTime.Now.Date.ToString()));
            result.Add(new XElement("Time", DateTime.Now.TimeOfDay.ToString()));

            return result;
        }
    }
}

Program.cs

Now replace the Program.cs stub code that was generated when the project was created with this code:

using System; 
using System.ServiceModel; 
namespace ConsoleApplicationWCF 
{ 
    class Program 
    { 
        private static ServiceHost dataService;

        static void Main(string[] args) 
        { 
            try 
            { 
                dataService = new ServiceHost(typeof(DataService)); 
                dataService.Open(); 
            } 
            catch (Exception e) 
            { 
                Console.WriteLine("Unable to start DataService:" + e.ToString()); 
            }

            Console.WriteLine("Service started at http://localhost:1234/DataService"); 
            Console.ReadLine();
        } 
    } 
} 

The WCF DataService is hosted directly in the Console by ServiceHost. When implementing this within a Windows Service the code can be pretty much lifted verbatim and put into the OnStart event which will then start the WCF DataService when the Windows Service starts.

 

App.config

The key to getting WCF to work is a correct service configuration file.  Add an empty App.config file to the project and then paste the following XML into the file.  This configuration is about as simple as can be defined for a basic HTTP data service.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="DataServiceBehavior">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service behaviorConfiguration="DataServiceBehavior" name="ConsoleApplicationWCF.DataService">
        <endpoint address="" binding="basicHttpBinding" contract="ConsoleApplicationWCF.IDataService">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:1234/DataService" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>


The actual WCF definition is within the <system.serviceModel> element but it is being wrapped within a <configuration> element that is used by App.Config which the console application can access directly as part of its implementation structure.

The <system.serviceModel> element contains two main regions which allow for multiple <behaviours> and <services> to be defined.  The <behaviour> define how a <service> will function.

The service <behaviour> for a WCF data service requires that httpGet is enabled.  I also enable includeExceptionDetailInFaults on the basis that you can never have too much information about what has gone wrong and you can always switch it off later.

Creating the <service> itself is usually where it all goes wrong as there are so many parts to it; any one of which will cause an error (or worse no service).

The <service> element contains one or more <endpoint> elements and a <host> element.

<service>   
  <endpoint />   
  <host /> 
</service>

Note that a service can have multiple endpoint definitions.  This is important as the endpoints provide different data depending on their configuration.

The service behaviour uses the behaviourConfiguration to map a behaviour to the service using the behaviour attribute name.

<behavior name="DataServiceBehavior"> 
<service behaviorConfiguration="DataServiceBehavior"  …

The name of the service should be the same as the name in the interface and should include the namespace.

<service behaviorConfiguration="DataServiceBehavior" name="ConsoleApplicationWCF.DataService">

The service exposes two endpoints one is used to access the data, the other is used get the metadata that describes the service.  This second service is vital as it simplifies the whole process of making use of the service by auto-generating a while bunch of plumbing files that hide away most of the complexity of WCF.

The first <endpoint> is for the data service.  It is exposing the service as HTTP and it is using the IDataService contract that has been defined. The namespace should be used in the definition of the contract.

<endpoint address="" binding="basicHttpBinding" contract="ConsoleApplicationWCF.IDataService">

The dns specifies “localhost”.  This is perfectly valid but it should not be confused with using the name of your machine.  You should change both the dns and the actual endpoint if you want to use a machine name.

The second endpoint is for getting the service metadata that will be used to generate the WCF service support files in a client application.  The service metadata can be accessed by invoking the service endpoint in a browser along with the ?wsdl parameter (the parameter triggers access to the MEX endpoint).  Eg

http://localhost:1234/DataService?wsdl

Finally the address of the service can be defined.  I tend to use the same name as the service contract for my services and also leave them open ended – ie no terminating /

That’s about it for this posting.  The code should compile and run and will show the following console:

image

Written by metadevblog

March 23, 2011 at 9:22 am

Posted in WCF

Tagged with ,

Using Silverlight with a Windows Service – Part 1

leave a comment »

One of the big projects I have been involved in over the last couple of years had a requirement to support   tasks that could last for many hours with connections to other services that had to be kept open for the task duration.

While there are any number of possible solutions, the technology I chose to implement the application in was based around using Windows Services.  Windows Services are both very powerful but also pretty simple and while they provide a defined interface for installation and for being started and stopped they don’t have any other out of the box interfaces which can make communicating with them somewhat problematic… 

While the ability to execute very long running tasks concurrently was a primary requirement the application had to also undertake a lot of other operations and I needed an interface to expose these services.  Again there are a whole raft of different approaches that could be used but I chose WCF and specifically HTTP due to its ubiquity.

The final solution has a Windows Service based application installed on all the servers in the farm and provides a very resilient, distributed network that is scaled out simply adding additional servers running the application.   The services communicate with each other using the HTTP SOAP interface which is largely transparent to my application code as it is managed by WCF.

While most of the application is automated there is a need for administrative services that require a UI and for this I wrote a simple Windows Form application which had a WCF Service Reference to the exposed HTTP services. 

The principle UI for users was implemented using ASP.NET within SharePoint 2007.  With ASP the browser client is primarily responsible for UI and rendering while the server side code can access data and make service calls.

Requirements for a second version of the application are being put together and it is clear that the existing UI is going to need significant extensions to cover new features and while ASP.NET can handle the task I find it quite slow to author complex UI compared to Windows Forms or Silverlight.  However it has become apparent very quickly that there is an issue with Silverlight, a simple test application threw the following exception while attempting to make a service call to the Windows Service HTTP endpoint:

"An error occurred while trying to make a request to URI ‘http://server:8000/application&#8217;. This could be due to attempting to access a service in a cross-domain way without a proper cross-domain policy in place, or a policy that is unsuitable for SOAP services. You may need to contact the owner of the service to publish a cross-domain policy file and to ensure it allows SOAP-related HTTP headers to be sent. This error may also be caused by using internal types in the web service proxy without using the InternalsVisibleToAttribute attribute. Please see the inner exception for more details."

This diagram shows what is being attempted.  The Silverlight XAP file is being loaded from a web server that is running on one port and then attempting to access a HTTP service running on a different port.  Note that Silverlight treats requests on different ports on the same server as being in different domains.

image

 

Silverlight has inbuilt security to prevent it making unauthorised connections to remote services which could be used by malicious programmers to gain access to secure information or launch hacking attacks.  An explanation of the security process can be found here:

http://msdn.microsoft.com/en-us/library/cc645032(VS.95).aspx

In order to support cross domain access Silverlight has to request one of two policy files that have to be present on the remote server.  Silverlight will first of all request and parse its policy file: clientaccesspolicy.xml and if this is not present or is invalid it will fall back and request the Flash policy file: crossdomain.xml if this is also not present or invalid then access to the service is denied.

For conventional web servers the provision of these policy files is relatively simple.  All that has to be done is create the correct policy file and drop it at the right location on the server (at the domain root).  Silverlight will then request and parse the file.

Another option is to avoid the cross domain issue altogether by hosting the Silverlight XAP file on the same domain as the HTTP web service:

image

The problem is that WCF on Windows Service does not have the capability to serve up this file automatically and it is necessary to write a specific service to provide it and the service has to be configured correctly so that the browser can request the silverlight xap file.

Having now completed the proof of concept it turns out that both solutions can be implemented with the same code.  I’ll post some details of this in a while…

Written by metadevblog

March 22, 2011 at 4:48 pm

Posted in Silverlight

Tagged with ,