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.
var handshake = new HandshakeController ();
handshake.Handshake();
It tries to get the server's id for this client device.
public void Handshake()
{
XDocument ourSettings=null, theirSettings=null;
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
private string GetDeviceId()
{
return GetLocalDeviceId() ?? GetNewDeviceId();
}
So that's what we do.
private string GetNewDeviceId()
{
var client = new TourismClient();
var id = client.Handshake("iOS", Globals.Hardware.GetVersion().ToString());
...snip...
}
The app-specific request ...
public string Handshake(string platform, string hardware)
{
return base.Request<string, string, string>("Handshake", platform, hardware);
}
... calls an even more generic abstracted request ...
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...
private T Request<T>(string methodName, Type[] parameterTypes, IList<object> parameters)
{
if (parameterTypes.Length != parameters.Count)
throw new RestException(400, "Bad Request", "Parameter counts do not match.");
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.");
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:
[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:
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:
var httpMethodAttribute = method.GetCustomAttributes(typeof(HttpMethodAttribute), false).FirstOrDefault() as HttpMethodAttribute;
var httpMethod = httpMethodAttribute == null ? HttpMethods.Get : httpMethodAttribute.Value;
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());
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;
}
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.