Command And View Plugin


A command and view plugin provides a connection between the client and the administration. The data transfer is completely managed, so you don't have to care about it a lot. For such a plugin are two projects necessary: One for the client and one for the administration.

Needed libraries

Let's start

This will be a plugin which provides two dll files: One for the client and one for the administration. We'll put both in one solution. The recommended way is to name your administration plugin like MyAwesomeFeature and the client plugin then MyAwesomeFeature.Payload. That makes it easy to know which library has which purpose. Let's start by creating the solution. Because the first plugin (which gives the solution the name) will be the administration plugin, we have to address the .Net Framework 4.5 and create a WPF User Control Library.

We will directly create the project for the client: To add a new project to our solution, you have to right click the root item in the Solution Explorer > Add > New Project...

Now, because our second project will be loaded by the client, it must address the .Net Framework 3.5. As I said above, we'll name this like our administration plugin but ending with a .Payload.

You can remove the entries generated by default in each project (Class1.vb and UserControl1.xaml) so our solution explorer looks like that:

We will start with adding the needed references. For the administration plugin, you should add the following libraries: Orcus.Administration.Plugins.dll, Orcus.Shared.dll and Sorzus.Wpf.Toolkit.dll

The client plugin needs the following references: Orcus.Plugins.dll and Orcus.Shared.dll. You can find the libraries in the /libraries folder of your Orcus package. Make sure that you have the newest version.

The administration plugin

We will start with creating the entry file: We create a new class called Plugin (the name doesn't matter, but it makes clear that it's the entry point) and implement the interface ICommandAndViewPlugin. Please make sure that the class is public!

Imports Orcus.Administration.Plugins

Public Class Plugin
    Implements ICommandAndViewPlugin

    Public ReadOnly Property Command As Type Implements ICommandAndViewPlugin.Command
    Public ReadOnly Property CommandView As Type Implements ICommandAndViewPlugin.CommandView
    Public ReadOnly Property View As Type Implements ICommandAndViewPlugin.View
End Class

As you can see, there are three properties defined, all are just types. It's important to note that the Command must inherit Command, the CommandView must implement ICommandView and the View must inherit FrameworkElement.

Let's begin. We create three new items: Two classes and one UserControl.

I'll name my first class MyAwesomeFeatureCommand, my second class ViewModel and the UserControl View.

Now, we come to part which might be a little bit diffcult for developers who don't have experience with WPF/MVVM. But that's not so bad, it's makes a lot of fun if you understood it and if you don't you can also continue like you're always doing (no MVVM).

Orcus automatically connects the View to the CommandView by setting the DataContext of the View to the CommandView. So, if you access the DataContext property of your View, it will be your CommandView class.

Anyway, first, we will implement and inherit all necessary stuff. The View is fine, UserControl inherits FrameworkElement by default. The MyAwesomeFeatureCommand class must inherit Command:

Imports Orcus.Administration.Plugins

Public Class MyAwesomeFeatureCommand
    Inherits Command

    Public Overrides Sub ResponseReceived(parameter As Byte())
        Throw New NotImplementedException
    End Sub

    Protected Overrides Function GetId() As UInteger
        Throw New NotImplementedException
    End Function
End Class

This is the logic part of your plugin. GetId() must return a unique id. You have to generate it using TODO.

We will add a simple method which will send a few bytes to the client:

Imports System.Text
Imports Orcus.Administration.Plugins

Public Class MyAwesomeFeatureCommand
    Inherits Command

    Public Overrides Sub ResponseReceived(parameter As Byte())
        'Here we'll process the response sent by the client
    End Sub

    Public Sub SendTextToClient(text As String)
        ConnectionInfo.SendCommand(Me, Encoding.UTF8.GetBytes(text))
        LogService.Send("Send a few bytes to the client")
    End Sub

    Protected Overrides Function GetId() As UInteger
        Return 23894873
    End Function
End Class

As you can see, we have access to the ConnectionInfo which provides everything to communicate with the client. The first parameter must be our command, the second parameter are our raw bytes. The ConnectionInfo will automatically compress the bytes if necessary, the client will exactly receive these bytes.

The next thing is the ViewModel which must implement ICommandView. Also, it's recommended to inherit from PropertyChangedBase which can be found in the Sorzus.Wpf.Tookit-library. The class PropertyChangedBase implements and manages INotifyPropertyChanged, you will see:

Imports Orcus.Administration.Plugins
Imports Sorzus.Wpf.Toolkit

Public Class ViewModel
    Inherits PropertyChangedBase
    Implements ICommandView

    Private _myAwesomeFeatureCommand As MyAwesomeFeatureCommand
    Private _customText As String
    Private _buttonClickCommand As RelayCommand

    Public ReadOnly Property ButtonClickCommand As RelayCommand
            If _buttonClickCommand Is Nothing Then
                _buttonClickCommand = New RelayCommand(Sub()
                                                           If String.IsNullOrWhiteSpace(CustomText) Then
                                                               MessageBoxEx.Show(Application.Current.MainWindow, "The text must not be empty!")
                                                           End If

                                                           CustomText = Nothing
                                                       End Sub)
            End If

            Return _buttonClickCommand
        End Get
    End Property

    Public Property CustomText As String
            Return _customText
        End Get
            SetProperty(Value, _customText)
        End Set
    End Property

    Public Sub New()
        Name = "My Awesome Command"
        Category = Category.Fun
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        'Here you can dispose everything you've created
    End Sub

    Public Sub Initialize(clientController As IClientController, crossViewManager As ICrossViewManager) Implements ICommandView.Initialize
        _myAwesomeFeatureCommand = clientController.Commander.GetCommand(Of MyAwesomeFeatureCommand)
    End Sub

    Public Sub LoadView() Implements ICommandView.LoadView
        'Here you can put code which loads something you need. This void will be called when the user clicks on the command for the first time
    End Sub

    Public ReadOnly Property Name As String Implements ICommandView.Name
    Public ReadOnly Property Category As Category Implements ICommandView.Category
End Class

Okay, let me explain the code above which might be a little bit difficult. The two properties Name and Category define the appearance of your command view in the action list:

Initialize is called directly after creation and allows you to take everything from the IClientController. One thing you should do is to store your Command in a variable. You can easily do that using the ICommander as you can see in the sample code.

If you are not familiar with MVVM, the RelayCommand might confuse you. But it's not that complicated: We will create a button in the view and when the user clicks on the button, the code from the RelayCommand will be executed. As you can also see, we access the property CustomText but never set it. We don't need to. In the view, we'll create a TextBox which sets it's TextBox.Text property to our CustomText property.

Let's go to our last element, the View:

<UserControl x:Class="MyAwesomeFeature.View"
d:DataContext="{d:DesignInstance local:ViewModel}"
<StackPanel Width="200"
<TextBox Text="{Binding CustomText}" />
<Button Margin="0,5,0,0"
Command="{Binding ButtonClickCommand}"
Content="Send Text" />

The first thing to note is the part d:DataContext="{d:DesignInstance local:ViewModel}". It just tells the auto complete feature of Visual Studio (IntelliSense) that the DataContext has the type local:ViewModel. local is a namespace (like imports in the code) and points to the default namespace of our project: xmlns:local="clr-namespace:MyAwesomeFeature". Because your project will have another name, you have to change this part.

XAML is really like to write HTML but it's much easier because IntelliSense supports you. The first element in the UserControl is a <Grid>...</Grid>. A grid allows infinity children and they can set their position by themself. We add a StackPanel with a width of 200 which is horizontally and vertically centered in the Grid. The StackPanel stacks it's childs vertically (because we've set the Orientation to Vertical). The top element is a TextBox. As you can see, the Text-Property is bound to our CustomText property. That means that they will be equal. If we set the CustomText property in our code, the TextBox will apply that and if the user inputs text into the TextBox, CustomText will apply that.

The next element is a button. We set the Margin to 0,5,0,0 which means that the space between the element above and the button is 5 units great. We bound the Command property of the button to our ButtonClickCommand which means that it will get executed when someone clicks on the button. Here you can see how the XAML code looks in the WPF designer:

Here you can see how it would look like in the administration:

You can see that the final style will be applied in the administration. The name of the theme the administration uses is mahapps.metro

The client command

The client command will be much easier than the administration part, I swear ;). As mentioned above, the client plugin must contain a class which inherits Command.

We create a new class in the MyAwesomeFeature.Payload project called MyAwesomeFeatureCommand. Please don't just name it Command or Plugin because the name of it will be included in the exception report and it's easier to resolve your plugin if your command has a strong name. Please don't forget to make the class public:

Imports System.Text
Imports System.Windows.Forms
Imports Orcus.Plugins

Public Class MyAwesomeFeatureCommand
    Inherits Command

    Public Overrides Sub ProcessCommand(parameter() As Byte, connectionInfo As IConnectionInfo)
        Dim receivedText = Encoding.UTF8.GetString(parameter)
    End Sub

    Protected Overrides Function GetId() As UInteger
        Return 23894873
    End Function
End Class

In GetId(), we return the same id like we did in the command for the administration. Now we just process the received bytes. In the sample code above, the string gets decoded and displayed in a MessageBox. That's it.

Some tips

  • You can use the Orcus.Shared.NetSerializer.Serializer to serializer the objects you want to send. It's optimized to make the data as small as possible
  • If you want to make a little bit more complex plugin, you can use an enum to seperate the different calls. Every first byte of the byte arrays you send would be the enum so you can easily identify the package. Just take a look at the sample plugins.

Sample Projects