Fixing Access Denied Issue with Adding Images to your Site Feed

Applies To: SharePoint 2013

The SharePoint Site Feed can be an easy tool for quick discussion and sharing (especially if you don’t have Yammer) and is included in several site templates by default. I ran into an issue that surprised me when attempting to post a reply. I had no trouble posting links and messages, but trying to add an image (clicking the camera icon) gave me an Access Denied error. Huh?

Turns out this isn’t an issue with the user’s account but rather with the App Pool Identity account. Taking a look at the documentation for Site Feeds and you’ll see this sentence:

“In SharePoint Server 2013, we recommend that the same service account be used for both the My Site host web application and the web application hosting the team sites.”

Well, crap. Turns out that I used different app pool accounts for the My Site web application vs my standard web application. So do we need to switch one or the other? Nope! Thankfully, I came across this helpful bit of powershell that will fix this issue for you:

$wa = Get-SPWebApplication "http://my.wirebear.local"
$wa.GrantAccessToProcessIdentity("wirebear\SP_Pool")

You’ll need to run this from the SharePoint Management Shell or use the Add-PSSnapin Microsoft.SharePoint.PowerShell command.

The first line gets the web application where your My Sites are hosted using the Get-SPWebApplication command. Obviously, you’ll need to specify your My Site host web application’s URL or name (instead of mine).

The second line is where the magic happens. The GrantAccessToProcessIdentity method grants permission to the specified App Pool account (use the service account running your problem web application’s app pool).

That’s it. No need to reset IIS or anything else. Just refresh the page with Site Feed on it and now you can upload photos. Here’s one to get you started:

581669_10200896834489112_601749778_n

Autosave in Visio

Applies To: Microsoft Visio

Did you know that unlike EVERY OTHER Microsoft Office program, Visio does NOT have autosaving turned on by default? If so, you probably found out the hard way like me.

Making Visio diagrams comes up pretty often when documenting workflows, server configurations, etc. Each of these diagrams always takes me far longer than it should and losing one that I have been working on makes we weep uncontrollably naked in the corner for 4+ hours. Here, I made a diagram:

Autosave Diagram

 

Generally I’m slapping Ctrl-S like it’s a gopher in one of those gopher slapping games or in some other situation where frequent slapping occurs. But for whatever reason when I’m trying to get those lines perfectly aligned to the grid or I’m making minor font tweaks, etc. I tend to lose my rhythm. Then if an errant update, a crash, or me dumbly closing the wrong window while ignoring the warning dialogs puts an end to my precious diagram, I go searching for the auto recovery files just like I would with Word or Excel.

But if you haven’t manually configured this, they are no where. All you’ll find in your AppData folder are shape caches and regret.

Turning on Visio Autosave

These instructions are for Visio 2013 but should generally work for 2010 and 2016 as well.

  1. Open Visio
  2. Click the File tab/menu in the upper left
  3. Choose Options
    FileMenu
  4. In the Options dialog, choose the Save section from the navigation on the left
  5. Check the box next to Save AutoRecover information every X minutes
    SaveOptions
  6. Click OK
  7. Reap the benefits of lower blood pressure

I’ve set mine to 5 minutes since I’m extra paranoid (The standard setting for other office programs is 10 minutes).

Go do this before you forget. It’s too late for me, perhaps you’ll do better.

post-36423-earn-this-gif-earn-it-Tom-Hank-nFkP

Debugging Sitecore dlls

Applies To: Sitecore, ASP.NET, Visual Studio

Recently I was experimenting with Sitecore’s Linq based search. I was having an issue with my facets (perhaps if I ever solve it I’ll write about that too) and I wanted to look into how these were actually created within the Sitecore code.

Using .NET Reflector I was able to decompile the Sitecore.ContentSearch.Linq dll and take a look at the code. (You can do this in the .NET Reflector Object Browser within Visual Studio if you have the .NET Reflector plugin installed). In order to debug it I had to select the Debug option in the same window. This generates the proper symbols and tells Visual Studio to use them. This is really awesome and you can begin walking through your code (Attach to Process w3wp.exe).

Unfortunately, I was unable to see the values of any variables. When attempting to evaluate them I was getting:

Cannot obtain value of local or argument ‘[varablename]’ as it is not available at this instruction pointer, possibly because it has been optimized away.

This is one of those rare cases where the message was actually related to the cause. In order to see those values of the decompiled Sitecore dlls and any other dlls in .NET you need to disable optimization.

“Great!” you shout while working alone in a darkened room full of loneliness. A quick trip to Google reveals you simply need to create an ini file with the same name as the dll with the following values:

[.NET Framework Debugging Control]
GenerateTrackingInfo=1
AllowOptimize=0

In my case I named this file Sitecore.ContentSearch.Linq.ini. Super easy, wow! But then nothing happens. Placing this file next to the dll file in the bin directory won’t accomplish anything. You will need to place it where the dll is actually living while the website is running.

To find this special place, attach to the process like normal and then choose DEBUG > Windows > Modules form within Visual Studio. This will show you all the dlls that are being used. Scroll down to the dll you want to target and copy the path (it’s probably somewhere in the Temporary ASP.NET Files) and open it in Windows Explorer. Copy your ini file there.

Stop debugging and re-attach to the process. Now you can see the values! Soon you will see the issue is probably your own code, but isn’t it nice to pretend it’s somebody else’s fault? You bet it is!

Hakin

Now you big time hacker!

Using SharePoint RPC to Create Directories

Applies To: SharePoint, VB.NET

SharePoint provides many ways to create directories and to upload documents. One of the oldest, and possibly least understood, ways is to use the SharePoint Foundation RPC Protocol.

Generally, using RPC can be more trouble than it’s worth. However, RPC performs and scales well. It supports uploading your content and setting the metadata in one call. You can also use streams rather than just byte arrays. There are a few other reasons why you might choose RPC over the more common solutions, but I’ll assume you know what you’re trying to accomplish.

You can find a decent overview of options by Steve Curran that might give you a little more insight. If you’re still convinced RPC is the way to go, follow along!

In this post I’ll give you some basics about executing RPC methods and demonstrate by showing you how to create multiple directories in a single call.

Getting Started with RPC

The most frustrating part of working with RPC can be trying to track down working examples. In addition, the documentation is pretty sparse. It can also be difficult to find out exactly how you should be encoding your commands and exactly which part(s) should be encoded. Ultimately, you are just making an HTTP POST, but figuring out the correct payload can take a lot of trial and error. Especially since RPC is a little light on helpful error messages.

I’ve broken things down into several utility functions that should help keep things relatively simple and eliminate a lot of the low level troubleshooting that can slow you down.

I’m using VB.NET because the project I initially integrated these calls into was written in VB.NET. Nearly every example I saw out there was in C# and it shouldn’t be too hard to translate my code as needed. Should you have any difficulty, just leave a comment below. I have also placed all of my code inside a Module named SPUploader for convenience.

Basic Encoding Functions

Imports System.Net
Imports System.Text
Imports System.Web
Imports System.IO
Public Module SPUploader

    Public Function EncodeString(value As String) As String
        Return HttpUtility.UrlEncode(value).Replace(".", "%2e")
    End Function

    Private Function escapeVectorCharacters(value As String) As String
        Return value.Replace("\", "\\").Replace(";", "\;").Replace("|", "\|").Replace("[", "\[").Replace("]", "\]").Replace("=", "\=")
    End Function

Above are just a couple of simple functions that help to prepare strings. The characters that need to be escaped and the way in which certain parts are encoded can be difficult to sort through in RPC. We’ll be using these both quite a bit.

The EncodeString function uses the standard UrlEncode method with one additional encoding for periods. Some RPC methods don’t seem to have a problem with periods, but they all work with encoded ones. The escapeVectorCharacters function escapes the following characters \;|[]=

RPC Method Helpers

RPC methods are called using the method name, exact SharePoint version, service name and then any parameters. For instance, the create url-directories method that we will be using to create directories should be called like this:

method=create url-directories:server_extension_version&service_name=/&urldirs=list_of_url_directories

This presents a few challenges. First, we need the exact version of SharePoint before we make any calls. Hardcoding this is just asking for trouble. Second, what is the service_name? (Hint: It generally doesn’t matter and can almost always be left as /) and finally what are the parameters and how should those be included?

We’ll get to the specifics of the create url-directories method, but first let’s look at a series of functions that simplify how we call RPC methods in general:

    Public Function SharePointVersion(sharepointURL As String) As String
        Using client As New WebClient()
            client.UseDefaultCredentials = True
            client.DownloadString(sharepointURL)
            Return client.ResponseHeaders("MicrosoftSharePointTeamServices")
        End Using
    End Function

I adapted the above function from Joshua on Stackoverflow. This is a quick call to SharePoint that will give you that exact version string needed in all RPC methods. The general idea is that you can call this before an RPC method and cache the result for additional calls.

Here’s a helper method that takes care of building the properly encoded method string:

    Private Function methodValue(method As String, SPVersion As String) As String
        Return EncodeString(String.Format("{0}:{1}", method, SPVersion))
    End Function

This function simplifies generating the method:server_extension_version portion of the command.

Helper Class: RPCParameter

When dealing with additional parameters for methods (anything beyond method and service_name), the encoding of those parameters can get a little tricky. I’ve written a helper class called RPCParameter that can help smooth this trickiness:

Imports System.Web
Public Class RPCParameter
    Public Key As String
    Public Value As String = String.Empty
    Public IsMultiValue As Boolean = False
    Public Encode As Boolean = True

    Public Sub New(_key As String, _value As String, Optional _isMultiValue As Boolean = False, Optional _encode As Boolean = True)
        Key = _key
        Value = _value
        IsMultiValue = _isMultiValue
        Encode = _encode
    End Sub

    Public Function IsValid() As Boolean
        Return Not String.IsNullOrEmpty(Key)
    End Function


    Public Overrides Function ToString() As String
        If IsMultiValue Then
            Return String.Format("{0}=[{1}]", Key, IIf(Encode, SPUploader.EncodeString(Value), Value))
        Else
            Return String.Format("{0}={1}", Key, IIf(Encode, SPUploader.EncodeString(Value), Value))
        End If
    End Function
End Class

In general, parameters come exactly as you’d expect with a key=encodedvalue. However, there are some variations that can complicate things. This object may seem a little strange but it will become more obvious when we actually see it used. By default, a simple RPCParameter object is just a Key Value Pair with a custom ToString override that outputs key=encodedvalue.

There are some additional properties, however, that can customize this behavior. You can turn off encoding for the value by specifying the Encode property as false. The IsMultiValue property will insert the square brackets before encoding the value. This is important because often the values need to be encoded, but not the brackets.

Generating the Command

Every RPC method is just a string of parameters (Command String) that we convert to a byte array to upload as part of an HTTP POST. Here are a series of overloaded functions to help generate that command string into a byte array:

    Public Function CommandBytes(method As String, SPVersion As String, parameter As RPCParameter, Optional serviceName As String = "/") As Byte()
        Return Encoding.UTF8.GetBytes(CommandString(method, SPVersion, parameter, serviceName))
    End Function

    Public Function CommandBytes(method As String, SPVersion As String, parameters As List(Of RPCParameter), Optional serviceName As String = "/") As Byte()
        Return Encoding.UTF8.GetBytes(CommandString(method, SPVersion, parameters, serviceName))
    End Function

    Public Function CommandString(method As String, SPVersion As String, parameter As RPCParameter, Optional serviceName As String = "/") As String
        Dim parameters As New List(Of RPCParameter)
        parameters.Add(parameter)
        Return CommandString(method, SPVersion, parameters, serviceName)
    End Function

    Public Function CommandString(method As String, SPVersion As String, parameters As List(Of RPCParameter), Optional serviceName As String = "/") As String
        Dim command As New StringBuilder
        command.AppendFormat("method={0}&service_name={1}", methodValue(method, SPVersion), EncodeString(serviceName))
        For Each parameter As RPCParameter In parameters
            If parameter.IsValid Then
                command.AppendFormat("&{0}", parameter.ToString)
            End If
        Next
        Return command.ToString
    End Function

The CommandBytes functions (lines 27-33) encode the result string into a byte array and allow you to specify either a single RPCParameter object or a List of RPCParameter objects.

The CommandString fuctions actually build the string (lines 35-50). The only difference between them is that if you specify a single parameter, it gets converted to a List of parameters.

The real work for all 4 of these functions occurs in the final CommandString function (lines 41-50). We build the initial method=method&service_name=/ string that is used for every RPC method (line 43). Notice that we use our methodValue helper function to simplify things and and we always encode the service_name value.

Finally, we loop through the RPCParameter objects and append them if they have keys (IsValidusing the RPCParameter.ToString call that takes into account our individual encoding preferences.

So now that we can build the Command String, how do we actually send it to the server?

Executing an RPC Method

Executing an RPC method is really just sending an HTTP POST to a specific dll using the Command String as the payload. This can be easily done using a WebClient object:

    Public Function ExecuteRPC(webURL As String, data As Byte(), Optional creds As NetworkCredential = Nothing) As String
        Dim result As String = String.Empty
        If creds Is Nothing Then creds = CredentialCache.DefaultCredentials

        Using client As New WebClient With {.UseDefaultCredentials = False, .Credentials = creds}
            client.Headers("Content") = "application/x-vermeer-urlencoded"
            client.Headers("X-Vermeer-Content-Type") = "application/x-vermeer-urlencoded"
            client.Headers("user-agent") = "FrontPage"
            result = Encoding.UTF8.GetString(client.UploadData(webURL & "/_vti_bin/_vti_aut/author.dll", "POST", data))
        End Using

        Return result
    End Function

The ExecuteRPC function creates a WebClient object and sets the Content, X-Vermeer-Content-Type, and user-agent headers (lines 56-59). The main action happens in line 60 when we call the UploadData method causing the POST to the author.dll (there are additional dlls that can be used depending on your method, but this was the only one I’ve needed so I left it this way for simplicity) using the byte array we generate from the CommandString functions.

So far all we’ve done is setup all the helper functions so that we can call generic RPC methods. Any confusion on how we take advantage of all this code should clear up once we look at actually executing a real method.

Creating Directories

Our goal is to create a directory (or more often, multiple directories) using the create url-directories RPC method. We want to be able to take a folder path and ensure every folder in that path exists or is created. For instance, given the folder path “Some Folder/Sub Folder 1/Some Other Folder” we need to potentially create 3 directories in a single RPC method call.

The create url-directories method has only one parameter: urldirs. This parameter is an array of directories, along with properties for each, that we would like created if they don’t already exist. For our purposes, we’re just going to create standard folders without specifying any additional properties. Here’s what the code looks like:

    Public Function CreateFolder(webURL As String, libraryName As String, folderPath As String, SPVersion As String, Optional creds As NetworkCredential = Nothing) As String
        If Not String.IsNullOrEmpty(folderPath) Then
            folderPath = folderPath.Trim("/")

            'create url-directories method: http://msdn.microsoft.com/en-us/library/ms431862(v=office.14).aspx
            Return ExecuteRPC(webURL, CommandBytes("create url-directories", SPVersion, New RPCParameter("urldirs", folderPathToURLDirectories(libraryName, folderPath), True)), creds)
        End If
        Return "folderPath is Empty!"
    End Function

    Private Function folderPathToURLDirectories(libraryName As String, folderPath As String) As String
        Dim directories As New StringBuilder
        Dim parent As New StringBuilder

        folderPath = folderPath.Trim("/")
        libraryName = libraryName.Trim("/")

        parent.Append(libraryName & "/")

        Dim folders As String() = Split(folderPath, "/")
        For Each folder As String In folders
            directories.Append("[url=")
            directories.Append(parent.ToString & escapeVectorCharacters(folder))
            directories.Append(";meta_info=[]]")
            parent.Append(escapeVectorCharacters(folder) & "/")
        Next

        Return directories.ToString
    End Function

The CreateFolder function takes a web URL (this does not have to be the root site in a site collection), the name of the library in which to create the folder(s), the folderpath, the SharePoint version, and optionally the credentials to be used during the call (if not specified, the default credentials are used).

If the folderPath isn’t a blank string (line 67), then we strip off any trailing forward slashes (line 68). We then call our ExecuteRPC function using the bytes generated by calling the CommandBytes function using the method create url-directories.

In our ExecuteRPC function call we pass a single RPCParameter object. This is the urldirs parameter and we specify that it is a MultiValue parameter (this will ensure we have the required square brackets around the value). We set the value of the RPCParameter to the result of the folderPathToURLDirectories function. Finally we return the result from the server.

The folderPathToURLDirectories function is used to build our urldirs parameter. Directories need to be created in the proper order (parents first) and each need to have their full path (including the libraryName) in the form [url=path;meta_info=[]]. So if we have the path “Some Folder/Sub Folder 1/Some Other Folder” for the library Documents we want to end up with:

[url=Documents/Some Folder;meta_info=[]][url=Documents/Some Folder/Sub Folder 1;meta_info=[]][url=Documents/Some Folder/Sub Folder 1/Some Other Folder;meta_info=[]]

We do this by splitting the folderPath (line 85) and tracking the parent (including the libraryName) as we create each entry (lines 86-91).

 

That’s it! Now we can create a bunch of directories in SharePoint using the RPC Protocol! WOWEE! Stay tuned for my next post where we will add to our code to allow us to upload documents and set metadata all within a single call!

Construct 2 in WinForms

Applies To: Construct 2, CefSharp, VB.NET, WinForms, jsMessage

In my previous post, Embedded Chromium in WinForms, I walked you through creating a simple WinForms application that will load local html resources into an embedded Chromium browser using CefSharp. This article will build on that application to show you how to host Construct 2 games inside your WinForms application. Additionally, I will show you how to use my Construct 2 jsMessage plugin to communicate directly from .NET code to your running game.

Hosting a Construct 2 Game

For this article I will be using the jsMessageTest Basic game available on the jsMessage CodePlex site as an example download. You can use either the paid or free versions of Construct 2 to create the resources but you will need to install the jsMessage plugin. More details about this plugin and about the example game we’ll be using can be found in my posts, Introducing jsMessage for Construct 2 and jsMessage Basic Example.

You can also just use your own game or one of the many sample games that comes with Construct 2. However, you won’t be able to follow along with the Sending/Receiving messages section of this post without the jsMessage plugin.

Building the Game

Open the jsMessageTest Basic.capx in Construct 2. Choose File > Export project… In the dialog, choose HTML5 website and click Next:

ExportC2

In the Export Options dialog choose the location as a C2 folder inside your application’s directory (mine is bin/x64/debug/C2). You also need to uncheck the Minify script checkbox. I’m unsure of the reason but currently you will receive an error in the console if you attempt to load a game that has been minified using C2. I suspect this is an issue with the version of Construct 2 I am running but it could easily be CefSharp. Either way, uncheck the box for now and click Next:

C2ExportOptions

Choose Normal Style in the template options and click Export.

Loading the Game

Assuming you are using the same WinForms project created in my Embedded Chromium in WinForms post you can just switch the address in the browser constructor to local://C2/index.html

Go ahead and run the application. If the game shows up for you, fantastic!

Unfortunately, I get a blank screen when using the default settings. I’m able to fix this by disabling the GPU hardware acceleration.  This is a known issue with certain versions of Chromium when paired with specific drivers/hardware. If you have this issue, you can easily pass Chromium Command Line Switches using the CefSettings object in CefSharp. We can do this by adding this line right before we call the Cef.Initialize function in our Form constructor:

        settings.CefCommandLineArgs.Add("disable-gpu","1")

When you run the application it should look similar to the following:

C2inWinForms

This “game” doesn’t have much to offer without some additional plumbing (see below) but it is fully interactive. If you were to switch it out for a platformer game or something similar you would see that all the key presses, clicks, etc. are all passed just like you’d expect!

Sending Messages to Construct 2

The jsMessageTest Basic game was built with the jsMessage plugin. This plugin allows the game to respond to jQuery events and to trigger events of its own. You can find a lot more detail about how this works with this game in my post, jsMessage Basic Example.

It’s really pretty straightforward if you’re familiar with jQuery events so we won’t be spending much time on explaining it. Suffice it to say we are going to be injecting JavaScript in to our browser that will allow us to interact directly with the game from the code.

I’ve added a GroupBox labeled Send Messages and inside I’ve put a TextBox called txtMessageToSend and a Button called btnSendMessage. Here’s the code for the btnSendMessage Click EventHandler and the helper sub SendMessage:

    Private Sub btnSendMessage_Click(sender As Object, e As EventArgs) Handles btnSendMessage.Click
        If Not String.IsNullOrEmpty(txtMessageToSend.Text) Then
            SendMessage(txtMessageToSend.Text)
            txtMessageToSend.Text = String.Empty
        End If
    End Sub

    Private Sub SendMessage(Message As String)
        If browser IsNot Nothing Then
            addActivity(String.Format("MESSAGE: {0}", Message))
            browser.ExecuteScriptAsync(String.Format("$(document).trigger('CKjsMessageSend','{0}');", Message))
        End If
    End Sub

In the Click EventHandler (lines 49-54) we’re just making sure there is a message to send, calling the SendMessage sub and clearing the txtMessageToSend box.

The SendMessage sub is doing the actual interesting work. First, we verify the browser is setup. Then we use our addActivity sub to log the message. Finally, we call the ExecuteScriptAsync method which allows us to execute JavaScript directly on the page within our browser. This JavaScript triggers the CKjsMessageSend event with our message as the parameter (this is the format expected by the jsMessage plugin).

Run the application, type something in the box and click Send and you should have something like the following:

InitialMessageSend

Receiving Messages from Construct 2

Construct 2 can send messages via jQuery events using the jsMessage plugin. We can easily register a JavaScript function to be performed when that event is triggered. But how do we respond to that with .NET code?

CefSharp provides the ability to expose a .NET class to JavaScript. This is totally awesome. There are some limitations regarding the complexity of the objects and their return types, etc. all of which you can find on their project page. For our purposes, we just need a simple proxy object that can accept messages and route them.

Add another class to your project called MessageReceiver.vb and copy/paste the following code into it:

Public Class MessageReceiver

    Private logReceiver As Action(Of String)

    Public Sub New(LogReceiverAction As Action(Of String))
        logReceiver = LogReceiverAction
    End Sub

    Public Sub log(Message As String)
        logReceiver(String.Format("RECEIVED: {0}", Message))
    End Sub

End Class

This is not particularly exciting code but it should illustrate what is possible. It should also look somewhat familiar if you followed the steps to make our LogDialogHandler object in the last article. In our constructor (lines 5-7) we accept an Action(Of String) which we will use to handle our logging. We store this Action into our private logReceiver object (line 6) so that we can use it later.

There is just one method, log, which takes a string, adds “RECEIVED:” to the front of it and calls the logReceiver action. I’ve lowercased this method to match what will happen once exposed to JavaScript. CefSharp automatically changes methods and properties into JavaScript-casing (the first letter is downcased). I find it less confusing to just do that directly in the object.

Now we just need to register our object into our browser. We can do this once the browser is initialized using the RegisterJsObject method. Here is the line of code to do that in the Form constructor right after setting up our JsDialogHandler:

browser.RegisterJsObject("messageReceiver", New MessageReceiver(New Action(Of String)(AddressOf addActivity)))

The RegisterJsObject takes 2 parameters: The name we want to use in JavaScript for the object and the object itself. In our case we want it called messageReceiver (this will be a global object) and we just create a new instance of our MessageReceiver pointing the logReceiver Action to our addActivity method.

Go ahead and run the project and click the DevTools button. Switch to the console and start to type messageReceiver. You’ll find that Chrome’s autocomplete recognizes that there is a global messageReceiver object. If you call the messageReceiver.log function with a string you’ll see it show up in the Activity feed:

messageReceiver

Now we just need to tell jQuery to call this function when receiving a message from the Construct 2 game. We do this by using the ExecuteScriptAsync method we used earlier when sending messages.

However, we have to make sure the game is loaded before we insert the event handler or it won’t take effect. We can do this by taking advantage of the browser’s IsLoadingChanged event. Add the following line to your Form constructor right after our RegisterJsObject call:

AddHandler browser.IsLoadingChanged, AddressOf onBrowserIsLoadingChanged

So now let’s add the onBrowserIsLoadingChanged sub to our Form code:

    Private Sub onBrowserIsLoadingChanged(sender As Object, e As CefSharp.IsLoadingChangedEventArgs)
        If e.IsLoading = False Then
            browser.ExecuteScriptAsync("$(document).on('CKjsMessageReceive',function(e,m){messageReceiver.log(m);});")
            RemoveHandler browser.IsLoadingChanged, AddressOf onBrowserIsLoadingChanged
        End If
    End Sub

The IsLoadingChanged event provides us with a helpful event argument that tells us if the Browser is loading or not. We verify that it is no longer loading then inject our JS event handler and remove the .NET event handler from the IsLoadingChanged event (since we only need to call this once).

Run the application and type a message in the game textbox and click the jsMessage plugin icon (the turquoise speech bubble) and you’ll see that message come into the Activity feed:

C2Received

 

You now have all the basic plumbing in place to host a Construct 2 game directly in your WinForms application and to be able to send and receive messages directly from the game! This opens up a wide range of possible applications. I wrote all of this for an integrated project I’m working on, but I hope you find it helpful too!

Embedded Chromium in WinForms

Applies To: WinForms, VB.NET, CefSharp

Getting Chromium to show up inside a .NET WinForms application is relatively easy using CefSharp. CefSharp is an open source project which provides Embedded Chromium for .NET (WPF & WinForms). It’s a great way to get Chrome hosted inside of your .NET application.

You can get a simple browser up and running in 5 minutes – which I’ll show you first. There are some additional steps required to use local resources and to handle console messages and dialog prompts which I’ll also show you.

Embedding a Chromium Browser

There’s an easy example over on the CefSharp project page called the MinimalExample that will get you up and running quickly with a bunch of the basics. I’m going to walk you through some very quick steps to just get an embedded browser working in a WinForm.

Getting CefSharp

First, create a new WinForms project. I’m using VB.NET (but C# works great too, of course) and am targeting .NET 4.5. Once you’ve got a project, you’ll need to add the CefSharp binaries. This is done through NuGet and is really simple.

In Visual Studio choose PROJECT > Manage NuGet Packages… In the Package Manager window switch to Online and search for CefSharp. You’ll select CefSharp.WinForms (My version is 37.0.0 but the package should always have the latest stable release) and choose Install:

CefSharpWinFormsNuGet

This will take just a few seconds while it also adds all of the dependent packages. In the end you’ll have 4 packages (CefSharp.WinForms, CefSharp.Common, cef.redist.x64 and cef.redist.x86).

Initial Build

CefSharp doesn’t support the default AnyCPU build configuration. You’ll need to choose BUILDConfiguration Manager… Then change the Active solution platform to either x64 or x86 (just choose new and it’ll walk you through it). Here’s what my Debug configuration looks like:

ConfigurationManager

Go ahead and build the project to make sure there are no reference errors.

Adding the Browser

Open your form and slap a Panel control on there (I’ve named mine panBrowser and set it’s Dock property to Fill). This isn’t required, but it certainly makes it easier to move around when changing your form later.

Switch to the Form’s code and go to the New sub (Constructor in C#). Here’s the code:

Imports CefSharp.WinForms
Imports CefSharp

Public Class Form1

    Private WithEvents browser As ChromiumWebBrowser

    Public Sub New()
        InitializeComponent()

        Dim settings As New CefSettings()
        CefSharp.Cef.Initialize(settings)

        browser = New ChromiumWebBrowser("http://thechriskent.com") With {
            .Dock = DockStyle.Fill
        }
        panBrowser.Controls.Add(browser)

    End Sub
End Class

Be sure to make the appropriate references above (lines 1-2). In this code, we have a ChromiumWebBrowser object (line 6) that we create with a default address and a dock style of Fill (lines 14-15). We then add that control to the Panel we added above. The only other thing we need to do is to call the Cef.Initialize function (line 12). We’re just passing default settings for now (line 11). Run it and you should see something similar to this:

WinFormsBasicBrowser

Congratulations, you’ve got Chrome in your Form!

Loading From Local Resources

In a previous article, Use Local Files in CefSharp, I showed you how to make the necessary objects to register a CefCustomScheme that would load resources from the local file system. I also posted a similar article, Use Embedded Resources in CefSharp, to demonstrate how to load files directly from your project’s manifest. Those articles will give you more detail about how to do this. If you’re following along in C# just head over to that article and copy the objects. Otherwise, here they are in VB.NET:

LocalSchemeHandler

Add a new Class to your project called LocalSchemeHandler.vb and copy/paste the following:

Imports CefSharp
Imports System.IO
Public Class LocalSchemeHandler
    Implements ISchemeHandler

    Public Function ProcessRequestAsync(request As IRequest, response As ISchemeHandlerResponse, requestCompletedCallback As OnRequestCompletedHandler) As Boolean Implements ISchemeHandler.ProcessRequestAsync
        Dim u As New Uri(request.Url)
        Dim filepath As String = u.Authority & u.AbsolutePath

        If File.Exists(filepath) Then
            Dim bytes As Byte() = File.ReadAllBytes(filepath)
            response.ResponseStream = New MemoryStream(bytes)
            Select Case Path.GetExtension(filepath)
                Case ".html"
                    response.MimeType = "text/html"
                Case ".js"
                    response.MimeType = "text/javascript"
                Case ".png"
                    response.MimeType = "image/png"
                Case ".appcache" OrElse ".manifest"
                    response.MimeType = "text/cache-manifest"
                Case Else
                    response.MimeType = "application/octet-stream"
            End Select
            requestCompletedCallback()
            Return True
        End If
        Return False
    End Function

End Class
LocalSchemeHandlerFactory

Add another new Class to your project called LocalSchemeHandlerFactory.vb and copy/paste the following:

Imports CefSharp
Public Class LocalSchemeHandlerFactory
    Implements ISchemeHandlerFactory

    Public Function Create() As ISchemeHandler Implements ISchemeHandlerFactory.Create
        Return New LocalSchemeHandler
    End Function

    Public Shared ReadOnly Property SchemeName() As String
        Get
            Return "local"
        End Get
    End Property

End Class
Registering the Scheme

To tell the browser to use the LocalSchemeHandler we just need to adjust our settings object before the Cef.Initalize function from our Form constructor above:

        Dim settings As New CefSettings()
        settings.RegisterScheme(New CefCustomScheme() With {
                                .SchemeName = LocalSchemeHandlerFactory.SchemeName,
                                .SchemeHandlerFactory = New LocalSchemeHandlerFactory
                                })
        CefSharp.Cef.Initialize(settings)
Proving it Works

To make sure everything is working we’ll need to have an actual local resource to load. Open your application’s directory (mine is bin/x64/debug) and create a folder called web. Cut and paste the following into a text file and save it as index.html in that web folder:

<!DOCTYPE html>
<html>
    <body>
        <h1>My First Heading</h1>
        <p>My first paragraph.</p>
        <img src="images/truck.png"/>
        <script type="text/javascript">
            console.log("Hello from the console!");
            //alert("Hello from a dialog!");
        </script>
    </body>
</html>

Create an images folder inside the web folder and save this image as truck.png in there:

truck

None of these files need to be added to your project, they just need to be in the directory with your executable. Now if you switch the address from the browser initialization code above to local://web/index.html and run the project you should see something like this:

LocalResource

Mapping the Console

You might have noticed a <script> tag in our index.html above with a call to the console. CefSharp provides us with an easy event, ConsoleMessage, that allows us to grab those messages and do whatever we want with them. In our case we just want to display those messages in a TextBox.

I’ve added a Groupbox below the panBrowser Panel control from earlier and put a TextBox control called txtActivity inside it. I’ve set the txtActivity properties like so: Dock=Fill, Multiline=True and ScrollBars=Both. Now we just need to add the following code to our Form code right after the constructor:

    Private Sub onBrowserConsoleMessage(sender As Object, e As CefSharp.ConsoleMessageEventArgs) Handles browser.ConsoleMessage
        If e.Line > 0 Then
            addActivity(String.Format("CONSOLE: {0} ({1}|{2})", e.Message, e.Source, e.Line))
        Else
            addActivity(String.Format("CONSOLE: {0}", e.Message))
        End If
    End Sub

    Private Sub addActivity(Message As String)
        If txtActivity.InvokeRequired Then
            txtActivity.Invoke(New Action(Of String)(AddressOf addActivity), Message)
        Else
            txtActivity.AppendText(Message & vbCrLf)
        End If
    End Sub

We’ve added a handler for the ConsoleMessage event (lines 25-31). The ConsoleMessageEventArgs object provides us with three useful properties: Message, Source and Line. We always receive the Message and depending on how the call to the console was made we may receive information about the Source. When the Line is greater than 0 we output the Message and the Source/Line information, otherwise it’s just the Message.

We’ve also added a helper sub, addActivity, to take care of handling displaying the output. This allows us to easily change our handling in the future but it also simplifies thread considerations. Our embedded browser is multithreaded and the ConsoleMessage event generally doesn’t fire on the main thread. This is the reason for the InvokeRequired/Invoke code in lines 34-36. The actual display happens in line 37 where we use the AppendText method to ensure our textbox scrolls appropriately. Run the project and should see something similar to this:

ConsoleActivity

Handling Dialogs

What about dialog messages? If you uncomment the alert command from the index.html above and run the application you’ll see that in WinForms this gets automatically handled with a MsgBox:

dialogMsgBox

In our application we’d like to redirect these to our Activity feed as well. In CefSharp, you override the default handling of dialogs (alerts, confirmations and prompts) by implementing an IJsDialogHandler object.

In our case we just want everything noted in the activity feed and ignored. To do this, add a class to your project called LogDialogHandler.vb and copy/paste the following code into it:

Imports CefSharp
Public Class LogDialogHandler
    Implements IJsDialogHandler

    Private logReceiver As Action(Of String)

    Public Sub New(LogReceiverAction As Action(Of String))
        logReceiver = LogReceiverAction
    End Sub

    Public Function OnJSAlert(browser As IWebBrowser, url As String, message As String) As Boolean Implements IJsDialogHandler.OnJSAlert
        logReceiver(String.Format("ALERT: {0}", message))
        Return True
    End Function

    Public Function OnJSConfirm(browser As IWebBrowser, url As String, message As String, ByRef retval As Boolean) As Boolean Implements IJsDialogHandler.OnJSConfirm
        logReceiver(String.Format("CONFIRM: {0}", message))
        retval = True
        Return True
    End Function

    Public Function OnJSPrompt(browser As IWebBrowser, url As String, message As String, defaultValue As String, ByRef retval As Boolean, ByRef result As String) As Boolean Implements IJsDialogHandler.OnJSPrompt
        logReceiver(String.Format("PROMPT: {0} ({1})", message, defaultValue))
        result = defaultValue
        retval = True
        Return True
    End Function
End Class

The IJsDialogHandler interface requires us to implement the OnJSAlert, OnJSConfirm and OnJSPrompt functions. We’ve also added a construtor that takes an Action(Of String) which we will use to handle our logging. We store this Action into our private logReceiver object (line 8) so that we can use it later. Then in our implementation of the functions we just call logReceiver with an informative message regarding what’s occurred. In each case we return True to indicate that the alert, etc. has been handled.

To use our new object we just add the following line to the end of our Form constructor:

browser.JsDialogHandler = New LogDialogHandler(New Action(Of String)(AddressOf addActivity))

This allows us to direct any message from our LogDialogHandler to our txtActivity box just like our console messages. Now when we run the application we should see something like this:
AlertActivity

DevTools

Anyone that’s used Chrome for web development is very familiar with the F-12 DevTools and knows they can be invaluable in tracking down issues. Wouldn’t it be nice to have those when working within your .NET application? With CefSharp, it’s super easy. I’ve added a simple button underneath my Activity GroupBox called btnDevTools. Here’s the Click EventHandler:

    Private Sub btnDevTools_Click(sender As Object, e As EventArgs) Handles btnDevTools.Click
        browser.ShowDevTools()
    End Sub

That’s it! Now when you push the button you get the full power of the Chrome DevTools all while running in your form:

DevTools

Conclusion

Obviously, there is a lot you can do with this and there are many features we haven’t even begun to demonstrate, but this should get you started. For me the goal has been to get a Construct 2 game running in a .NET application. In my next post I’ll show you exactly that.