# Thursday, September 29, 2005

On-Demand Updates with ClickOnce

ClickOnce supports both automatic updating and on-demand updates. The default model checks for updates automatically at application launch (when connected), and applies those updates immediately. There are a number of options available for installed applications (available online and offline), including whether to check automatically at all, whether to do it on a background thread, whether to do it on a timed interval, and other options.

 

However, sometimes you may want to do updates on demand, either as the only update model, or in combination with automatic updating in the background on a periodic interval. To do that, you will need to use the ClickOnce API defined in the System.Deployment framework assembly.

 

The main class you will use to support on-demand updates is the ApplicationDeployment class defined in the System.Deployment.Application. You will typically add code in response to a user action (such as selecting a Check For Updates menu item) that goes out and checks to see if updates are available, and if so retrieves them. You will then need to restart the application to have those changes applied.

 

A simple but common pattern to accomplish this is something like the following:

 

// First check to see if we are running in a ClickOnce context

if (ApplicationDeployment.IsNetworkDeployed)

{

   // Get an instance of the deployment

   ApplicationDeployment deployment = ApplicationDeployment.CurrentDeployment;

 

   // Check to see if updates are available

   if (deployment.CheckForUpdate())

   {

      DialogResult res = MessageBox.Show("A new version of the   

         application is available, do you want to update?",

         "Application Updater", MessageBoxButtons.YesNo);

      if (res == DialogResult.Yes)

      {

         // Do the update

         deployment.Update();

         DialogResult res2 = MessageBox.Show("Update complete, do you

            want to restart the application to apply the update?",

            "Application Updater", MessageBoxButtons.YesNo);

         if (res2 == DialogResult.Yes)

         {

            // Restart the application to apply the update

            Application.Restart();

         }

      }

   }

   else

   {

      MessageBox.Show("No updates available.", "Application Updater");

   }

}

else

{

   MessageBox.Show("Updates not allowed unless you are launched through ClickOnce.");

}

 

There are of course asynchronous versions of the CheckForUpdate and Update methods if you want to avoid blocking your UI while this happens.

 

The project settings you will need may not be completely apparent. The first important setting is that you need to change the update behavior of the ClickOnce deployment to stop automatically checking for updates if you are doing only on-demand updates. You do this through the Publish tab of the project properties settings, Updates button, shown in Figure 1.

 

Publish settings

Figure 1: ClickOnce Update Behavior Settings access

 

When you click that button, the dialog in Figure 2 will show with default settings selected as shown.

Update Settings defaults

 

Figure 2: ClickOnce Update Settings Dialog

 

What you will want to do for a pure on-demand updates application is to uncheck the box that says the application should check for updates at the top. The other trick that is not apparent but required is that you have to specify an Update location at the bottom or you will get an obscure error message when you try to launch the application on the client. So you should set up the update settings like shown in Figure 3.

Modified update settings

Figure 3: ClickOnce On-Demand Update Settings

 

With those settings in place, when you publish your application out and the client launches it, they can invoke the code shown earlier to check for and apply updates on-demand.

 

If you wanted to combine on-demand updates with periodic background checking for updates, you can do that by leaving updates enabled, but you will need to select the option to check for updates after the application starts. You will then want to configure the frequency of checking using the options in the middle of the dialog.

 



.NET | ClickOnce | Languages and Tools

Thursday, September 29, 2005 10:49:06 PM (GMT Daylight Time, UTC+01:00)
Comments [0]  | 

Generating a good stored procedure CRUD Layer with CodeSmith

If you are not already using CodeSmith to avoid repetitive coding tasks, you should really take a look at it. One of the things I use it for frequently is to generate a clean stored procedure layer on top of my tables for doing standard CRUD (SELECT, INSERT, UPDATE, DELETE) operations on those tables.

Specifically, what you usually need for most tables in your database are:
- A SELECT proc that returns all rows
- A SELECT proc that takes a primary key value and returns the corresponding row
- An INSERT proc that adds a row to the table
- A DELETE proc that removes a row
- An UPDATE proc that modifies a row

I actually prefer to just have a single SELECT proc that takes a primary key parameter that defaults to NULL. If that parameter is NULL, it returns all row, otherwise it returns just the one row requested. That saves on the number of adapters/commands you have to create to do SELECTs.

In combination with these procs, you will want to add a column to your tables if at all possble that can be used for optimistic concurrency checking. You can use a datetime column that gets updated with every modification to a row, a timestamp column, or a uniqueidentifier with the rowguid property set to get it to auto-update.

If you use this pattern or want to, I wrote a CodeSmith template that will code generate all the stored procs for you. You feed it a table name and the name of the column that is used for optimistic concurrency checking. It will then generate the appropriate stored procs to ensure everything gets updated correctly based on the optimistic checking column type. You can download it here.
http://www.softinsight.com/downloads/StoredProcsForConcurrencyColumnTables.zip
Also in that zip is another template that will let you generate all the procs for all the tables in your database. It will skip any tables that do not have the concurrency column name specified, or that do not have a primary key.

This pattern also happens to work beautifully with typed data sets and table adapters in VS 2005.

As an example, if you add a Modified datetime column to the Employees table in Northwind, and set its default value to the getdate() function, you now have a good column that can be used for optimistic concurrency checking, as long as you wrap it in stored procs that update the Modified column on updates. The template I wrote generates the following code with the click of a button:

/****** Object:  Stored Procedure dbo.DeleteEmployees    Script Date: Wednesday, September 28, 2005 ******/
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[DeleteEmployees]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[DeleteEmployees]
GO

/****** Object:  Stored Procedure dbo.GetEmployees    Script Date: Wednesday, September 28, 2005 ******/
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[SelectEmployees]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[SelectEmployees]
GO

/****** Object:  Stored Procedure dbo.InsertEmployees    Script Date: Wednesday, September 28, 2005 ******/
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[InsertEmployees]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[InsertEmployees]
GO

/****** Object:  Stored Procedure dbo.UpdateEmployees    Script Date: Wednesday, September 28, 2005 ******/
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[UpdateEmployees]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[UpdateEmployees]
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS OFF
GO
------------------------------------------------------------------------------------------------------------------------
-- Date Created: Wednesday, September 28, 2005
-- Created By:   Generated by CodeSmith
------------------------------------------------------------------------------------------------------------------------

CREATE PROCEDURE dbo.DeleteEmployees
 @EmployeeID int,
 @Modified datetime
AS

DELETE FROM [dbo].[Employees]
WHERE
 
 [EmployeeID] = @EmployeeID
 AND [Modified] = @Modified
GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS OFF
GO
------------------------------------------------------------------------------------------------------------------------
-- Date Created: Wednesday, September 28, 2005
-- Created By:   Generated by CodeSmith
------------------------------------------------------------------------------------------------------------------------

CREATE PROCEDURE dbo.SelectEmployees
 @EmployeeID int = NULL
AS

 IF (@EmployeeID IS NOT NULL)
 BEGIN
  SELECT
   [EmployeeID],
   [LastName],
   [FirstName],
   [Title],
   [TitleOfCourtesy],
   [BirthDate],
   [HireDate],
   [Address],
   [City],
   [Region],
   [PostalCode],
   [Country],
   [HomePhone],
   [Extension],
   [Photo],
   [Notes],
   [ReportsTo],
   [PhotoPath],
   [Modified]
  FROM
   [dbo].[Employees]
  WHERE
   [EmployeeID] = @EmployeeID
 END
 ELSE
 BEGIN
  SELECT
   [EmployeeID],
   [LastName],
   [FirstName],
   [Title],
   [TitleOfCourtesy],
   [BirthDate],
   [HireDate],
   [Address],
   [City],
   [Region],
   [PostalCode],
   [Country],
   [HomePhone],
   [Extension],
   [Photo],
   [Notes],
   [ReportsTo],
   [PhotoPath],
   [Modified]
  FROM
   [dbo].[Employees]
 END

GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

------------------------------------------------------------------------------------------------------------------------
-- Date Created: Wednesday, September 28, 2005
-- Created By:   Generated by CodeSmith
------------------------------------------------------------------------------------------------------------------------

CREATE PROCEDURE dbo.InsertEmployees
 @LastName nvarchar(20),
 @FirstName nvarchar(10),
 @Title nvarchar(30),
 @TitleOfCourtesy nvarchar(25),
 @BirthDate datetime,
 @HireDate datetime,
 @Address nvarchar(60),
 @City nvarchar(15),
 @Region nvarchar(15),
 @PostalCode nvarchar(10),
 @Country nvarchar(15),
 @HomePhone nvarchar(24),
 @Extension nvarchar(4),
 @Photo image,
 @Notes ntext,
 @ReportsTo int,
 @PhotoPath nvarchar(255),
 @Modified datetime OUTPUT,
 @EmployeeID int OUTPUT
AS

SET @Modified=getdate()
INSERT INTO [dbo].[Employees] (
 [LastName],
 [FirstName],
 [Title],
 [TitleOfCourtesy],
 [BirthDate],
 [HireDate],
 [Address],
 [City],
 [Region],
 [PostalCode],
 [Country],
 [HomePhone],
 [Extension],
 [Photo],
 [Notes],
 [ReportsTo],
 [PhotoPath],
 [Modified]
) VALUES (
 @LastName,
 @FirstName,
 @Title,
 @TitleOfCourtesy,
 @BirthDate,
 @HireDate,
 @Address,
 @City,
 @Region,
 @PostalCode,
 @Country,
 @HomePhone,
 @Extension,
 @Photo,
 @Notes,
 @ReportsTo,
 @PhotoPath,
 @Modified
)
SET @EmployeeID = @@IDENTITY

 

GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO
SET ANSI_NULLS OFF
GO
------------------------------------------------------------------------------------------------------------------------
-- Date Created: Wednesday, September 28, 2005
-- Created By:   Generated by CodeSmith
------------------------------------------------------------------------------------------------------------------------

CREATE PROCEDURE dbo.UpdateEmployees
  @EmployeeID int,
  @LastName nvarchar(20),
  @FirstName nvarchar(10),
  @Title nvarchar(30),
  @TitleOfCourtesy nvarchar(25),
  @BirthDate datetime,
  @HireDate datetime,
  @Address nvarchar(60),
  @City nvarchar(15),
  @Region nvarchar(15),
  @PostalCode nvarchar(10),
  @Country nvarchar(15),
  @HomePhone nvarchar(24),
  @Extension nvarchar(4),
  @Photo image,
  @Notes ntext,
  @ReportsTo int,
  @PhotoPath nvarchar(255),
  @Modified datetime OUTPUT
AS
DECLARE @CurrentModified DateTime
 SET @CurrentModified = getdate()
UPDATE [dbo].[Employees] SET
 [LastName] = @LastName,[FirstName] = @FirstName,[Title] = @Title,[TitleOfCourtesy] = @TitleOfCourtesy,[BirthDate] = @BirthDate,[HireDate] = @HireDate,[Address] = @Address,[City] = @City,[Region] = @Region,[PostalCode] = @PostalCode,[Country] = @Country,[HomePhone] = @HomePhone,[Extension] = @Extension,[Photo] = @Photo,[Notes] = @Notes,[ReportsTo] = @ReportsTo,[PhotoPath] = @PhotoPath,[Modified] = @CurrentModified
WHERE
 [EmployeeID] = @EmployeeID
 AND [Modified] = @Modified

SET @Modified = @CurrentModified
GO

SET QUOTED_IDENTIFIER OFF
GO
SET ANSI_NULLS ON
GO


 



Community | Languages and Tools

Thursday, September 29, 2005 10:44:02 PM (GMT Daylight Time, UTC+01:00)
Comments [5]  | 

Smart Client Deployment with ClickOnce talks in St. Louis and KC

I gave a talk on ClickOnce in both St. Louis and Kansas City Monday and Tuesday evening this week and had a really good time. After the St. Louis talk I was able to go out for a beer with Bill Evjan, Scott Spradlin, and some of the other group members, which is always a great chance to network while I am there. KC was more of a quick strike since I had to fly out first thing in the morning to head to the MVP summit in Seattle.

The code samples and slides can be downloaded here.



.NET | ClickOnce | Community | Languages and Tools | Speaking | Travel

Thursday, September 29, 2005 10:42:26 PM (GMT Daylight Time, UTC+01:00)
Comments [2]  | 


 # Friday, September 23, 2005

Major headbanging fix

Apparently Ten Thousand Fists released about a week ago. I happened upon it tonight as I was doing some coding to maximum volume Disturbed and it occurred to me they were due for a new album. Listening to it and burning from Rhapsody now.

This album F*&cking rocks. Period. First impressions: ((((The Sickness + Believe)++)++)++)++

Yes, my dirty little secret about music preferences is out. Yes, this is the same guy who blogged about Bond a while back. Look, my first album was Black Sabbath, my first concert was Kiss, Ted Nugent, and Montrose at Anaheim stadium at the tender young age of 11 in 1976. Lets just say my earliest music influences were HEAVY METAL. But I do have disparate tastes that span just about anything depending on mood other than country and opera.

But when I need to code or work out, rock is where I live.

Or, in the case of favorite bands like Distrubed, Limp Bizkit, Rage Against the Machine, Linkin Park, etc., it would be "Angry music" as my wife calls it. :)



Community

Friday, September 23, 2005 6:20:53 AM (GMT Daylight Time, UTC+01:00)
Comments [0]  | 

Nice little SOA podcast

My associate Michele is part of a panel discussion podcast that Microsoft is doing on Service Oriented Architecture that will be ongoing over a period of time. The first one just hit the streets and is great. Check it out here.

Very nice little discussion of what matters.



.NET | Architecture

Friday, September 23, 2005 6:05:52 AM (GMT Daylight Time, UTC+01:00)
Comments [0]  | 


 # Wednesday, September 14, 2005

System.Transactions and connection pooling

Update: I had put this post up yesterday and then accidentally deleted it while trying to do an update due to a technical inaccuracy. Sorry if you get a duplicate post, if so use this one. The technical inaccuracy had to do with the section describing when the transaction is promoted to the DTC.

---------------

One of the reviewers of my Data Binding in Windows Forms 2.0 book raised a question regarding System.Transactions and connection pooling that is important to understand.  I didn’t go into detail on this issue in the book (because it is not a data access book, it is a data binding book… you know completely different layers, hopefully separated by a business layer…).

 

The discussion centered around the following code snippet:

public void ExecuteTransaction()

{

   CustomersTableAdapter adapter = new CustomersTableAdapter();

   // Start the transaction

   using (TransactionScope scope = new TransactionScope())

   {

      CustomersDataSet.CustomersDataTable customers;

      customers = adapter.GetData();  // First query

      customers[0].Phone = "030-0074321";

      adapter.Update(customers);      // Second query

      scope.Complete();               // Commits the transaction

   }

}

 

The code above opens a transaction scope and performs two queries within that scope. The call to Complete on the scope votes to commit the transaction. If there is no ambient transaction when this call is executed, a new transaction will be created when the scope is constructed. If there is an ambient transaction, this scope will be enlisted in the ambient transaction, and will not be committed until its enclosing transaction is committed.

 

The misunderstanding was that the reviewer thought that in order to scope the transaction across two queries (GetData and Update on the table adapter), Microsoft had implemented it by keeping the connection open, which would be a bad thing from a connection pooling perspective.

 

<UpdatedSection> 

The truth is that transactions in System.Transactions have scope greater than a single connection. If the queries run against a single connection to a SQL Server 2005 database, then a lightweight transaction will scope the queries through that connection. If the queries run against more than one connection instance (as is likely with the code above or most multi-query scenarios) or any other resource manager other than 2005 at this point (i.e. SQL 2000, Oracle, MSMQ, etc.), then the transaction will auto-promote to a DTC transaction, which can obviously scope multiple connections.

 </UpdatedSection>

 

In the code above, the connection is opened and closed automatically by the data adapter that is encapsulated in the table adapter for each of the calls (GetData and Update) in the same way as in .NET 1.1, regardless of the target database. The close doesn’t really close the connection, it puts it back on the connection pool. The transaction scope is greater than each connection and can span both connections, <UpdatedSection> after promotion to the DTC</UpdatedSection>.

 

In case you want to see it in action, you can download a simple sample here that demonstrates this fact. This sample makes similar calls to the above, but adds a partial class extenstion and method to the table adapter allowing you to check the connection state in between calls:

namespace SystemTransactionsSample.CustomersDataSetTableAdapters

{

   public partial class CustomersTableAdapter

   {

      public bool IsConnectionOpen()

      {

         return Connection.State == System.Data.ConnectionState.Open;

      }

   }

}

 

If you insert a call to IsConnectionOpen on the adapter between GetData and Update, you will see that the connection is in fact in a closed state between calls, even though the transaction is alive and uncommitted.

 



.NET | Languages and Tools

Wednesday, September 14, 2005 5:53:15 PM (GMT Daylight Time, UTC+01:00)
Comments [0]  | 


 # Monday, September 5, 2005

Wow! DFW no longer sucks!

I'm on my way out to the west coast for some work with a customer and to attend a VSTS SDR prior to PDC and connected through DFW (Dallas Fort Worth) today. This is like a whole new airport since the last time I connected through here.

I always liked one thing and one thing only about DFW before - it is one of the only airports in the country where Pepsi has the primary soda contract. Anyone who knows me knows you will rarely see me without a bottle of Diet Pepsi nearby. I can tolerate Diet Coke, but greatly prefer Diet Pepsi.

However, even my Diet Pepsi addiction was not enough to make me like the DFW of days past. You inevitably had to do a marathon walk or take their clunky slow tram to get from one end of the all-to-linear airport to the other, and the hallways were crowded and narrow. The past couple of years of connecting through here has seen lots of construction going on with the promise of something better, but I didn't believe it until I saw it. The major improvement is the Skyway, which connects all the terminals with a well designed high speed elevated rail system. Now when you come in on one end of terminal A and have to go to the far end of terminal C, you don't have to don a pair of marathon shoes and you will be there in a few minutes. The hallways are all expanded out, and seem even more so because of the lower foot traffic resulting from the skyway. To add ]icing to the cake, they have nice little laptop lounges with desks, power outlets, and Herman Miller chairs by the escalators for the Skyways, so there is a great place to camp out and get a little work done between flights.

If you avoided DFW as a connection in the past as I did, you might want to give it another try.



Travel

Monday, September 5, 2005 4:07:06 PM (GMT Daylight Time, UTC+01:00)
Comments [0]  | 


















May, 2013 (2)
April, 2013 (2)
March, 2013 (2)
February, 2013 (2)
January, 2013 (2)
December, 2012 (3)
November, 2012 (1)
October, 2012 (1)
August, 2012 (2)
June, 2012 (2)
May, 2012 (3)
April, 2012 (1)
March, 2012 (2)
February, 2012 (2)
January, 2012 (1)
November, 2011 (4)
October, 2011 (1)
September, 2011 (2)
August, 2011 (1)
July, 2011 (1)
May, 2011 (5)
March, 2011 (4)
February, 2011 (2)
January, 2011 (3)
November, 2010 (4)
October, 2010 (1)
September, 2010 (5)
August, 2010 (5)
July, 2010 (6)
June, 2010 (8)
May, 2010 (2)
April, 2010 (2)
January, 2010 (1)
December, 2009 (3)
November, 2009 (2)
October, 2009 (3)
September, 2009 (3)
August, 2009 (2)
July, 2009 (3)
May, 2009 (3)
April, 2009 (2)
March, 2009 (1)
February, 2009 (2)
January, 2009 (2)
December, 2008 (1)
November, 2008 (2)
October, 2008 (5)
September, 2008 (4)
August, 2008 (2)
July, 2008 (1)
June, 2008 (2)
May, 2008 (2)
April, 2008 (3)
February, 2008 (6)
January, 2008 (3)
December, 2007 (1)
November, 2007 (1)
October, 2007 (5)
September, 2007 (1)
July, 2007 (3)
June, 2007 (8)
April, 2007 (2)
March, 2007 (4)
February, 2007 (1)
December, 2006 (2)
November, 2006 (9)
October, 2006 (5)
September, 2006 (3)
August, 2006 (2)
July, 2006 (4)
June, 2006 (5)
May, 2006 (10)
April, 2006 (4)
March, 2006 (2)
February, 2006 (12)
January, 2006 (7)
December, 2005 (2)
November, 2005 (15)
October, 2005 (6)
September, 2005 (7)
August, 2005 (3)
July, 2005 (10)
June, 2005 (11)
May, 2005 (7)
April, 2005 (8)
March, 2005 (6)
February, 2005 (2)
January, 2005 (6)
December, 2004 (3)
November, 2004 (5)
October, 2004 (2)
September, 2004 (5)
August, 2004 (13)
July, 2004 (6)
June, 2004 (14)
May, 2004 (17)
April, 2004 (12)
March, 2004 (8)
February, 2004 (10)
January, 2004 (14)
December, 2003 (9)
November, 2003 (13)
October, 2003 (3)

Sign In
Copyright © 2006-2012 Brian Noyes. All rights reserved.

designed by NUKEATION STUDIOS