Implementing a Custom ClickOnce Deployment Server File Repository

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies.
This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.
To create client business applications using current Microsoft technologies, see patterns & practices' Prism.

This topic describes how to create a custom deployment file repository that can be used to store the files in a database rather than storing the files on a server in the usual way.

Note

To create this example, we used Visual Studio 2010 and SQL Server 2008 Developer Edition running on Windows 7 with Internet Information Services (IIS) 7.5.

Steps

To create and configure a custom ClickOnce repository, you will complete the following steps:

  1. Set up the basic structure and projects in the Visual Studio solution.
  2. Define the base repository provider project.
  3. Implement a custom HTTP deployment repository handler.
  4. Define a SQL Server file repository database.
  5. Add a typed data set data access layer to the SQL file repository provider.
  6. Implement the SQL file repository provider.
  7. Configure the Web site to host the handler and provider.
  8. Deploy the Web site to the Web server.
  9. Create a utility to populate the database table with ClickOnce application files.
  10. Create a Windows-based application to deploy.
  11. Use the administrative application to read the published files into the database.
  12. Test the deployment.

Step 1: Creating the Solution

In this step, you use Visual Studio to create a solution and set up the basic framework of the projects and references.

To create the Visual Studio solution

  1. Start Visual Studio as an administrator.

  2. On the File menu, click New, and then click Project.

  3. On the left pane, expand Other Project Types, select Visual Studio Solutions, and then, in the middle pane, select Blank Solution (see Figure 1).

    Ff699275.a585aa23-4886-4c2c-b3a5-b1305e858541(en-us,PandP.10).png

    Figure 1

    Creating a blank solution

  4. In the Name text box, type ClickOnceDeploymentRepository, and then click OK.

  5. Select the solution, right-click, select Add, and then select New Project.

  6. In the center pane, select Class Library as the project type. Type DeploymentRepositoryProvider in the Name text box, and then click OK (see Figure 2).

    Ff699275.6ad95577-b8a7-4daf-bd0f-2249d1988a42(en-us,PandP.10).png

    Figure 2

    Adding the DeploymentRepositoryProvider project

  7. Select the solution, right-click, select Add, and then select New Project.

  8. In the center pane, select ASP.NET Web Application as the project type. Type DeploymentSite in the Name text box, and then click OK (see Figure 3).

    Ff699275.180cb537-e861-4053-b4ff-30f4b1aca28b(en-us,PandP.10).png

  9. Select the solution, right-click, select Add, and then select New Project.

  10. In the center pane, select Class Library as the project type. Type SqlRepositoryProviderLib in the Name text box, and then click OK. (see Figure 4)

    Ff699275.d3396edb-6bf4-466e-9b39-df91b62ccf70(en-us,PandP.10).png

    Figure 4

    Add SqlRepositoryProviderLib class library

  11. In the DeploymentSite project, add references to both the DeploymentRepositoryProvider and SqlRepositoryProviderLib projects.

  12. In the SqlRepositoryProviderLib project, add a reference to the DeploymentRepositoryProvider project.

  13. In the DeploymentRepositoryProvider project, add references to the System.Configuration and System.Web.

Step 2: Setting up the Base Deployment Repository Provider

After you create the solution and projects, define the repository interface and then implement the repository factory.

To define the repository provider interface

  1. In the DeploymentRepositoryProvider class library, delete Class1.cs.
  2. Add an interface definition to the DeploymentRepositoryProvider class library:
    1. Right-click the DeploymentRepositoryProvider project, select Add, select New Item, and then select Interface.

    2. Type IDeploymentRepository in the Name text box, and then click OK.

    3. Add the following code.

      public interface IDeploymentRepository
      {
         byte[] GetFile(string filePath);
      }
      

To implement a deployment repository provider factory

  1. In Solution Explorer, open the DeploymentRepositoryProvider project, double-click Properties to open the property pages, and then select the Settings tab.

  2. Click the link to add a Settings file.

  3. Add an application-scoped setting of type string, and name it DeploymentRepositoryProvider.

  4. To create the appropriate schema in the class library project's App.config file, set the value of the DeploymentRepositoryProvider to SampleProvider.

  5. Click Save to save the new setting.

  6. Add a class named DeploymentRepositoryProviderFactory to the class library.

  7. Add the following using statements to the top of the file.

    using DeploymentRepositoryProvider.Properties;
    using System.Configuration;
    using System.Reflection;
    
  8. Modify the DeploymentRepositoryProviderFactory class to match the following code.

    public static class DeploymentRepositoryProviderFactory
    {
       public static IDeploymentRepository CreateInstance()
       {
          string assemblyName;
          string typeName;
          GetTypeInfo(Settings.Default.DeploymentRepositoryProvider, 
             out assemblyName, out typeName);
          if (string.IsNullOrEmpty(assemblyName) || 
            string.IsNullOrEmpty(typeName))
          {
             throw new ConfigurationErrorsException(
                "Invalid type information in configuration for provider.");
          }
          // This code dynamically loads the assembly using normal
          // resolution.
          Assembly assem = Assembly.Load(assemblyName);
          if (assem == null)
          {
             throw new ConfigurationErrorsException(
                String.Format("Could not load assembly {0}.",  
                assemblyName));
          }
          // This code creates the dynamic instance.
          IDeploymentRepository instance = 
             assem.CreateInstance(typeName) as IDeploymentRepository;
          return instance;
       }
    
       // This code is the Helper method to extract the type 
       // information from a string. It expects
       // the following format:
       // Fully.Qualified.TypeName, AssemblyName
       private static void GetTypeInfo(string componentTypeInfo, 
          out string assemblyName, out string typeName)
       {
          assemblyName = null;
          typeName = null;
          // Configuration entries should be split into 
          // two parts and in the following form:
          // Fully.Qualified.TypeName, AssemblyName
          string[] typeInfo = componentTypeInfo.Split(',');
          if (typeInfo == null || typeInfo.Length < 2)
          {
             return; // invalid type info, ignore
          }
          assemblyName = typeInfo[1];
          typeName = typeInfo[0];
          if (string.IsNullOrEmpty(assemblyName) ||
              string.IsNullOrEmpty(typeName))
          {
             return; // invalid type info, ignore
          }
          assemblyName = assemblyName.Trim();
          typeName = typeName.Trim();
       }
    }
    

Step 3: Implementing a Custom HTTP Deployment Repository Handler

To implement a custom HTTP deployment repository handler

  1. Add a class named DeploymentRepositoryHandler to the DeploymentRepositoryProvider project.

  2. Add the following using statements to the top of the class file for the System.Web and System.IO namespaces.

    using System.Web;
    using System.IO;
    
  3. Modify the class definition to match the following code.

    public class DeploymentRepositoryHandler : IHttpHandler
    {
       public bool IsReusable
       {
          get { return true; }
       }
    
       public void ProcessRequest(HttpContext context)
       {
          string requestPath = context.Request.Url.ToString();
          string rootPath = context.Request.ApplicationPath;
          requestPath = requestPath.Substring(
             requestPath.IndexOf(rootPath) + rootPath.Length + 1);
          requestPath = requestPath.Replace('/', '\\');
          IDeploymentRepository provider = 
             DeploymentRepositoryProviderFactory.CreateInstance();
          byte[] bits = provider.GetFile(requestPath);
          context.Response.OutputStream.Write(bits,0,bits.Length);
          switch (Path.GetExtension(requestPath).ToLower())
          {
             case “.application” :
                 context.Response.ContentType = 
                   "application/x-ms-application";
                 break;
             case “.manifest” :
                 context.Response.ContentType = "application/x-ms-manifest";
                 break;
             case “.deploy” :
                 context.Response.ContentType = "application/octet-stream";
                 break;
          }
       }
    }
    

Step 4: Defining the SQL Server Repository Database

To define a SQL Server file repository database

  1. Start SQL Server Management Studio, and open SQL script ClickOnceFileRepository_CreateDatabase.sql. This script creates the database. Check the file path for the database and transaction log in the CREATE DATABASE script, and set the path to the location of your other SQL Server database files. Execute the script.
  2. In SQL Server Management Studio, open SQL script ClickOnceFileRepository_CreateTablesEtc.sql. This script creates the tables, stored procedures, and so on. Execute the script.
  3. After you create the ClickOnceFileRepository database, complete the following required steps:
    1. Add the user account used by the Application Pool assigned to the Web site in IIS as a valid login to SQL Server 2008.
    2. Under User Mapping, grant the user account db_owner permission to the ClickOnceFileRepository database.

Step 5: Adding a Data Access Layer to the Repository Provider

To add a typed data set data access layer to the SQL file repository provider

  1. Add a typed data set definition to the SqlRepositoryProviderLib project. To do this:

    1. Right-click, select Add, select New Item, and then select DataSet.
    2. Type FileRepositoryDataSet in the Name text box, and then click OK.
  2. Open Server Explorer, and add a data connection to the ClickOnceFileRepository database.

  3. In Server Explorer, expand the data connection ClickOnceFileRepository.

  4. Expand Stored Procedures.

  5. Drag the SelectFiles stored procedure onto the design surface.

  6. In the designer, select the name in the title bar of the SelectFiles data table definition, and then change that name to Files.

  7. At the bottom of the table definition, change the name of the table adapter to FilesTableAdapter (see Figure 5).

    Ff699275.12749b1d-9555-4d57-adaa-34a6c3c49f30(en-us,PandP.10).png

    Figure 5

    FileRepositoryDataSet

  8. Right-click FilesTableAdapter, and then click Add Query. This starts the TableAdapter Query Configuration wizard.

  9. In the wizard, click Use existing stored procedure, and then click Next (see Figure 6).

    Ff699275.a32ed501-6a30-4c10-a46d-75e03ac6a766(en-us,PandP.10).png

    Figure 6

    First Page of the Table Adapter Query Configuration wizard

  10. On the next page of the wizard, click GetFileBitsByPath in the Select the stored procedure to call drop-down list box, and then click Next (see Figure 7).

    Ff699275.3b450f4d-c5af-4813-97d0-da4af9b55c58(en-us,PandP.10).png

    Figure 7

    Selecting the stored procedure

  11. On the next page of the wizard, click A single value, and then click Next (see Figure 8). This option determines whether the data access method generated should return a data table, a single value, or no return value.

    Ff699275.ba2b6dec-6d8c-46dc-a519-3f7400c74b14(en-us,PandP.10).png

    Figure 8

    Selecting the shape of the stored procedure data returned

  12. On the last page of the wizard, accept the default name for the generated data access method, and then click Finish (see Figure9).

    Ff699275.030f05be-c824-4397-b5fd-d269bedaf129(en-us,PandP.10).png

    Figure 9

    Specify the generated method name

Step 6: Implementing the SQL File Repository Provider

To implement the SQL file repository provider

  1. In the SqlFileRepositoryProviderLib class library project, change the file name of Class1.cs to SqlFileRepositoryProvider.cs. When the prompt to rename the class appears, accept it.

  2. Add the following using statement to the top of the file.

    using DeploymentRepositoryProvider;
    
  3. Change the class definition to match the following code.

    public class SqlFileRepositoryProvider : IDeploymentRepository
    {
       byte[] IDeploymentRepository.GetFile(string filePath)
       {
          FileRepositoryDataSetTableAdapters.FilesTableAdapter adapter = new FileRepositoryDataSetTableAdapters.FilesTableAdapter();
          return adapter.GetFileBitsByPath(filePath) as byte[];
       }
    }
    

Step 7: Configuring the Web Site to Host the Handler and Provider

To configure the Web site to host the handler and provider

  1. Open the Web.config file for the DeploymentSite Web application.

  2. In the DeploymentRepositoryProvider class library project, open the App.config file.

  3. Add all the configuration elements from the App.config file in the DeploymentRepositoryProvider class library to the Web.config file just under the configuration root element. (See the following example.)

    <configSections>
            <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
                <section name="DeploymentRepositoryProvider.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
            </sectionGroup>
        </configSections>
        <applicationSettings>
            <DeploymentRepositoryProvider.Properties.Settings>
                <setting name="DeploymentRepositoryProvider" serializeAs="String">
                    <value>SampleProvider</value>
                </setting>
            </DeploymentRepositoryProvider.Properties.Settings>
        </applicationSettings>
    
  4. In the value tag, change SampleProvider to SqlRepositoryProviderLib.SqlFileRepositoryProvider, SqlRepositoryProviderLib.

    Note

    Note: This entry is case-sensitive; therefore, type it exactly as shown.

  5. In the system.webServer element, under the modules element, add the following element for handlers.

        <handlers>
          <add name="DeploymentSite_application" path="*.application"
               type="DeploymentRepositoryProvider.DeploymentRepositoryHandler, DeploymentRepositoryProvider" verb="*"/>
          <add name="DeploymentSite_manifest" path="*.manifest"
               type="DeploymentRepositoryProvider.DeploymentRepositoryHandler, DeploymentRepositoryProvider" verb="*"/>
          <add name="DeploymentSite_deploy" path="*.deploy"
               type="DeploymentRepositoryProvider.DeploymentRepositoryHandler, DeploymentRepositoryProvider" verb="*"/>
        </handlers>
    
  6. In the Web.config file, replace the <connectionStrings/> element with the one from the SqlRepositoryProviderLib app.config file, as shown in the following example. Save your changes.

      <connectionStrings>
        <add name="SqlRespositoryProviderLib.Properties.Settings.ClickOnceFileRepositoryConnectionString"
        connectionString="Data Source=MVPDEVPC;Initial Catalog=ClickOnceFileRepository;Integrated Security=True"
        providerName="System.Data.SqlClient" />
      </connectionStrings>
    

Step 8: Deploying the Web Site

Deploy the Web site to the Web server

  1. Select the DeploymentSite project, right-click, and then select Publish.

  2. In the Publish Profile text box, type DeploymentSite (see Figure 10).

    Ff699275.1f55193b-f300-4cf3-b04a-83486de2c892(en-us,PandP.10).png

    Figure 10

    Publishing the Web application

  3. In the Publish method drop-down list, select File System.

  4. In the Target Location text box, type https://localhost/DeploymentSite.

  5. Select Delete all existing files prior to publish (see Figure 11).

    Ff699275.699d425c-27d6-4e42-aa34-c530233ce950(en-us,PandP.10).png

    Figure 11

    Publishing to the Web server folder

  6. Click Save to save the publishing information, and then click Publish.

  7. If you want to verify that the Web site is running, start the IIS Manager. (Click Start, click Control Panel, click System and Security, click Administrative Tools, and then click IIS Manager.) You should see an entry for DeploymentSite under Default Web Site (see Figure◦12).

    Ff699275.2ced64a0-73b6-477d-a85c-6b323f6587ff(en-us,PandP.10).png

    Figure 12

    DeploymentSite running on the IIS server

Step 9: Populating the Database

To create a utility to populate the database table with ClickOnce application files

  1. Add a new Windows-based application project to the solution. Name the project SqlRepositoryAdmin.

  2. Add a typed data set to the project (on the Add menu, point to New Item, and then click DataSet). Name the data set FileRepositoryDataSet.

  3. Open Server Explorer, and expand the ClickOnceFileRepository database folders until you see the Files table.

  4. Drag the Files table onto the data set designer. Show the design view of the form.

  5. Open the Data Sources window (on the Data menu, click Show Data Sources).

  6. Drag the Files table onto the form to add a data-bound grid.

  7. Click the small triangle in the upper right of the grid to view the smart tag (see Figure◦13).

    Ff699275.1ed8a0e3-9eff-4a90-967d-97e372973e0e(en-us,PandP.10).png

    Figure 13

    DataGridView smart tag

  8. Click the Dock in Parent Container link.

  9. Clear the Enable Adding check box.

  10. Click the Edit Columns link.

  11. Remove the FileBits, Modified, and OriginalPath columns.

  12. Set the AutoSizeMode property of the FilePath column to Fill.

  13. Click OK to close the column editor.

  14. In the non-visual components tray at the bottom of the designer, delete filesBindingNavigator.

  15. In the Toolbox, double-click ToolStrip to add a ToolStrip to the form.

  16. Use the drop-down menu to add a button to the ToolStrip (see Figure 14).

    Ff699275.9b31f212-aa54-4029-9fd5-b6357674aab1(en-us,PandP.10).png

    Figure 14

    Adding a ToolStrip

  17. Set the DisplayStyle property to Text.

  18. Set the Text property to Add File.

  19. Add another button that has the DisplayStyle property set to Text, and the Text property set to Save.

  20. Double-click the Add File ToolStrip button to add a Click event handler to it.

  21. Add a using phrase to the code-behind for System.IO.

    using System.IO;
    
  22. Add the following code to the Add File event handler.

          private void toolStripButton1_Click(object sender, EventArgs e)
          {
             // Prompt for the file path
             OpenFileDialog ofd = new OpenFileDialog();
             ofd.Filter = "All Files(*.*)|*.*";
             if (ofd.ShowDialog() == DialogResult.OK)
             {
                string fileName = ofd.FileName;
                FileRepositoryDataSet.FilesRow newRow = 
                   fileRepositoryDataSet.Files.NewFilesRow();
                newRow.FilePath = Path.GetFileName(ofd.FileName);
                newRow.OriginalPath = ofd.FileName;
                newRow.Modified = DateTime.Now;
                byte[] bits;
                using (FileStream fstream = File.OpenRead(ofd.FileName))
                {
                   bits = new byte[fstream.Length];
                   fstream.Read(bits, 0, (int)fstream.Length);
                }
                newRow.FileBits = bits;
                fileRepositoryDataSet.Files.AddFilesRow(newRow);
             }
             ofd.Dispose();
          }
    
  23. Switch to Design view, and then double-click the Save button to add a Click handler to it.

  24. Add the following code to the Save event handler.

          private void toolStripButton2_Click(object sender, EventArgs e)
          {
             Validate();
             filesBindingSource.EndEdit();
             filesTableAdapter.Update(fileRepositoryDataSet.Files);
          }
    

Step 10: Creating an Application to Deploy

The Windows-based application that you create in this step will allow you to test the custom file repository and handler that you created.

To create a Windows-based application to deploy

  1. Add a new Windows-based application project to the solution. Name the application SomeClickOnceApp.
  2. Double-click the Properties for this project and select the Publish tab.
  3. Type https://localhost/SomeClickOnceApp in the Publishing Folder Location text box.
  4. Type https://localhost/DeploymentSite/SomeClickOnceApp in the Installation Folder URL text box.
  5. Click Options. On the Description tab, type the Publisher name and Product name.
  6. Save the changes, and click Publish Now.

Step 11: Adding the Published Files to the Database

To use the administrative application to read the published files into the database

  1. Start the SqlRepositoryAdmin application. To do this, right-click on the project, select Debug, and then select Start New Instance.

  2. Click Add File.

  3. Browse to C:\inetpub\wwwroot\SomeClickOnceApp\SomeClickOnceApp.application. Select SomeClickOnceApp.application, and then click Open.

  4. Click Add File again.

  5. Browse to C:\inetpub\wwwroot\ SomeClickOnceApp\Application Files\SomeClickOnceApp_1_0_0_0\SomeClickOnceApp.exe.manifest. Select SomeClickOnceApp.exe.manifest, and then click Open.

  6. In the grid, edit the File Path for that file. Update it to Application Files\SomeClickOnceApp_1_0_0_0\SomeClickOnceApp.exe.manifest.

  7. Click Add File again.

  8. Browse to C:\inetpub\wwwroot\SomeClickOnceApp\Application Files\SomeClickOnceApp_1_0_0_0\SomeClickOnceApp.exe.deploy. Select SomeClickOnceApp.exe.deploy, and then click Open.

  9. In the grid, edit the File Path for that file. Update it to Application Files\SomeClickOnceApp_1_0_0_0\SomeClickOnceApp.exe.deploy.

  10. Click Save. The files should have the paths shown in Figure 15.

    Ff699275.643a9da1-8546-47a7-9610-18ae096d2d4d(en-us,PandP.10).png

    Figure 15

    Paths after adding files to the SQL Repository

  11. Close the application.

Step 12: Testing the Deployment

To test the deployment

  • Open a browser instance, and then type the following address in the Address text box:
    https://localhost/DeploymentSite/SomeClickOnceApp.application.