views:

1022

answers:

4

I have a .NET Compact Framework 3.5 program that is used as a 'Occasionally Connected' line of business (LOB) application. If it can see an online webservice, it will use that for data access, but if network connection is lost it will use a local cache.

What is the best way to handle all of the connection options and state changes?

  • OpenNetCF's ConnectionManager class?
  • Microsoft.WindowsBile.State.SystemState?
  • API calls?

How do you get it to understand the difference between WiFi, Cradle, and GPRS and use the best method available?

Anyone have any guidance on this?

+1  A: 

What about using the SystemState class at the Microsoft.WindowsMobile.Status namespace? You can monitor the current state of the system and get notifications, when the state changes. See this post for some code.

SystemState is only about the status of the connections. You can use a specific connection through the ConnectionManager. I recommend reading this article. If you are using .NET Compact Framework 3.5, a managed API is included. You can also use OpenNetCF ConnectionManager.

kgiannakakis
How do I use the SystemState to tell it to try to connect (i.e. start GPRS if it is available)?
JasonRShaver
+1  A: 

I just create a simple shared class that I can call like this:

If MyConnectionClass.IsConnected then
     'Do connected stuff
Else
     'Do local save
End If

Then all of my actual business classes/functions can use this to hide this nastiness from the UI code.

The MyConnectionClass' IsConnected property would have something like this:

Public ReadOnly Property IsConnected As Boolean
   Get
    Try

        Dim HostName As String = Dns.GetHostName()
        Dim thisHost As IPHostEntry = Dns.GetHostByName(HostName)
        Dim thisIpAddr As String = thisHost.AddressList(0).ToString

        return (thisIpAddr <> Net.IPAddress.Parse("127.0.0.1").ToString())

    Catch ex As Exception
        Return False
    End Try
   End Get
End Property

It is also recommended that you poll for connection status using a background thread and then fire an event back to the main app thread when the state changes. Here is the detailed writeup:

Testing for and responding to network connections in the .NET Compact Framework

EDIT:

Now, for GPRS support:

If you are using Web Requests or Web Services, the framework will handle the connection for you. If you are diving deeper into TCPClient or UDPClient, you need to handle it with the Connection manager API's yourself like so:

public class GPRSConnection
{
    const int S_OK = 0;
    const uint CONNMGR_PARAM_GUIDDESTNET = 0x1;
    const uint CONNMGR_FLAG_PROXY_HTTP = 0x1;
    const uint CONNMGR_PRIORITY_USERINTERACTIVE = 0x08000;
    const uint INFINITE = 0xffffffff;
    const uint CONNMGR_STATUS_CONNECTED = 0x10;
    static Hashtable ht = new Hashtable();

    static GPRSConnection()
    {
        ManualResetEvent mre = new ManualResetEvent(false);
        mre.Handle = ConnMgrApiReadyEvent();
        mre.WaitOne();
        CloseHandle(mre.Handle);
    }

    ~GPRSConnection()
    {
        ReleaseAll();
    }

    public static bool Setup(Uri url)
    {
        return Setup(url.ToString());
    }

    public static bool Setup(string urlStr)
    {
        ConnectionInfo ci = new ConnectionInfo();
        IntPtr phConnection = IntPtr.Zero;
        uint status = 0;

        if (ht[urlStr] != null)
            return true;

        if (ConnMgrMapURL(urlStr, ref ci.guidDestNet, IntPtr.Zero) != S_OK)
            return false;

        ci.cbSize = (uint) Marshal.SizeOf(ci);
        ci.dwParams = CONNMGR_PARAM_GUIDDESTNET;
        ci.dwFlags = CONNMGR_FLAG_PROXY_HTTP;
        ci.dwPriority = CONNMGR_PRIORITY_USERINTERACTIVE;
        ci.bExclusive = 0;
        ci.bDisabled = 0;
        ci.hWnd = IntPtr.Zero;
        ci.uMsg = 0;
        ci.lParam = 0;

        if (ConnMgrEstablishConnectionSync(ref ci, ref phConnection, INFINITE, ref status) != S_OK &&
            status != CONNMGR_STATUS_CONNECTED)
            return false;

        ht[urlStr] = phConnection;
        return true;
    }

    public static bool Release(Uri url)
    {
        return Release(url.ToString());
    }

    public static bool Release(string urlStr)
    {
        return Release(urlStr, true);
    }

    private static bool Release(string urlStr, bool removeNode)
    {
        bool res = true;
        IntPtr ph = IntPtr.Zero;
        if (ht[urlStr] == null)
            return true;
        ph = (IntPtr)ht[urlStr];
        if (ConnMgrReleaseConnection(ph, 1) != S_OK)
            res = false;
        CloseHandle(ph);
        if (removeNode)
            ht.Remove(urlStr);
        return res;
    }

    public static void ReleaseAll()
    {
       foreach(DictionaryEntry de in ht)
       {
           Release((string)de.Key, false);
       }
       ht.Clear();
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct ConnectionInfo
    {
        public uint cbSize;
        public uint dwParams;
        public uint dwFlags;
        public uint dwPriority;
        public int bExclusive;
        public int bDisabled;
        public Guid guidDestNet;
        public IntPtr hWnd;
        public uint uMsg;
        public uint lParam;
        public uint ulMaxCost;
        public uint ulMinRcvBw;
        public uint ulMaxConnLatency;
    }

    [DllImport("cellcore.dll")]
    private static extern int ConnMgrMapURL(string pwszURL, ref Guid pguid, IntPtr pdwIndex);

    [DllImport("cellcore.dll")]
    private static extern int ConnMgrEstablishConnectionSync(ref ConnectionInfo ci, ref IntPtr phConnection, uint dwTimeout, ref uint pdwStatus);

    [DllImport("cellcore.dll")]
    private static extern IntPtr ConnMgrApiReadyEvent();

    [DllImport("cellcore.dll")]
    private static extern int ConnMgrReleaseConnection(IntPtr hConnection, int bCache);

    [DllImport("coredll.dll")]
    private static extern int CloseHandle(IntPtr hObject);
}

And to use it, do this:

public void DoTcpConnection()
    {
        string url = "www.msn.com";
        bool res = GPRSConnection.Setup("http://" + url + "/");
        if (res)
        {
            TcpClient tc = new TcpClient(url, 80);
            NetworkStream ns = tc.GetStream();
            byte[] buf = new byte[100];
            ns.Write(buf, 0, 100);
            tc.Client.Shutdown(SocketShutdown.Both);
            ns.Close();
            tc.Close();
            MessageBox.Show("Wrote 100 bytes");
        }
        else
        {
            MessageBox.Show("Connection establishment failed");
        }
    }

This was from Anthony Wong's blog here:

Anthony Wong

And remember you only need this for lower level TCP or UDP stuff. HTTPRequests don't need this.

Jeremy Sena
How do I tell it to try to connect (i.e. start GPRS if it is available)? The posted method (and link) seems to only address seems like WiFi where the connection is either On or Off.
JasonRShaver
+1  A: 

I try to write Mobile apps so they don't even know there's a network involved. I keep good-enough validation data locally, and then write transactions to a local queue which clears while connected; the queue reader includes a timer to retry when not connected. The queue messages are bidirectional, so local refreshes can be supplied as well. Basic Message Queue patterns.

This allows me to treat the network connection in the simplest way, using basic socket open/close/read/write/ioctl logic that's highly portable; and your connections don't need to persist for any significant time at all. (I'd hate to imagine what would be required to stay in sync with all the MS architecture variations over the last several years - which still hasn't settled out IMHO.)

le dorfier
I agree a lot with what you wrote, I had a mobile app that when WiFi is not there will switch to using SMS as a protocol. That was easy to do because of a 'Message Queue' based xfer system that I built into CLSA for CE. I wish I could still use it but that project dropped the phone option =(
JasonRShaver
A: 

I have found the Microsoft.WindowsMobile.State.SystemState reporting of network connections to be unreliable. This was 6.0 and earlier. I did not do exhaustive testing but it was abandoned when it would say there was no connection when there was.