Click here to Skip to main content
15,879,535 members
Please Sign up or sign in to vote.
4.43/5 (3 votes)
I've been fighting this one for a few weeks, and haven't had any luck. Hope you might be able to help. I'm also trying Apple Developer Support and Xamarin.


  • This app, originally writted for iOS <6. It was in the store. It worked.
  • I recently updated it for iOS 7 compatibility. I tested it in the simulator, on an iPad3, on an iPhone 3GS; in Debug and Release; I uploaded it to Testflight and installed it from there. All combinations worked fine.
  • I uploaded it to the App Store. It was approved. It doesn't work.

Specifically, when the app is launched, it initially tries to HTTP GET an XML file from my server to see if the local data store needs to be updated. If fails during this process, before it even makes the network request.

From process of elimination of try/catch blocks, the culprit is this chain of events:

We start a handshake process between the client and server.
C#
var handshake = new HandshakeController ();
handshake.Handshake();

It tries to get the server's id for this client device.
C#
/// <summary>
/// Handshakes with the server.
/// </summary>
public void Handshake()
{
    XDocument ourSettings=null, theirSettings=null;

    // Get (or create) a unique device ID.
    Globals.DeviceId = GetDeviceId ();
    if(Globals.DeviceId == null)
        throw new ApplicationException("Network and databases unavailable");
    ...snip...
}

This is the first time it is being run, so it needs a new ID
C#
/// <summary>
/// Gets the device identifier.
/// </summary>
/// <returns>
/// The device identifier.
/// </returns>
private string GetDeviceId()
{
    return GetLocalDeviceId() ?? GetNewDeviceId();
}

So that's what we do.
C#
/// <summary>
/// Gets a new device identifier.
/// </summary>
/// <returns>
/// The new device identifier.
/// </returns>
private string GetNewDeviceId()
{
    var client = new TourismClient();
    var id = client.Handshake("iOS", Globals.Hardware.GetVersion().ToString());

    ...snip...
}

The app-specific request ...
C#
/// <summary>
/// Initializes a new session.
/// </summary>
/// <param name="platform"></param>
/// <returns>
/// A unique id.
/// </returns>
public string Handshake(string platform, string hardware)
{
    return base.Request<string, string, string>("Handshake", platform, hardware);
}

... calls an even more generic abstracted request ...
C#
/// <summary>
/// Calls a WebMethod and returns the results.
/// </summary>
/// <typeparam name="T">The type to be returned.</typeparam>
/// <typeparam name="U">The type of the first parameter.</typeparam>
/// <typeparam name="V">The type of the second parameter.</typeparam>
/// <param name="methodName">Name of the method.</param>
/// <param name="param1">The first parameter.</param>
/// <param name="param2">The second parameter.</param>
/// <returns>The result of the request.</returns>
protected T Request<T, U, V>(string methodName, U param1, V param2)
{
    return Request<T>(methodName, new [] { typeof(U), typeof(V) }, new object[] { param1, param2 });
}

... that calls the method that does the dirty work...
C#
    /// <summary>
    /// Calls a WebMethod and returns the results.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="methodName">Name of the method.</param>
    /// <param name="parameterTypes">The parameter types.</param>
    /// <param name="parameters">The parameters.</param>
    /// <returns>The result of the request.</returns>
    /// <exception cref="XmlException">An XmlException occured while parsing an XML document returned by the web service.</exception>
    /// <exception cref="InvalidCastException">The data returned by the web service cannot be converted to the return type.</exception>
    private T Request<T>(string methodName, Type[] parameterTypes, IList<object> parameters)
    {
        // Sanity check.
        if (parameterTypes.Length != parameters.Count)
            throw new RestException(400, "Bad Request", "Parameter counts do not match.");

        // Get the Contract. This class inherits from it and it has a WebServiceAttribute.
        var contract = (from I in GetType().GetInterfaces()
                        where I.GetCustomAttributes(typeof(WebServiceAttribute), false).Length == 1
                        select I).FirstOrDefault();
        if(contract == null)
            throw new RestException(500, "Server Error", "The proxy client does not implement a contract.");

        // Get the Method
        var method = contract.GetMethod(methodName, parameterTypes);
        if(method == null)
            throw new Exception(methodName);
    ...snip...
}

At this point the REST client does some checks. Classes that use it are required to implement an interface. It does, and this is the line in the interface that is applicable:
C#
/// <summary>
/// Initializes a new session.
/// </summary>
/// <param name="platform">The platform.</param>
/// <param name="hardware">The hardware.</param>
/// <returns>
/// A unique id.
/// </returns>
[HttpMethod(HttpMethods.Get)]
[WebMethod("Initializes a new session.")]
[HttpCredentials(false)]
string Handshake(string platform, string hardware);

Back to the Request method, the interface is found using reflection. Now the critical part:
C#
// Get the Method
var method = contract.GetMethod(methodName, parameterTypes);
if(method == null)
    throw new Exception(methodName);

In all cases, except when the app is installed from the App Store, this test passes, and the REST client goes on to build and invoke an HTTP GET request, and life goes on.

When installed from the App Store, this test fails. I throws new Exception("Handshake"), which is caught and handled.

Total mystery.

Note: the REST client/server code used here was written by me close to 8 years ago and is in dozens of applications in production environments. While it is possible that I missed a bug, a doubt it.

To describe the problem another way, when the REST client uses reflection to check for the existence of the method in question, it should not be possible to fail, because if that method was not present the application would not compile, since that method is defined in the interface and the line
public string Handshake(string platform, string hardware)<br />
implements the contract.

I don't think I am wrong about where the error is occurring, because (a) this is the only throw that sends the method name, which is reported to the UI; (b) a few lines later the network request is invoked, and there is not network activity by the app at all. The only code between the method name check and the network call is:
C#
// Get the HttpMethod.
 var httpMethodAttribute = method.GetCustomAttributes(typeof(HttpMethodAttribute), false).FirstOrDefault() as HttpMethodAttribute;
 var httpMethod = httpMethodAttribute == null ? HttpMethods.Get : httpMethodAttribute.Value;

 // Build the query string.
 var queryString = "";
 var methodParameters = method.GetParameters();
 for (var j = 0; j < methodParameters.Length; j++)
     queryString += (j == 0 ? "" : "&") + methodParameters[j].Name + "=" + HttpUtility.UrlEncode(parameters[j] == null ? string.Empty : parameters[j].ToString());

 // Build the request
 Uri uri;
 if (httpMethod == HttpMethods.Get || httpMethod == HttpMethods.Put || httpMethod == HttpMethods.Delete)
     uri = new Uri(_baseUri, methodName + "?" + queryString);
 else
     uri = new Uri(_baseUri, methodName);
 var request = WebRequest.Create(uri) as HttpWebRequest;
 request.Method = httpMethod.ToString().ToUpper();
 request.UserAgent = GetType().AssemblyQualifiedName;
 request.Accept = "*/*";

 if (httpMethod == HttpMethods.Post)
 {
     request.ContentType = "application/x-www-form-urlencoded";
     var queryStringBytes = Encoding.UTF8.GetBytes(queryString);
     request.ContentLength = queryStringBytes.Length;
     using(var requestStream = request.GetRequestStream())
         requestStream.Write(queryStringBytes, 0, queryStringBytes.Length);
 }

 if (Credentials != null)
 {
     request.Credentials = Credentials;
     request.PreAuthenticate = true;
 }

 // Submit the request and get the response data
 HttpWebResponse response;
 try { response = request.GetResponse() as HttpWebResponse; }
 catch(WebException we) { response = we.Response as HttpWebResponse; }

and none of that would throw an exception where the message is "Handshake".

The main thing is, how do I debug this? Since the app store is involved, one debug cycle takes about a week.

I guess I could jailbreak the device, compile using the AppStore profile, and try to debug that way.

For now I guess I'll try to attach a debugger to the app store version and see if I can step through CIL btyecode to find what's up.

Or if I'm lucky, I missed something obvious and you'll tell me.
Posted
Updated 31-Jan-14 6:46am
v2
Comments
Maarten Kools 31-Jan-14 13:13pm    
Just a thought, but have you tried, where you're calling contract.GetMethod(), to call contract.GetMethods()[^] and see what the list contains?

Although I can't think of any reason why that particular part would fail when the app is downloaded from the App Store (the server is the same whether you do or don't). You're sure that's the line that is causing the error?
Yvan Rodrigues 31-Jan-14 13:16pm    
1. I have tried that in the debugger. It shows to methods with different signatures, which is true.
2. That's what I keep asking myself :)
Maarten Kools 31-Jan-14 13:34pm    
And the parameterTypes array is correct as well?

To be sure that's the line that throws the exception, have you actually set a breakpoint there? Or did something like throw new Exception("This is really the right one")?
Yvan Rodrigues 31-Jan-14 13:53pm    
1. When inspected in the debugger, parameterTypes match the expected values.
2. I can put a breakpoint there (throw), but it will never get hit, because it works in a test environment.
3. I haven't tried that yet -- mainly due to the length of the App Store approval process. It might come to that.

One thing I did do it made sure that the last build that I sent to the App Store contained debugging symbols. I'm about to attempt to attach to the installed app... if that is possible.
Maarten Kools 31-Jan-14 14:11pm    
Oh! I didn't realize this was actual client code, since it's C#. I was really confused about point 3, but Xamarin takes care of that. In that case, if you're going to change the exception, I'd print the method name and the names of the parameter types (and perhaps include a list of available methods with their signatures as well, might as well right?)

1 solution

After many weeks I found it. I compared the csproj sections of Release and AppStore builds, and the AppStore had the additional line:
XML
<mtouchlink>Full</mtouchlink>

Since my app uses reflection to invoke the RESTful client library, the linker did not think I needed certain functionality and optimized it away.
 
Share this answer
 
Comments
Maciej Los 14-Feb-14 16:03pm    
Deserves for 5!
Maarten Kools 14-Feb-14 16:03pm    
Oh boy, just that one line. Glad you found what the problem was!

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900