ClickOnce and the App.Config file

ClickOnce and the App.Config file

Old forum URL: forums.lhotka.net/forums/t/370.aspx


kdubious posted on Wednesday, June 14, 2006

Anyone have a solution for this:

My client has 4 different DB's and they use the same desktop application to connect to each.  They also use ClickOnce deployment.  To do this, since the app.config is sigend, they have to publish 4 different installation points.

CONFESSION: I have NOT yet bought the 2.0 Book.

Have we moved beyond the connection string / portal wiring in the app.config?  Can a user change the PortalServer or the Connection String at runtime yet?  I'm more interested in being able to change the PortalServer at runtime I guess.

Help?

Kevin

ajj3085 replied on Wednesday, June 14, 2006

Kevin,

I'll likely need similar functionality as well.  At some point I'll need a method to 'disconnect' my application, and have it start using a local database instead of a remote one. 

I solved a similar problem though; I have different settings for development, unit testing, user testing, and production.  I ended up creating my own ConfigurationManager class with its own AppSettings property.

The trick is that I define a section for each configuration (<production />, <testing />, etc) and my configuratin manager at a special setting to figure out which one to use... so my config file looks something like this:

<configuration>

    <configSections>
        <section name="localDev" type="System.Configuration.AppSettingsSectionHandler,System.Configuration"/>
        <section name="production" type="System.Configuration.AppSettingsSectionHandler,System.Configuration/>
    </configSections>

    <appSettings>
        <add key="environment" value="localDev" />
    </appSettings>

    <localDev>
          <add key="key" value="value" />
    </localDev>
   
    <devel>
          <add key="key" value="value23" />
    </devel>

</configuration>

I actually have my own section handler which I wrote as well, to support more sane configuration of the DAL.  But that's not required.

Maybe you could do something similar, except have your ConfigurationManager look at the environment and automatically determine which group of settings to use.

Or maybe I'm way off...

HTH
Andy

ajj3085 replied on Wednesday, June 14, 2006

Oh, one more thing.  If you do go this route, don't forget to remove the reference to System.Configuration in Csla and replace it with the reference to your assembly which contains your configuration manager.  You'll also have to update any using / Imports statements... but this is only in a few places.


kdubious replied on Wednesday, June 14, 2006

I was hoping to do something like this:

Add a Database class to my BO library with a Shared Property that returns the connection string. In ALL (ugly) of my Criteria ctor's, I'd set a property in my Criteria object to hold the connection string.  Then, when the DataPortal (remote in my case) looks for the connection string, it would use the value in the Criteria object.

I need to use ClickOnce, which means I can't edit the app.config after deployment to point to another DataPortal URL. And, I need a way in the UI to specify which Database at the remote host to use.

I don't think ajj3085's solution works in my case.

Kevin

ajj3085 replied on Wednesday, June 14, 2006

Nothing I've said means you have to change the config file once its signed.

At any rate, how does your database BO figure out which connection string to use?  That's pretty important to finding a solution.

You meantion that you haven't bought the 2.0 book, but are you using the 2.0 clsa framework?  If so, is there any reason you can't use the ApplicationContext to send this information along with your dataportal call?  That way you avoid having to put all that code in your Criteria classes.

Andy

kdubious replied on Wednesday, June 14, 2006

OK, I need to buy the book.  I'm using the 2.0 in new apps, fumbling through the new features, really.

Maybe if I pose the question in terms of an example, ajj3085 or someone cold pose a solution:

Let's say you have a project tracker application that you host for multiple companies.  You opt to NOT store each company in the same Database, you instead have a new Database for each one.  You're using the remote dataportal.

You have a tech support staff, and they need the option of switching between diferent databases on the fly, from within the same installation. (Publishing a new ClickOnce installer, and forcing a new install, just to get the app to point to a new DataPortal, just so the new DataPortal can use a new Connection String is messy)  You can't point the app.config to a new DataPortal URL on the fly (right??), but you can put multiple Connection Strings in the DataPortal's web.config file.

The question becomes, how do I best (from the front end) switch between the various Connection Strings in the web.config? 

I hope ApplicationContext hlolds the answer, but I'll have to read up on it...

I went to Borders, and alas, No Book.

Kevin

ajj3085 replied on Thursday, June 15, 2006

Ok, now I see where you are trying to go.  When a client runs your application, it should use a connection string to a certain db.  Your tech support should be able to run the SAME application, but pick between the connection strings.

This is a tricky one, whether or not you use Csla at all.  Right off the bat, if your client application sends some data over the wire to tell the server which database it should use, you open up a potential security hole.  If your tech team can use the client to switch, so could a malitious end user.  If you're not obfucsating your code, it would be trivial for anyone using something like Reflector to figure what data to send to modify someone else's DB.  Even if you obfuscate, this is still a danger, as someone really commited to figuring it out would, eventually. 

Sorry if you've already reached these conclusions, but I wanted to point them out just in case...

That said, certainly if your application on the client can decide which database to use, you can use ApplicationContext to just pass some kind of identifier to the dataportal.   You would probably have some kind of login procedure.  You could create a CommandBase subclass to actually perform a login.  The command would have some fields so that you can send the user / password to the server, and some other fields which would be set when the command returns.  One of those fields could be a list of Databases (likely these would be busienss objects) which the user is allowed to connect to.  You can store the entire list in ApplicationContext for convience, and store the currently selected one there as well, which code in your DataPortal_XXX procedures would use to determine the connection string to use.

Here's code I use to create a session (using WindowsPrincipal).  You could pass a user name / password instead of a WindowsIdentity (although you'd likely return a principal subclassed from PrincipalBase in Csla).

        #region OpenSessionCommand class

        /// <summary>A command which opens
        /// a session (login) on the datastore.</summary>
        /// <remarks>This class is embedded in my Session static class</remarks>
        [Serializable]
        private class OpenSessionCmd : CommandBase {
            #region Fields

            private WindowsIdentity ident;
            private int dbUserId;

            #endregion

            #region Properties

            /// <summary>The database id of the
            /// user.</summary>
            /// <remarks>This value is only valid
            /// after the command has executed.</remarks>
            public int DbUserId {
                get { return dbUserId; }
                private set { dbUserId = value; }
            }

            #endregion

            #region Constructors

            /// <summary>Initializes a new <see cref="OpenSessionCmd"/>.</summary>
            /// <param name="ident">The <see cref="WindowsIdentity"/>
            /// of the user for which to open the session.</param>
            public OpenSessionCmd( WindowsIdentity ident ) {
                this.ident = ident;
            }

            #endregion

            #region Data Access

            /// <summary>Performs the command.</summary>
            [Transactional( TransactionalTypes.TransactionScope )]
            protected override void DataPortal_Execute() {
                DataSearcher<Data.Shared.Employee> searcher;
                List<Data.Shared.Employee> employees;
                SelectionCriteria crit;
                DirectoryUser dUser;
                int employeeId;
                Data.Shared.Employee e;

                // Create this object to manage the connection
                e = new Data.Shared.Employee();

                try {
                    e.Provider.BeginBatch();

                    crit = new SelectionCriteria( "LoginName", Operator.Equals );
                    crit.Values.Add( ident.Name );

                    searcher = new DataSearcher<Data.Shared.Employee>();
                    searcher.AddSelectionCriteria( crit );
                    employees = searcher.Find();

                    if ( employees.Count == 1 ) {
                        employeeId = employees[ 0 ].PersonId.Value;
                    }
                    else {
                        dUser = DirectoryUser.NewUser( ident );
                        dUser.CreateDBEntry();
                        employeeId = dUser.PersonId;
                    }

                    DbUserId = employeeId;
                }
                finally {
                    e.Provider.EndBatch();
                }
            }

            #endregion
        }

        #endregion

The SelectionCriteria, DirectorUser and Data.Shared.* classes are helper classes or part of my DAL.  You could do whatever work you need to in the DataPortal_Execute method, which would be where you can load the list of DBs which the user may access.

My login procedure uses the command like this:

            openSessionCmd = DataPortal.Execute<OpenSessionCmd>(
                new OpenSessionCmd( WindowsIdentity.GetCurrent() )
            );

Once this executes successfully, I read out DbUserId and stuff it in the application context.

This won't do what you want it to, but you can use the same pattern here to acomplish what you need.

HTH
Andy

kdubious replied on Thursday, June 15, 2006

Thanks a bunch!  I bought the book, and will look at it and your example and see what I can do.

odie replied on Thursday, June 15, 2006

We have a similare situation where we have 3 Regions each with their own seperate database due to distance between locations. Additionally we have a development copy of the database for our own testing.

It may not be eligant but it works.

I have the following in my app.config file because until I get the connection string I can't get to a server.
<add key="ConfigRegionCount" value="3" />
<
add key="ConfigRegion1" value="Calgary" />
<
add key="ConfigRegion2" value="Edmonton" />
<
add key="ConfigRegion3" value="Regina" />

On my login form the user selects the region they wish to log into. Additionally I have a check box option for development database, but I wont go into that here.

Then based on which region they select I know which encrypted connection string to get from the database which looks like this.

<add key="DB:Calgary" value="bbNSKIAS8yO7...=" />
<
add key="DB:Edmonton" value="j2X70krzexwuHFjNX...="
/>
<
add key="DB:Regina" value="HFjNXLK5MeJ0GN3K.."
/>
<
add key="DB:Development" value="Qs7FXSVjM2k/uETt3cn5qnC6g..." />

This allows for any number of connection strings, if you have to add another you only have to add tow lines to your app.config file. Based on the region selected it knows what connection string to use. If the user wants to switch database they simply logoff which brings up the logon screen again and they select a different database. I store the users last logged on region to start with as the user is likely logging into the same database all the time. I don't have to worry about users logging into the wrong database as this is cared for by the security model we have implemented.

Additionally the connections string is encrypted multiple times embedding both the data database connection string as well as the security database connection string together. But that another story.

Copyright (c) Marimer LLC