WCF Web Services & iOS - Part 2
By Mike Gledhill
So, we now have a very basic WCF Web Service which takes a string parameter, and returns it back, as a string in JSON format.
In this section, we're going to add some new services, which will return some data (in JSON format) from a SQL Server database, whilst walking through some of the problems you might encounter.
For this tutorial, you will need the following:
-- Restore a Northwind backup (which I've saved to the folder C:\SQL Server)
RESTORE DATABASE [Northwind]
FROM DISK = 'C:\SQL Server\Northwind.bak'
WITH MOVE 'Northwind' TO 'C:\SQL Server\Northwind.mdf',
MOVE 'Northwind_log' TO 'C:\SQL Server\Northwind_Log.mdf'
By the end of this chapter, we'll have a set of three web services which return JSON data (click on the links below):
http://www.iNorthwind.com/Service1.svc/getAllCustomers
http://www.iNorthwind.com/Service1.svc/getOrdersForCustomer/ANATR
http://www.iNorthwind.com/Service1.svc/getCustomerOrderHistory/ANTON
And actually, just with the first two of these web services, we'll have all the data we'd need to populate a Master-Detail view like this:
(You can click here to see how to create this example.).
Linking our web service to a SQL Server database
Adding the GetAllCustomers WCF Web Service, which returns JSON data
wsCustomer
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
namespace JSONWebService
{
[DataContract]
public class wsCustomer
{
[DataMember]
public string CustomerID { get; set; }
[DataMember]
public string CompanyName { get; set; }
[DataMember]
public string City { get; set; }
}
Now, let's create a web service which can return a collection (List) of these records.
Let's start with the IService1.cs file. Add the following lines, at the top of the class:
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate = "getAllCustomers")]
List<wsCustomer> GetAllCustomers();
...
Customer
records, and turn them into wsCustomer
records, which is what our
web services have been setup to read and write.
public class Service1 : IService1
{
public List<wscustomer> GetAllCustomers()
{
try
{
NorthwindDataContext dc = new NorthwindDataContext();
List<wscustomer> results = new List<wscustomer>();
foreach (Customer cust in dc.Customers)
{
results.Add(new wsCustomer() {
CustomerID = cust.CustomerID,
CompanyName = cust.CompanyName,
City = cust.City
});
}
return results;
}
catch (Exception ex)
{
// Return any exception messages back to the Response header
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = System.Net.HttpStatusCode.InternalServerError;
response.StatusDescription = ex.Message.Replace("\r\n", "");
return null;
}
}
catch
code a little later, as it's both important, and brilliant ! http://localhost:15021/Service1.svc/getAllCustomers
JSON format
GetAllCustomersResults
wrapper around your JSON values. BodyStyle = WebMessageBodyStyle.Wrapped
"
from the IService1.cs file:
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, UriTemplate = "getAllCustomers")]
List<wsCustomer> GetAllCustomers();
DateTime values
{ Name: "Mike's gym appointment", StartTime: "/Date(1415612328877+0000)/", EndTime: "/Date(1415612264832+0000)/" }
DateTime
values in UTC format, you can use my following code to get your WCF services to
return them in the user's local timezone:A quick word about JSON
Error handling in WCF Web Services
try..catch
code which I included in the web service function above (and will be including
in all of my other web services).
try...catch
in your web services code, then, if something goes wrong, the service will fail.ErrorMessage
string to each of your DataContracts
, which you'd populate
with error messages, if something goes wrong.
catch (Exception ex)
{
// Return any exception messages back to the Response header
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = System.Net.HttpStatusCode.InternalServerError;
response.StatusDescription = ex.Message.Replace("\r\n", "");
return null;
}
null
, but it actually works brilliantly.
try..catch
in place, let's see what Google Chrome now shows in it's Network tab when I call a web service, but it can't connect to the database:
statusText
attached to it.
getAllCustomers
web service using Angular, and display any exception messages which occur:
$http.get('http://localhost:15021/Service1.svc/getAllCustomers') .then(function (data) { // We successfully loaded the list of Customer names. $scope.ListOfCustomerNames = data.GetAllCustomerNamesResult; }, function (errorResponse) { // The WCF Web Service returned an error. // Let's display the HTTP Status Code, and any statusText which it returned. var HTTPErrorNumber = errorResponse.status; var HTTPErrorStatusText = errorResponse.statusText; alert("An error occurred whilst fetching Customer Names\r\nHTTP status code: " + HTTPErrorNumber + "\r\nError: " + HTTPErrorStatusText); });
try..catch
code into every one of your web services.
Adding the GetOrdersForCustomer
WCF Web Service, which returns JSON data
http://localhost:15021/Service1.svc/GetOrdersForCustomer/ANATR
wsOrder
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
namespace JSONWebService
{
[DataContract]
public class wsOrder
{
[DataMember]
public int OrderID { get; set; }
[DataMember]
public string OrderDate { get; set; }
[DataMember]
public string ShippedDate { get; set; }
[DataMember]
public string ShipName { get; set; }
[DataMember]
public string ShipAddress { get; set; }
[DataMember]
public string ShipCity { get; set; }
[DataMember]
public string ShipPostcode { get; set; }
}
}
Now, let's create the web service which will return the list of Order
records for a particular Customer
.
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate = "getAllCustomers")]
List<wsCustomer> GetAllCustomers();
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate = "getOrdersForCustomer/{customerID}")]
List<wsOrder> GetOrdersForCustomer(string customerID);
...
GetAllCustomers()
function:
public List<wsorder> GetOrdersForCustomer(string customerID)
{
try
{
NorthwindDataContext dc = new NorthwindDataContext();
List<wsorder> results = new List<wsorder>();
System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.GetCultureInfo("en-US");
foreach (Order order in dc.Orders.Where(s => s.CustomerID == customerID))
{
results.Add(new wsOrder()
{
OrderID = order.OrderID,
OrderDate = (order.OrderDate == null) ? "" : order.OrderDate.Value.ToString("d", ci),
ShipAddress = order.ShipAddress,
ShipCity = order.ShipCity,
ShipName = order.ShipName,
ShipPostcode = order.ShipPostalCode,
ShippedDate = (order.ShippedDate == null) ? "" : order.ShippedDate.Value.ToString("d", ci)
});
}
return results;
}
catch (Exception ex)
{
// Return any exception messages back to the Response header
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = System.Net.HttpStatusCode.InternalServerError;
response.StatusDescription = ex.Message.Replace("\r\n", "");
return null;
}
}
Order
records from our SQL Server database,
then we convert them into our new wsOrder
records. The WCF service itself takes care of converting these wsOrder
records into JSON for us.
http://localhost:15021/Service1.svc/getOrdersForCustomer/ALFKI
http://localhost:15021/Service1.svc/getOrdersForCustomer/ANATR
Passing parameters to WCF Web Service in a URL
Customer
table has a Primary Key of type nchar(5)
, which
is why our GetOrdersForCustomer
function required one string
parameter.
public List<wsOrder> GetOrdersForCustomer(string customerID)
{
NorthwindDataContext dc = new NorthwindDataContext();
...
integer
, you must still use parameters of type string
.
int
parameter, won't work.
[ServiceContract]
...
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate = "getOrderDetails/{orderID}")]
List<wsOrder> GetOrderDetails(int orderID);
...
string
parameter, then convert the parameter values into an
integer
yourself.
[ServiceContract]
...
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate = "getOrderDetails/{orderID}")]
List<wsOrder> GetOrderDetails(string orderID);
...
public List<wsOrder> GetOrderDetails(string orderID)
{
NorthwindDataContext dc = new NorthwindDataContext();
int orderIDnumber = int.Parse(orderID)
var orders = dc.Orders.Where(s => s.OrderID == orderIDnumber);
...
Calling a SQL Server Stored Procedure
CustOrderHist
stored procedure. This takes
the ID
of a Customer
record as a parameter, and it returns a list of ProductName
strings, and Total
integers.
USE [Northwind]
GO
EXEC [dbo].[CustOrderHist] 'ANTON'
ID
parameter in the URL...http://localhost:15021/Service1.svc/getCustomerOrderHistory/ANTON
CustOrderHist
stored
procedure from the Server Explorer window to the right-hand side of the dbml file.
CustomerOrderHistory
class in it, containing
the two fields which our JSON will be returning for each record:
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Serialization;
namespace JSONWebService
{
[DataContract]
[Serializable]
public class CustomerOrderHistory
{
[DataMember]
public string ProductName { get; set; }
[DataMember]
public int Total { get; set; }
}
}
CustomerID
parameter, and will
return a List of these CustomerOrderHistory
records. In our IService1.cs file, let's add an endpoint
for this new service:
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, UriTemplate = "getCustomerOrderHistory/{customerID}")]
List<CustomerOrderHistory> GetCustomerOrderHistory(string customerID);
CustomerOrderHistory
record.
CustOrderHist
stored procedure, it will
return each row of results in a new class, called CustOrderHistResult
. LINQ created this class for you when you drag'n'dropped
the stored procedure in the .dbml file.
CustOrderHistResult
records that were returned
from our stored procedure, we need to create a new web-service-friendly CustomerOrderHistory
record, and add it to our
list of results.
public List<CustomerOrderHistory> GetCustomerOrderHistory(string customerID)
{
try
{
List<CustomerOrderHistory> results = new List<CustomerOrderHistory>();
NorthwindDataContext dc = new NorthwindDataContext();
foreach (CustOrderHistResult oneOrder in dc.CustOrderHist(customerID))
{
results.Add(new CustomerOrderHistory()
{
ProductName = oneOrder.ProductName,
Total = oneOrder.Total ?? 0
});
}
return results;
}
catch (Exception ex)
{
// Return any exception messages back to the Response header
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = System.Net.HttpStatusCode.InternalServerError;
response.StatusDescription = ex.Message.Replace("\r\n", "");
return null;
}
}
Summary
Comments