Daniel Vaughan

.NET Adventures

ViewModel Active Awareness in a Prism Based Application

clock February 5, 2010 15:13 by author Daniel Vaughan

Yesterday, while chatting with the highly talented Jeremiah Morrill and other WPF Disciples about some MVVM subtleties (for the full post see here), Jeremiah briefly touched on the topic of providing ViewModels with the awareness of being active or inactive within a Prism based application. I wanted to explore this further, and decided to integrate this functionality into Calcium. What I provide here isn't rocket science, and merely serves to illustrate one of indeed many design approaches that could be applied to accomplish the same thing.

For this I had two goals. The first, to provide the capability without coupling the ViewModel to the View. That is, without requiring the ViewModel to have a reference to the View. The second, to not depend on a base class for the functionality; forever tying the developer to my base class implementation (ViewModelBase). So, indeed, I chose an interface based approach. To be Prism-esque I have adapted Jeremiah's approach which was to implement Prism's IActiveAware interface on my base view class. I then feed an intermediary object to the ViewModel via an interface named IViewAware. The intermediary object is an instance of ActiveAwareUIElementAdapter. This class is used to provide a UIElement instance with Prisms IActiveAware interface. It does so by monitoring its Got and LostFocus events.

 

ActiveAwareUIElementAdapter

 

/// <summary>
/// Wraps a <see cref="UIElement"/> to provide an <see cref="IActiveAware"/>
/// implementation based on its focus state.
/// </summary>
public class ActiveAwareUIElementAdapter : IActiveAware
{
    bool active;

    public ActiveAwareUIElementAdapter(UIElement uiElement)
    {
        ArgumentValidator.AssertNotNull(uiElement, "uiElement");
        uiElement.GotFocus += OnGotFocus;
        uiElement.LostFocus += OnLostFocus;
    }

    void OnLostFocus(object sender, RoutedEventArgs e)
    {
        IsActive = false;
    }

    void OnGotFocus(object sender, RoutedEventArgs e)
    {
        IsActive = true;
    }

    public bool IsActive
    {
        get
        {
            return active;
        }
        set
        {
            if (active != value)
            {
                active = value;
            }
            OnIsActiveChanged(EventArgs.Empty);
        }
    }

    #region event IsActiveChanged

    event EventHandler isActiveChanged;

    public event EventHandler IsActiveChanged
    {
        add
        {
            isActiveChanged += value;
        }
        remove
        {
            isActiveChanged -= value;
        }
    }

    protected void OnIsActiveChanged(EventArgs e)
    {
        if (isActiveChanged != null)
        {
            isActiveChanged(this, e);
        }
    }

    #endregion
}

We then use this class within any IView UIElement implementation, but in particular the base ViewControl class. We instantiate the ActiveAwareUIElement adapter within the view's constructor, and pass it the instance of the view itself. The ActiveAwareUIElement then simply subscribes to the GotFocus and LostFocus events of the view, which is of course a UIElement.

 

ViewControl Implementation

 

/// <summary>
/// The base class for <see cref="IView"/>s.
/// </summary>
public class ViewControl : UserControl, IView, IActiveAware /* (not abstract for Blendability) */
{
    #region ViewModel Dependency Property

    public static DependencyProperty ViewModelProperty = DependencyProperty.Register(
        "ViewModel", typeof(IViewModel), typeof(ViewControl), new PropertyMetadata(null, OnViewModelChanged));

    static void OnViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var viewControl = (ViewControl)d;
        viewControl.SetViewAwareAssociations((IViewModel)e.OldValue, (IViewModel)e.NewValue);
    }

    [Description("The view model for this view.")]
#if !SILVERLIGHT
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
#endif
    public IViewModel ViewModel
    {
        get
        {
            return (IViewModel)GetValue(ViewModelProperty);
        }
        set
        {
            SetValue(ViewModelProperty, value);
        }
    }

    #endregion

    /// <summary>
    /// Initializes a new instance of the <see cref="ViewControl"/> class.
    /// </summary>
    public ViewControl()
    {
        Loaded += OnLoaded;
        activeAwareUIElementAdapter = new ActiveAwareUIElementAdapter(this); 
    }

    bool alreadyLoaded;

    void OnLoaded(object sender, RoutedEventArgs e)
    {
        if (!alreadyLoaded)
        {
            alreadyLoaded = true;
            OnViewLoaded(e);
        }
    }

    #region ViewLoaded event

    event EventHandler<EventArgs> viewLoaded;

    /// <summary>
    /// Occurs when the view has been loaded.
    /// </summary>
    public event EventHandler<EventArgs> ViewLoaded
    {
        add
        {
            viewLoaded += value;
        }
        remove
        {
            viewLoaded -= value;
        }
    }

    /// <summary>
    /// Closes the view.
    /// </summary>
    /// <param name="force">if set to <c>true</c> the control will be forced
    /// to close even if e.g., there is unsaved data and the user chooses 
    /// to cancel the closure.</param>
    /// <returns></returns>
    public virtual bool Close(bool force)
    {
        var viewService = ServiceLocatorSingleton.Instance.GetInstance<IViewService>();
        return viewService.CloseView(this, force);
    }

    /// <summary>
    /// Raises the <see cref="E:ViewLoaded"/> event.
    /// </summary>
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
    protected void OnViewLoaded(EventArgs e)
    {
        if (viewLoaded != null)
        {
            viewLoaded(this, e);
        }
    }
    #endregion

    /// <summary>
    /// Gets a value indicating whether the control is in a designer.
    /// </summary>
    /// <value><c>true</c> if design time; otherwise, <c>false</c>.</value>
    protected bool DesignTime
    {
        get
        {
            return DesignerProperties.GetIsInDesignMode(this);
        }
    }

    #region IActiveAware and related

    void SetViewAwareAssociations(IViewModel oldViewModel, IViewModel newViewModel)
    {
        var oldViewAware = oldViewModel as IViewAware;
        var newViewAware = newViewModel as IViewAware;
        if (oldViewAware != null)
        {
            oldViewAware.DetachActiveAware();
        }

        if (newViewAware != null)
        {
            newViewAware.Attach(activeAwareUIElementAdapter);
        }
    }

    readonly ActiveAwareUIElementAdapter activeAwareUIElementAdapter;

    bool IActiveAware.IsActive
    {
        get
        {
            return activeAwareUIElementAdapter.IsActive;
        }
        set
        {
            activeAwareUIElementAdapter.IsActive = value;
        }
    }

    event EventHandler IActiveAware.IsActiveChanged
    {
        add
        {
            activeAwareUIElementAdapter.IsActiveChanged += value;
        }
        remove
        {
            activeAwareUIElementAdapter.IsActiveChanged -= value;
        }
    }

    #endregion
}

We see that the dependency property ViewModel, when changed, prompts the attachment of the ActiveAwareUIElement instance to the ViewModel. The mechanism for performing this is via the IViewAware implementation. If an IViewModel wishes to be aware of its view's state, in particular when it becomes active or inactive; without having to have a direct reference to the view, then it can implement the IViewAware interface.

 

IViewAware Interface


/// <summary>
/// Provides for advanced presentation behaviour in a <see cref="IViewModel"/>s.
/// </summary>
public interface IViewAware
{
    /// <summary>
    /// Attaches the specified active aware instance so that changes in the <see cref="IActiveAware.IsActive"/>
    /// state can be monitored.
    /// </summary>
    /// <param name="activeAware">The active aware.</param>
    void Attach(IActiveAware activeAware);

    /// <summary>
    /// Detaches the active aware instance. Changes in the <see cref="IActiveAware.IsActive"/>
    /// state will no longer be monitored.
    /// </summary>
    void DetachActiveAware();
}

The ViewModel base class implementation is provided next in full:

/// <summary>
/// A base implementation of the <see cref="IViewModel"/> interface.
/// </summary>
public abstract class ViewModelBase : IViewModel, INotifyPropertyChanged, IViewAware
{
    IActiveAware activeAwareInstance;

    protected ViewModelBase()
    {
        notifier = new PropertyChangeNotifier(this);
    }

    #region Title Property

    object title;

    [Description("The text to display on a tab.")]
#if !SILVERLIGHT
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
#endif        
    public object Title
    {
        get
        {
            return title;
        }
        set
        {
            notifier.Assign("Title", ref title, value);
        }
    }

    #endregion

    #region Property Change Notification
    readonly PropertyChangeNotifier notifier;

    protected PropertyChangeNotifier Notifier
    {
        get
        {
            return notifier;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged
    {
        add
        {
            notifier.PropertyChanged += value;
        }
        remove
        {
            notifier.PropertyChanged -= value;
        }
    }

    protected AssignmentResult Assign<TProperty>(
        string propertyName, ref TProperty property, TProperty newValue)
    {
        return notifier.Assign(propertyName, ref property, newValue);
    }

    #endregion

    #region Active Aware

    void IViewAware.Attach(IActiveAware activeAware)
    {
        ReplaceActiveAware(activeAware);
    }

    void IViewAware.DetachActiveAware()
    {
        ReplaceActiveAware(null);
    }

    void ReplaceActiveAware(IActiveAware activeAwareInstance)
    {
        if (this.activeAwareInstance != null)
        {
            this.activeAwareInstance.IsActiveChanged -= OnIsActiveChanged;
        }
        this.activeAwareInstance = activeAwareInstance;
        if (activeAwareInstance != null)
        {
            activeAwareInstance.IsActiveChanged += OnIsActiveChanged;
        }
    }

    bool lastActiveState;

    void OnIsActiveChanged(object sender, EventArgs e)
    {
        Notifier.NotifyChanged("Active", lastActiveState, Active);
        lastActiveState = Active;
    }

    /// <summary>
    /// Gets a value indicating whether this instance is being notified 
    /// of when it becomes active or inactive, 
    /// this may occur for example when its view gains focus or loses focus.
    /// </summary>
    /// <value><c>true</c> if monitoring the active state 
    /// of its view; otherwise, <c>false</c>.</value>
    public bool ActiveAware
    {
        get
        {
            return activeAwareInstance != null;
        }
    }

    /// <summary>
    /// Gets a value indicating whether this <see cref="ViewModelBase"/> 
    /// is active within the user interface.
    /// </summary>
    /// <value><c>true</c> if active; otherwise, <c>false</c>.</value>
    public bool Active
    {
        get
        {
            return activeAwareInstance != null ? activeAwareInstance.IsActive : false;
        }
    }

    #endregion

    public override string ToString()
    {
        return title != null ? title.ToString() : base.ToString();
    }
}

So there it is. Just one way to provide a ViewModel with active awareness. Please be aware that this code is preliminary and may be subject to change. The full source code will be available via the Calcium source download page soon.

 

Thanks to Kent Boogaart for his post on MVVM Infrastructure ActiveAwareCommand which the ActiveAwareUIElementAdapter was inspired from.



Calcium wins again!

clock December 22, 2009 17:54 by author Daniel Vaughan

I'm pleased to report that the article Calcium Part 3 has won the Best Overall Article award for November 2009.

The article covers a lot of the VS templating work I did for Calcium in the last release. Thanks to those who voted, and to all those who have been supporting the project by providing comments and feedback.

 



Clog Project Source Integrated into Calcium

clock December 2, 2009 17:44 by author Daniel Vaughan

I am pleased to announce that the Clog project has been fully integrated into Calcium. The source code for both the Silverlight and Desktop CLR Clog projects is now located along with the Calcium source code on Codeplex. Why did I take this step? The reason is that there are shared projects across Clog and Calcium, and maintaining two codebases would not be a good thing. One of my goals when publishing software is to make things easy for you. So, a single point of the latest source seems to makes sense. Please note that I will still be releasing Clog binaries on the dedicated Clog Codeplex site. However, the most up to date Clog source can be found with Calcium.



Article Published: Calcium Part 3

clock November 21, 2009 17:43 by author Daniel Vaughan

I’ve just published the third part of the Calcium series of articles. Calcium is now provided as an SDK, and comes with 12 VS Project templates (3 for 2008, 2010, C#, and VB.NET)

This article looks at the changes that have taken place in the Calcium project since the first release, a number of months ago. It features an introduction to the File Service, which abstracts the displaying of dialogs and handling common IO exceptions, in a platform agnostic manner; allowing the user to select other alternative locations for files, while providing feedback to the user. It also features an overview of the View Service, allowing content to be shown or hidden using a view type association mechanism.


View Article



Speaking at the Swiss ALT.NET Group on 25 November

clock November 19, 2009 23:47 by author Daniel Vaughan

I will be speaking at the next ALT.NET Swiss user group on the 25th of November. I will be showing of latest version of the Calcium SDK, and looking at some other MVVM frameworks created by my fellow Disciples.

Special thanks go out to Frédéric Schäfer, and Atif Aziz of Cargill International SA, for making the meeting a reality. Nice one guys.

The details are:
25 November at 18:30
Cargill Conference Center
Cargill International SA
14 chemin de Normandie
1206 Geneva

More details



Calcium SDK Website Launch

clock November 5, 2009 12:03 by author Daniel Vaughan

I’ve just launched a new website for the Calcium project. It includes a video on how to use the new version of the Calcium SDK, which I’ve also just released. I’m pretty excited about this one. The new version includes an installer that makes working with Calcium much easier, and a bunch of API refinements.

 



Article Published: Calcium Part 2

clock July 6, 2009 19:00 by author Daniel Vaughan

I’ve just published the 2nd of my Calcium series of articles. This article covers the Messaging system that I recently blogged about, the Web Browser module, and the Output Module. There’s also some nifty stuff on injecting RoutedCommand handlers into the shell, which I quite like.

You can find it at http://www.codeproject.com/KB/WPF/CalciumPart02.aspx



A location agnostic Message Service

clock June 15, 2009 21:20 by author Daniel Vaughan

When developing an application, clearly it’s prudent to have uniformity in the manner certain tasks are carried out, thereby avoiding violation of the DRY principle. An example of this is displaying common dialog boxes. But wait, if you think this post is just going to be about an abstracted dialog box system, then think again. While Calcium does provide a common dialog system, it also allows us to display a dialog to the user from the server during any WCF call! Moreover, it allows us to consume the same api on the client and the server. This means we are able to interact with the user directly, without having to e.g., interpret the result of a WCF call in the client. It effectively narrows the tier gap.

In this post, firstly we will discuss the client-side message service, then we will examine how it is leveraged from the server-side to provide the location agnosticism.

So, obviously it’s unwise for each member of a development team to be creating his or her own dialogs for simple tasks such as asking the user a closed ended question (e.g. a Yes/No question box). If we decide to change the caption in the dialogs across the board, it is rather more difficult if dialogs are scattered across the project. Likewise if we wish to port the application from WPF to Silverlight, or even to a command line interface, think Powershell or mobile applications, it’s great to be able to swap out the implementation for any given scenario. Clearly an abstracted system is in order.

Out of the box, Calcium comes with a number of Message Service implementations. There is a client implementation for WPF, another client-side implementation for command line driven applications, and a server-side implementation that sends messages back to the client via a callback, and leverages the client-side IMessageService implementation.

The Message Service has various overloads which allow the caption and message to be specified.

Let’s take a look at the IMessageService interface and client-side implementations.

Diagram: IMessageService allows us to interact with the user in a UI agnostic manner.

The Message Service allows for a Message Importance level to be specified. This allows for a threshold noise level to be specified by the user. If the MessageImportance is lower than the user’s preference, then the user won’t be bothered with the message. In a later version of Calcium we shall see a Preferences Service for specifying the preferred level.

Diagram: MessageService and CommandLineMessageService both override ShowCustomDialog method of MessageServiceBase to cater for their particular environments.

By applying variation through merely overriding the ShowCustomDialog method it also makes it very easy to mock the MessageServiceBase class for testing.

Our client-side WPF implementation channels all messaging requests through the ShowCustomDialog method as shown in the following excerpt.

/// <summary>
/// WPF implementation of the <see cref="IMessageService"/>.
/// </summary>
public class MessageService : MessageServiceBase
{
    public override MessageResult ShowCustomDialog(string message, string caption,
        MessageButton messageButton, MessageImage messageImage, 
        MessageImportance? importanceThreshold, string details)
    {
        /* If the importance threshold has been specified 
         * and it's less than the minimum level required (the filter level) 
         * then we don't show the message. */
        if (importanceThreshold.HasValue && importanceThreshold.Value < MinumumImportance)
        {
            return MessageResult.OK;
        }

        if (MainWindow.Dispatcher.CheckAccess())
        {    /* We are on the UI thread, and hence no need to invoke the call.*/
            var messageBoxResult = MessageBox.Show(MainWindow, message, caption, 
                messageButton.TranslateToMessageBoxButton(), 
                messageImage.TranslateToMessageBoxButton());
            return messageBoxResult.TranslateToMessageBoxResult();
        }

        MessageResult result = MessageResult.OK; /* Satisfy compiler with default value. */
        MainWindow.Dispatcher.Invoke((ThreadStart)delegate
        {
            var messageBoxResult = MessageBox.Show(MainWindow, message, caption, 
                messageButton.TranslateToMessageBoxButton(), 
                messageImage.TranslateToMessageBoxButton());
            result = messageBoxResult.TranslateToMessageBoxResult();
        });

        return result;
    }

    static Window MainWindow
    {
        get
        {
            return UnitySingleton.Container.Resolve<IMainWindow>() as Window;
        }
    }
}

I have created various extension methods for translating between the native WPF enums MessageBoxButton, MessageBoxImage, and MessageBoxResult. Why go to all of this trouble? On first appearances it appears like an antipattern. The reason is in fact a good one: there are differences in these enums in WPF and Silverlight, and this allows us to cater for bother without duplication.

I am considering expanding the service to support message details, and perhaps reducing the number of overloads with a reference type Message parameter. Another improvement would be to implement a Don’t show again checkbox system. I leave that for a future incarnation. I may even use Karl Shifflet’s excellent Common TaskDialog project http://www.codeproject.com/KB/WPF/WPFTaskDialogVistaAndXP.aspx (with Karl's permission).

Server-side Message Service

We’ve looked at the basic client-side implementation of IMessageService, but now things are going to get a little more interesting. With Calcium we have the capability to consume the IMessageService in the same way on both the client and server. To accomplish this we use a WCF custom header and a duplex service callback.

The demonstration application contains a module called the MessageServiceDemoModule. The view for this module contains a single button that becomes enabled when the CommunicationService has connectivity with the server.

This is code in a WCF service, which we can execute asynchronously as shown, and present the user with dialogs on the client side. Remember, this code executes server-side!

        public void DemonstrateMessageService()
        {
            var messageService = UnitySingleton.Container.Resolve<IMessageService>();
            ThreadPool.QueueUserWorkItem(delegate
            {
                messageService.ShowMessage(
                    "This is a synchronous message invoked from the server." + 
                    "The service method is still executing and waiting for your response.",
                    "DemoService", MessageImportance.High);
                bool response1 = messageService.AskYesNoQuestion(
                    "This is a question invoked from the server. Select either Yes or No.",
                    "DemoService");
                string responseString = response1 ? "Yes" : "No";
                messageService.ShowMessage(string.Format(
                    "You selected {0} as a response to the last question.", responseString), 
                    MessageImportance.High);
            });
        }
    }

In the above excerpt, we retrieve the IMessageService instance from the Unity container. This is done in same manner as we would do on the client! Being location agnostic allows us to move business logic far more easily. I have placed the Message Service calls here inside a QueueUserWorkItem delegate just to demonstrate the independence of the Message Service, in that the WCF call returns almost immediately, yet a child thread will continue to work in the background and will still retain the capability to communicate with the user. Without using a child thread to communicate with the user, we may experience a timeout if the user fails to respond fast enough. Please note that we are required to retrieve the Message Service from the Unity container before the service call completes. Without doing so will raise an exception, as the OperationContext for the service call will no longer be present.

  Figure: Server causes dialog to be presented client-side.

 

Figure: Question asked from server.

Figure: Response received and echoed back to user.

 

How does it all work? When we need a service channel we use our IChannelManager instance to retrieve an instance.

I’ve discussed the IChannelManager in a couple of articles, in particular here and here here

In order to support identification of the application instance from any WCF service call we place a custom header into every service channel we create, as shown in the following excerpt from the ServiceManagerSingleton and InstanceIdHeader classes:

public static class InstanceIdHeader
{
    public static readonly String HeaderName = "InstanceId";
    public static readonly String HeaderNamespace 
            =  OrganizationalConstants.ServiceContractNamespace;
}

We use this static class to place the header, and also to retrieve the header on the server.

void AddCustomHeaders(IClientChannel channel)
{
    MessageHeader shareableInstanceContextHeader = MessageHeader.CreateHeader(
        InstanceIdHeader.HeaderName,
        InstanceIdHeader.HeaderNamespace,
        instanceId.ToString());

    var scope = new OperationContextScope(channel);
    OperationContext.Current.OutgoingMessageHeaders.Add(shareableInstanceContextHeader);
}

So, when we create the service channel we add the custom header as shown in the following excerpt.

public TChannel GetChannel<TChannel>()
{
    Type serviceType = typeof(TChannel);
    object service;

    channelsLock.EnterUpgradeableReadLock();
    try
    {
        if (!channels.TryGetValue(serviceType, out service))
        {    /* Value not in cache, therefore we create it. */
            channelsLock.EnterWriteLock();
            try
            {
                /* We don't cache the factory as it contains a list of channels 
                 * that aren't removed if a fault occurs. */
                var channelFactory = new ChannelFactory<TChannel>("*");

                service = channelFactory.CreateChannel();
                AddCustomHeaders((IClientChannel)service);

                var communicationObject = (ICommunicationObject)service;
                communicationObject.Faulted += OnChannelFaulted;
                channels.Add(serviceType, service);
                communicationObject.Open(); 
                ConnectIfClientService(service, serviceType);

                UnitySingleton.Container.RegisterInstance<TChannel>((TChannel)service);
            }
            finally
            {
                channelsLock.ExitWriteLock();
            }
        }
    }
    finally
    {
        channelsLock.ExitUpgradeableReadLock();
    }

    return (TChannel)service;
}

We cache the channel until it is closed or faults. But as soon as we create a new channel, we add the header to be consumed on the server. We also have duplex channel support, which works in much the same way.

We must talk to the ICommunicationService in order to create a callback, before we attempt to consume the Message Service on the server-side. This is done automatically by the CommunicationModule. In fact, the Communication Module polls the server periodically to let it know that it is still alive. It also will detect network connectivity, or the lack thereof, and disable or enable the polling accordingly.

/// <summary>
/// Notifies the server communication service that the client is still alive.
/// Occurs on a ThreadPool thread. <see cref="NotifyAlive"/>
/// </summary>
/// <param name="state">The unused state.</param>
void NotifyAliveAux(object state)
{
    try
    {
        if (!NetworkInterface.GetIsNetworkAvailable())
        {
            return;
        }
        var channelManager = UnitySingleton.Container.Resolve<IChannelManager>();
        var communicationService 
            = channelManager.GetDuplexChannel<ICommunicationService>(callback);
        communicationService.NotifyAlive();
        if (!connected)
        {
            connected = true;
            connectionEvent.Publish(ConnectionState.Connected);
        }
        lastExceptionType = null;
    }
    catch (Exception ex)
    {
        Type exceptionType = ex.GetType();
        if (exceptionType != lastExceptionType)
        {
            Log.Warn("Unable to connect to communication service.", ex);
        }
        lastExceptionType = exceptionType;
        if (connected)
        {
            connected = false;
            connectionEvent.Publish(ConnectionState.Disconnected);
        }
    }
    finally
    {
        connecting = false;
    }
}

As an aside, the Communication Module itself has no UI. Its purpose is to interact with the CommunicationService, and to provide notifications to the client when various server-side events take place. One such is CompositeEvent is the ConnectionEvent, which can be seen in the previous excerpt.

I plan on expanding the Message Service system to allow text input, drop down list selection, and maybe even custom dialogs.

To find out more about Calcium please see the CodePlex site.



About the author

Daniel VaughanDaniel Vaughan is a software developer with a decade of commercial experience across a wide range of industries including e-commerce, multimedia, and finance. While originally from Australia and the UK, Daniel is currently based in Geneva Switzerland; working with WPF, WCF, and WF within the finance industry. In his spare time Daniel likes to spend time concocting novel ideas, such as employing neural networks to predict user navigation behaviour in WPF applications, and a grid computing framework for Silverlight. Daniel is also the creator of a number of open-source projects including Calcium, and Clog. E-mail me Send mail

 

Microsoft MVP logo Disciple
CodeProject MVP
WPF and Silverlight Insiders

 

 

RecentComments

Comment RSS

Sign in