Friday, December 2, 2016

Consuming a WCF service using REST from Angular

I know this doesn't have much to do with WPF but I've been wanting to write RESTful WCF services for some time and consuming them from Angular was plain good fun!

You can click on any of the images below to see full size versions.

We're going to use the AdventureWorks2012 SQL Server sample database from Microsoft.

Start Visual 2015 and start a new WCF Service Application project and call it "products".



At this point Visual Studio has created a sample service called Service1 that contains examples of code. You can delete Service1 and IService1 or simply ignore them.

Our next step is to add our own service called Products. In the solution explorer, right-click on the Products project, select Add and then select New Item.


Select WCF Service and name it "Products". Click [Add]


Now your Solution Explorer looks like this and you can get a clean build.



We will write a Products service that returns a list of products. The first thing to do is to enhance our interface to include a definition of the product class and a definition of the products service. Lets' start with the product class. Open the IProducts class and remove the examples that Visual Studio put there for us. Replace the contents with the following class definition.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace Products
{
    [DataContract]
    public class Product
    {
        [DataMember]
        public int ID { get; set; }

        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public string ProductNumber { get; set; }

        [DataMember]
        public string Color { get; set; }

        [DataMember]
        public decimal Price { get; set; }
    }
 }

Note the [DataContract] and [DataMember] attributes. They tell WCF how to send this class and its members across the wire to the client.

Now add the interface for the GetProductList method. Note it also has attributes that indicate how we will invoke it (with a GET) and how data will be returned (in Json format). If we wanted to be able to save a product list we would add a similar definition but with Method="POST" and referencing a different method (maybe SaveProductList). The UriTemplate would be the same.

    [ServiceContract]
    public interface IProducts
    {
        [OperationContract]
        [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.Bare, UriTemplate = "ProductList/")]
        List<Product> GetProductList();
    }

To recap, this interface converts a request to http://.../ProductList/ using the GET method into a call to GetProductList() and converts the returned data into JSON which is attached to the response. In effect we have a layer that converts REST to SOAP. We will see below that we have the ability to hide or expose the underlying SOAP functionality.

At this point the IProducts.cs file has a "Products" namespace that contains a "Product" class and an IProducts interface.

Before we go any further we need to put our connection string into Web.config so we can access the database. Add the following to your Web.config file inside <configuration>.

  <connectionStrings>
    <add name="localhost" connectionString="Server=localhost;Database=AdventureWorks2012;Trusted_Connection=yes"/>
  </connectionStrings>

While we are here we can define our endpoint behaviors. Merge the following tags into <system.serviceModel> in your Web.config file. Some of these tags will appear to be invalid until they are all in the correct places. Two attributes we could change are httpGetEnabled which determines if SOAP behavior is exposed by the endpoint and includExceptionDetailInFaults which determines if detailed error messages are returned.

    <services>
      <service name="Products.Products" behaviorConfiguration="serviceBehavior">
        <endpoint address="" binding="webHttpBinding" contract="Products.IProducts" behaviorConfiguration="web"/>
      </service>
    </services>

    <behaviors>
      <serviceBehaviors>
        <behavior name="serviceBehavior">
          <serviceMetadata httpGetEnabled="false"/>
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
    </behaviors>

I am going to use a data extension to convert a datatable to a list of dictionaries which means I need to add a reference to System.Data.DataSetExtensions. In the Solution Explorer right-click on References and select Add Reference.

Scroll to System.Data.DataSetExtensions, click on it to make the checkbox visible, then check the checkbox and click [OK].


Now we can write the products class. Open the Products.svc.cs file. Remove all the example code and replace it with the following. If we had decided to support saving a product list we would write SaveProductList here (after defining the interface in IProducts).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace Products
{
    public class Products : IProducts
    {
        public List<Product> GetProductList() 
        {
            String SQL = "SELECT ProductID, Name, ProductNumber, Color, ListPrice FROM Production.Product ORDER BY ProductID";
            String ConnString = System.Configuration.ConfigurationManager.ConnectionStrings["localhost"].ConnectionString;
            DataTable dt = new DataTable();

            using (SqlConnection conn = new SqlConnection(ConnString))
            {
                conn.Open();
                using (SqlCommand comm = new SqlCommand(SQL, conn))
                {
                    using (SqlDataAdapter da = new SqlDataAdapter(comm))
                    {
                        da.Fill(dt);
                    }
                }
            }
            List<Product> ProductList = dt.AsEnumerable().Select(r => new Product()
            {
                ID = r.Field<int>("ProductID"),
                Name = r.Field<String>("Name"),
                ProductNumber = r.Field<String>("ProductNumber"),
                Color = r.Field<String>("Color"),
                Price = r.Field<Decimal>("ListPrice")
            }).ToList();
            return ProductList;
        }

    }
}

We define the GetProductList method that pulls the data from the database and converts it into a list of product objects. The list is automatically converted to JSON by the ResponseFormat = WebMessageFormat.Json clause in the interface's attribute.

At this point you can right-click on Products.svc and select "View in Browser". We disabled SOAP when we set httpGetEnabled="false"  above so we will see the message "Metadata publishing for this service is currently disabled". If we append "/ProductList/" to the URL we will get a JSON file back that represents all the products.

Now it is time to consume the data. Add an html page to the project called "Default" and set it as the start page. It will use some simple Angular to $http.get the JSON and render it in a basic table. Replace the contents of Default.html with this.

<!DOCTYPE html>
<html ng-app="DefaultApp">
<head>
    <title>products</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
    <script>
        angular.module("DefaultApp", [])
        .constant("productsUrl", "/Products.svc/ProductList/")
        .controller("DefaultCtrl",
        function ($scope,$http,productsUrl) {

            $scope.getData = function () {
                $http.get(productsUrl)
                .then
                (
                    function (result) { $scope.products = result.data; }
                    ,
                    function (result) { $scope.statusText = result.statusText; }
                )
            };

            $scope.getData();
        });
    </script>
</head>
<body ng-controller="DefaultCtrl">
    <div>{{statusText}}</div>
    <table>
        <tr ng-repeat="item in products">
            <td>{{item.ID}}</td>
            <td>{{item.Name}}</td>
            <td>{{item.ProductNumber}}</td>
            <td>{{item.Color}}</td>
            <td>{{item.Price | currency}}</td>
        </tr>
    </table>
</body>
</html>

Now run the project. The result will be a table of products.