Smart Client: So where is the End-Point?

WindowsMobileLogo In my last post about my adventures in Smart Client land I talked about Using WCF Services with Smart Client Applications. In this post I wand to talk about how you can make things like database and service end-points dynamically adjust for different deployment scenarios.

Being a developer at a large corporation presents challenges not faced by those who develop and then deploy to a public customer web hosting.  My company has a fairly rigorous deployment strategy.  While it is designed to safeguard our production environment from any unwanted changes it presents some difficult programming challenges. At my current employer we have 3 main environments, development, test, and production.  The difficulty comes in when you start making database connections or use web or applications services in you application, since each environment will have different connection information.

In this post I am going to detail a possible solution to this issue that I used on my current project, which is a Smart Device project that uses WCF Services on a handheld device to talk to our business layer and data provider.  There aren’t any database connections from the Smart Device so all of the examples here will deal with changing application service end-points.  Please not that this technique would work equally well for database connection info.

The setup, a Smart Device application targeting the .NET Compact Framework 3.5 and using WCF Services.  When I develop I want the services to point our local machine, likewise, when the application is deployed to Test and Production the services should point to the correct resources in that environment.

The first pieced information the application needs to know is what environment are it is currently running in.  I have used different methods to configure this in the past, a setting in the machine.config (for web applications), or a setting the in the app.config for windows client.  In this case I decided to go with a command line argument to simplify switching from one environment to another.  I realize this may not be the best solution, but with the limited access to the OS on the Smart Device I felt changing a shortcut was much easier than trying to dive into the file system and make changes to a config file.

Command line arguments to be passed:

  • Local, to use local services
  • Test, to use services in the Test environment
  • null is passed telling the app to use services in the Production environment

Now put the endpoints for each environment in the the config file using the following naming convention:  Name_environment where environment is Local, Test, or Production.

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <appSettings>
   4:   
   5:      <add key="MyService_Local" value="http://localhost/MyServer/Service.svc" />
   6:      <add key="MyService_Test" value="http://test.example.com/MySerivce/Service.svc" />
   7:      <add key="MyService_Production"  value="http://production.example.com/MService/Service.svc" />
   8:   
   9:      <add key="MyService_Timeout"  value="180" />
  10:      <add key="MyService_MaxReceivedMessageSize" value="65536"/>
  11:      <add key="MyService_MaxBufferSize" value="65536"/>
  12:   
  13:    </appSettings>
  14:  </configuration>

The next step is to create an ApplicationSettings class that will be used to get the correct endpoints for the service.

   1:  using System;
   2:  using System.Linq;
   3:  using System.Reflection;
   4:  using System.Xml.Linq;
   5:   
   6:  namespace MyClient.Core
   7:  {
   8:      public static class ApplicationSettings
   9:      {
  10:   
  11:          public static string RunTimeEnvironment { get; set; }
  12:   
  13:   
  14:          public static string MetroServiceEndPoint
  15:          {
  16:              get { return ConfigManager.GetAppSetting("MyService_" + RunTimeEnvironment); }
  17:          }
  18:   
  19:          public static TimeSpan MetroServiceTimeout
  20:          {
  21:              get
  22:              {
  23:                  int seconds = Convert.ToInt32(ConfigManager.GetAppSetting("MyService_Timeout"));
  24:                  return new TimeSpan(0, 0, seconds);
  25:              }
  26:          }
  27:   
  28:          public static long MaxReceivedMessageSize
  29:          {
  30:              get { return Convert.ToInt64(ConfigManager.GetAppSetting("MyService_MaxReceivedMessageSize")); }
  31:          }
  32:   
  33:          public static int MaxBufferSize
  34:          {
  35:              get { return Convert.ToInt32(ConfigManager.GetAppSetting("MyService_MaxBufferSize")); }
  36:          }
  37:   
  38:      }
  39:  }

This simple implementation does exactly what we want, given a known environment it will return the correct value from the configuration file.  If the runtime environment is set to Local it will return the information from “MyService_Local”, and adjust for each environment we are in.

So earlier I stated we need to know what environment we are in, this is still unknown. A command line argument has been defined but hasn’t been used to connect the request and the application settings together.  The ApplicationSetting class has a property for RunTimeEnvironment and this will need to be set when the application starts.

Here is what the Main function looks like from the Smart Device application:

   1:  internal static class Program
   2:      {
   3:          [MTAThread]
   4:          private static void Main(string[] args)
   5:          {
   6:              ApplicationSettings.RunTimeEnvironment = GetRunTimeEnvironment(args);
   7:   
   8:              new LogonForm().ShowDialog();
   9:          }
  10:   
  11:          private static string GetRunTimeEnvironment(string[] commandLine)
  12:          {
  13:              if (commandLine.Length != 0)
  14:              {
  15:                  if (commandLine[0].ToUpper().Contains("LOCAL")) return "Local";
  16:                  if (commandLine[0].ToUpper().Contains("DEV")) return "Development";
  17:                  if (commandLine[0].ToUpper().Contains("QA")) return "Test";
  18:              }
  19:   
  20:              return "Production";
  21:          }
  22:      }

The GetRunTimeEnvironment  method sets the correct ApplicationSettings.RunTimeEnvironment property based on the command line argument.  Now we have tied it all together and can change the end-points of our applications services simply by changing the command line in the shortcut on the device.

I hope that has helped ease some of the pain of working with Smart Device applications and using WCF Services.  I have learned a ton about using Smart Device project and the limitations of the .NET Compact framework.  I can’t wait to get back to development on the full framework, which will hopefully be soon.

Using WCF Services with Smart Client Application

WindowsMobileLogo The current project I am assigned to at work is Smart Client application running own Windows CE that communicates with WCF Services that it uses to communicate with SAP via BizTalk Services.  We choose to use WCF because of the power that we felt it would bring to the solution.  What we didn’t realize was the limited support in Visual Studio for consuming WCF Services for a Smart Client application.

In a WinForms application you have two choices to add an Application Reference to your project you can use the SvcUtil.exe or you can right click on the References node of the solution and select Add Application Reference.  In the Smart Client world you don’t have either option, you can try to use the SvcUtil.exe but it will not create a proxy that can compile against NetCF libraries because NetCF does not include all the classes referenced from that source code.

Fortunately we found the Power Toys for .NET Compact Framework 3.5 which contains NetCFSvcUtil.exe that could be used to generate NetCF complaint service proxies.  Once install the power toys you will find the NetCFSvcUtil.exe in the C:\Program Files (x86)\Microsoft.NET\SDK\CompactFramework\v3.5\bin directory.

We setup a batch file to generate the service files for us.  So, all we had to do was make sure we check out the files and then run the command file to update the proxies and check the files back in and we are good.

You should be aware that NetCF does not support all WCF Bindings, it supports: BasicHttpBinding, CustomBinding, WindowsMobileMailBinding, and ExchangeWebServiceMailBinding.  Andrew Arnott has a post about The WCF subset supported by NetCF and other good information.

A good friend and member of our team Devlin Liles, has put together a great post, Batch File and Command File running in Visual Studio, to show how you can execute a command file from the Visual Studio IDE and use the output window for any text.  I highly recommend you check it out.

NetCFSvcUtil.exe http://localhost/MyService.svc

This will generate the CFClientBase.cs and the MyService.cs files we need to access the WCF Service.  If you want to control the namespace that the proxies get created in you can use the /namespace argument to specify this.

NetCFSvcUtil.exe namespace:*,MyNameSpace.Proxies http://localhost/MyService.svc

Likewise you can also control where the files are generated by using the /directory argument.

NetCFSvcUtil.exe namespace:*,MyNameSpace.Proxies http://localhost/MyService.svc /directory:../MyApp/Proxies

There are a lot of other options you can use to control how the proxies are generated you can execute NetCFSvcUtil.exe /? to see all of them and what they do.

This gets you up and running with WCF Services in a Smart Client application, without the robust configuration that is offered with WinForms or WebForm projects.  In my next post I will walk you through how we configured our WCF Service endpoints and set some custom binding information to control how our clients behaved when consuming the services.

Modify Log4Net AdoNetAppender Connection String at run-time

Log4Net

In my every day development I always have to figure out ways to keep my connection information in sync with the environment my application is deployed to.  We currently go through 3 environments; Dev, Test, and Production.  Each with a different set of database and service connections.  Today I was working on setting up Log4Net to use the AdoNetAppender and wanted to have it’s connection string move with my ApplicationSettings so that it would point to the appropriate database for the environment.  After about 3 hours of searching and code pounding I came up with a solution and since it was so hard to find I wanted to share what I learned.

First a little background of how my application is setup.  I have created the following application settings;

  • RunEnvironment: used to show where we are deployed, valid values are Development, Test, or Production
  • Database_Development: stores the database connection for development
  • Database_Test: stores the database connection for test
  • Database_Production: stores the database connection for Production

I also have a static ApplicationSettings class like this:

   1:  using System.Collections.Specialized;      
   2:  using System.Configuration;      
   3:   
   4:  public static class ApplicationSettings
   5:  {
   6:      private static readonly NameValueCollection Settings = ConfigurationManager.AppSettings;
   7:   
   8:      public static string RunEnvironment
   9:      {
  10:          get { return Settings["RunEnvironment"]; }
  11:      }
  12:      
  13:      public static string DatabaseConnectionString
  14:      {
  15:          get { return Settings["Database_" + RunEnvironment]; }
  16:      }
  17:  }

This approach allows me to just ask ApplicationSettings for the DatabaseConnectionString and it is always adjusted for the correct RunEnvironment.

Ok, so now to the Log4Net goodness.  I used the XML Configuration in my app.config to configure Log4Net and tried several ways to get the currently active configuration and manipulate it to reflect the new database connection string.  The solution that finally worked was to implement my own AdoNetAppender derived from the AdoNetAppender and override the ConnectionString Property.

Here is the new CustomAdoNetAppender class:

   1:  using log4net.Appender;
   2:   
   3:  public class CustomAdoNetAppender : AdoNetAppender
   4:  {
   5:      public new string ConnectionString
   6:      {
   7:          get
   8:          {
   9:              return base.ConnectionString;
  10:          }
  11:          set
  12:          {
  13:              base.ConnectionString = ApplicationSettings.DatabaseConnectionString;
  14:          }
  15:      }
  16:  }

 

The next step, which was left out of many of the post regarding this matter, was to modify the Log4Net configuration in your app/web.config to use the new CustomAdoNetAppender like below:

This must be added to the Configuration->ConfigSections of you config file

<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />

After the Configuration->ConfigSections and at the same level in your config file add the Log4Net

   1:  <log4net>
   2:      <appender name="CustomAdoNetAppender" type="MyCustomNamespace.CustomAdoNetAppender">
   3:        <bufferSize value="100" />
   4:        <connectionType value="System.Data.SqlClient.SqlConnection, System.Data,  />
   5:        Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
   6:        <connectionString value="data source=[DbServer];initial catalog=[DbName]; />
   7:        integrated security=false;persist security info=True;User ID=[DB_USER];Password=[DB_PASS]"
   8:        <commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message],[Exception])  />
   9:        VALUES (@log_date, @thread, @log_level, @logger, @message, @exception)"
  10:        <parameter>
  11:          <parameterName value="@log_date" />
  12:          <dbType value="DateTime" />
  13:          <layout type="log4net.Layout.RawTimeStampLayout" />
  14:        </parameter>
  15:        <parameter>
  16:          <parameterName value="@thread" />
  17:          <dbType value="String" />
  18:          <size value="255" />
  19:          <layout type="log4net.Layout.PatternLayout">
  20:            <conversionPattern value="%thread" />
  21:          </layout>
  22:        </parameter>
  23:        <parameter>
  24:          <parameterName value="@log_level" />
  25:          <dbType value="String" />
  26:          <size value="50" />
  27:          <layout type="log4net.Layout.PatternLayout">
  28:            <conversionPattern value="%level" />
  29:          </layout>
  30:        </parameter>
  31:        <parameter>
  32:          <parameterName value="@logger" />
  33:          <dbType value="String" />
  34:          <size value="255" />
  35:          <layout type="log4net.Layout.PatternLayout">
  36:            <conversionPattern value="%logger" />
  37:          </layout>
  38:        </parameter>
  39:        <parameter>
  40:          <parameterName value="@message" />
  41:          <dbType value="String" />
  42:          <size value="4000" />
  43:          <layout type="log4net.Layout.PatternLayout">
  44:            <conversionPattern value="%message" />
  45:          </layout>
  46:        </parameter>
  47:        <parameter>
  48:          <parameterName value="@exception" />
  49:          <dbType value="String" />
  50:          <size value="2000" />
  51:          <layout type="log4net.Layout.ExceptionLayout" />
  52:        </parameter>
  53:      </appender>
  54:      <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
  55:        <layout type="log4net.Layout.PatternLayout">
  56:          <param name="Header" value="[Header]\r\n" />
  57:          <param name="Footer" value="[Footer]\r\n" />
  58:          <param name="ConversionPattern" value="%d [%t] %-5p %m%n" />
  59:        </layout>
  60:      </appender>
  61:      <root>
  62:        <level value="DEBUG" />
  63:        <appender-ref ref="CustomAdoNetAppender" />
  64:        <appender-ref ref="ConsoleAppender" />
  65:      </root>
  66:    </log4net>

You can see on line 2 that we specified our new CustomAdoNetAppender as the name and pointed the type to MyCustomNamespace.CustomAdoNetAppender.  This tells Log4Net what class to use and where to find it.

Now you have a Custom AdoNetAppender that gets the database connection from your application settings at run-time.

(407) Proxy Authentication Required in Windows Client Apps

I have downloaded many apps in the past to use at work only to find out that they do not properly handle the proxy authentication at my location.  At home they work just fine, but at work no way!  One was Witty the WPF twitter client, I actually downloaded the code and patched and submitted that back to the developer.  You now have the option to configure the proxy settings in the options windows.

Recently I downloaded Podder, version 2 has some awesome new skins for an WPF application but still doesn’t work through my proxy server.

I did some research and did find a solution, by adding the following to the config file, Podder.exe.config in this case you can define the default proxy behavior for the application. 

<system.net>
<defaultProxy enabled="true" useDefaultCredentials="true">
<proxy bypassonlocal="True" proxyaddress="http://proxy.example.com:8080"/>
</defaultProxy>
</system.net>

It needs to be in the <configuration> node and it worked best if I put it at the bottom below everything else.  Putting it at the top made Podder crash, not sure that really had anything to do with it, but putting it at the bottom didn’t crash.

I wish more developers provided ways to configure the proxy server in their internet enabled apps, but they don’t.  At least now if can work around their laziness and hopefully get it to work any way.

Other apps that I plan to try this on are Blu, and DigiTweet to start.

StructureMap 2.5.1 Released!

Jeremy D. Miller has just announced the availability of StructureMap 2.5.1.  This incremental release has a bunch of bug fixes and a few new features.

Here are a few of the updates from the release notes:

* Fixed issue with With<T>(object).GetInstance<U>() functionality.  This is now much more robust.
* Bug fix for Hybrid scoping
* TryGetInstance() and TryGetNamedInstance(). 
* Ignore setter emitting for indexer properties -- fixed a bug reported on the forum
* Multithreading lock problem fixed by Josh
* Patch for session lifetime.  There is now an option for "CacheBy(InstanceScope.HttpSession)"
* Fixed Public IDictionary setter injection in xml : http://groups.google.com/group/structuremap-users/browse_thread/thread/21265544d73cd395?hl=en
* Pete Johnson's fix for ObjectFactory.Configure() setting policy.  You can now set the scoping/lifecycle of a PluginType from Configure()
* EjectAllInstances<T>() clears out Singleton objects now (and calls Dispose() if appropriate)
* Josh has added an experimental debugger visualizer for StructureMap Container's
* The AutoMocker isn't as whiny about RhinoMocks versions.  There is also an AutoMocker strategy for Moq now as well 
* A minor performance enhancement that stops StructureMap from doing a scan of all types on the StructureMap assembly itself.

Jeremy also posted a list of items that he plans for the 2.6 release, and also says that this is a good time to ask for things. 

If you haven’t signed up for the StructureMap Google list be sure to do so and ask your questions there.

View the full article at: http://codebetter.com/blogs/jeremy.miller/archive/2008/12/22/structuremap-2-5-1-is-released.aspx

Download StructureMap 2.5.1 here

Calendar

<<  November 2017  >>
MonTueWedThuFriSatSun
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar

Month List