I’ve had WCF services running on in-house computers exposed to the Internet for some time. Moving a web service from an in-house fully accessible computer environment to a web hosting environment like GoDaddy, HostGator and others that limit your access is a challenge when troubleshooting the service. Here’s what I did when everything looked like it should work but didn’t. Calling out to a contract returned… nothing. No data, no errors, nothing. This article only covers this one narrow case debugging WCF in the cloud, other articles will cover other techniques for other problems.
To understand the service it’s hosted on IIS 7.0 running over http (for debugging purposes) and https, .Net 4, has a handful of contracts, and uses a database hosted as well on the ISP. Architecturally it’s a simple database application in the cloud. I have the service working fully on my development environment and need to replicate it at the ISP. Data access is through stored procedures at the database and the interface on the code side uses Linq.
Linq is not an issue with hosting parameters as I’ve laid out, you could just as easily use Entity Framework, ADO.NET and other technologies to access the stored procedures.
First thing to do is get the database working correctly. The hosting service has some means of creating and uploading existing database for your use, GoDaddy which is where this service is hosted has a means, other ISPs do too. I assume you’ve verified through some means that the database is deployed and operational. With stored procedures, you’ve called them and verified that they are working, your credentials and accessibility works and you are ready for the WCF part.
Second, upload your WCF service through the FTP mechanism or better yet through Visual Studio’s publishing wizard. Now, one thing to include in the service if you haven’t is a contract that returns a simple string, a version number, without interacting with the database. You call this contract to verify that WCF is working without needing the database. This is a simple and effective sanity check. I always include a version number contract in my services for this purpose and as a means of identifying the revision of the service.
namespace Service.ServiceContracts { public partial interface IServiceContract { [OperationContract(Action = "http://polymorf.com/2010/01/ServiceContract/Version"] Service.MessageContracts.VersionResponse Version(); } } namespace Service.MessageContracts { [MessageContract(IsWrapped = false)] public partial class VersionResponse { private string versionResponseField; public string versionResponse { get { return versionResponseField; } set { versionResponseField = value; } } } }
This contract returns a string wrapped in a class from the service, the string contains a manually set version number, not a dynamically set value. You need to know that a string constant is what will be returned from the service. You will understand this shortly.
The implementation of version() is
public override Service.MessageContracts.VersionResponse Version() { VersionResponse response = new VersionResponse(); response.versionResponse = "002"; return response; }
Test 1 – Use a web browser and navigate to the web service root page (where the .SVC file is).
Does the page appear on the browser? If not, what does the error message say? Have you turned off custom errors while you are troubleshooting? Do not proceed further until you’ve got the service functioning at the most basic level.Anything else is pointless. The service page has to appear correctly and the WSDL page (click on the WSDL link) has to appear correctly. If not, you have a fundamental problem and go fix it. Once it works,
Test 2 – Execute the version contract.
Using a tool like SoapUI or Visual Studio’s WCFTestClient you test this contract. You should get back the version string you expect. If not, why not? Did your service timeout? Did your service indicate there is an argument problem meaning the string type in the response is not the right type?
Local testing of your service enabled you to have confidence that your service for just this one contract was correct. The likely cause of problems at this point are: something in the web.config, the wrong app pool on the hosting service (you need .Net 2.0 and it’s set to .Net 4.0 or vice-versa), or you are calling the wrong service/contract. If there is a fault message you have to fix the problem (other articles of mine will cover various problems), if there is no message and not returned content you have to retrace your code. Is your contract working locally (on your development environment)? The version() contract returns a string constant (earlier I said I would make this clear) and this is why. If you dynamically create the version string, whatever means of construction you take may be the culprit and not the service. If the string is a constant you know it’s just the service and not your means of building the string. At this point it’s just checking your work – right service, right contract, local testing proves this works, you’ve attended to any fault message. If all this is proved out and correct your service will work. If not, let me know a condition where it still does not.
Test 3 – Execute a simple database-backed contract.
Here’s where the rubber meets the road. Either select retrieval contract, something that with minimal arguments forces the database to retrieve some data or create a contract for this purpose. The one case of interest in this article is a properly formatted request to retrieve data returns nothing at all. No errors and no data. Other cases where you receive a fault string or the data is wrong are not the point of this article. Those failures can be dealt with in a variety of ways but good input and no output is infuriating. The ISP won’t let you debug the service in the way you are use to, running a debugger, so what do you do? Here’s my line attack.
Suspect the database connection first. You’ve already proven that the service fundamentally works with the first steps. Now you’ve added a database interaction into the mix so it’s the likely cause of the problem. Create a simple ASPX web page that can query the database just like the service would. The page can be the most simplistic thing imaginable – enter a value, hit SUBMIT and get back results. You have to use the same stored procedure that you contract test uses. This is important. Don’t create an alternative means of retrieving the data. This is where you can mislead yourself – always use the same path in troubleshooting. Create the page, test it locally, publish it and test with it. What happens? Does it return the sample data? If not why? If it does return the data correctly and the contract does not, it’s likely that your service reference to the database is wrong. Find all the references the service uses (.dbml files, property settings, and your code) and find the problem there. There has to be a place in the code that the connectionString is pointing to the wrong location.
The following aspx page and code-behind assumes in the web.config there is a connection string named ‘ProductConnString’.
DebugPage.ASPX
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="DebugPage.aspx.cs" Inherits="DebugPage" Debug="true" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %>
DebugPage.aspx.cs (code-behind)
//#define DEBUG using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; public partial class DebugPage : System.Web.UI.Page { public string Date { get { return (string)Session["Date"]; } set { Session["Date"] = value; } } public List DebugResults = new List(); // Write messages to the page. protected void MSG(string msg) { Response.Write(msg + " | "); } protected void Page_Load(object sender, EventArgs e) { MSG("Page load"); Date = Request["DateValue"]; // Fetch the data on the retun trip to the client. if (IsPostBack) { MSG("after IsPostBack"); if (string.IsNullOrEmpty(Date)) return; MSG("after Date check"); SqlConnection conn = null; SqlDataReader rdr = null; string connString = System.Configuration.ConfigurationManager.ConnectionStrings["ProductConnString"]. ConnectionString; // create and open a connection object conn = new SqlConnection(connString); conn.Open(); // Setup the call to the sproc 'GetAllFor'. SqlCommand cmd = new SqlCommand("GetAllFor", conn); cmd.CommandType = CommandType.StoredProcedure; string startDate = Date; string endDate = Date; int ascending = 1; cmd.Parameters.Add(new SqlParameter("@StartDate", startDate)); cmd.Parameters.Add(new SqlParameter("@EndDate", endDate)); cmd.Parameters.Add(new SqlParameter("@asc", ascending)); DebugResults.Clear(); MSG("before ExecuteReader"); SqlDataReader r = cmd.ExecuteReader(); while (r.Read()) { int id = (int) r["id"]; DateTime date = (DateTime) r["datetime"]; string action = (string)r["action"]; string description = (string)r["description"]; string result = id.ToString() + "(" + date + ") " + action + " : " + description; DebugResults.Add(result); } r.Close(); conn.Close(); } } }
Sample run
Page load | after IsPostBack | after Date check | before ExecuteReader |
Enter a date
3212(7/15/2011 7:31:08 AM) IN :
3213(7/15/2011 10:43:22 AM) OUT :
3214(7/15/2011 11:16:37 AM) IN :
3215(7/15/2011 12:00:00 PM) OUT :
3216(7/15/2011 1:29:48 PM) IN :
3217(7/15/2011 5:57:39 PM) CP : Left off – https wsdl fails import with ‘cant find soap1.1’ in SF
3218(7/15/2011 5:57:45 PM) OUT :
What’s important here is that the debug messages appeared and the data retrieval worked. If this page works but your service doesn’t, it’s the database connection or connectivity. It’s just that simple. You have to hunt down the place in your service that you’ve referred to the database in the wrong way.