2

I want tp build a WPF (or Windows Forms if it can solve the problem) that can display simultaneously the same web page with different credentials (it's testing tool for a web application with complex user action flow).

I have, by now, a very simple WPF app :

<Window x:Class="TestTool.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="600" Width="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <WebBrowser Margin="8" Source="http://theapptotest" />
        <WebBrowser Margin="8" Source="http://theapptotest" Grid.Column="1" />
    </Grid>
</Window>

This is not the target layout of the application, but it will allows to test if my goal is possible.

The application, as expected, display side by side the same web app. However, if I log in in the first web brower control, the second is logged in too. I guess it's because the cookies are shared for the current user.

I checked into the WebBrowser class documentation but I did not find anything useful.

How can I reach my goal?

Is it possible to "isolate" one webbrowser from the others?

Is it possible to "impersonate" a user control? Like some applications (web browser mostly) are spawning different processes, can I "spawn" processes under a different account and display it in my main window ?

Please note that I don't have to interact with the web pages. I just want to able to switch user by changing a tab of my application (one tab per user).

PS: I'm not stick to Internet Explorer engine, if a solution exists with alternatives browsers, let me know

PS: the target application use two authentication mechanisms. A user can authenticate with Windows account, or a custom Form based authentication. if required, I can use only forms authentication (a separate cookie container would do the job, if I can plug it).

[Edit] I've tried to spawn with other users credentials new instances of Internet Explorer, then attach the process main window by settings its parent to one of my windows form host. This is however, very unreliable (process often crash, can't work if any existing instance of IE is running, etc.

Steve B
  • 36,818
  • 21
  • 101
  • 174
  • Are you sure it's due to the cookies? Session cookies? Persistent cookies? This could impact the solution. For example, let's say the target site authentication is based on IP (even if it's a stupid idea), then the solution would be different. – Simon Mourier Jun 28 '12 at 09:17
  • the target application is a asp.net application (actually a sharepoint application). It uses the standard asp.net authentication behavior (using some cookies, mix between persistent and session cookies, if I'm not wrong). However, I don't know how this behave when the user authenticate with windows credentials. I can rely only on forms authentication so it won't be a problem if I have to forget Windows auth – Steve B Jun 28 '12 at 09:21
  • Have you tried to call ClearAuthenticationCache in one of the window as described here: http://blogs.msdn.com/b/ieinternals/archive/2010/04/05/understanding-browser-session-lifetime.aspx ? – Simon Mourier Jun 28 '12 at 09:33
  • how can I send this command with the webbrowser control? Moreover, I'm not it will help. As stated, `the ClearAuthenticationCache command clears ALL session cookies, Authentication, and Client Certificates for ALL sites running in the current session`. There is however something interesting. If ran "iexplore.exe -nomerge", the spawned IE is isolated. I can run two instance of IE, and log in separately in the two ie. Still have to find a way to embed this in my app. – Steve B Jun 28 '12 at 09:47

1 Answers1

2

Ok, I finally found a way to reach my goal, with the help of Simon Mourrier previous answer.

I've created a custom user control:

Xaml part:

<UserControl x:Class="WpfApplication1.AppIsolatedView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" SizeChanged="UserControl_SizeChanged"
             d:DesignHeight="300" d:DesignWidth="300" Loaded="UserControl_Loaded" Unloaded="UserControl_Unloaded">
    <Grid>
        <WindowsFormsHost Margin="12" Name="windowsFormsHost1" />

    </Grid>
</UserControl>

With this code behind :

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    public partial class AppIsolatedView : UserControl
    {
        public AppIsolatedView()
        {
            InitializeComponent();   
            _panel = new System.Windows.Forms.Panel();
            windowsFormsHost1.Child = _panel;    
        }

        string url = "http://theapptotest";   
        System.Windows.Forms.Panel _panel;
        Process _process;    

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            if (IsInDesignModeStatic) return; // Avoid consumer of the control to explode when in design mode

            ProcessStartInfo psi = new ProcessStartInfo("iexplore.exe", "-noframemerging -private \"" + url + "\"");
            _process = Process.Start(psi);
            _process.WaitForInputIdle();
            Thread.Sleep(2000);
            SetParent(_process.MainWindowHandle, _panel.Handle);

            // remove control box
            int style = GetWindowLong(_process.MainWindowHandle, GWL_STYLE);
            style = style & ~WS_CAPTION & ~WS_THICKFRAME;
            SetWindowLong(_process.MainWindowHandle, GWL_STYLE, style);

            // resize embedded application & refresh
            ResizeEmbeddedApp();

        }

        private void UserControl_Unloaded(object sender, RoutedEventArgs e)
        {
            if (_process != null)
            {
                _process.Refresh();
                _process.Close();
            }

        }

        private void ResizeEmbeddedApp()
        {
            if (_process == null)
                return;

            SetWindowPos(_process.MainWindowHandle, IntPtr.Zero, 0, 0, (int)_panel.ClientSize.Width, (int)_panel.ClientSize.Height, SWP_NOZORDER | SWP_NOACTIVATE);
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            Size size = base.MeasureOverride(availableSize);
            ResizeEmbeddedApp();
            return size;
        }

        private static bool? _isInDesignMode;

        /// <summary>
        /// Gets a value indicating whether the control is in design mode (running in Blend
        /// or Visual Studio).
        /// </summary>
        public static bool IsInDesignModeStatic
        {
            get
            {
                if (!_isInDesignMode.HasValue)
                {
#if SILVERLIGHT
            _isInDesignMode = DesignerProperties.IsInDesignTool;
#else
                    var prop = DesignerProperties.IsInDesignModeProperty;
                    _isInDesignMode
                        = (bool)DependencyPropertyDescriptor
                        .FromProperty(prop, typeof(FrameworkElement))
                        .Metadata.DefaultValue;
#endif
                }

                return _isInDesignMode.Value;
            }
        }

        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            ResizeEmbeddedApp();

        }
        #region pinvoke stuff
        [DllImport("user32.dll")]
        private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

        [DllImport("user32")]
        private static extern IntPtr SetParent(IntPtr hWnd, IntPtr hWndParent);

        [DllImport("user32")]
        private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

        private const int SWP_NOZORDER = 0x0004;
        private const int SWP_NOACTIVATE = 0x0010;
        private const int GWL_STYLE = -16;
        private const int WS_CAPTION = 0x00C00000;
        private const int WS_THICKFRAME = 0x00040000;
        #endregion
        }
}

I have to cheat a bit :

  • I have to add -noframemerging when firing IExplore.exe in order to avoid internal IE session sharing.
  • I have to sleep a bit after having launch the process. Don't know exactly why, but the MainWindowHandle property change after some few milliseconds (I guess iexplore has a bootstrapper process to either connect to existing process or spawn a new one, with new handle, or something like this).

I still have to extend the user control to accept input parameters (url is hardcoded), but the idea is here. I just have to concentrate on the app itself.

Community
  • 1
  • 1
Steve B
  • 36,818
  • 21
  • 101
  • 174
  • Still some improvement required... after the application closes, instances of Internet Explorer remains presents in task manager. – Steve B Jun 28 '12 at 11:27
  • Wow :-) If you're going down that route (why not if there is no other option), you also might want to check out UI Automation (see other answers here on SO: http://stackoverflow.com/questions/4665045/how-to-get-the-word-under-the-cursor-in-windows/4734275#4734275) which will allow you to control some parts of IE (or any other app) in an external process. Useful at least to force it to quit. – Simon Mourier Jun 28 '12 at 13:06
  • If there is a better solution, I'll be happy to hear it :). Fortunately, my only goal is to simplify the testing of complex web application that requires to log in with several users. If the tester have to manually enter 8 passwords at the beginning of the session, I can live with that. – Steve B Jun 28 '12 at 13:17