Monday, November 28, 2011

10-Minute App - WCF service for Windows Phone 7

This is Part 2 in the Windows Phone 7 app. Due to length of the articles, the "10 minutes" is split up in three articles. Part 1 talks about setting up IIS to support the intermediary WCF service.


After an explanation on the trusted intermediary in AIF, and the IIS setup needed to be able to run our WCF service for use with Windows Phone 7 (WP7), we're ready to code! We will create two Visual Studio projects, one is the WCF service that consumes the original AIF service and exposes it back out again (translating the authentication in the process). The second project is the actual WP7 app consuming our WCF service to display the AX data.

Part of the trusted intermediary article was setting up the AIF service enhanced port for item data and allowing a trusted intermediary for that web service. We will re-use that in this article, so if you haven't done so already, please go through the article for the setup.
Also a must is the setup necessary for IIS. You need to go through this setup first, before we can get into the code below.

First, the WCF application. To work with the IIS setup, you will need to start Visual Studio 2010 in administrator mode. To do this, right-click Visual Studio 2010 in your start menu, and select "Run as administrator". Once in Visual Studio 2010, create a new WCF project of type "WCF Service Application". I called my project "DAXMusings_WCF".

First thing is changing the setup to use our IIS setup instead of the built-in development server of Visual Studio. On the Project menu, select "DAXMusings_WCF Properties..." (if you named your project differently, you should see that here). In the properties window, select the Web tab.

Select the "Use Local IIS Web server", and enter the URL for the website we created, including a new sub-folder (I changed the default, which is the project name, to something short: WP7). Don't forget to use the FQDN (fully qualified domain name) that IIS used for the certificate! Also make sure you enter httpS to indicate it's an SSL site. If you setup the website on another port, make sure to add the port (for example if you used port 80443: https://myserver:80443/WP7).

Once entered, click the "Create Virtual Directory". This will create a new virtual sub-folder in the site, pointing to the output folder of your Visual Studio project. Unfortunately, IIS assigns the default application pool to it, so we'll need to go back to IIS and change the application pool. Under administrative tools, open "Internet Information Services (IIS) Manager". Under sites, browse to the site we created (if you followed to the letter, this will be called "DAXMusings"), and click on the new virtual directory called "WP7". On the right-hand side panel, select "Advanced Settings".

In the application pool setting, click the ellipsis and select your application pool and click OK. That's it.

Back in Visual Studio, we'll add a reference to the AX 2012 AIF service we've setup in the trusted intermediary article article. Check back there what to setup and how to find the WSDL URL to use.
To add the service reference, right-click on References in your project and click "Add Service Reference".

On the dialog, type in or paste in the address for the item service WSDL and click the "Go" button. In the namespace field, type "ItemService". Click OK to add the service.

Now, there is a catch which took me a long time to figure out. By default, the proxies are generated for the item service from AX. My idea was to just re-use those objects for this WCF service. They are already a data contract, plus the service from AX will return the object, so you can just pass it along. Well, until the WP7 app tries to consume it. You will get the following error:
Inner Exception: Type 'System.ComponentModel.PropertyChangedEventHandler' cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.
Apparently this is a SilverLight issues, but it took me a while to figure out how to circumvent it. Turns out it's all in the proxies generated by the service reference we just added. By default, the reference tries to be helpful and adds events for property changes, so the proxy objects return from the service can be used for data binding and other cool stuff. But that is exactly what we cannot pass along to a silverlight service without making it part of the data contract. Since the data contract is auto-generated, we're stuck. I finally found a helpful blog post explaining how to turn off the automatic addition of the events. To do this, right-click your project and select "Open Folder in Windows Explorer". In the folder, you will find the "Service References" folder, inside of it, the ItemService folder.

In this folder, we need to edit a file called "Reference.svcmap". You can edit this file in notepad or even in Visual Studio, it's an XML file. In the XML file, the second field is called "EnableDataBinding" and is set to true. Setting this to false will avoid the service proxies having the notification properties.

Save the file and close it. Back in Visual Studio, right-click the service reference "ItemService" and select "Update Service Reference". This will regenerate the proxies and effectively remove the events.

Ok, so now to the code. We need to change the service interface (IService1), and then the implementation itself as well. Open the IService1.cs file by double clicking on it in the project.

To cut things short, all we want to do is add a method to the interface (feel free to clean up the rest, we won't use it). We'll return an array of InventTable records coming from the AIF service, so add the following line under the "TODO: add your service operations here" comment:
Click here for full code screenshot
[OperationContract]
ItemService.AxdEntity_InventTable[] GetItem(string username, string itemIdFrom, string itemIdTo);

As you can see, we're cheating on the authentication here by just passing in the username as a parameter to the operation. No password here either. Please look into proper authentication mechanisms for WCF (I know I am). As I said in the previous article, a good place to start is this article on MSDN.
As far as the operation, we'll use the "Find" method of the AX service which can use a range of filters, so we'll do a from and to value for the find range.
Next in the Service1.svc file, we want to implement this new operation inside the Service1 class, as follows:
Click here for full code screenshot
public ItemService.AxdEntity_InventTable[] GetItem(string username, string itemIdFrom, string itemIdTo)
{
 ItemService.AxdItem item = null;

 ItemService.ItemServiceClient client = new ItemService.ItemServiceClient();
 ItemService.CallContext context = new ItemService.CallContext();

 context.Company = "CEU";
 context.MessageId = Guid.NewGuid().ToString();
 context.LogonAsUser = String.Format("DAXMusingsAuth\\{0}", username);

 try
 {
  ItemService.QueryCriteria criteria = new ItemService.QueryCriteria();
  criteria.CriteriaElement = new ItemService.CriteriaElement[1];
  criteria.CriteriaElement[0] = new ItemService.CriteriaElement();
  criteria.CriteriaElement[0].DataSourceName = "InventTable";
  criteria.CriteriaElement[0].FieldName = "ItemId";
  criteria.CriteriaElement[0].Operator = ItemService.Operator.Range;
  criteria.CriteriaElement[0].Value1 = itemIdFrom;
  criteria.CriteriaElement[0].Value2 = itemIdTo;
  item = client.find(context, criteria);
 }
 catch (Exception ex)
 {
  Console.WriteLine(ex.Message);
  throw ex;
 }

 return item.InventTable;
}

As you can see this uses the context for the intermediary user (read the article if you haven't already!). We re-throw any errors coming back from AIF, otherwise we return the InventTable datasource coming from AIF... Ok, last thing to do is actually tell WCF we want to use https and basic authentication. To do that we need to change the Web.config file (double-click on the Web.config in your project). First, we need to add a section for basic http binding. Inside the "bindings" node, after the </netTcpBinding> node, add the following:
<basicHttpBinding>
        <binding name="webBinding">
          <security mode="Transport" />
        </binding>
      </basicHttpBinding>

Next, under behaviors/servicebehaviors we'll add the following node after the </behavior> :
<behavior name="ServiceBehaviors">
          <serviceDiscovery/>
          <serviceMetadata httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>

Next, we add a services node with a service setup, linking to the basic http binding, the service behavior, and the data contract. If you used any other names for the service reference namespace make sure to change the service class name and the data contract interface name (shown in bold) below:
<services>
      <service name="DAXMusings_WCF.Service1" behaviorConfiguration="ServiceBehaviors">
        <endpoint name="Endpoint"
                  address=""
                  binding="basicHttpBinding"
                  contract="DAXMusings_WCF.IService1"
                  bindingConfiguration="webBinding"/>
        <endpoint kind="udpDiscoveryEndpoint" />
      </service>
    </services>

Lastly, at the top of the web.config, we need to remove the targetFramework attribute in the compilation node, so it looks like this:

I know this was a lot, so feel free to look at the actual web.config file as I'm using it. It's up for download here.
We're done with the WCF service. You can hit F5 to run the service. Nothing much to see, you can browse to your https site under /WP7/Service1.svc?WSDL to see if it's working. We'll need this URL later for the WP7 app itself.

On to the WP7 app!

No comments:

Post a Comment