Print Page | Close Window

Manual Custom Connection without having to leave the application

Printed From: IdeaBlade
Category: Cocktail
Forum Name: Community Forum
Forum Discription: A professional application framework using Caliburn.Micro and DevForce
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=3449
Printed Date: 09-Apr-2020 at 5:48am


Topic: Manual Custom Connection without having to leave the application
Posted By: giotis
Subject: Manual Custom Connection without having to leave the application
Date Posted: 21-May-2012 at 3:14pm
1.how we can import connections from an external file like

    connections.config

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
          <connectionStrings>
                <add name="admin_TempHireEntities" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=TempHire1;Integrated ...     
                <add name="admin_TempHireEntities" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=TempHire2;Integrated ...     .
                <add name="user_TempHireEntities"  connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=TempHire3;Integrated ...         
                <add name="user_TempHireEntities"  connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=TempHire4;Integrated  ...    
        </connectionStrings>

    </configuration>

  a)so that it reads from an external source which preferably will change outside
  b)be filtered with a value, for example only admin_ for this only user_ for that
  c)maybe a ComboBox at LoginView the final choice

2.any time after Login to choose connection without having to leave the application



Replies:
Posted By: mgood
Date Posted: 21-May-2012 at 11:14pm
Using a DataSourceExtension you can connect to different environments/DBs at runtime. That's typically what you use if the user can select different environments/DBs from a drop down during login. In Cocktail you specify the DataSourceExtension through the ConnectionOptions. You can implement an IConnectionOptionsResolver to dynamically initialize the proper ConnectionOptions based on information that the user supplies during login. More on the DataSourceExtension can be found here:

http://drc.ideablade.com/xwiki/bin/view/Documentation/create-entitymanager-datasource-extension - http://drc.ideablade.com/xwiki/bin/view/Documentation/create-entitymanager-datasource-extension

To supply the connection string information for your environments/DBs programmatically from an external file for example see the following topic. 

http://drc.ideablade.com/xwiki/bin/view/Documentation/configure-programmatically#HSpecifyingdatabaseconnectionsincode - http://drc.ideablade.com/xwiki/bin/view/Documentation/configure-programmatically#HSpecifyingdatabaseconnectionsincode

In most case you'll be implementing a custom DataSourceKeyResolver. You can find an example here:

http://drc.ideablade.com/xwiki/bin/view/Documentation/code-sample-custom-datasourcekeyresolver - http://drc.ideablade.com/xwiki/bin/view/Documentation/code-sample-custom-datasourcekeyresolver


Posted By: giotis
Date Posted: 22-May-2012 at 5:50pm
I followed the debugging steps I have found that the "EntityManagerProvider" is the
class responsible the possible initialization of selected of "ConnectionOptions".
I only have three choices Default Fake and DesignTime, whether to change something ?

Two days I trying and I still did not get anything,
I have read all the relevant issues help documentation samples examples ... nothing,
can someone help me here in the forum because my next option is to go to mediums.

Thanks


Posted By: mgood
Date Posted: 22-May-2012 at 7:20pm
Default, Fake and DesignTime are just three out-of-the-box preconfigured ConnectionOptions that cover the standard use cases. You can create as many of your own as you need. The easiest way is to clone an existing one if you only need to change one or two things. So, let's say I want to setup TempHire with a Development, Test and Production DB and I want to be able to specify at runtime, which of the three DBs I want to connect to. To keep it simple, I have hardcoded connection strings in my Web.config for each of the three environments:
  <connectionStrings>
    <add name="TempHireEntities_DEV" connectionString="..." />
    <add name="TempHireEntities_TEST" connectionString="..." />  
<
add name="TempHireEntities_PROD" connectionString="..." />   </connectionStrings>
 
The part after the underscore is the aforementioned DataSourceExtension, which lets you choose at runtime from a list of connections.
 
The first thing I do is create myself a ConnectionOptionsResolver that can produce the appropriate ConnectionOptions object for each of the environments.
 
    public class EnvironmentResolver : IConnectionOptionsResolver
    {
        public ConnectionOptions GetConnectionOptions(string name)
        {
            if (name == "Development")
                return ConnectionOptions.Default.WithDataSourceExtension("DEV").WithName("Development");
            if (name == "Test")
                return ConnectionOptions.Default.WithDataSourceExtension("TEST").WithName("Test");
            if (name == "Production")
                return ConnectionOptions.Default.WithDataSourceExtension("PROD").WithName("Production");
 
            return null;
        }
 
I simply clone ConnectionOptions.Default and change the DataSourceExtension and Name, because everything else stays the same in this case. The DataSourceExtension needs to match the part after the underscore in the connection strings above.
 
Now, when I create the EntityManagerProvider in the EntityManagerProviderFactory, I simply configure it with the correct environment.
 
        [Export]
        public IEntityManagerProvider<TempHireEntities> TempHireEntityManagerProvider
        {
            get
            {
                var provider = new EntityManagerProvider<TempHireEntities>()
                    .Configure(config => config.WithConnectionOptions("Test"));
                return provider;
            }
        }
 
Instead of hardcoding the configuration, I would of course have a drop down on my login screen to let the user choose the environment and I store the user's selection somewhere where I can grab it from the EntityManagerProviderFactory.
 
Also, don't forget to configure the authentication service with the same ConnectionOptions, so you login against the correct environment.
 
 
 


Posted By: giotis
Date Posted: 23-May-2012 at 4:06pm
many thanks  beautiful


Posted By: giotis
Date Posted: 22-Jul-2012 at 1:59pm
1. How do I switch between two or more connections?
       start app
            login (in LoginView we have one ComboListBox with Connections)
                 -  Production
                 -  Development
                
x Test
            logout
            login
                 -  Production
                 x  Development
                
- Test
            logout
       exit  

2. How configure the authentication service with the same ConnectionOptions?

3. If we dont have the connections in app.config how we can to load them? (I mean with code)



Posted By: mgood
Date Posted: 22-Jul-2012 at 2:14pm
1. Setup a global shared UserSession object or something like that where you can store the current selected connection. You can then inject the UserSession into the LoginViewModel were you set it and in the EntityManagerFactory to read it in order to configure the EntityManagerProvider.

2. Before you call login, configure the AuthenticationService with the correct ConnectionOptions.

3. By implementing a DataSourceKeyResolver. Here's an example:  http://drc.ideablade.com/xwiki/bin/view/Documentation/code-sample-custom-datasourcekeyresolver - http://drc.ideablade.com/xwiki/bin/view/Documentation/code-sample-custom-datasourcekeyresolver


Posted By: giotis
Date Posted: 22-Jul-2012 at 4:13pm
DataSourceKeyResolver run only once, how do I switch?


Posted By: mgood
Date Posted: 22-Jul-2012 at 7:39pm
I'm not sure I follow. IDataSourceKeyResolver.GetKey gets called for each unique keyName/keyExtension pair. So, if we go back to my earlier example. The first time you connect to DEV it gets called with keyName="TempHireEntities" and keyExtension="DEV". The first time you connect to TEST it gets called with keyName="TempHireEntities" and keyExtension="TEST" and so on. In each case you return the appropriate connection string to the corresponding physical DB.


Posted By: giotis
Date Posted: 28-Aug-2012 at 4:21pm
Dear Marcel so far no so good,

my steps

a. public interface IUserSession   {
        string Name { get; set; }
    }

    [Export(typeof(IUserSession)), PartCreationPolicy(CreationPolicy.Shared)]
    public class UserSession : IUserSession    {
        public UserSession()        {   }
        public string Name { get; set; }
    }

b.public class EnvironmentResolver : IConnectionOptionsResolver  {     
        public ConnectionOptions GetConnectionOptions(string name)
        {
            IUserSession userSession = Composition.GetInstance<IUserSession>(CreationPolicy.Shared);
            if (name == userSession.Name)
                return ConnectionOptions.Default.WithDataSourceExtension(
                                userSession.Name.ToUpper()).WithName(userSession.Name);
            return null;
        }
    }
c. <connectionStrings>
      <add name="SecurityEntities" connectionString=...
  </connectionStrings>

d.class CustomDataSourceKeyResolver : IDataSourceKeyResolver {
    public GetKey(string keyName, string keyExtension, bool onServer) {
      if (!onServer) {return null;}
      if (keyName != "TempHireEntities" || string.IsNullOrEmpty(keyExtension)) { return null; }
      String connectionString = "";
      if (keyExtension == "PROD")   {
          connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=TempHire_Production; ...
      }
      if (keyExtension == "TEST")    {
          connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=TempHire_Test; ...
      }

      if(string.IsNullOrEmpty(connectionString ) { return null; }

      // Now build and return a "ClientEdmKey". 
      string name = string.IsNullOrEmpty(keyExtension) ? keyName : keyName + "_" + keyExtension;
      return new ClientEdmKey(name, connectionString, false, "FromResolver");
    }
  }

e.[Export]
    public IEntityManagerProvider<TempHireEntities> TempHireEntityManagerProvider    {
            get    {
                IUserSession userSession = Composition.GetInstance<IUserSession>(CreationPolicy.Shared);
                var provider = new EntityManagerProvider<TempHireEntities>()
                    .Configure(config => config.WithConnectionOptions(userSession.Name));
                return provider;
            }
        }

f. [ImportingConstructor]
   public LoginViewModel(IAuthenticationService authenticationService, IWindowManager windowManager,
                              [Import(AllowDefault = true)] IPreLoader preLoader, IUserSession userSession
           ....
            _userSession = userSession;
           ....

   _userSession.Name = filled from one new field named KeyName
    public string KeyName
        {
            get { return _keyName; }
            set
            {
                _keyName= value;
                _userSession.Name = value;
                NotifyOfPropertyChange(() => KeyName);
                NotifyOfPropertyChange(() => CanLogin);
            }
        }


g. before Login
    public IEnumerable<IResult> Login()  {
            _authenticationService. ??? There is no implementation to configure ConnectionOptions
            using (Busy.GetTicket()) ...

I am stuck

ps. If CustomDataSourceKeyResolver return null how can it manage from LoginViewModel







Posted By: mgood
Date Posted: 04-Sep-2012 at 12:17am
Sorry, I was swamped. What exactly are you stuck on? You can make this a lot simpler by writing a ConnectionOptionsResolver that simply replaces the system default ConnectionOptions based on the user's selection. 


public class EnvironmentResolver : IConnectionOptionsResolver
{
private static ConnectionOptions DevEnv = ConnectionOptions.Default.WithDataSourceExtension("DEV");

        private static ConnectionOptions TestEnv = ConnectionOptions.Default.WithDataSourceExtensioin("TEST");

        private static ConnectionOptions ProdEnv = ConnectionOptions.Default.WithDataSourceExtension("PROD");

        private IUserSession _userSession;

        [ImportingConstructor]
        public EnvironmentResolver(IUserSession userSession)
        {
              _userSession = userSession;
        }

        public ConnectionOptions GetConnectionOptions(string name)
        {
if (name == ConnectionOptions.Default.Name)
                {
switch (_userSession.Name)
                        {
                               "DEV" : return DevEnv.WithName(ConnectionOptions.Default.Name);
                               "TEST" : return TestEnv.WithName(ConnectionOptions.Default.Name);
                               "PROD" : return ProdEnv.WithName(ConnectionOptions.Default.Name);

                                default : return null;
                        }
                }

                return null;
        }
}

Now you don't have to worry about configuring the EntityManagerProvider or the AuthenticationService. They'll keep looking for the default ConnectionOptions and the above resolver simply feeds a properly configured ConnectionOptions instead of the system default.


Posted By: giotis
Date Posted: 04-Sep-2012 at 10:28pm
perfect
I noticed when  GetConnectionOptions take value is always "Default" and ConnectionOptions.Default.Name having the same value , always , that is correct?
because in this case we do not need to check out.

Also when we change the connection not fill the comboboxes at second database,
when change again the first database the comboboxes is correct


Posted By: mgood
Date Posted: 04-Sep-2012 at 10:37pm
Well, Default is used if the EntityManagerProvider and AuthenticationSerivce are not configured with another ConnectionOptions. You should always check what name needs to be resolved. You may decide later on to use different ConnectionOptions names. If you can't resolve the name than you return null in which case Cocktail looks for another resolver that might now how to resolve the name. There's a default resolver that knows how to resolve Default, Fake and DesignTime if all fails.


Posted By: giotis
Date Posted: 04-Sep-2012 at 10:53pm
thank you very much
Also when we change the connection not fill the comboboxes at second database,
when change again the first database the comboboxes is correct why?



Posted By: mgood
Date Posted: 04-Sep-2012 at 10:56pm
I'm not following your question. Please elaborate.


Posted By: giotis
Date Posted: 04-Sep-2012 at 11:07pm
After selecting base (Prod) and connect all is well
When after changing the base(Test) the combobox(State) from Adresses are not filled,
If we go back to first base again all well


Posted By: giotis
Date Posted: 04-Sep-2012 at 11:11pm
Sorry filled,
the combobox not show the state from Adresses
 


Posted By: giotis
Date Posted: 04-Sep-2012 at 11:45pm
I put a TextBlock in StaffingResourceAddressListView
  <TextBlock Text="{Binding Item.StateId}" />

everything work fine
ComboBox have problem

<ComboBox Grid.Row="4"
                                  Grid.Column="1"
                                  Width="140"
                                  Height="25"
                                  Margin="2"
                                  HorizontalAlignment="Left"
                                  DisplayMemberPath="Name"
                                  ItemsSource="{Binding ElementName=LayoutRoot,
                                                        Path=DataContext.States}"
                                  MaxDropDownHeight="200"
                                  SelectedValue="{Binding Item.StateId,
                                                          ValidatesOnDataErrors=True,
                                                          ValidatesOnExceptions=True,
                                                          Mode=TwoWay}"
                                  SelectedValuePath="Id" />
 


Posted By: mgood
Date Posted: 05-Sep-2012 at 10:14am
Not sure without debugging the code. Are you logging out  before switching the database?


Posted By: giotis
Date Posted: 06-Sep-2012 at 7:42am
test by your self (Cocktail ver 19545) I'll only take 2 minutes

namespace Common {
    public interface IUserSession   {
        string Name { get; set; }
    }
}
namespace Common{
    [Export(typeof(IUserSession)), PartCreationPolicy(CreationPolicy.Shared)]
    public class UserSession : IUserSession    {
        private string _name;
        public UserSession()        {        }
        public string Name {
            get { return _name; }
            set { _name = value; }
        }
    }
}
namespace TempHire {
  public class CustomDataSourceKeyResolver : IDataSourceKeyResolver {
    public IdeaBlade.EntityModel.IDataSourceKey GetKey(string keyName, string keyExtension, bool onServer) {
      if (!onServer) { return null; }
      if (string.IsNullOrEmpty(keyExtension)) { return null; }
      if (keyName != "TempHireEntities") { return null; }
      String connectionString = "";
      if (keyExtension == "PROD")      {          connectionString = @"Data Source=|DataDirectory|TempHire_Prod.sdf";      }
      if (keyExtension == "TEST")      {          connectionString = @"Data Source=|DataDirectory|TempHire_Test.sdf";      }    
      string name = string.IsNullOrEmpty(keyExtension) ? keyName : keyName + "_" + keyExtension;
      return new ClientEdmKey(name, connectionString, false, "FromResolver");
    }
  }
}
namespace TempHire {
    public class EnvironmentResolver : IConnectionOptionsResolver    {
        private static ConnectionOptions TestEnv = ConnectionOptions.Default.WithDataSourceExtension("TEST");
        private static ConnectionOptions ProdEnv = ConnectionOptions.Default.WithDataSourceExtension("PROD");
        private IUserSession _userSession;
        [ImportingConstructor]
        public EnvironmentResolver(IUserSession userSession)  {
            _userSession = userSession;
        }
        public ConnectionOptions GetConnectionOptions(string name)   {   
            if (name == ConnectionOptions.Default.Name)  {
                    switch (_userSession.Name)  {
                        case "Test": return TestEnv.WithName(ConnectionOptions.Default.Name);
                        case "Prod": return ProdEnv.WithName(ConnectionOptions.Default.Name);

                        default: return null;
                    }
                    //return ConnectionOptions.Default.WithDataSourceExtension(_userSession.Name.ToUpper()).WithName(ConnectionOptions.Default.Name);
                }
            return null;
        }
    }
}

namespace TempHire.ViewModels.Login
{  using System.Linq;

    [Export]
    public class LoginViewModel : Screen, IResult   {
        .......
        private IUserSession _userSession;
        private string _displayMemberPath;
        private BindableCollection<object> _items;
        private object _selectedItem;

        [ImportingConstructor]
        public LoginViewModel(IAuthenticationService authenticationService, IWindowManager windowManager,
                              [Import(AllowDefault = true)] IGlobalCache globalCache, IUserSession userSession)    {
            _userSession = userSession;
            DisplayMemberPath = "WithName";
            Items = new BindableCollection<object>() { new Connections() { WithName = "Test" }, new Connections() { WithName = "Prod" } };
        }
        private class Connections   {
            public Connections() { }
            public string WithName { get; set; }
        }       
        public string DisplayMemberPath        {
            get { return _displayMemberPath; }
            set  {
                _displayMemberPath = value;
                NotifyOfPropertyChange(() => DisplayMemberPath);
            }
        }
        public object SelectedItem        {
            get { return _selectedItem; }
            set  {
                _selectedItem = value;
                NotifyOfPropertyChange(() => SelectedItem);
                _userSession.Name = ((Connections)SelectedItem).WithName;
            }
        }
        public BindableCollection<object> Items    {
            get { return _items; }
            set     {
                _items = value;
                NotifyOfPropertyChange(() => Items);
                SelectedItem = _items.FirstOrDefault();
            }
        }

           LoginView after password
            <ComboBox x:Name="Items"
                  Grid.Row="3" Grid.Column="1"
                  Height="25"
                  Margin="2,2,81,2"
                  VerticalAlignment="Center"
                  DisplayMemberPath="{Binding DisplayMemberPath}" Width="137" />


Posted By: giotis
Date Posted: 12-Sep-2012 at 9:41am
hello Marcel
you found time for analysis


Posted By: mgood
Date Posted: 12-Sep-2012 at 10:13am
Sorry, I have not. I should have time next week.


Posted By: mgood
Date Posted: 22-Sep-2012 at 1:59am
Sorry it took me a while to look into this. The issue here is that the States entities are cached by the StaffingResourceAddressListViewModel between sessions. You have to clear them when the VM closes, so they get loaded fresh from the correct data source, otherwise they don't match. Modify StaffingResourceAddressListViewModel.OnDeactivate as follows:
 
        protected override void OnDeactivate(bool close)
        {
            base.OnDeactivate(close);
 
            if (!close) return;
 
            ClearAddresses();
            States = null;
        }



Print Page | Close Window