Rick Glos Life in Portland, Oregon.

Using ASP.NET Web API to disable Windows applications

27. February 2013 14:10 by Rick Glos in

Had an interesting problem to solve where sometimes at a client we need to disable applications during maintenance upgrades, overnight failures, etc. that would cause the application to either crash or show data that isn’t quite right, thereby confusing the users.  With a web application I’ve been using the App_Offline.htm trick for some time.  However what if it’s a windows application?

2-26-2013 4-48-01 AM

To solve this, I added a block of code that gets called during startup in these apps to determine if the application should be disabled.  Then someone, can use the web application to set a bit that the app is disabled and optionally add a reason that will get presented to the user.

In the windows applications I’m coding these days I’ve been using Prism heavily.  The best spot I could identify to place this method is in the InitializeShell before the user gets to see any UI and in the case of Prism, if the shell loads, it will start loading any modules you have registered with a region.  There, we call a method to go to the Web API and get back information.

   1:/// <summary>
   2:/// Initializes the shell.
   3:/// </summary>
   4:/// <remarks>
   5:/// The base implementation ensures the shell is composed in the container.
   6:/// </remarks>
   7:protected override void InitializeShell()
   8:{
   9:// Do not allow the application to launch if instructed not to do so
  10:    CheckToSeeIfApplicationIsDisabled();
  11: 
  12:base.InitializeShell();
  13: 
  14:    Application.Current.MainWindow = (Window)this.Shell;
  15:    Application.Current.MainWindow.Show();
  16:}

In the CheckToSeeIfApplicationIsDisabled method I call a service called InfrastructureService which handles the calling of the ASP.NET Web API.  In our case, we’ll need the application name, environment (test, production, beta, etc) and the url.

   1:private void CheckToSeeIfApplicationIsDisabled()
   2:{
   3:// Use whatever services necessary to get the infrastructure url, application name and environment - this is application dependent
   4:    var applicationSettingsService = Container.Resolve<IApplicationSettingsService>();
   5:    var assemblyInformationService = Container.Resolve<IAssemblyInformationService>();
   6:string infrastructureUrl = applicationSettingsService.InfrastructureUrl;
   7:string applicationName = assemblyInformationService.ProductName;
   8:string environment = applicationSettingsService.Environment.ToString();
   9: 
  10:// Use the InfrastructureService to check to see if the application is disabled
  11:    var infrastructureService = new InfrastructureService(infrastructureUrl);
  12:    var serverApplicationMetadata = infrastructureService.GetMetadata(applicationName, environment);
  13:if (serverApplicationMetadata != null && serverApplicationMetadata.Disabled)
  14:    {
  15:        var caption = string.Format("{0} - {1} - Application Disabled", applicationName, environment);
  16:        MessageBox.Show(serverApplicationMetadata.DisabledReason, caption, MessageBoxButton.OK, MessageBoxImage.Information);
  17:        Application.Current.Shutdown();
  18:    }
  19:}

Lastly we do the part where we call the service and get back data.  The data that we get back is in JSON format but using the ReadAsAsync method we can have the JSON get deserialized into a typed object if we want.  What’s cool is you don’t have to define every bit of information in the JSON, only those properties you care about.  If you don’t want to do it that way, you can also just use JToken objects to parse the returned JSON and grab the values you need.

Note that because this is an intranet, I need to pass in credentials and so I’m using a HttpClientHandler and passing that into the HttpClient.  I have it swallowing exceptions as I don’t want the applications to fail to load if the server is down or some other strange exception is thrown.  We should circle back around and at least log the exception using the Exception Handling Application Block so it gets logged somewhere.

   1:/// <summary>
   2:/// An encapsultion of calling the web api to get application information, etc.
   3:/// </summary>
   4:/// <remarks>
   5:/// <para>
   6:/// When using this service, add the Web API Client Libraries package to your project.  To do this, 
   7:/// click 'Manage NuGet Packages for Solution...', select 'Online', type 'Microsoft.AspNet.WebApi.Client' 
   8:/// in the search box, select the package and click Install.
   9:/// </para>
  10:/// <para>
  11:/// We may want to put this in a library if it's used alot or across different languages so that apps can just reference an external library.
  12:/// </para>
  13:/// </remarks>
  14:public class InfrastructureService
  15:{
  16:private readonly string _infrastructureUrl;
  17: 
  18:/// <summary>
  19:/// Default constructor requires the url.
  20:/// </summary>
  21:/// <param name="infrastructureUrl">Url, Example: http://yourserver/beta/Infrastructure</param>
  22:public InfrastructureService(string infrastructureUrl)
  23:    {
  24:this._infrastructureUrl = infrastructureUrl;
  25:    }
  26: 
  27:/// <summary>
  28:/// Returns an object with infromation regarding this application from a central server.
  29:/// </summary>
  30:/// <param name="applicationName">The name of the application as it exists in [infrastructure].[application].</param>
  31:/// <param name="environment">Production, Beta</param>
  32:/// <returns></returns>
  33:/// <remarks>
  34:/// We may want to make the environment parameter an enum or leave it string so it's very flexible in case more environments are added.
  35:/// </remarks>
  36:public ApplicationMetadata GetMetadata(string applicationName, string environment)
  37:    {
  38:        ApplicationMetadata result = null;
  39: 
  40:try
  41:        {
  42: using (var handler = new HttpClientHandler() { UseDefaultCredentials = true })
  43:            {
  44: using (var client = new HttpClient(handler))
  45:                {
  46:                    client.BaseAddress = new Uri(_infrastructureUrl);
  47:                    var requestUri = string.Format("api/applications?environment={0}&name={1}", environment, applicationName);
  48:                    HttpResponseMessage response = client.GetAsync(requestUri).Result;// Blocking call!
  49: if (response.IsSuccessStatusCode)
  50:                    {
  51: // Example #1: transform into somthing typed
  52:                        var convertTask = response.Content.ReadAsAsync<ApplicationMetadata>();
  53:                        var convertTaskResult = convertTask.Result as ApplicationMetadata;
  54:                        result = convertTaskResult;
  55: 
  56: // Example #2: transform into something not-typed/dynamic (useful for peeking at all the JSON)
  57: //var taskResults = response.Content.ReadAsAsync<Newtonsoft.Json.Linq.JToken>();
  58: //var taskResult = taskResults.Result as Newtonsoft.Json.Linq.JObject;
  59: //foreach (var item in result)
  60: //{
  61: //    var jitem = item as JObject;
  62: //    var disabled = jitem["Disabled"];
  63: //    var disabledReason = jitem["DisabledReason"];
  64: //}
  65:                    }
  66:                }
  67:            } 
  68:        }
  69:catch (Exception)
  70:        {
  71: // TODO: Handle the exception by logging it or swallowing it.  Do not rethrow as we don't want the inability to communicate with infrastructure app to bring down the application.
  72:        }
  73: 
  74:return result;
  75:    }
  76: 
  77:public class ApplicationMetadata
  78:    {
  79:public int ID { get; set; }
  80:public string Name { get; set; }
  81:public bool Disabled { get; set; }
  82:public string DisabledReason { get; set; }
  83:    }
  84:}

PDXLAN 21

19. February 2013 07:17 by Rick Glos in

Gone is another PDXLAN.  I’m still recovering from staying up until 4 AM for 3 days in a row.

WP_20130219_001

I did win a prize in the raffle this year.  A mechanical keyboard (Thermaltake KB-MEK007 – $89) and a mouse pad ($29).  I tried the keyboard for about 5 minutes before switching back to my existing keyboard.  The lack of a windows key was apparent in the first few minutes as I tried to open Windows Explorer with Windows+E.  The fact that is was a compact keyboard was also very difficult to overcome.  Mechanical keyboards are supposed to offer better tactile feedback and be ‘faster’.  It certainly is heavier and noisier since you audibly hear each individual keystroke.  I am giving the mouse pad a try.  We shall see.

WP_20130219_003

We also made a Harlem Shake video which is the current internet meme rage.

All signed up for the next PDXLAN 22 on Jul 12-15 2013.