Monthly Archives: February 2010

iPhone: the rise and fall of the latest Apple gadget

The iPhone.  It’s a wonderful device.  It was a game changer.  3 years ago when it was released, it revolutionized the phone landscape.  For the first time, people had a portable digital convergence device.  (Ok, maybe it wasn’t the first, maybe it wasn’t the best, maybe it was just targeted at regular people instead of corporate users.)  For the first time, we could walk around with a web browser, a calendar, a music player, an address book, and really manage our lives on the go.  (Ok, I’ll grant that BlackBerry defined that niche for business users, but the iPhone made it cool and made it work for consumers.)

I’m hesitant to sit on the bleeding edge.  I waited until Service Pack 1 to get my iPhone 3G.  It was nearly 2 years ago.  It was a game changer for me too.  At random times in random places, I could check email, surf the web to answer the nagging question, create calendar entries, or just horse around.  I could pull up a map of where I am and where I wanted to go, so the preparation for travel wasn’t as urgent.  I could schedule stuff on my calendar, so no more wads of post-its hoping I guessed right.  And reading email in the 2 minutes standing in line or on the walk home from dropping kids off at school is awesome.  It truly revolutionized my world for the better.

Fast forward 3 years for Apple, nearly 2 years for me.  I waited with baited breath at every Apple announcement for the thing that’d keep the iPhone cool or for them to switch to a new carrier.  (The tag-line for the iPhone I’ve heard not a few times is, “The iPhone is so cool it almost makes up for being stuck on AT&T.”)  In that time, they’ve started to back-fill some missing features, and made subtle improvements, but they’ve really never leaped out and grabbed me again.

The following features were instantly missing from my experience, and with few exceptions, Apple really never delivered:

  • OTA Syncing
  • contact and calendar categories
  • turn-by-turn voice navigation
  • copy & paste
  • an IM and VOIP client that’s always on (e.g. I’ll still get new IMs or receive VOIP calls if I switched over to read my email)
  • 3rd party data on maps (e.g. “map all my contacts” or “where is the closest gas station on my route” or “does Acme, Inc. have an office near me?”  I ranted about this previously — that I want to shove a kml file into maps.)
  • OpenVPN / RDP client
  • disable auto-rotation based on the accelerometer — so I can read in bed

Ok, I’ll grant that some of these things have come with third-party apps.  Some have come as jailbreak apps.  Some are just not there.

And I get that I’m hardly a typical user.  Most users looking for “IM client” on a phone would point me at text messaging.  (Yeah, but I can’t text into an MSN IM contact.  And if a Skype contact IMs me back but I’m sending an email, I never get it.  Yeah, I know Trillian Astra is “almost there”.  I’ve been waiting on them for as long too.)

Meanwhile, over the last 3 years since the iPhone changed the landscape, Android and most recently Windows Mobile 7 have come into view.  Neither is as polished as the iPhone was at launch.  (I’ve not seen a Win 7 phone, but I’ve seen a good deal of Android phones.)  Android only recently got pinch-to-zoom, and really only on Nexus One, and the Droid’s map application.  But I see a lot more potential in these devices than in the iPhone.

For instance, me and my family were on vacation in California.  We were caravaning with friends on an unfamiliar stretch of highway.  We popped open the Droid, put in our destination in the voice guidance app, and went back to the home screen.  When something of interest popped up in a friends’ cars, they’d text us or tweet.  As the perfect example, my wife was on the web looking up something, the navigation was telling me where to turn, and a text came in.  It all happened simultaneously.  It just worked.

I’ve played with the Droid pretty extensively since.  I can push the search button, and pull up a web page or a contact or directions.  The back button is just truly awesome — getting me back a web page or back into the previous app or back a page in the app.   It just works.  The syncing is awesome because it just works.  I’m very good at forgetting day-to-day stuff, and so I often find I haven’t sync’d my calendar in a while, and when I do, I double-booked myself.  There’s no android sync procedure — it just does it.

And don’t get me started on the iPhone carrier lock-in.  I probably would’ve gotten a 3GS if my carrier of choice was announced last July.  As it stands, I’m the only member of my circle of mobiles that can’t use free mobile-to-mobile minutes … because I’m stuck in AT&T — say nothing of the service.

When Android finally got pinch-to-zoom, it was a done deal.  I’m sold.  As soon as my AT&T contract is up, I’m sailing away on Android.  It’ll let me use it the way I want.

Rob

VS’s Cassini (Web Server) + Fiddler = Remote testing

From time to time, I have a need to debug a web site from a browser not installed on my current machine.  Safari/Mac or phone browsers are the quintessential example.  Granted, if it happens server-side, it happens for all browsers, and if it happens client-side, debugging server-side may not help.  But there’s that one random time every now and again when this becomes quite important: debug server, client isn’t on localhost.  Perhaps it’s easier to replay the request from the browser where the problem happened rather than figure out how to simulate it locally.  Perhaps it’s a weird combination of race conditions that make it happen.  Perhaps it’s how the cookies built up that led to cruft not easily reproducible elsewhere.  Alas, being able to debug the server from a non-local client comes in mighty handy now-and-again.

As we all know, Visual Studio’s built in web server (originally based on Cassini) is hard-coded to only bind to loopback.  If I had my way that wouldn’t be the case, but here we are.  Traditionally, we’d need to install our site in IIS, attach to the w3svc service, and debug that way.  (Even if you’re not on a 64-bit box, you can empathize with how intense that is.)

Fiddler is an awesome tool for watching network traffic flow between browser and web server.  In the end, everything you send between server and browser is either HTML, CSS, or JavaScript (or binary content like images and plug-ins), so watching it happen is incredibly insiteful.  We’ll not use Fiddler for that here, but I highly recommend it for that work as well.  (There’s times when watching the traffic yields insights quite difficult to see any other way.)  Alas, today we’ll just mis-use it as a proxy server that can forward traffic back and forth from a LAN address to a loopback address.

Here’s the steps:

1. Download and install Fiddler from www.fiddlertool.com on the same machine as Visual Studio.

2. Tools menu -> Fiddler Options -> Connections tab: click on “Allow remote computers to connect”, and push ok.  (While I’m in here, I usually turn off IPv6 in the General tab as that’s another no-no in VS’s version of Cassini.)

3.  Rules menu -> Customize Rules.  For me, it opened up CustomRules.js in notepad.

4. Scroll down to the function labeled OnBeforeRequest and add a line kinda like this:

if (oSession.host.toLowerCase() == “192.168.1.5:8888”) oSession.host = “localhost:45678”;

Replace the first IP with the address of your machine (ipconfig /all from a cmd will give you this), and replace the port at the end with what ever you set / VS assigned to your project.  (Inside Visual Studio, in the project properties window, in the web tab, I always set it to “Specific Port” so I don’t have to come in here and futz with it every time VS wants to “help”.)

This line says “if the inbound request says it’s going to this ip and port (that fiddler is listening to), reroute it to this other ip and port (that Visual Studio’s web debugger is listening to).

5. Scroll down a bit further in CustomRules.js to the OnBeforeResponse function and add a line kinda like this:

oSession.utilReplaceInResponse(“localhost:45678″,”192.168.1.5:8888”);

You’ll also need to change the IP and port here to match the changes above as well, but note they’re reversed.

This line says “if the body of the outbound message includes text that routes to Visual Studio’s debugger, rewrite it to point to Fiddler.”

6. Save this file, restart Fiddler, and try it out.

(You may also have to adjust firewall rules on your development machine to allow inbound TCP connections on port 8888.)

And that’s it!  Now you’re remote debugging your project from a non-local address.

One final note: ideally, we’d create another rule in OnBeforeResponse that would rewrite headers as well.  I haven’t been very successful in doing that, so I haven’t included all the ways that don’t work.  But if you’re sending back a redirect to a full url without this rule, your client will happily time out looking for content on itself.  Why itself?  Because you redirected to localhost.  :D

Rob

Logging and Rewriting Service Calls

I love web services.  I love how easy it is to wire up a JavaScript client either through jQuery or through a ServiceProxy.  I love how I can point a .net client at a service, and I get strongly typed command parameters.  And my latest love of asmx services in C# is the SoapExtension.  A SoapExtension is a class that allows you to inspect outgoing or incoming services to get at the data involved.  I use them for logging service calls (frequency, duration, errors), and for rewriting output (fixed a “double html encoded” bug, remove whitespace, rewrite SoapFaults, etc).

The technique is incredibly awesome, and I can’t claim original ownership, but I can claim excellent results.  A few simple steps:

1. Create a class that derives from SoapExtension.

2. Override ProcessMessage, handling the SoapMessage’s Stage that you’re interested in.

3. Add either a web.config entry (to handle all inbound and outbound service calls), or put an attribute on each WebMethod you want to track.

Voila!  You’ve got a soap extension.

Here’s my ExampleSoapExtension (business logic replaced with TODO to keep it simple):

namespace ExampleNamespace {



    #region using

    using System;

    using System.Data.Linq;

    using System.IO;

    using System.Linq;

    using System.Text.RegularExpressions;

    using System.Threading;

    using System.Web;

    using System.Web.Services.Protocols;

    #endregion



    public class ExampleSoapExtension : SoapExtension {



        private Stream originalStream; // The original stream

        private Stream internalStream; // Our new stream we chained into place



        public ExampleSoapExtension() {

        }



        // Called the first time the Web service is used if configured with a config file

        public override object GetInitializer( Type t ) {

            return typeof( ExampleSoapExtension );

        }



        // Called the first time the Web service is used if configured with an attribute

        public override object GetInitializer( LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute ) {

            return attribute;

        }



        // Called each time the Web service is used and gets passed the data from GetInitializer() method

        public override void Initialize( object initializer ) {

            // Get the attribute here and pull whatever you need off it.

            //ExampleSoapExtensionAttribute attr = initializer as ExampleSoapExtensionAttribute;

        }



        // The ChainStream() method gives us a chance to grab the SOAP messages as they go by

        public override Stream ChainStream( Stream stream ) {

            // Save the original stream

            originalStream = stream;

            // Create and return our own in its place

            internalStream = new MemoryStream();



            return internalStream;

        }



        // The ProcessMessage() method is called between each step in the server-side process

        public override void ProcessMessage( SoapMessage message ) {



            switch ( message.Stage ) {



                case SoapMessageStage.BeforeDeserialize:

                    // About to handle incoming SOAP request



                    string requestContent = StreamToText( this.originalStream, false );

                    WriteIncomingToLog( message, requestContent );

                    TextToStream( requestContent, this.internalStream, true );

                    break;



                case SoapMessageStage.AfterDeserialize:

                    // incoming SOAP request has been deserialized



                    WriteInputToLog( message );

                    break;



                case SoapMessageStage.BeforeSerialize:

                    // About to prepare outgoing SOAP Response



                    WriteOutputToLog( message );

                    break;



                case SoapMessageStage.AfterSerialize:

                    // outgoing SOAP response is ready to go



                    string responseContent = null;



                    if ( message.Exception != null ) {

                        // Rewrite soap fault as valid soap message with internal error code

                        // Though it might be nice to do this before serialization, we can’t inject our own return value easily

                        responseContent = RewriteError( message );

                    } else {

                        responseContent = StreamToText( this.internalStream, true );

                    }



                    WriteOutgoingToLog( message, responseContent );

                    TextToStream( responseContent, this.originalStream, false );

                    break;



                default:

                    throw new Exception( “invalid stage” );

            }

        }



        private static string StreamToText( Stream stream, bool rewindBefore ) {

            if ( rewindBefore ) {

                stream.Position = 0;

            }

            TextReader reader = new StreamReader( stream );

            string content = reader.ReadToEnd();

            return content;

        }



        private static void TextToStream( String text, Stream stream, bool rewindAfter ) {

            TextWriter writer = new StreamWriter( stream );

            writer.Write( text );

            writer.Flush();

            if ( rewindAfter ) {

                stream.Position = 0;

            }

        }



        #region WriteIncomingToLog

        /// <summary>

        /// Write the incoming stream content to the logs

        /// </summary>

        private void WriteIncomingToLog( SoapMessage Message, string Content ) {



            HttpRequest req = HttpContext.Current.Request;



            // TODO: Log various parameters here including the incoming xml and start date



        }

        #endregion



        #region WriteInputToLog

        private void WriteInputToLog( SoapMessage Message ) {



            ServiceInputObject input = null;

            if ( Message.MethodInfo.InParameters.Length > 0 ) {

                try {

                    input = Message.GetInParameterValue( 0 ) as ServiceInputObject;

                } catch ( Exception /*ex*/ ) {

                    input = null;

                }

            }

            if ( input != null ) {

           

                // TODO: Log the message details

               

            }



        }

        #endregion



        #region WriteOutputToLog

        private void WriteOutputToLog( SoapMessage Message ) {



            if ( Message.Exception != null ) {

                // The return object doesn’t exist because an exception was thrown

                // RewriteError will create one

                return;

            }



            ServiceOutputObject output = null;

            if ( Message.MethodInfo != null && Message.MethodInfo.ReturnType != null && Message.MethodInfo.ReturnType.FullName != “System.Void” ) {

                output = Message.GetReturnValue() as ServiceOutputObject;

            } else {

                output = null;

            }

            if ( output != null ) {

           

                // TODO: Log the output details

               

            }



        }

        #endregion



        #region WriteOutgoingToLog

        /// <summary>

        /// Write the message in the stream to the log4net log

        /// </summary>

        private void WriteOutgoingToLog( SoapMessage Message, string Content ) {



            string outboundData = GetStreamText( StreamToLog );



            // TODO: Log the outgoing xml string and end date



        }

        #endregion



        #region CaptureError

        /// <summary>

        /// Insure the exception is logged

        /// </summary>

        private Stream CaptureError( SoapMessage Message, Stream stream ) {



            // Log the exception

            Exception ex = Message.Exception;

            while ( ex != null && ex.InnerException != null && ex.GetType() == typeof( SoapException ) ) {

                ex = ex.InnerException;

            }

           

            // TODO: Log the exception

           

            // TODO: Form the output reply and serialize it

            string soapMessage = null;



            // Now send this as the out stream instead of the <soap:Fault> stream we had before

            MemoryStream outStream = new MemoryStream();

            TextWriter writer = new StreamWriter( outStream );

            writer.WriteLine( soapMessage );

            writer.Flush();

            outStream.Position = 0;



            // And send the 200 status instead of 400

            //HttpContext.Current.Response.StatusCode = 200;



            // Here’s the content to send instead of what ever you had

            return outStream;

        }

        #endregion



    }



    [AttributeUsage( AttributeTargets.Method )]

    public class ExampleSoapExtensionAttribute : SoapExtensionAttribute {



        public ExampleSoapExtensionAttribute() {

            this.Priority = 1;

        }



        public override int Priority { get; set; }



        // Specifies the class of the SOAP Extension to use with this method

        public override Type ExtensionType {

            get { return typeof( ExampleSoapExtension ); }

        }



    }



}

My current quest: how do I do this in WCF?  Is there such a thing as a “message watcher” or “handler” or “pipeline interceptor”?  All these terms on Google come up blank.  With WCF’s new configuration-less services and MS’s desire to move on past asmx, WCF seems a foregone conclusion.  For me, this is the last hurdle.

Rob