Quantcast
Channel: .NET – The Chris Kent
Viewing all 19 articles
Browse latest View live

Prettify Your XML in .NET

$
0
0
Applies To: .NET (C#, VB.NET)

If you do much work with XML in either VB.NET or C# you’re probably looking for a way to control it’s formatting and make it look “pretty”. This has come up a few times for me so I thought I’d share a quick method for doing this.

Most often I’m using this to format XML from Web Services (Mostly SharePoint) or to take a look at XML I’ve generated for Web Services to see what’s wrong. But for this example, I’ve got a couple of helper functions that generate some XML using objects from the System.Xml namespace. Here’s how I generate the XML used here:

    Private Function GetXML() As String
        Dim doc As New XmlDocument
        Dim rn As XmlNode = doc.CreateElement("TMNT")
        Dim sn As XmlNode = doc.CreateElement("Turtles")
        sn.AppendChild(CreateTurtleNode(doc, "Leonardo", "Blue", "Katana"))
        sn.AppendChild(CreateTurtleNode(doc, "Raphael", "Red", "Sai"))
        sn.AppendChild(CreateTurtleNode(doc, "Michelangelo", "Orange", "Nunchaku"))
        sn.AppendChild(CreateTurtleNode(doc, "Donatello", "Purple", "Bo"))
        rn.AppendChild(sn)
        doc.AppendChild(rn)
        Return doc.InnerXml
    End Function

    Private Function CreateTurtleNode(doc As XmlDocument, Name As String, Color As String, Weapon As String) As XmlNode
        Dim tn As XmlNode = doc.CreateElement("Turtle")
        Dim na As XmlAttribute = doc.CreateAttribute("Name")
        na.Value = Name
        tn.Attributes.Append(na)
        Dim ca As XmlAttribute = doc.CreateAttribute("Color")
        ca.Value = Color
        tn.Attributes.Append(ca)
        Dim wa As XmlAttribute = doc.CreateAttribute("Weapon")
        wa.Value = Weapon
        tn.Attributes.Append(wa)
        Return tn
    End Function

This is just sample code to get some unformatted XML and if you display the results of the GetXML function, here’s what you get:

<TMNT><Turtles><Turtle Name=”Leonardo” Color=”Blue” Weapon=”Katana” /><Turtle Name=”Raphael” Color=”Red” Weapon=”Sai” /><Turtle Name=”Michelangelo” Color=”Orange” Weapon=”Nunchaku” /><Turtle Name=”Donatello” Color=”Purple” Weapon=”Bo” /></Turtles></TMNT>

This isn’t terrible and if you’re just using this in your code, no worries! But if you want to display this to an end user or even yourself, proper lines and indentation can make a huge difference – especially since your XML is almost guaranteed to be more complex than my example above.

There are some crazy examples out there of reading through the string and manually inserting line returns and spaces when detecting the less than or greater than symbols. These are usually error prone and won’t take into account all the various possibilities for XML. They’re inefficient, ugly, and lame. Fortunately, there are some helpful objects in the System.IO namespace and the System.Xml namespace that make all of this very easy. Here’s the function:

    Private Function PrettyXML(XMLString As String) As String
        Dim sw As New StringWriter()
        Dim xw As New XmlTextWriter(sw)
        xw.Formatting = Formatting.Indented
        xw.Indentation = 4
        Dim doc As New XmlDocument
        doc.LoadXml(XMLString)
        doc.Save(xw)
        Return sw.ToString()
    End Function

I imagine this could be improved (feel free to share in the comments), but it definitely does the job. The key elements are the XmlTextWriter’s properties Formatting and Indentation. There are several other properties and methods you can use to customize even further, but the above produces a fairly nice result:

<?xml version="1.0" encoding="utf-16"?>
<TMNT>
    <Turtles>
        <Turtle Name="Leonardo" Color="Blue" Weapon="Katana" />
        <Turtle Name="Raphael" Color="Red" Weapon="Sai" />
        <Turtle Name="Michelangelo" Color="Orange" Weapon="Nunchaku" />
        <Turtle Name="Donatello" Color="Purple" Weapon="Bo" />
    </Turtles>
</TMNT>

Now all the lines and indentation are there as expected! We even get the nice XML Declaration free of charge.



Change Your Formatted XML’s Encoding

$
0
0
Apples To: .NET (C#, VB.NET)

In my previous post, Prettify Your XML in .NET I showed a method for taking some XML and making it pretty (indentation, new lines, etc.). Using the method also produced the XML Declaration node for us. Unfortunately, because strings are UTF-16 encoded in .NET, the XML Declaration node generated by this method is always listed as “utf-16″ which may not always be what we want.

Here’s the results of the previous post’s prettified XML:

<?xml version="1.0" encoding="utf-16"?>
<TMNT>
    <Turtles>
        <Turtle Name="Leonardo" Color="Blue" Weapon="Katana" />
        <Turtle Name="Raphael" Color="Red" Weapon="Sai" />
        <Turtle Name="Michelangelo" Color="Orange" Weapon="Nunchaku" />
        <Turtle Name="Donatello" Color="Purple" Weapon="Bo" />
    </Turtles>
</TMNT>

As mentioned you can see that encoding=”utf-16″. But what it you want something else (Most likely UTF8)? Well, there are several ways you can do it with Streams, XMLWriter and XMLWriterSettings objects and other junk, but you can also use a neat little method I found on Project 20 which involves subclassing the StringWriter class. (This idea originally comes from Jon Skeet).

So, just add a new class to your project and call it StringWriterWithEncoding or something similar and override the Encoding property. Here is the entire class:

Public Class StringWriterWithEncoding
    Inherits IO.StringWriter

    Private _encoding As System.Text.Encoding

    Public Sub New(encoding As System.Text.Encoding)
        MyBase.New()
        _encoding = encoding
    End Sub

    Public Sub New(encoding As System.Text.Encoding, formatProvider As IFormatProvider)
        MyBase.New(formatProvider)
        _encoding = encoding
    End Sub

    Public Sub New(encoding As System.Text.Encoding, sb As System.Text.StringBuilder)
        MyBase.New(sb)
        _encoding = encoding
    End Sub

    Public Sub New(encoding As System.Text.Encoding, sb As System.Text.StringBuilder, formatProvider As IFormatProvider)
        MyBase.New(sb, formatProvider)
        _encoding = encoding
    End Sub

    Public Overrides ReadOnly Property Encoding As System.Text.Encoding
        Get
            Return _encoding
        End Get
    End Property

End Class

So all we’ve really done is provided constructors that allow us to specify the encoding the StringWriter object should use. Then we’ve overriden the Encoding property to always return the value specified in the constructor. The result is the StringWriter uses our encoding. So then we can take the PrettyXML code and swap the StringWriter object creation to a StringWriterWithEncoding like so:

    Private Function PrettyXML(XMLString As String) As String
        Dim sw As New StringWriterWithEncoding(System.Text.Encoding.UTF8)
        Dim xw As New XmlTextWriter(sw)
        xw.Formatting = Formatting.Indented
        xw.Indentation = 4
        Dim doc As New XmlDocument
        doc.LoadXml(XMLString)
        doc.Save(xw)
        Return sw.ToString()
    End Function

Then when we run our XML through it we get the results we wanted:

<?xml version="1.0" encoding="utf-8"?>
<TMNT>
    <Turtles>
        <Turtle Name="Leonardo" Color="Blue" Weapon="Katana" />
        <Turtle Name="Raphael" Color="Red" Weapon="Sai" />
        <Turtle Name="Michelangelo" Color="Orange" Weapon="Nunchaku" />
        <Turtle Name="Donatello" Color="Purple" Weapon="Bo" />
    </Turtles>
</TMNT>

Automatically Setting Up PDF Icon Mapping in SharePoint 2010

$
0
0
Applies To: SharePoint 2010

Nearly everyone who has ever used SharePoint has had to setup the PDF icon mapping so that PDF documents will have the familiar Adobe logo rather than the blank, unknown icon SharePoint uses by default. This is relatively simple and there are guides to do doing this all over the internet. (Microsoft’s can be found here).

Here is a very brief summay of the steps that must be performed manually on every server:

  1. Copy the PDF icon picture from Adobe and put it in your 14 Hive (TEMPLATE\IMAGES)
  2. Edit the DOCICON.xml file in your 14 Hive (TEMPLATE\XML) to add a Mapping element for pdf documents pointing to your new icon
  3. Reset IIS

These aren’t super complicated steps but there are some pretty big problems (or at least irritations) with using this approach:

  • Manual changes can often be error-prone, especially for those not familiar with XML
  • The change must be performed on every server
  • The change must be performed whenever a new server is added to the farm
  • The change will have to be redone in the event of disaster recovery

So, like many before me, I thought, surely this can all be automated! So I looked and I found some solutions for SharePoint 2007 and several solutions that only worked for Standalone Servers or for only one server in the farm. These were of help, but still no good for my needs. So, I wrote my own.

You can find it over on CodePlex as WireBear PDFdocIcon. There’s some stuff about it’s license over there (Free for personal and commercial use, etc.) and the basic installation instructions. It’s super easy to setup since it’s just a standard SharePoint Solution that you globally deploy.

The full source code is available on CodePlex, but I’ll be going in depth about how it works over the next few posts. But to summarize, here’s what happens:

  • The Adobe PDF icon file is copied to the 14\TEMPLATE\IMAGES folder using standard resource deployment
  • On Activation and Deactivation a one time Service Timer Job is run.
  • On Activation, the Timer job searches for a mapping for PDF documents within the 14\TEMPLATE\XML\DOCICON.xml file. If not found, it adds one (in alphabetic order) and points it to the icon file
  • An IIS Reset is performed to get the changes activated
  • When Deactivating, the Timer job removes the mapping for PDF documents
So why use this thing?
  • The changes will be reapplied in the event of Disaster Recovery
  • The changes will be applied to new servers as they are added to your farm
  • You don’t have to personally edit the 14 Hive on every server in your farm
  • It makes a special place in your heart of hearts that keeps the beast at bay

In making this, I came across several blog entries that were especially helpful, here are most of these (Thanks!):

I’ve found this to be a helpful approach and I hope you do too.


Implementing a Custom SharePoint Timer Job

$
0
0
Applies To: SharePoint 2010

As mentioned in my previous post, I’ve recently put together a solution for automatically configuring your SharePoint servers to use the Adobe PDF icon for PDF files. You can download the solution as well as the source for free from CodePlex here: WireBear PDFdocIcon. I’m going to show some of the code as it currently exists below, but be sure to check out the CodePlex site to ensure you have the latest version.

In order to perform the necessary work on each server in the farm, the PDFdocIcon solution uses a custom Timer Job. This post will focus on the plumbing necessary to setup your own custom timer job that runs on every server in the farm. The actual code to change the DOCICON.XML file will be saved for later.

Choosing Your Job Definition Type

To make your own Timer Job you’ll want to subclass an exisiting Job Definition object and override the Execute method. There are several to choose from, here’s a helpful table:

Job Definitions you can inherit from in the Microsoft.SharePoint.Administration namespace:
SPAdministrationServiceJobDefinition Invokes the SharePoint Administration Service
SPAllSitesJobDefinition Iterates through all sites in a Web Application
SPContentDatabaseJobDefinition Executed per Web Application and each Content Database is processed by individual jobs (Pausable)
SPFirstAvailableServiceJobDefinition Timer Job that runs on the first available server where the specified service exists (Pausable)
SPJobDefinition Base Class for Timer Jobs (Generally, this is the one to use)
SPPausableJobDefinition Timer Job that can be paused
SPServerJobDefinition Executed on a specific server (Pausable)
SPServiceJobDefinition Runs on every server in the farm where the service exists (Pausable) – This is the one I chose
SPWorkItemJobDefinition  Works with the Timer Job to process work items (Pausable)

For simple jobs the SPJobDefinition is the most flexible and is what you’ll generally want to use. For the PDFdocIcon solution, I needed the Timer Job to execute on every server in the farm. So I used the SPServiceJobDefinition and specified the Timer Service.

Storing Persistent Properties

You may not need properties, but if you’re doing anything even mildly complex you probably will. There are a couple of different alternatives here, but basically your properties need to serialize down to strings. You can look up a few examples of custom properties objects that do this, or you can just use my method of storing your properties in the JobDefinition’s Properties object (HashTable).

Here’s how I store the Boolean property _installing:

    Private Const InstallingKey As String = "DocIconJob_InstallingKey"
    Private Property _installing() As Boolean
        Get
            If Properties.ContainsKey(InstallingKey) Then
                Return Convert.ToBoolean(Properties(InstallingKey))
            Else
                Return True
            End If
        End Get
        Set(ByVal value As Boolean)
            If Properties.ContainsKey(InstallingKey) Then
                Properties(InstallingKey) = value.ToString
            Else
                Properties.Add(InstallingKey, value.ToString)
            End If
        End Set
    End Property

Basically, you have a String key for each property that you use to store/retrieve the value from the Properties HashTable. By wrapping those calls in a property you can treat it like a standard variable in the rest of your code and forget all about the specialized storage/retrieval required.

Constructors

You are required to have an empty (parameterless) constructor for serialization, so make sure you’ve got that:

    Public Sub New()
        MyBase.New()
    End Sub

But you will probably need to implement at least a matching constructor with some custom properties. In my Timer Job, I wanted to pass three properties (which I then store using the method above), so I use this:

    Public Sub New(JobName As String, service As SPService, Installing As Boolean, FileExtension As String, ImageFilename As String)
        MyBase.New(JobName, service)
        _installing = Installing
        _fileExtension = FileExtension
        _imageFilename = ImageFilename
    End Sub

Execution

Depending on the base Job Definition class you chose, the Execute method may have a slightly different signature, but either way this is the method to override to provide your own custom logic. In an SPServiceJobDefinition subclass the signature looks like this:

    Public Overrides Sub Execute(jobState As Microsoft.SharePoint.Administration.SPJobState)
        'Custom code here!!
    End Sub

Installing Your Job with a Solution

Using Visual Studio you can create a new Empty SharePoint Project and add your Timer Job class to it. To deploy it you’ll need to add a Feature (Right-click on Features and choose Add Feature). To install your job, you’ll need to add an Event Receiver (Right-click on your new Feature and choose Add Event Receiver).

Uncomment the FeatureActivated and FeatureDeactivating methods. Create a new method (Mine is named RunDocIconJob) with a Boolean and SPFeatureReceiverProperties parameters. This will be the method where we install or uninstall your custom job. In your FeatureActivated and FeatureDeactivating methods call this new method accordingly:

    Public Overrides Sub FeatureActivated(properties As Microsoft.SharePoint.SPFeatureReceiverProperties)
        RunDocIconJob(True, properties)
    End Sub

    Public Overrides Sub FeatureDeActivating(properties As Microsoft.SharePoint.SPFeatureReceiverProperties)
        RunDocIconJob(False, properties)
    End Sub

Then your Job method will look something like this:

    Private _fileExtension As String = "pdf"
    Private _iconFileName As String = "ICPDF.png"

    Public Sub RunDocIconJob(Installing As Boolean, properties As SPFeatureReceiverProperties)
        Dim JobName As String = String.Format("DocIconJob_{0}", _fileExtension)

        'Ensure job doesn't already exist (delete if it does)
        Dim query = From job As SPJobDefinition In properties.Definition.Farm.TimerService.JobDefinitions Where job.Name.Equals(JobName) Select job
        Dim myJobDefinition As SPJobDefinition = query.FirstOrDefault()
        If myJobDefinition IsNot Nothing Then myJobDefinition.Delete()

        Dim myJob As New DocIconJob(JobName, SPFarm.Local.TimerService, Installing, _fileExtension, _iconFileName)

        'Get that job going!
        myJob.Title = String.Format("{0} icon mapping for {1}", IIf(Installing, "Adding", "Removing"), _fileExtension)
        myJob.Update()
        myJob.RunNow()
    End Sub

This is the method I use for my SPServiceJobDefinition. I am not doing any kind of scheduling since this job just runs once on initial deployment and once when being removed. However, you may want to adjust your method to include a schedule (Just set the myJob.Schedule parameter before the Update() call).

Lines 5-10 are finding any existing job definitions that share the same name and deleting them since creating jobs with duplicate names will cause an error. The Title doesn’t have to be unique, but the name does.

Line 12 actually creates the job with my default parameters and then line 15 sets a Title. This is where you would introduce a schedule if you wanted the job to run more than once, but if not just call Update() to save your job. I want my job to run immediately, so in line 17 I call the RunNow() method to do exactly that.

That’s it! You now have a shell for setting up and installing a custom job – specifically one that runs on every server in the farm. My next post will cover what I’m actually doing in the Execution to ensure the DOCICON.xml file is updated appropriately.

Quick Note about testing: In many cases you will need to either restart the Timer Job Service on each server or change your Assembly Version number to get the timer job to pick up any code changes. This doesn’t always happen, but it happens enough to be annoying.

Updating an XML File in the 14 Hive Using a Custom Timer Job

$
0
0
Applies To: SharePoint 2010, .NET Framework (C#, VB.NET)

As mentioned in a previous post, I’ve recently put together a solution for automatically configuring your SharePoint servers to use the Adobe PDF icon for PDF files. You can download the solution as well as the source for free from CodePlex here: WireBear PDFdocIcon. I’m going to show some of the code as it currently exists below, but be sure to check out the CodePlex site to ensure you have the latest version.

I’ve also provided the bulk of the code and some explanation for installing/uninstalling a custom job from a SharePoint solution in my last post: Implementing a Custom SharePoint Timer Job. In this post we’ll explore what’s actually happening in the execution of the timer job.

The goal is to update the DOCICON.xml file in the 14\TEMPLATE\XML folder within the SharePoint 2010 Hive to include or remove a mapping entry for a specific file extension. Here is the entire DocIconJob class:

The Code:

Imports Microsoft.SharePoint.Administration
Imports System.IO
Imports Microsoft.SharePoint.Utilities
Imports System.Xml

Public Class DocIconJob
    Inherits SPServiceJobDefinition

#Region "Properties"

    Private _dociconPath As String
    Public ReadOnly Property DocIconPath() As String
        Get
            If String.IsNullOrEmpty(_dociconPath) Then _dociconPath = SPUtility.GetGenericSetupPath("TEMPLATE\XML\DOCICON.XML")
            Return _dociconPath
        End Get
    End Property

    Private Const InstallingKey As String = "DocIconJob_InstallingKey"
    Private Property _installing() As Boolean
        Get
            If Properties.ContainsKey(InstallingKey) Then
                Return Convert.ToBoolean(Properties(InstallingKey))
            Else
                Return True
            End If
        End Get
        Set(ByVal value As Boolean)
            If Properties.ContainsKey(InstallingKey) Then
                Properties(InstallingKey) = value.ToString
            Else
                Properties.Add(InstallingKey, value.ToString)
            End If
        End Set
    End Property

    Private Const FileExtensionKey As String = "DocIconJob_FileExtensionKey"
    Private Property _fileExtension() As String
        Get
            If Properties.ContainsKey(FileExtensionKey) Then
                Return Convert.ToString(Properties(FileExtensionKey))
            Else
                Return String.Empty
            End If
        End Get
        Set(ByVal value As String)
            If Properties.ContainsKey(FileExtensionKey) Then
                Properties(FileExtensionKey) = value
            Else
                Properties.Add(FileExtensionKey, value)
            End If
        End Set
    End Property

    Private Const ImageFilenameKey As String = "DocIconJob_ImageFilenameKey"
    Private Property _imageFilename() As String
        Get
            If Properties.ContainsKey(ImageFilenameKey) Then
                Return Convert.ToString(Properties(ImageFilenameKey))
            Else
                Return String.Empty
            End If
        End Get
        Set(ByVal value As String)
            If Properties.ContainsKey(ImageFilenameKey) Then
                Properties(ImageFilenameKey) = value
            Else
                Properties.Add(ImageFilenameKey, value)
            End If
        End Set
    End Property

#End Region

    Public Sub New()
        MyBase.New()
    End Sub

    Public Sub New(JobName As String, service As SPService, Installing As Boolean, FileExtension As String, ImageFilename As String)
        MyBase.New(JobName, service)
        _installing = Installing
        _fileExtension = FileExtension
        _imageFilename = ImageFilename
    End Sub

    Public Overrides Sub Execute(jobState As Microsoft.SharePoint.Administration.SPJobState)
        UpdateDocIcon()
    End Sub

    Private Sub UpdateDocIcon()
        Dim x As New XmlDocument
        x.Load(DocIconPath)

        Dim mapNode As XmlNode = x.SelectSingleNode(String.Format("DocIcons/ByExtension/Mapping[@Key='{0}']", _fileExtension))

        If _installing Then
            'Create DocIcon entry
            If mapNode Is Nothing Then
                'Create Attributes
                Dim keyAttribute As XmlAttribute = x.CreateAttribute("Key")
                keyAttribute.Value = _fileExtension
                Dim valueAttribute As XmlAttribute = x.CreateAttribute("Value")
                valueAttribute.Value = _imageFilename

                'Create Node
                mapNode = x.CreateElement("Mapping")
                mapNode.Attributes.Append(keyAttribute)
                mapNode.Attributes.Append(valueAttribute)

                Dim byExtensionNode = x.SelectSingleNode("DocIcons/ByExtension")
                Dim NodeAdded As Boolean = False
                If byExtensionNode IsNot Nothing Then
                    'Add in alphabetic order
                    For Each mapping As XmlNode In byExtensionNode.ChildNodes
                        If mapping.Attributes("Key").Value.CompareTo(_fileExtension) > 0 Then
                            byExtensionNode.InsertBefore(mapNode, mapping)
                            NodeAdded = True
                            Exit For
                        End If
                    Next

                    If Not NodeAdded Then byExtensionNode.AppendChild(mapNode)
                    x.Save(DocIconPath)
                End If
            End If
        Else
            'Remove DocIcon entry
            If mapNode IsNot Nothing Then
                Dim byExtensionNode = x.SelectSingleNode("DocIcons/ByExtension")
                If byExtensionNode IsNot Nothing Then
                    byExtensionNode.RemoveChild(mapNode)
                    x.Save(DocIconPath)
                End If
            End If
        End If
    End Sub

End Class

What’s Going On:

Lines 9-73 are just the declaration of and logic needed to persist some properties. Again more information can be found in my last post, but basically I am using the SPJobDefinition’s Properties HashTable to store my own properties as specified in the constructor. Except for in the case of the DocIconPath property which is really just wrapping up some logic to get a reference to the 14 Hive’s TEMPLATE\XML directory using the SPUtility class.

The Execute method beginning in line 86 is what is called when the Timer Job actually runs. I override this method to ensure my custom code gets called instead. My custom code really begins in the UpdateDocIcon method starting at line 90.

In lines 91-94, I load the DOCICON.xml file into and XmlDocument object and attempt to find the mapping node that applies to the appropriate file extension (In this case it’s going to be pdf).

If this job is installing (Running on Solution Activation), then I just check to see if the node was found. If so, all done! If not, then it’s time to add it. I create the node and setup it’s attributes in lines 100-108 using standard objects from the System.Xml namespace.

In order to work, the mapping node needs to be added as a child of the ByExtension element, so we find that in line 110. By default the mapping nodes are listed in alphabetical order by their extension. Since I’m anal, I use a method in lines 114-120 presented by Steve Goodyear to ensure I insert the mapping node in it’s proper position. Failing that, I add it to the end in Line 122 and save the file in line 123.

If this job is uninstalling (Running on Solution Deactivation) and the mapping exists, we delete it and save the file in lines 128-134.

Isn’t that Super Exciting?!?! Hopefully this example will help make the concepts I was talking about in my previous post make some sense. If not, then sadness will fill my soul and flowers will no longer bloom or something.


Changing the Default Expansion of MetaData Navigation on Initial Page Load

$
0
0
Applies To: SharePoint 2010

We’ve recently begun making fairly heavy use of the MetaData Navigation available for lists. It’s intuitive and easy to use and our users are really liking it. But one minor, but frequently mentioned, irritation brought up by nearly everyone who tried to use the site was that the navigation elements were collapsed by default when they went to the site.

This isn’t a big deal for most people since they get used to where the navigation elements are (just expand the folder), but does create an extra barrier for new users as they try and figure out how to use the site. Microsoft has taken an awesome feature and traded it’s potentially intuitive use to account for some potential performance issues.

The issue seems to be that navigation using taxonomies with several top level items would significantly delay initial page load. This is certainly true, but the solution is not to cripple all uses of MetaData Navigation, the solution is to not use it with those types of taxonomies! Not only would you have had that performance issue on initial expansion anyway, having that many top level items makes for bad navigation. If your taxonomy has several top level items (2000+) then it is either not a good candidate for MetaData navigation or you need to group those items into sub-nodes.

In searching for an answer to this problem I came across this answer on technet by Entan Ming. In it he gives the manual steps to take care of this issue. Here is a brief summary of the steps that must be performed manually on every server:

  1. Open the MetadataNavTree.ascx file in your 14 Hive (TEMPLATE\CONTROLTEMPLATES) using notepad
  2. Change the line ExpandDepth=”0″ to ExpandDepth=”2″
  3. Save the changes and refresh the page(s) with MetaData Navigation

These are easy to do and you can follow them and be done. However, just like PDF Icon Mapping there are some problems with this approach:

  • Manual changes can often be error-prone
  • The change must be performed on every server
  • The change must be performed whenever a new server is added to the farm
  • The change will have to be redone in the event of disaster recovery

So, using the same technique I use for PDF Icon Mapping entries, I’ve created a SharePoint solution to do this automatically.

You can find this solution over on CodePlex as WireBear MetaDataNavExpansion. It is free for personal and commercial use (License here). You can also find basic installation instructions as well. It’s super easy to setup since it’s just a standard SharePoint Solution that you globally deploy.

The full source code is available on CodePlex and I’ll write up another article about what’s really happening, but here’s a general summary:

  • On Activation and Deactivation a one time Service Timer Job is run.
  • On Activation, the Timer Job creates a backup of the MetaDataNavTree.ascx file within the 14\TEMPLATE\CONTROLTEMPLATES folder.
  • The MetaDataNavTree has it’s tree’s ExpandDepth property changed from 0 to 2
  • When Deactivating, the Timer Job restores the original MetaDataNavTree.ascx file

It’s pretty straightforward. It just automates the manual steps listed above. This allows it to be applied automatically to any new servers added to the farm and will be reapplied in the event of disaster recovery.

Here’s what it looks like on initial page load

Standard: With MetaDataNavExpansion:
   
“Der… What do I do?” “WOWEE! This site is amazing!”

Changing a UserControl in the 14 Hive Using a Custom Timer Job

$
0
0
Applies To: SharePoint 2010, .NET Framework (C#, VB.NET)

As mentioned in a previous post, I’ve recently put together a solution for automatically updating the MetaDataNavTree.ascx User Control to default the MetaData Navigation expansion to include the actual taxonomy items. You can download the solution as well as the source for free from CodePlex here: WireBear MetaDataNavExpansion.

This post is very similar to my post Updating an XML File in the 14 Hive Using a Custom Timer Job and assumes you know some basics about custom timer job creation (If not, check my other post Implementing a Custom SharePoint Timer Job). Either way, most of the code will be given right here anyway.

The goal for this timer job is to either backup the MetaDataNavTree.ascx file in the 14\TEMPLATE\CONTROLTEMPLATES folder with the SharePoint Hive and to adjust the Tree’s ExpandDepth property from 0 to 2 or to restore the backup previously made. Here is the entire MetaDataNavExpansionJob class:

Imports Microsoft.SharePoint.Administration
Imports Microsoft.SharePoint.Utilities
Imports System.Text.RegularExpressions

Public Class MetaDataNavExpansionJob
    Inherits SPServiceJobDefinition

#Region "Properties"

    Private _userControlPath As String
    Public ReadOnly Property UserControlPath() As String
        Get
            If String.IsNullOrEmpty(_userControlPath) Then _userControlPath = SPUtility.GetGenericSetupPath("TEMPLATE\CONTROLTEMPLATES\MetadataNavTree.ascx")
            Return _userControlPath
        End Get
    End Property

    Private _userControlBackupPath As String
    Public ReadOnly Property UserControlBackupPath() As String
        Get
            If String.IsNullOrEmpty(_userControlBackupPath) Then _userControlBackupPath = SPUtility.GetGenericSetupPath("TEMPLATE\CONTROLTEMPLATES\MetadataNavTree.ascx.bak")
            Return _userControlBackupPath
        End Get
    End Property

    Private Const InstallingKey As String = "DocIconJob_InstallingKey"
    Private Property _installing() As Boolean
        Get
            If Properties.ContainsKey(InstallingKey) Then
                Return Convert.ToBoolean(Properties(InstallingKey))
            Else
                Return True
            End If
        End Get
        Set(ByVal value As Boolean)
            If Properties.ContainsKey(InstallingKey) Then
                Properties(InstallingKey) = value.ToString
            Else
                Properties.Add(InstallingKey, value.ToString)
            End If
        End Set
    End Property

#End Region

    Public Sub New()
        MyBase.New()
    End Sub

    Public Sub New(JobName As String, service As SPService, Installing As Boolean)
        MyBase.New(JobName, service)
        _installing = Installing
    End Sub

    Public Overrides Sub Execute(jobState As Microsoft.SharePoint.Administration.SPJobState)
        AdjustMetaDataNavExpansion()
    End Sub

    Private Sub AdjustMetaDataNavExpansion()
        If _installing Then
            If My.Computer.FileSystem.FileExists(UserControlPath) Then
                'Backup the original
                My.Computer.FileSystem.CopyFile(UserControlPath, UserControlBackupPath, True)
                Dim contents As String = My.Computer.FileSystem.ReadAllText(UserControlPath)

                'Replace the Expansion with First Level Expansion
                My.Computer.FileSystem.WriteAllText(UserControlPath, Regex.Replace(contents, "ExpandDepth=""\d+""", "ExpandDepth=""2"""), False)
            End If
        Else
            If My.Computer.FileSystem.FileExists(UserControlBackupPath) Then
                'Restore the original
                My.Computer.FileSystem.MoveFile(UserControlBackupPath, UserControlPath, True)
            End If
        End If
    End Sub

End Class

Lines 8-44 are just the declaration of and logic needed to persist some properties. Again, more information can be found in my previous post, but basically I am using the SPJobDefinition’s Properties HashTable to store my own properties as specified in the constructor. Except for in the case of the UserControlPath and UserControlBackupPath properties which are really just wrapping up some logic to get a reference to specific files in the 14 Hive’s TEMPLATE\CONTROLTEMPLATES directory using the SPUtility class.

The Execute method beginning in line 55 is what is called when the Timer Job actually runs. I override this method to ensure my custom code gets called instead. My custom code really begins in the AdjustMetaDataNavExpansion method starting at line 59.

If this job is installing (Running on Solution Activation), the MetaDataNavTree.ascx file is copied to MetaDataNavTree.ascx.bak as a backup of the original in line 63. The UserControl file is then read in as text and a regular expression searches for and replaces the ExpandDepth=”SomeNumber” property and replaces it with ExpandDepth=”2″. This is all done and saved back into the file in lines 66-67.

If this job is uninstalling (Running on Solution Deactivation), the backup file (MetaDataNavTree.ascx.bak) created on activation is restored in line 72.

To run this from activation and deactivation I simply copy my design from the PDFdocIcon project and create and run a new version of the job. Since this code is nearly identical to what I’ve already explained, I won’t go into detail but I will save you some time and have copied it below. This is the Main.EventReceiver:

Option Explicit On
Option Strict On

Imports System
Imports System.Runtime.InteropServices
Imports System.Security.Permissions
Imports Microsoft.SharePoint
Imports Microsoft.SharePoint.Security
Imports Microsoft.SharePoint.Administration

''' <summary>
''' This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
''' </summary>
''' <remarks>
''' The GUID attached to this class may be used during packaging and should not be modified.
''' </remarks>

<GuidAttribute("a885d247-f5e8-4456-abd2-6cfebb2bdfde")> _
Public Class MainEventReceiver
    Inherits SPFeatureReceiver

    Public Sub RunMetaDataNavExpansionJob(Installing As Boolean, properties As SPFeatureReceiverProperties)
        Dim JobName As String = "MetaDataNavExpansionJob"

        'Ensure job doesn't already exist (delete if it does)
        Dim query = From job As SPJobDefinition In properties.Definition.Farm.TimerService.JobDefinitions Where job.Name.Equals(JobName) Select job
        Dim myJobDefinition As SPJobDefinition = query.FirstOrDefault()
        If myJobDefinition IsNot Nothing Then myJobDefinition.Delete()

        Dim myJob As New MetaDataNavExpansionJob(JobName, SPFarm.Local.TimerService, Installing)

        'Get that job going!
        myJob.Title = String.Format("Configuring MetaData Navigation for {0} Expansion", IIf(Installing, "First Level", "Default"))
        myJob.Update()
        myJob.RunNow()
    End Sub

    Public Overrides Sub FeatureActivated(ByVal properties As SPFeatureReceiverProperties)
        RunMetaDataNavExpansionJob(True, properties)
    End Sub

    Public Overrides Sub FeatureDeactivating(ByVal properties As SPFeatureReceiverProperties)
        RunMetaDataNavExpansionJob(False, properties)
    End Sub

End Class

I hope you’re beginning to see that automating any manual changes to the 14 Hive can follow the Service Timer Job Solution pattern I’ve now demonstrated twice. This isn’t true for everything (Web.config changes should be done through the object model or a config.something.xml file, workflow actions can have their own file, etc.), but for those little one off things that don’t have a better alternative, this is a great way to take care of it.


OWSTimer Debugger Annoyances

$
0
0
Applies To: SharePoint 2010, Visual Studio 2010

If you’re running Visual Studio on the same machine with SharePoint 2010 you are probably familar with this error message:

“An unhandled exception (‘System.Security.Cryptography.CryptographicException’) occurred in OWSTIMER.EXE [#]. The Just-In-Time debugger was launched without necessary security permissions. To debug this process, the Just-In-Time debugger must be run as an Administrator. Would you like to debug this process?”

In fact, you are probably very familiar with this dialog since it will pop up at least once a day. If you haven’t logged in in a while, then you will have multiple windows to cancel debugging in.

The problem is due to a threading issue related to an encryption key used by the OWSTIMER service. In SharePoint 2010 the timer service gets recycled daily (default is 6 AM) using a timer job mysteriously called “Timer Service Recycle”. The details aren’t all that important, but you can read more here and get even more information about how the mistake really occurs here. To summarize, the key isn’t found due to impersonation issues. (BTW, that number at the end of the error message is just the process ID and will change each time.)

Bottom line for me is that the error is not really a problem and can safely be ignored in your logs. The annoyance comes when you have Visual Studio installed and the JIT debugger is enabled.

You can either adjust your settings using the registry, or even better, just open up Visual Studio (2010) and adjust your options. Using the menu, choose Tools > Options. Then expand Debugging from the tree on the left (if it isn’t showing, check the Show all settings box) then choose Just-In-Time. To turn it off, just uncheck all the boxes and press OK:

Now those annoying messages will stop and the people will rejoice. Just remember that it can be very helpful to turn these back on when attempting to debug certain types of things (Custom Timer Jobs for instance), but be sure to bookmark this page because you will forget to turn it back off and then you will be sad again and I don’t want you to be sad.



Make Your Cisco IP Phone Ring Using .NET

$
0
0
Applies To: C#, VB.NET, Cisco Phones

I often get interrupted during the day. This is irritating but a part of office life and you get used to it. What I can’t seem to get used to, however, is hearing the same 3 hour story about my coworker’s dog’s stranger anxiety and all the mundane solutions they tried in order to fix poor Rover and even though that veterinarian is a “sweetheart” they just don’t know what they’re talking about sometimes blah blah blah – EVERY SINGLE DAY OF MY LIFE. I often find myself in conversations I neither started nor encouraged to continue that have long since passed the polite listening timeout.

Generally a good strategy is to get a friendly coworker to come and rescue you. Unfortunately, they may not always be around or may not have noticed. Another option is to fake a call. If you’ve got a Cisco IP Phone sitting on your desk and don’t mind writing a little code, you can have a handy app in just a few minutes that can send disarm the Chatinators*. Even if you are able to fully function in society without the help of fake social cues, you might find it interesting what you can do with that phone on your desk.

Cisco IP Phones can accept a wide variety of commands and it’s worth taking a look at the documentation sometime. The basic idea, however, is to send the phone an HTTP Post with some XML. In this case we are going to use the ExecuteItem command with a URI. That URI will contain a Play command. Sound confusing? It is a little, but that’s why I’m going to provide the code for you to cut and paste.

To send a command using VB.NET, you can use this helper function:

    Private Function SendCommand(Address As String, Command As String, Username As String, Password As String) As String

        Dim ResponseXML As String = String.Empty

        Dim request As HttpWebRequest = WebRequest.Create(String.Format("http://{0}/CGI/Execute", Address))
        request.Timeout = 30 * 1000
        request.Method = "POST"
        request.Accept = "*/*"
        request.ContentType = "application/x-www-form-urlencoded"
        request.Credentials = New NetworkCredential(Username, Password)
        request.PreAuthenticate = True

        Dim bytes As Byte() = Encoding.UTF8.GetBytes(String.Format("XML={0}", HttpUtility.UrlEncode(Command)))
        Using outStream As Stream = request.GetRequestStream
            outStream.Write(bytes, 0, bytes.Length)
            outStream.Close()
        End Using

        Using response As WebResponse = request.GetResponse
            Using responseStream As Stream = response.GetResponseStream
                Using reader As New StreamReader(responseStream)
                    If reader IsNot Nothing Then
                        ResponseXML = reader.ReadToEnd
                        reader.Close()
                    End If
                    responseStream.Close()
                End Using
            End Using
            response.Close()
        End Using

        Return ResponseXML
    End Function

In line 5 we setup the HttpWebRequest object to send the POST to the phone. The URL that accepts the commands is either your phone’s IP Address or DNS entry followed by “/CGI/Execute“. To find your phone’s IP Address, press the settings button on the device. There should be a Phone Information section that will have your phone’s address. You may also see an entry for Host Name. This is the name of your phone and will often be the DNS entry for it. In my case it was the fully qualified version of this host name. So SEP#####.domain.com. If you are unsure, just use the IP Address and look at the response in Fiddler or something similar.

Lines 6-11 setup all the required properties to make this POST acceptable to the phone. Depending on your network settings, you’ll need to provide a username and password. This means writing programs that cause other people’s phones to ring or display funny pictures is going to be extra hard. For our phones, our AD accounts were all that was needed to authenticate with the phones. If you were given a website to configure your phone’s address book or speed dials, it’s going to be the same login information. The PreAuthenticate setting is not required, but does reduce the number of 401 challenge responses when sending multiple commands in succession.

We write out the body of the response in lines 13-17 using UTF8 and a URL Encoded XML String that starts with XML=. Finally we close the request and capture the phone’s response as XML and return it in lines 19-32.

Okay, so now we can send a command, but what does the command look like? A basic play command looks like this:

<CiscoIPPhoneExecute><ExecuteItem Priority="2" URL="Play:Classic1.raw" /></CiscoIPPhoneExecute>

It’s pretty straightforward XML. The ExecuteItem element has 2 attributes, Priority and URL. The Priority attribute can be set from 0 to 2:

  • 0 = Execute Immediately (The command takes priority over anything else the phone might be doing)
  • 1 = Execute When Idle (The command waits until the phone isn’t busy before executing)
  • 2 = Execute If Idle (The command executes if the phone isn’t busy, otherwise it’s ignored)

For a fake ring program, priority 2 is best. That way you don’t get any extra ringing if someone actually is trying to call you.

The second attribute, URL, can take an actual URL to more commands or a simple URI depending on what your phone accepts. More information can be found in that documentation I mentioned, but for what we’re doing a simple Play followed by a colon and the name of the ringtone file takes care of things.

So now you’ve got the command and a send command function. You can write whatever fancy code you want to wrap these things up. I’ve written a little taskbar app that listens for a global key press and sends rings in a configurable loop to the phone. This allows me to secretly reach for the keyboard while the talker is distracted. Most of that’s beyond this article, but I will show you my Ring method and let you fill in the blanks:

    Private Sub Ring()
        If String.IsNullOrEmpty(My.Settings.PhoneIP) Then
            ShowSettings()
        Else
            Try
                For i As Integer = 0 To My.Settings.RingRepeat - 1
                    SendCommand(My.Settings.PhoneIP, String.Format("<CiscoIPPhoneExecute><ExecuteItem Priority=""2"" URL=""Play:{0}"" /></CiscoIPPhoneExecute>", My.Settings.RingTone), My.Settings.Username, My.Settings.Password)
                    If i < My.Settings.RingRepeat - 1 Then Threading.Thread.Sleep(3000)
                Next

            Catch wex As WebException
                MsgBox("Error when talking to the phone, please check your settings!" & vbCrLf & "(Probably your credentials)" & vbCrLf & vbCrLf & wex.ToString, MsgBoxStyle.Critical, "No Ring Ring :( ")
                ShowSettings()
            Catch ex As Exception
                MsgBox("Error when talking to the phone, please check your settings!" & vbCrLf & vbCrLf & ex.ToString, MsgBoxStyle.Critical, "No Ring Ring :( ")
                ShowSettings()
            End Try
        End If
    End Sub

The ShowSettings method is just a helper method that instantiates a Windows Form to allow some configuration. You can do something similar or just hardcode everything. Lines 6-9 are the important lines, everything else is just error handling with the assumption that the settings are wrong.

In a loop corresponding to the number of rings we want, I call line 7. This is just our SendCommand function from above. Then I wait 3 seconds and do it again.

That should get you started. Pretty soon you’ll be interrupting Talkaholics with ease. There are actually several really cool things you can do with your phone and the SendCommand function above should help you get going.

One last thing, I did a bunch of guess work with the names of the ringtones in my phone. These are configured by your administrator and may be totally different for you, but here are the ringtone filenames I found worked for me:

  • AreYouThere.raw
  • Analog1.raw
  • Analog2.raw
  • Bass.raw
  • Chime.raw
  • CiscoStandard.raw
  • CiscoSymphonic.raw
  • CiscoTechno.raw
  • Classic1.raw
  • Classic2.raw
  • ClockShop.raw
  • Drums1.raw
  • Drums2.raw
  • FilmScore.raw
  • HarpSynth.raw
  • Jamaica.raw
  • KotoEffect.raw
  • MusicBox.raw
  • Piano1.raw
  • Pop.raw
  • Pulse1.raw
  • Sax1.raw
  • Sax2.raw
  • Vibe.raw

I should note that for whatever reason sending Piano2.raw crashed my entire phone. Also, just for fun, you can take a screenshot of your phone by using the following address in your browser: http://YOURPHONEIP/CGI/Screenshot

*Chatinators © 2012 (and for all time), Chris Kent

DevConnections 2012

$
0
0
Applies To: SharePoint, SQL, .NET, HTML5

I just got back from DevConnections 2012 in Las Vegas, Nevada. I learned several things that the made the trip worthwhile and I’m glad I got to be a part of it. I choose DevConnections over SPC because I wanted to take workshops from a variety of tracks including SQL, .NET and HTML5. Choosing DevConnections also meant I didn’t have to go alone.

There were several good speakers and I received plenty of swag (8+ T-Shirts, an RC helicopter, a book and more). Not surprisingly, I enjoyed the SharePoint Connections track the most and Dan Holme was my favorite speaker.

For all those that didn’t get to go I thought I’d share my notes from the sessions I attended and any insights I gained as well. My notes are not a total reflection of what was said, but represent things I found interesting or useful.


Where Does SharePoint Designer 2010 fit in to Your SharePoint Application Development Process?

Asif RehmaniSharePoint-Videos.com

My Notes:

  • Always use browser first when possible
  • Anything Enterprise wide should be VS or buy
  • DVWP also called Data Form WP
  • DVWP multiple sources to XML with XSLT
  • LVWP specific to SP lists
  • Browser limits in views don’t apply in Designer (Grouping, Sorting, etc.)
  • Import Spreadsheet is only through browser – NOT SP Designer
  • Conditional Formatting in views including icons (put all icons in cell and change content conditional formatting on each).Pictures automatically go in SiteAssets
    • Sometimes img uploads retain local path (switch to code and correct src url)
  • Formulas: select parameter and double click the function to have the selection become the 1st parameter
  • DVWP can easily be changed to do updates: switch from text to text box, then add a row and insert a form action button and choose the commit action (save)
  • Parameters for all sorts of stuff (username, query string, etc)can be used all over including in conditional formatting
  • SPD was designed and intended to be used in production – not a lot of support for working in Dev and moving to Production
    • WP can be packaged (DVWP) for import elsewhere
    • Reusable workflows can also be packaged

Key Insights:

  • SharePoint Designer is fine to be used in production (and in fact requires it in certain cases). However, there are things you can do to minimize the amount of work done in production.
  • SP Designer is pretty powerful and can replace a lot of extra VS development

Overall Impression:

Just as in his videos, Asif was a great presenter. He was very personable and knowledgeable. The session ended up being less about when SP Designer should be used in your environment and more a broad demo of what can be done with Designer. This was a little disappointing but I learned enough tips and tricks that I really didn’t mind too much. Interestingly, some people in the audience asked about an intermittent error they’ve been receiving in SP 2010 for some Web Parts they’d applied conditional formatting too. This was almost certainly the XSLT Timeout issue and I was able to provide them a solution.


Data Visualization: WPF, Silverlight & WinRT

Tim HuckabyInterknowlogy

My Notes:

  • WPF is great at 3D, cool demo of scripps molecule viewer (codeplex)
  • Silverlight is dead
  • Winforms is dead
  • HTML 5 hysteria is in full swing
  • HTML 5 has a canvas and SVG support
  • ComponentOne has neat HTML 5 sales dashboard demo

Key Insights:

  • Silverlight has lost to HTML 5 and we shouldn’t expect another version.

Overall Impression:

This was obviously a recycled workshop from several years ago (he actually said so) that he added a couple of slides to. In his defense, he planned to show a WinRT demo but the Bellagio AV guys were unable to get the display working. Regardless it seemed more like a bragging session. He showed pictures of him with top Microsoft people, showed his company being featured in Gray’s Anatomy, and alluded to all the cool things he’s involved with that he couldn’t mention.

This was pretty disappointing. I am already aware that .NET can do some pretty awesome things including some neat visualizations. I was hoping to get some actual guidance on getting started. Instead I got Microsoft propaganda from 3 years ago about why .NET (specifically WPF) is awesome. Tim Huckaby is obviously a very smart guy and has a lot of insight to share. Hopefully I’ll be able to attend a workshop from him in the future on a topic he cares a little more about.


Building Custom Applications (mashups) on the SharePoint Platform

Todd Baginski - http://toddbaginski.com/blog/

My Notes:

  • Used Silverlight but recommends HTML 5
  • Suggests that all mashups should be Sandbox compatible
  • Bing Maps has great examples requiring little work
  • SL to SL: localmessagesender use SendAsync method. In receiver setup allowedSenderDomains list of strings. Use localmessagereceiver and messagereceived event. Be sure to call the listen() method!!
  • Assets Library great for videos
  • Silverlight video player included in SP 2010/2013. 2013 has an additional fallback HTML 5 player.
  • External Data Column: Works as lookup for BCS
  • OOTB \14\TEMPLATE\LAYOUT\MediaPlayer.js: _spBodyOnLoadFunctionNames.push(‘mediaPlayer.createOverlayPlayer’); after you’ve made links hook ‘em up: mediaPlayer.attachToMediaLinks((document.getElementById(‘idofdivholdinglinks’)), ['wmv','mp3']);
  • OOTB \14\TEMPLATE\LAYOUT\Ratings.js: ExecuteDelayUntilScriptLoaded(RatingsManagerLoader, ‘ratings.js’); RatingsManagerLoader is huge, see slides. Then loop through everything you want to attach a rating to.
  • SL JS call: HtmlPage.Window.Invoke(“Jsfunctionname”, new string[] { parameter1, parameter2})
  • JQuery twitter plugin
  • SP 2013 has geolocation fields. Requires some setup & code. He has app to add GL column & map view to existing lists.
  • Even in SP 2013 the supported video formats are really limited
  • AppParts are really just iframes.  Connections work different. Not designed to communicate outside of app.

Key Insights:

  • Silverlight to Silverlight communication is pretty simple but will be pretty irrelevant in SharePoint 2013
  • Getting the Video Player to show your videos when using custom XSLT takes some work
  • Adding a working Ratings Control when using custom XSLT is even more complicated and convoluted
  • New GeoLocation columns in SP 2013 will be really cool, but adding them to existing lists is going to be a pain.

Overall Impression:

Todd had a lot of good information and you could tell he knew his stuff. Unfortunately he has a very dry style. Regardless, I enjoy demos that show actual architecture and code and there was plenty of that.

I do wish he’d updated his demos to use HTML 5 as he recommends. It’s very frustrating to hear a presenter recommend something different and then to spend an hour diving into the non-recommended solution. Additionally, although I prefer specific examples (and his were very good) I prefer to have more general best practices/recommendations presented as well. But despite all that he gave a few key tips that I will be using immediately and that is the primary thing I’m looking for in a technical workshop.


Creating Mobile-Enabled SharePoint Web Sites and Mobile Applications that Integrate with SharePoint

Todd Baginski - http://toddbaginski.com/blog/

My Notes:

  • AirServer $14.99 shows iPad on computer
  • Mobile is much better in SP 2013
  • Device channel panel allows content to target specific devices

Key Insights:

  • Mobile is important. You can struggle with SP 2010, but you should probably just upgrade to SP 2013

Overall Impression:

I enjoyed Todd’s other session (see above), but this one was too focused on SP 2013 to have any real practical value for me.


Getting Smarter about .NET

Kathleen Dollard - http://msmvps.com/blogs/kathleen/

My Notes:

  • Lambdas create pointers to a function
  • LINQ creates expressions that can be evaluated everywhere
  • int + int will still be an int even if larger than an int can be. No errors, but addition will be wrong. (default in C#, VB.NET will break for default)
  • There is no performance gain by using int16 over int32, some memory is saved but is only significant when processing multimillion values at the same time
  • VisualStudio 2012 will be going to quarterly updates
  • Static values are shared with all instances – Even among derived classes!
  • LINQ queries Count() does full query
  • Func last parameter is what is returned
  • Closure is the actual variable in a lambda (not copy) so multiple lambdas can be changing the same variable
  • Projects can be opened in both VS 2010 and VS 2012 at the same time

Key Insights:

  • Despite all the new and exciting things that keep getting added to .NET, a firm grip of the basics is what will really make a difference in your code and ability to make great applications
  • Static sharing even among derived classes makes for some potential mistakes, but also for some very powerful architecture
  • LINQ and Lambdas are some crazy cool stuff that I should stop ignoring
  • .NET is very consistent and following it’s logic rather than our own assumptions is key for truly understanding what your code is doing

Overall Impression:

I really enjoyed this session. It was the most challenging workshop I attended despite it’s focus of dealing with things at the most basic level. Kathleen kept it fun (although she could be a little intimidating) and continued to surprise everyone in the room both with the power of .NET and the dangers of our own misconceptions. She pointed out several gotcha areas and provided the reasoning behind them. This was a last minute session, but it was also one of the best.


Wish I’d Have Known That Sooner! SharePoint Insanity Demystified

Dan Holme - Intelliem

My Notes:

  • SQL alias: use a fake name for SQL server to account for server changes/moves. Use CLICONFIG.exe on each SP server in the farm. Do NOT use DNS for this (CNAMEs). Consider using tiers of aliases for future splitting of DBs: content, search, services – all start with the same target and changed as needed
  • ContentDB sizing: change initial size and growth. Defaults are 50mb and 1mb growth. Makes a BIG difference in performance.
  • ContentDBs can be up to 4 TB. Over 200 GB is not recommended.
  • SiteCollections can be same as ContentDBs but 100 GB is as high with OOTB tools
  • Limit of 60 million items per ContentDBs (each version counts)
  • Remote BLOB storage: SP is unaware. Common performance measurements are mostly inaccurate because they are based on single files. Externalizing all BLOBs is significant performance boost. 25-40%! Storage can be cheaper too but complexity increases. Using a SAN allows you to take advantage of SAN features (ie deduplication – which really reduces storage footprint). RBS OOTB is fine, but you can’t set business rules.
  • Office Web Apps no longer run on SP servers in 2013. These are great, test on SkyDrive consumer.
  • Get office365 preview account
  • Nintex highly recommended over InfoPath. InfoPath is supported but unenhanced in 2013, likely indicator of unannounced strategy.
  • AD RMS allows the cloud to be more secure than on-premise. Allows exported documents to have rights management that restricts actions regardless of location. Very difficult to setup infrastructure. Office365 has this which is compelling reason to migrate.
  • User Profile DB is extremely important and becomes much more so in SP 2013
  • Claims Authentication is apparently a dude pees on a server and then gets shot with lasers:
Pee on a server LASERS!
  • Upgrade to 2013 should be done as quickly as possible. Much easier than 7-10. Fully backward compatible. Both 14 & 15 hives.
  • Governance is very important!

Key Insights:

  • Preparing for growth up front with SQL aliases is a great idea
  • Nintex and Office 365 both need more investigation by me
  • Remote Blob Storage is a good idea for nearly everyone – very different perspective than what I’ve previously been told!

Overall Impression:

This session was full of great tips and best practice suggestions tempered with practical applications. This was exactly the kind of information I came to hear. Dan did a great job of presenting a lot of information (despite a massive drive failure just previous to the convention) while keeping it interesting. The only thing that was probably a little much was his in-depth explanation of Claims Authentication. His drawings were pretty rough and his enthusiasm for the topic didn’t really transfer to the audience. Regardless, this was a great session.


SharePoint Data Access Shootout

Scot Hillier - http://www.shillier.com

My Notes:

  • LINQ cannot query across multiple lists (unless there is a lookup connection)
  • SPSiteDataQuery can query all lists of a certain template using CAML within a Site or Site Collection
  • SPMetal.exe generates Object Relational Map needed for LINQ  (in hive bin)
  • LINQ isn’t going to have much support in SP 2013
  • SP 2013 has continuous crawl
  • Keyword Queries are very helpful
  • KQL: ContentClass determines the kind of results you get. (ie STS_Web, STS_Site, STS_ListItem_Events)
  • Search in 2013 provides a rest interface to use KQL in JavaScript
  • CSOM is very similar to Serverside OM
  • CSOM is JS or .NET

Key Insights:

  • Keyword Query Language (KQL) needs more consideration as an effective query language for SharePoint.
  • LINQ isn’t actually a great way to access SP data despite Microsoft’s big push over the past couple of years.

Overall Impression:

This was a strange session. He didn’t go into enough depth about any one data access method to provide any real insight to those of us familiar with them and he moved so quick that anyone new to them would just have been overwhelmed. This session would have been better if he’d given clear and practical advise on when to use these methods rather than just demoing them. My guess is that he was trying to cover way too much information in too little time. However, I did enjoy hearing more about the Keyword Query Language since this is something I haven’t done much of and is rarely mentioned. Those tips alone made the whole session worthwhile.


HTML5 JavaScript APIs: The Good, The Bad, The Ugly

Christian Wenz - http://www.hauser-wenz.de/s9y/

My Notes:

  • HTML5 is a large umbrella of technologies
  • Suggests Microsoft WebMatrix is a good Editor
  • Semantics for HTML elements is a powerful new feature: input type = email, number, range, date, time, month, week, etc. These customize the type of editor and adds validation
  • Suggests Opera Mobile Emulator is a good testing tool
  • Additional elements: aside, footer
  • Requesting location: navigator.geolocation.getCurrentPosition(function(result){console.log(result)});
  • to debug local cache use Google Chrome by going to: Chrome://app cache-internals
  • Worker() web worker allows messaging for functions
  • CORS allows cross domain requests
  • Web sockets do not have full support yet but will be very cool

Key Insights:

  • HTML 5 is going to dramatically change how we think of website capabilities – eventually.
  • Although exciting, HTML 5 has a long way to go and several of it’s most compelling features have little to no support in main stream browsers.

Overall Impression:

Christian did a good job of keeping the energy up about HTML 5 and showing off some of the cool features. Unfortunately he seemed to lose site of the big picture in favor of really detailed samples. I enjoyed the presentation but would like to have had more guidance about how to get started and what to focus on with this new style of web development.


Roadmap: From HTML to HTML 5

Paul D. Sheriff - http://weblogs.asp.net/psheriff/

My Notes:

  • Lots of new elements – but they don’t do anything. They make applying CSS easier and allow search engines to parse through a page easier.
  • Browsers that don’t understand new elements will treat them as divs – but styles won’t be applied.
  • Lots of new input types (color, tel, search, URL, email, number, range, date, date-time, time, week, month)
  • New attributes (autofocus, required, placeholder, form validate, min, max, step, pattern, title, disabled)
  • CSS3 has huge style upgrades but there are still a lot of browser incompatibilities
  • JqueryUI, Modernizr = good tools, use VS 2012 with IE10 or Opera
  • Modernizr allows you to use HTML 5 with automatic replacements in incompatible browsers. Uses JQuery and is included automatically with VS 2012.
  • This stuff is not ready for the prime time except for mobile browsers. Modernizr fills in those gaps.
  • Box-sizing can be either border-box or content-box, which helps with the div width interpretation problem
  • Dude appears to hate JavaScript and HTML 5, sure love hearing a presenter complain about what we all came to learn about!

Key Insights:

  • Use Visual Studio 2012 with Modernizr to make HTML 5 websites

Overall Impression:

This session really annoyed me. Paul was very knowledgeable and had a lot of information to share. Unfortunately, he was so busy bashing the technology we all came to see that it was hard to know why we were even there.


Scaling Document Management in the Enterprise: Document Libraries and Beyond

Dan Holme - Intelliem

My Notes:

  • Can store up to 50 million documents in a single document library
  • SP 2013 allows documents to be dragged onto the doc library in the browser to upload – no ActiveX required
  • When a User is a member of the default group for a site they get the links in office and on their mysite. Site Members is the default group by default, but this can be switched in the group settings. Suggests creating an additional group that contains everyone on the site and has no permissions, then this can be the default group.
  • Email enabled document libraries can be very helpful for receiving documents outside of your network
  • Pingar is a recommended product he briefly mentioned
  • Big improvements in navigation using managed metadata service in SP 2013
  • Content type templates can use the columns as quick parts in Word

Key Insights:

  • Separating Site Membership from Site Permissions by creating an additional group just for managing memberships is a great idea.
  • A lot can be done with SP 2010 but SP 2013 will add a few key features to make things easier (drag and drop on the browser will be awesome).

Overall Impression:

The tip about site membership was worth the whole session. Additionally he reviewed a lot of the basics of content types and the content type hub. While this wasn’t particularly helpful to me, I can’t wait to get ahold of his slides for both this and his other sessions. He had way too many slides for the amount of time he was given.

This session reminded me of how powerful SharePoint is at so many things. Document management is not a particularly exciting topic to me but it is one of the key reasons we are using the SharePoint platform. A review of the features available to maintain the integrity of our data and to simply the classification of that data was very helpful.


SharePoint in Action: What We Did at NBC Olympics

Dan Holme - Intelliem

My Notes:

  • Keep SharePoint simple. Use OOTB features as much as possible
  • 300 hours of content broadcasted per day
  • NBCOlympics.com streamed every competition live
  • Most watched event in TV history
  • 3,700 NBC Olympics team members
    • 1 SP admin/support
  • PDF viewing was turned on despite security concerns
  • Set as default IE page – very difficult to do, they used a script to set a registry entry to account for multiple OSs and browser versions
  • All additional web applications were exposed through SP using a PageViewer WP
    • Phone Directory, Calendar application
  • WebDAV was used to allow other apps to publish documents
  • Global Navigation on top site using tabs (drop down menus)
    • Quick launch had contextual items to site
    • Navigation centric homepage, kept navigation as simple as possible. Only homepage had global navigation.
  • No real branding (put picture on right and used a custom icon). They set the site icon to go to the main web page because it’s what users expected.
  • Did not use lists or libraries as terms instead used Content (libraries, other lists) and Apps (Calendar, Tasks, etc.)
  • Suggests hiding “I like it” and notes since they are not helpful and deprecated in SP 2013
  • Site mailboxes for teams using OWA
  • Embedded documentation: Put basic instructions right on the homepage of team sites as needed. Also above some document libraries – basic upload and open instructions.
  • Focus on usability since there was no time for training
  • Took out All Site Content link and all other navigation was on homepage of site. Sub sites had tab links to parent site.
  • Used InfoPath to customize List Forms mostly to add instructions (placed below field title on left)
  • Lots of calendars. Conference room calendars were very popular. Didn’t use exchange for this in order to accommodate outside users.
  • Used InfoPath lists and workflows to replace paper processes. Kept them simple but effective. Although not used in this case, he recommends Nintex for more advanced needs.
  • Self-service help desk: printer/app installs, FAQs. Showed faces of team.
    • PageViewer web part and point it to \\servername to show all printers and put instructions on right to get those installed directly out of SP
    • Kept running FAQ to show common solutions
  • IT Administration site: ticket system used issue tracking list highly customized with InfoPath list forms.
    • Inventory lists in IT admin, also DHCP lease reports using powershell to dump that information.
    • List for user requests. WF for approvals, then Powershell took care of approved memberships directly in AD.
    • Used a list for password resets. Powershell would set password to generic password as requested (scheduled task every 5 min)
    • Powershell script to create team sites through list requests
  • SP will never be used to broadcast the Olympics but very effective to manage those teams
  • You must understand your users and build to what users really want/need
    • Don’t overwork, don’t over brand – It just clutters.
    • Don’t over deliver or over train.

Key Insights:

  • Keeping global navigation centralized in a single location (without including everything) and providing contextual navigation as needed can keep things simple from a management perspective while still allowing things to be intuitive.
    • Ensuring all navigation needed is exposed through the Quick Launch eliminates the need for All Site Content (except for Admin) and ensures your sites are laid out well
    • As long as you allow users to easily return to the global navigation from any sub site (Using the site icon is very intuitive) there is no need to clutter every site with complicated menu trees.
  • Request lists and Scheduled Tasks running Powershell Scripts can be used to create easy to manage but very powerful automation.
  • Removal of “I like it” and notes icons is a good idea
  • Embedding instructions directly on a given site/list using OOTB editing tools can increase usability dramatically
  • InfoPath List forms can go a long way towards improving list usability by making things appear more intuitive and providing in line instructions.
  • There are so many cool things in SP it can be easy to forget all the amazing things you can do using simple OOTB functionality. It is far too tempting to over deliver and over share when end users really only want to get their job done in the simplest way possible.

Overall Impression:

This was the best session I attended and made the entire conference worthwhile. It is unfortunately rare that you can see SP solutions in the context of an entire site. Seeing how SP was used to help manage one of the largest and most daring projects I can imagine was both inspiring and reassuring.

Besides the several tips of things they did (many of which will soon be showing up in our environment), he was able to confirm several things we were already doing that we were a little unsure about. Even better was his focus on simplifying things. I get so excited about SP features that sometimes I overuse them or forget the real power of simple lists. It was a fantastic reminder that we are often over delivering and therefore complicating things that just make SP scary and hard to use for end users.


Convention Summary

DevConnections 2012 was great. I had a great time in Vegas and I brought home several insights that have immediate practical value. Really, there’s not much more you can ask for in a technical convention.


Use Local Files in CefSharp

$
0
0
Applies to: CefSharp, C#

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. Recently I had the need of loading web files directly from the file system. The easiest way to do that is to provide a custom scheme. You do this by creating an instance of CefSettings and using the RegisterScheme method with a CefCustomScheme wrapper object which requires an implementation of the ISchemeHandlerFactory which will in turn require an implementation of the ISchemeHandler. Got it?! It’s actually much less confusing than it seems. It will make much more sense in a moment.

Quick Note: This isn’t a tutorial on how to get CefSharp working or integrated into your product. You’ll want to install the packages using NuGet and go to the CefSharp project page for details.

ISchemeHandler

SchemeHandler objects process custom scheme-based requests asynchronously. In other words, you can provide a URL in the form customscheme://folder/yourfile.html”. The SchemeHandler object takes the request and gives you a chance to provide a custom response. In our case, we want to take anything with the scheme local and pull it from the file system. Here’s what that looks like:

using CefSharp;
using System.IO;
public class LocalSchemeHandler : ISchemeHandler
{
    public bool ProcessRequestAsync(IRequest request, ISchemeHandlerResponse response, OnRequestCompletedHandler requestCompletedCallback)
    {
        Uri u = new Uri(request.Url);
        String file = u.Authority + u.AbsolutePath;

        if (File.Exists(file))
        {
            Byte[] bytes = File.ReadAllBytes(file);
            response.ResponseStream = new MemoryStream(bytes);
            switch (Path.GetExtension(file))
            {
                case ".html":
                    response.MimeType = "text/html";
                    break;
                case ".js":
                    response.MimeType = "text/javascript";
                    break;
                case ".png":
                    response.MimeType = "image/png";
                    break;
                case ".appcache":
                case ".manifest":
                    response.MimeType = "text/cache-manifest";
                    break;
                default:
                    response.MimeType = "application/octet-stream";
                    break;
            }
            requestCompletedCallback();
            return true;
        }
        return false;
    }
}

I’ve named my implementation LocalSchemeHandler because I’m creative like that. You can see we are implementing the ISchemeHandler interface in line 3 (If this isn’t recognized be sure to include the using statements in lines 1 and 2 above). This interface only requires a single method, ProcessRequestAsync. Our job is to translate the request into a response stream, call requestCompletedCallback and indicate if we handled the request or not (return true or false).

The first thing we do is translate the request URL into a local file path (lines 7-8). If the file doesn’t exist, there isn’t any way for us to handle the request so we return false (line 36). Otherwise, we set the response.ResponseStream to a MemoryStream from the file’s bytes (lines 12-13). We then guess the Mime Type based on the file’s extension (lines 14-32). This is a pretty limited list but it was all I needed – Just expand as necessary.

Once we’ve got everything we need, we call the requestCompletedCallback and return true to indicate we have handled the request.

ISchemeHandlerFactory

SchemeHandlerFactory objects create the appropriate SchemeHandler objects. I’ve also added a convenience property to help out during registration. Here’s mine:

using CefSharp;
public class LocalSchemeHandlerFactory : ISchemeHandlerFactory
{
    public ISchemeHandler Create()
    {
        return new LocalSchemeHandler();
    }

    public static string SchemeName { get { return "local"; } }
}

This one is called LocalSchemeHandlerFactory (surprise!) and you can see we are implementing the ISchemeHandlerFactory interface in line 2 (If this isn’t recognized be sure to include the using statement in line 1). This interface also only requires a single method, Create. All we have to do is return a new instance of our custom SchemeHandler (line 6).

The SchemeName property in line 9 is just there to make things easier during registration.

Registering Your Custom Scheme

Now that we have our custom SchemeHandler and the corresponding Factory, how do we get the Chromium web browser controls to take advantage of them? This has to be done before things are initialized (before the controls are loaded). In WPF this can usually be done in your ViewModel’s constructor and in WinForms within the Form Load event. Here’s what it looks like:

CefSettings settings = new CefSettings();
settings.RegisterScheme(new CefCustomScheme()
    {
        SchemeName = LocalSchemeHandlerFactory.SchemeName,
        SchemeHandlerFactory = new LocalSchemeHandlerFactory()
    });
Cef.Initialize(settings);

You’ll create a new instance of a CefSettings object (line 1) and call the RegisterScheme method which takes a CefCustomScheme object (lines 2-6). the CefCustomScheme object needs to have its SchemeName set to whatever you are going to be using in the URLs to distinguish your custom scheme (we are using local) and its SchemeHandlerFactory should be set to a new instance of your custom SchemeHandlerFactory. Then call the Cef.Intialize with the settings object and all the plumbing is hooked up.

Giving it a go

In my application’s directory (bin/x64/debug) I have created a folder called web. Inside the folder is an html file (index.html) and an images folder with a single image (truck.png). The html page is very simple:

<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
<img src="images/truck.png"/>
</body>
</html>

Now if I set my ChromiumWebBrowser (WPF) or WebView (WinForms) Address property to local://web/index.html I get the following:

LocalSchemeHandler

You can see the web page loaded both the HTML and the linked image from the file system. There’s even a couple of dummy buttons underneath the truck to show you that it’s just a standard WPF window. This is all you need for simple web files stored locally. In some advanced cases you will need to adjust the BrowserSettings for your control to adjust permission levels (specifically FileAccessFromFileUrlsAllowed and UniversalAccessFromFileUrlsAllowed).


Use Embedded Resources in CefSharp

$
0
0
Applies to: CefSharp, C#

In my last post, Use Local Files in CefSharp, I showed you how to create a CefCustomScheme to pull web files directly from the file system. This post will follow along the same lines except this time I’ll show you how to pull web files directly from your assembly manifest (Embedded Resources). In practical terms, I’m using both. You just call the RegisterScheme method (see below) for each CefCustomScheme you want to use. This is especially helpful during development so that I can quickly tweak things in the file system and then when things are ready to include in my larger project I include everything in the project and embed them. This keeps my files from being browsable/editable outside of my application.

Intro

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. Sometimes you want to load sites/files that aren’t hosted on the web. The easiest way to do that is to provide a custom scheme. You do this by creating an instance of CefSettings and using the RegisterScheme method with a CefCustomScheme wrapper object which requires an implementation of the ISchemeHandlerFactory which will in turn require an implementation of the ISchemeHandler. If you didn’t read my last post about doing this from the file system (or even if you did) you might be a little confused. Fortunately, it isn’t near as complicated as it seems.

Quick Note: This isn’t a tutorial on how to get CefSharp working or integrated into your product. You’ll want to install the packages using NuGet and go to the CefSharp project page for details.

ISchemeHandler

SchemeHandler objects process custom scheme-based requests asynchronously. In other words, you can provide a URL in the form customscheme://folder/yourfile.html”. The SchemeHandler object takes the request and gives you a chance to provide a custom response. In our case, we want to take anything with the scheme resource and pull it from our assembly’s manifest. Here’s what that looks like:

using CefSharp;
using System.IO;
using System.Reflection;
public class ResourceSchemeHandler : ISchemeHandler
{
    public bool ProcessRequestAsync(IRequest request, ISchemeHandlerResponse response, OnRequestCompletedHandler requestCompletedCallback)
    {
        Uri u = new Uri(request.Url);
        String file = u.Authority + u.AbsolutePath;

        Assembly ass = Assembly.GetExecutingAssembly();
        String resourcePath = ass.GetName().Name + "." + file.Replace("/", ".");

        if (ass.GetManifestResourceInfo(resourcePath) != null)
        {
            response.ResponseStream = ass.GetManifestResourceStream(resourcePath);
            switch (Path.GetExtension(file))
            {
                case ".html":
                    response.MimeType = "text/html";
                    break;
                case ".js":
                    response.MimeType = "text/javascript";
                    break;
                case ".png":
                    response.MimeType = "image/png";
                    break;
                case ".appcache":
                case ".manifest":
                    response.MimeType = "text/cache-manifest";
                    break;
                default:
                    response.MimeType = "application/octet-stream";
                    break;
            }
            requestCompletedCallback();
            return true;
        }
        return false;
    }
}

I’ve named my implementation ResourceSchemeHandler. You can see we are implementing the ISchemeHandler interface in line 4 (If this isn’t recognized be sure to include the using statements in lines 1-3 above). This interface only requires a single method, ProcessRequestAsync. Our job is to translate the request into a response stream, call requestCompletedCallback and indicate if we handled the request or not (return true or false).

The first thing we do is translate the request URL into a file path (lines 8-9). Embedded Resources are stored in the form of AppName.Namespace(s).File.Extension. So we need to translate our file path to our resource path. We do this in lines 11-12. First we grab the current assembly (ass, hehe) because we’re going to need it anyway. Next we build our resource path by slapping our AppName (ass.GetName()) followed by a period onto our file path where we replace all the forward slashes with periods.

This allows us to create a folder inside our project (which will get mapped to a namespace in C#) and put our files in there. So if we were to use the address “resource://web/index.html” the file path will be “web/index.html” and the resource path (if our application is named C2Player) will be “C2Player.web.index.html”.

If the resource doesn’t exist (no info returned), there isn’t any way for us to handle the request so we return false (line 39). Otherwise, we set the response.ResponseStream directly to the ManifestResourceStream (line 16). We then guess the Mime Type based on the file’s extension (lines 17-35). This is a pretty limited list but it was all I needed – Just expand as necessary. If you are using both this SchemeHandler and the LocalSchemeHandler created in my last post, you should refactor this to a common function.

Once we’ve got everything we need, we call the requestCompletedCallback and return true to indicate we have handled the request.

ISchemeHandlerFactory

SchemeHandlerFactory objects create the appropriate SchemeHandler objects. I’ve also added a convenience property to help out during registration. Here’s mine:

using CefSharp;
class ResourceSchemeHandlerFactory : ISchemeHandlerFactory
{
    public ISchemeHandler Create()
    {
        return new ResourceSchemeHandler();
    }

    public static string SchemeName { get { return "resource"; } }
}

This one is called ResourceSchemeHandlerFactory (wowzers!) and you can see we are implementing the ISchemeHandlerFactory interface in line 2 (If this isn’t recognized be sure to include the using statement in line 1). This interface also only requires a single method, Create. All we have to do is return a new instance of our custom SchemeHandler (line 6).

The SchemeName property in line 9 is just there to make things easier during registration.

Registering Your Custom Scheme

Now that we have our custom SchemeHandler and the corresponding Factory, how do we get the Chromium web browser controls to take advantage of them? This has to be done before things are initialized (before the controls are loaded). In WPF this can usually be done in your ViewModel’s constructor and in WinForms within the Form Load event. Here’s what it looks like:

CefSettings settings = new CefSettings();
settings.RegisterScheme(new CefCustomScheme()
    {
        SchemeName = ResourceSchemeHandlerFactory.SchemeName,
        SchemeHandlerFactory = new ResourceSchemeHandlerFactory()
    });
Cef.Initialize(settings);

You’ll create a new instance of a CefSettings object (line 1) and call the RegisterScheme method which takes a CefCustomScheme object (lines 2-6). the CefCustomScheme object needs to have its SchemeName set to whatever you are going to be using in the URLs to distinguish your custom scheme (we are using resource) and its SchemeHandlerFactory should be set to a new instance of your custom SchemeHandlerFactory. Then call the Cef.Intialize with the settings object and all the plumbing is hooked up. Again, if you want to use additional custom schemes (such as our LocalSchemeHandler) just add another settings.RegisterScheme call before the Cef.Initialize call.

Giving it a go

In my solution I have created a folder called web. Inside the folder is an html file (index.html) and an images folder with a single image (truck.png). Both the html file and the image have their Build Action set to Embedded Resource. Resource paths are case sensitive. For whatever reason, the request will always come back all lower case (regardless of your address casing) so you’ll need to ensure all your files and folders are lower case as well. Here’s what my test files look like in Solution Explorer:

EmbeddedResources

The html page is very simple:

<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
<img src="images/truck.png"/>
</body>
</html>

Now if I set my ChromiumWebBrowser (WPF) or WebView (WinForms) Address property to resource://web/index.html I get the following:

LocalSchemeHandler

You can see the web page loaded both the HTML and the linked image from the assembly’s manifest. There’s even a couple of dummy buttons underneath the truck to show you that it’s just a standard WPF window.

Since I’m using WPF I also had the option of setting the Build Action to Resource and rewriting a small part of the SchemeHandler to pull from there. I chose Embedded Resource since it’s more widely applicable. Between these two posts hopefully you see that writing a CefCustomScheme is actually really straightforward. Have fun!


Embedded Chromium in WinForms

$
0
0
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.


Construct 2 in WinForms

$
0
0
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!


Using SharePoint RPC to Create Directories

$
0
0
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!



Using SharePoint RPC to Upload Documents

$
0
0
Applies To: SharePoint, VB.NET

In my last post, Using SharePoint RPC to Create Directories, I showed you the basics of using the SharePoint Foundation RPC Protocol. I provided a lot of the plumbing code needed for executing RPC methods and then demonstrated creating multiple directories in a single call.

In this post, I’ll be building on my SPUploader module to show you how to upload documents while setting the metadata all in a single call. You will need all the code in the first post to make this work.

Uploading Documents

Our goal is to upload a document to a library and set its metadata at the same time using the put document RPC method. There are some additional complications with Office docs that I won’t be addressing, but this should get you started for most file types.

The put document method has four parameters we care about: document, put_option, comment, and keep_checked_out.

Document Details

The document parameter is where you will specify the document name and all meta data. For a document named SomeStuff.csv with a title of “Some Stuff” to be uploaded in the Stuff folder of our Junk library the document parameter needs to look like this:

document=[document_name=Junk/Stuff/SomeStuff.csv;meta_info=[vti_title;SW|Some Stuff]]

Here are some utility functions that will help you generate all the information correctly:

    Private Function documentDetailString(fileName As String, properties As Dictionary(Of String, Object)) As String
        Return String.Format("[document_name={0};meta_info=[{1}]]", EncodeString(fileName), EncodeString(propertiesToString(properties)))
    End Function

    Private Function propertiesToString(properties As Dictionary(Of String, Object)) As String
        If properties Is Nothing Then Return String.Empty
        Dim props As New StringBuilder
        For Each kvp As KeyValuePair(Of String, Object) In properties
            If kvp.Value IsNot Nothing Then
                Dim field As String = kvp.Key
                If field = "Title" Then field = "vti_title"

                Dim value As String
                Dim typeCode As String = "S"
                Select Case (kvp.Value.GetType().FullName)
                    Case "System.Boolean"
                        typeCode = "B"
                        value = IIf(CType(kvp.Value, Boolean), "true", "false")
                    Case "System.DateTime"
                        value = CType(kvp.Value, DateTime).ToString("s") & "Z"
                    Case Else
                        value = escapeVectorCharacters(kvp.Value.ToString)
                End Select

                props.AppendFormat("{0};{1}W|{2};", field, typeCode, value)
            End If
        Next
        Return props.ToString.TrimEnd(";")
    End Function

The first function, documentDetailString (lines 96-98), will help generate the basic structure of the document parameter’s value. It uses the EncodeString function from my last post to escape both the file name and all the metadata information between the brackets. When we use this later in an RPCParameter object you’ll see that we disable additional encoding since RPC is really finicky about this stuff.

The metadata information for a file is generated using the propertiesToString function (lines 100-124) and expects your metadata to be in a dictionary object where the internal column name is the key (string) and the value is the value (object). We loop through each property and build a strange looking string that indicates which column we are setting, the type of the value (and what action we want performed), and the value itself.

Generally, every property key needs to be set to the internal name of the column (not the display name). However, the Title column is a special case. When you specify Title it actually needs to be vti_title. This is taken care of in lines 105-106.

The meta_info string for each property has two parts separated by semicolons. The first is the internal name of the column, the second is information about the value. This second part also has two parts separated by a pipe. The first part will be two characters. The first character indicates the type of the value (S for everything but Boolean values where you will use B). The second character will always be W (write) and specifies the action to take.

We take care of this with a simple case statement that checks the type of the value (line 110). When it’s a Boolean, we use a B for the type character and set the value to the string true or false (lines 111-113). When it’s a DateTime, we still use S for the type character but we format the value into the standard Sortable format and slap a Z on the end (lines 114-115). For everything else, we specify S for type and use our escapeVectorCharacters method to escape it properly (lines 116-117).

Once we’ve looped through all the properties, we remove the final semicolon (line 123).

Unique File Names

Here’s a freebie function should you need it:

    Public Function UniqueFileName(originalFileName As String) As String
        Return "C" & DateTime.Now.ToString("yyMMddHHmmssfffffff") & "K" & Path.GetExtension(originalFileName)
    End Function

The UniqueFileName function above is a quick and dirty way to ensure that you aren’t overwriting your documents when uploading them in bulk.

Uploading

    Public Function UploadToSharePoint(webURL As String, libraryName As String, FolderPath As String, FileName As String, fileBytes As Byte(), properties As Dictionary(Of String, Object), SPVersion As String, Optional creds As NetworkCredential = Nothing, Optional KeepCheckedOut As Boolean = False, Optional Comment As String = "") As String
        Dim result As String = String.Empty

        If Not (String.IsNullOrEmpty(webURL) OrElse String.IsNullOrEmpty(libraryName) OrElse String.IsNullOrEmpty(FileName)) Then

            If properties Is Nothing Then properties = New Dictionary(Of String, Object)
            If Not (properties.ContainsKey("Title") OrElse properties.ContainsKey("vti_title")) Then
                properties.Add("Title", Path.GetFileNameWithoutExtension(FileName))
            End If

            Dim parameters As New List(Of RPCParameter)
            parameters.Add(New RPCParameter("document", documentDetailString(libraryName & "/" & FolderPath & "/" & FileName, properties), False, False))
            parameters.Add(New RPCParameter("put_option", "overwrite,createdir,migrationsemantics", False, False)) 'Overwrites files (otherwise use edit),creates the directory as needed, preserves creation/modification information
            parameters.Add(New RPCParameter("comment", Comment))
            parameters.Add(New RPCParameter("keep_checked_out", IIf(KeepCheckedOut, "true", "false")))

            Dim data As New List(Of Byte)
            'put document method: http://msdn.microsoft.com/en-us/library/ms479623(v=office.14).aspx
            data.AddRange(CommandBytes("put document", SPVersion, parameters))
            data.AddRange(Encoding.UTF8.GetBytes(vbLf))
            data.AddRange(fileBytes)

            result = ExecuteRPC(webURL, data.ToArray, creds)

        End If

        Return result
    End Function

End Module

The UploadToSharePoint function takes a web URL (this does not have to be the root site in a site collection), the name of the library to upload into, the folderpath (only the immediate directory will be created if needed, if you need to create a full path of directories or ensure they are there, use the CreateFolder function demonstrated in the previous post), the filename (great time to use that UniqueFileName function!), the filebytes, a property dictionary (string, object), the SharePoint version, and some optional parameters (credentials, keepcheckedout, version comments).

If the property dictionary doesn’t contain a Title property, we automatically add one using the filename without the extension (lines 136-138). Now it’s time to create the method parameters.

The first parameter for the put document method is the document parameter (line 141) which we create using the documentDetailString function. Notice that we don’t just specify the filename, but rather the exact path (library + folderpath + filename). We also disable string encoding in the RPCParameter object since this parameter requires very specific encoding that is handled in our helper function.

The second parameter, put_option, is where we specify some combination of the following three strings separated by commas:

  • overwrite: overwrites existing files with the same filename
  • createdir: creates the parent directory (does not create a full path)
  • migrationsemantics: preserves the author, editor and creation information (requires administrative permissions or it is ignored)

For our purposes we are specifying all three (line 142).

The third parameter, comment, is a comment to include for the given version of the file (line 143).

The final parameter, keep_checked_out, takes a string of “true” or “false” (line 144). When true, the document is checked in then checked right back out. When false, the document is just checked in.

To upload the document we must specify all of the method parameters first. We generate the appropriate bytes using our CommandBytes function (line 148). We then have to add the actual file bytes (line 150) but these need to be separated from the command bytes with a line feed (line 149).

 

That’s it! Now you can upload documents while setting the metadata all in a single call!


Debugging Sitecore dlls

$
0
0
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!


DevConnections 2012

$
0
0
Applies To: SharePoint, SQL, .NET, HTML5

I just got back from DevConnections 2012 in Las Vegas, Nevada. I learned several things that the made the trip worthwhile and I’m glad I got to be a part of it. I choose DevConnections over SPC because I wanted to take workshops from a variety of tracks including SQL, .NET and HTML5. Choosing DevConnections also meant I didn’t have to go alone.

There were several good speakers and I received plenty of swag (8+ T-Shirts, an RC helicopter, a book and more). Not surprisingly, I enjoyed the SharePoint Connections track the most and Dan Holme was my favorite speaker.

For all those that didn’t get to go I thought I’d share my notes from the sessions I attended and any insights I gained as well. My notes are not a total reflection of what was said, but represent things I found interesting or useful.


Where Does SharePoint Designer 2010 fit in to Your SharePoint Application Development Process?

Asif RehmaniSharePoint-Videos.com

My Notes:

  • Always use browser first when possible
  • Anything Enterprise wide should be VS or buy
  • DVWP also called Data Form WP
  • DVWP multiple sources to XML with XSLT
  • LVWP specific to SP lists
  • Browser limits in views don’t apply in Designer (Grouping, Sorting, etc.)
  • Import Spreadsheet is only through browser – NOT SP Designer
  • Conditional Formatting in views including icons (put all icons in cell and change content conditional formatting on each).Pictures automatically go in SiteAssets
    • Sometimes img uploads retain local path (switch to code and correct src url)
  • Formulas: select parameter and double click the function to have the selection become the 1st parameter
  • DVWP can easily be changed to do updates: switch from text to text box, then add a row and insert a form action button and choose the commit action (save)
  • Parameters for all sorts of stuff (username, query string, etc)can be used all over including in conditional formatting
  • SPD was designed and intended to be used in production – not a lot of support for working in Dev and moving to Production
    • WP can be packaged (DVWP) for import elsewhere
    • Reusable workflows can also be packaged

Key Insights:

  • SharePoint Designer is fine to be used in production (and in fact requires it in certain cases). However, there are things you can do to minimize the amount of work done in production.
  • SP Designer is pretty powerful and can replace a lot of extra VS development

Overall Impression:

Just as in his videos, Asif was a great presenter. He was very personable and knowledgeable. The session ended up being less about when SP Designer should be used in your environment and more a broad demo of what can be done with Designer. This was a little disappointing but I learned enough tips and tricks that I really didn’t mind too much. Interestingly, some people in the audience asked about an intermittent error they’ve been receiving in SP 2010 for some Web Parts they’d applied conditional formatting too. This was almost certainly the XSLT Timeout issue and I was able to provide them a solution.


Data Visualization: WPF, Silverlight & WinRT

Tim HuckabyInterknowlogy

My Notes:

  • WPF is great at 3D, cool demo of scripps molecule viewer (codeplex)
  • Silverlight is dead
  • Winforms is dead
  • HTML 5 hysteria is in full swing
  • HTML 5 has a canvas and SVG support
  • ComponentOne has neat HTML 5 sales dashboard demo

Key Insights:

  • Silverlight has lost to HTML 5 and we shouldn’t expect another version.

Overall Impression:

This was obviously a recycled workshop from several years ago (he actually said so) that he added a couple of slides to. In his defense, he planned to show a WinRT demo but the Bellagio AV guys were unable to get the display working. Regardless it seemed more like a bragging session. He showed pictures of him with top Microsoft people, showed his company being featured in Gray’s Anatomy, and alluded to all the cool things he’s involved with that he couldn’t mention.

This was pretty disappointing. I am already aware that .NET can do some pretty awesome things including some neat visualizations. I was hoping to get some actual guidance on getting started. Instead I got Microsoft propaganda from 3 years ago about why .NET (specifically WPF) is awesome. Tim Huckaby is obviously a very smart guy and has a lot of insight to share. Hopefully I’ll be able to attend a workshop from him in the future on a topic he cares a little more about.


Building Custom Applications (mashups) on the SharePoint Platform

Todd Baginski – http://toddbaginski.com/blog/

My Notes:

  • Used Silverlight but recommends HTML 5
  • Suggests that all mashups should be Sandbox compatible
  • Bing Maps has great examples requiring little work
  • SL to SL: localmessagesender use SendAsync method. In receiver setup allowedSenderDomains list of strings. Use localmessagereceiver and messagereceived event. Be sure to call the listen() method!!
  • Assets Library great for videos
  • Silverlight video player included in SP 2010/2013. 2013 has an additional fallback HTML 5 player.
  • External Data Column: Works as lookup for BCS
  • OOTB \14\TEMPLATE\LAYOUT\MediaPlayer.js: _spBodyOnLoadFunctionNames.push(‘mediaPlayer.createOverlayPlayer’); after you’ve made links hook ‘em up: mediaPlayer.attachToMediaLinks((document.getElementById(‘idofdivholdinglinks’)), [‘wmv’,’mp3′]);
  • OOTB \14\TEMPLATE\LAYOUT\Ratings.js: ExecuteDelayUntilScriptLoaded(RatingsManagerLoader, ‘ratings.js’); RatingsManagerLoader is huge, see slides. Then loop through everything you want to attach a rating to.
  • SL JS call: HtmlPage.Window.Invoke(“Jsfunctionname”, new string[] { parameter1, parameter2})
  • JQuery twitter plugin
  • SP 2013 has geolocation fields. Requires some setup & code. He has app to add GL column & map view to existing lists.
  • Even in SP 2013 the supported video formats are really limited
  • AppParts are really just iframes.  Connections work different. Not designed to communicate outside of app.

Key Insights:

  • Silverlight to Silverlight communication is pretty simple but will be pretty irrelevant in SharePoint 2013
  • Getting the Video Player to show your videos when using custom XSLT takes some work
  • Adding a working Ratings Control when using custom XSLT is even more complicated and convoluted
  • New GeoLocation columns in SP 2013 will be really cool, but adding them to existing lists is going to be a pain.

Overall Impression:

Todd had a lot of good information and you could tell he knew his stuff. Unfortunately he has a very dry style. Regardless, I enjoy demos that show actual architecture and code and there was plenty of that.

I do wish he’d updated his demos to use HTML 5 as he recommends. It’s very frustrating to hear a presenter recommend something different and then to spend an hour diving into the non-recommended solution. Additionally, although I prefer specific examples (and his were very good) I prefer to have more general best practices/recommendations presented as well. But despite all that he gave a few key tips that I will be using immediately and that is the primary thing I’m looking for in a technical workshop.


Creating Mobile-Enabled SharePoint Web Sites and Mobile Applications that Integrate with SharePoint

Todd Baginski – http://toddbaginski.com/blog/

My Notes:

  • AirServer $14.99 shows iPad on computer
  • Mobile is much better in SP 2013
  • Device channel panel allows content to target specific devices

Key Insights:

  • Mobile is important. You can struggle with SP 2010, but you should probably just upgrade to SP 2013

Overall Impression:

I enjoyed Todd’s other session (see above), but this one was too focused on SP 2013 to have any real practical value for me.


Getting Smarter about .NET

Kathleen Dollard – http://msmvps.com/blogs/kathleen/

My Notes:

  • Lambdas create pointers to a function
  • LINQ creates expressions that can be evaluated everywhere
  • int + int will still be an int even if larger than an int can be. No errors, but addition will be wrong. (default in C#, VB.NET will break for default)
  • There is no performance gain by using int16 over int32, some memory is saved but is only significant when processing multimillion values at the same time
  • VisualStudio 2012 will be going to quarterly updates
  • Static values are shared with all instances – Even among derived classes!
  • LINQ queries Count() does full query
  • Func last parameter is what is returned
  • Closure is the actual variable in a lambda (not copy) so multiple lambdas can be changing the same variable
  • Projects can be opened in both VS 2010 and VS 2012 at the same time

Key Insights:

  • Despite all the new and exciting things that keep getting added to .NET, a firm grip of the basics is what will really make a difference in your code and ability to make great applications
  • Static sharing even among derived classes makes for some potential mistakes, but also for some very powerful architecture
  • LINQ and Lambdas are some crazy cool stuff that I should stop ignoring
  • .NET is very consistent and following it’s logic rather than our own assumptions is key for truly understanding what your code is doing

Overall Impression:

I really enjoyed this session. It was the most challenging workshop I attended despite it’s focus of dealing with things at the most basic level. Kathleen kept it fun (although she could be a little intimidating) and continued to surprise everyone in the room both with the power of .NET and the dangers of our own misconceptions. She pointed out several gotcha areas and provided the reasoning behind them. This was a last minute session, but it was also one of the best.


Wish I’d Have Known That Sooner! SharePoint Insanity Demystified

Dan Holme – Intelliem

My Notes:

  • SQL alias: use a fake name for SQL server to account for server changes/moves. Use CLICONFIG.exe on each SP server in the farm. Do NOT use DNS for this (CNAMEs). Consider using tiers of aliases for future splitting of DBs: content, search, services – all start with the same target and changed as needed
  • ContentDB sizing: change initial size and growth. Defaults are 50mb and 1mb growth. Makes a BIG difference in performance.
  • ContentDBs can be up to 4 TB. Over 200 GB is not recommended.
  • SiteCollections can be same as ContentDBs but 100 GB is as high with OOTB tools
  • Limit of 60 million items per ContentDBs (each version counts)
  • Remote BLOB storage: SP is unaware. Common performance measurements are mostly inaccurate because they are based on single files. Externalizing all BLOBs is significant performance boost. 25-40%! Storage can be cheaper too but complexity increases. Using a SAN allows you to take advantage of SAN features (ie deduplication – which really reduces storage footprint). RBS OOTB is fine, but you can’t set business rules.
  • Office Web Apps no longer run on SP servers in 2013. These are great, test on SkyDrive consumer.
  • Get office365 preview account
  • Nintex highly recommended over InfoPath. InfoPath is supported but unenhanced in 2013, likely indicator of unannounced strategy.
  • AD RMS allows the cloud to be more secure than on-premise. Allows exported documents to have rights management that restricts actions regardless of location. Very difficult to setup infrastructure. Office365 has this which is compelling reason to migrate.
  • User Profile DB is extremely important and becomes much more so in SP 2013
  • Claims Authentication is apparently a dude pees on a server and then gets shot with lasers:
Pee on a server LASERS!
  • Upgrade to 2013 should be done as quickly as possible. Much easier than 7-10. Fully backward compatible. Both 14 & 15 hives.
  • Governance is very important!

Key Insights:

  • Preparing for growth up front with SQL aliases is a great idea
  • Nintex and Office 365 both need more investigation by me
  • Remote Blob Storage is a good idea for nearly everyone – very different perspective than what I’ve previously been told!

Overall Impression:

This session was full of great tips and best practice suggestions tempered with practical applications. This was exactly the kind of information I came to hear. Dan did a great job of presenting a lot of information (despite a massive drive failure just previous to the convention) while keeping it interesting. The only thing that was probably a little much was his in-depth explanation of Claims Authentication. His drawings were pretty rough and his enthusiasm for the topic didn’t really transfer to the audience. Regardless, this was a great session.


SharePoint Data Access Shootout

Scot Hillier – http://www.shillier.com

My Notes:

  • LINQ cannot query across multiple lists (unless there is a lookup connection)
  • SPSiteDataQuery can query all lists of a certain template using CAML within a Site or Site Collection
  • SPMetal.exe generates Object Relational Map needed for LINQ  (in hive bin)
  • LINQ isn’t going to have much support in SP 2013
  • SP 2013 has continuous crawl
  • Keyword Queries are very helpful
  • KQL: ContentClass determines the kind of results you get. (ie STS_Web, STS_Site, STS_ListItem_Events)
  • Search in 2013 provides a rest interface to use KQL in JavaScript
  • CSOM is very similar to Serverside OM
  • CSOM is JS or .NET

Key Insights:

  • Keyword Query Language (KQL) needs more consideration as an effective query language for SharePoint.
  • LINQ isn’t actually a great way to access SP data despite Microsoft’s big push over the past couple of years.

Overall Impression:

This was a strange session. He didn’t go into enough depth about any one data access method to provide any real insight to those of us familiar with them and he moved so quick that anyone new to them would just have been overwhelmed. This session would have been better if he’d given clear and practical advise on when to use these methods rather than just demoing them. My guess is that he was trying to cover way too much information in too little time. However, I did enjoy hearing more about the Keyword Query Language since this is something I haven’t done much of and is rarely mentioned. Those tips alone made the whole session worthwhile.


HTML5 JavaScript APIs: The Good, The Bad, The Ugly

Christian Wenz – http://www.hauser-wenz.de/s9y/

My Notes:

  • HTML5 is a large umbrella of technologies
  • Suggests Microsoft WebMatrix is a good Editor
  • Semantics for HTML elements is a powerful new feature: input type = email, number, range, date, time, month, week, etc. These customize the type of editor and adds validation
  • Suggests Opera Mobile Emulator is a good testing tool
  • Additional elements: aside, footer
  • Requesting location: navigator.geolocation.getCurrentPosition(function(result){console.log(result)});
  • to debug local cache use Google Chrome by going to: Chrome://app cache-internals
  • Worker() web worker allows messaging for functions
  • CORS allows cross domain requests
  • Web sockets do not have full support yet but will be very cool

Key Insights:

  • HTML 5 is going to dramatically change how we think of website capabilities – eventually.
  • Although exciting, HTML 5 has a long way to go and several of it’s most compelling features have little to no support in main stream browsers.

Overall Impression:

Christian did a good job of keeping the energy up about HTML 5 and showing off some of the cool features. Unfortunately he seemed to lose site of the big picture in favor of really detailed samples. I enjoyed the presentation but would like to have had more guidance about how to get started and what to focus on with this new style of web development.


Roadmap: From HTML to HTML 5

Paul D. Sheriff – http://weblogs.asp.net/psheriff/

My Notes:

  • Lots of new elements – but they don’t do anything. They make applying CSS easier and allow search engines to parse through a page easier.
  • Browsers that don’t understand new elements will treat them as divs – but styles won’t be applied.
  • Lots of new input types (color, tel, search, URL, email, number, range, date, date-time, time, week, month)
  • New attributes (autofocus, required, placeholder, form validate, min, max, step, pattern, title, disabled)
  • CSS3 has huge style upgrades but there are still a lot of browser incompatibilities
  • JqueryUI, Modernizr = good tools, use VS 2012 with IE10 or Opera
  • Modernizr allows you to use HTML 5 with automatic replacements in incompatible browsers. Uses JQuery and is included automatically with VS 2012.
  • This stuff is not ready for the prime time except for mobile browsers. Modernizr fills in those gaps.
  • Box-sizing can be either border-box or content-box, which helps with the div width interpretation problem
  • Dude appears to hate JavaScript and HTML 5, sure love hearing a presenter complain about what we all came to learn about!

Key Insights:

  • Use Visual Studio 2012 with Modernizr to make HTML 5 websites

Overall Impression:

This session really annoyed me. Paul was very knowledgeable and had a lot of information to share. Unfortunately, he was so busy bashing the technology we all came to see that it was hard to know why we were even there.


Scaling Document Management in the Enterprise: Document Libraries and Beyond

Dan Holme – Intelliem

My Notes:

  • Can store up to 50 million documents in a single document library
  • SP 2013 allows documents to be dragged onto the doc library in the browser to upload – no ActiveX required
  • When a User is a member of the default group for a site they get the links in office and on their mysite. Site Members is the default group by default, but this can be switched in the group settings. Suggests creating an additional group that contains everyone on the site and has no permissions, then this can be the default group.
  • Email enabled document libraries can be very helpful for receiving documents outside of your network
  • Pingar is a recommended product he briefly mentioned
  • Big improvements in navigation using managed metadata service in SP 2013
  • Content type templates can use the columns as quick parts in Word

Key Insights:

  • Separating Site Membership from Site Permissions by creating an additional group just for managing memberships is a great idea.
  • A lot can be done with SP 2010 but SP 2013 will add a few key features to make things easier (drag and drop on the browser will be awesome).

Overall Impression:

The tip about site membership was worth the whole session. Additionally he reviewed a lot of the basics of content types and the content type hub. While this wasn’t particularly helpful to me, I can’t wait to get ahold of his slides for both this and his other sessions. He had way too many slides for the amount of time he was given.

This session reminded me of how powerful SharePoint is at so many things. Document management is not a particularly exciting topic to me but it is one of the key reasons we are using the SharePoint platform. A review of the features available to maintain the integrity of our data and to simply the classification of that data was very helpful.


SharePoint in Action: What We Did at NBC Olympics

Dan Holme – Intelliem

My Notes:

  • Keep SharePoint simple. Use OOTB features as much as possible
  • 300 hours of content broadcasted per day
  • NBCOlympics.com streamed every competition live
  • Most watched event in TV history
  • 3,700 NBC Olympics team members
    • 1 SP admin/support
  • PDF viewing was turned on despite security concerns
  • Set as default IE page – very difficult to do, they used a script to set a registry entry to account for multiple OSs and browser versions
  • All additional web applications were exposed through SP using a PageViewer WP
    • Phone Directory, Calendar application
  • WebDAV was used to allow other apps to publish documents
  • Global Navigation on top site using tabs (drop down menus)
    • Quick launch had contextual items to site
    • Navigation centric homepage, kept navigation as simple as possible. Only homepage had global navigation.
  • No real branding (put picture on right and used a custom icon). They set the site icon to go to the main web page because it’s what users expected.
  • Did not use lists or libraries as terms instead used Content (libraries, other lists) and Apps (Calendar, Tasks, etc.)
  • Suggests hiding “I like it” and notes since they are not helpful and deprecated in SP 2013
  • Site mailboxes for teams using OWA
  • Embedded documentation: Put basic instructions right on the homepage of team sites as needed. Also above some document libraries – basic upload and open instructions.
  • Focus on usability since there was no time for training
  • Took out All Site Content link and all other navigation was on homepage of site. Sub sites had tab links to parent site.
  • Used InfoPath to customize List Forms mostly to add instructions (placed below field title on left)
  • Lots of calendars. Conference room calendars were very popular. Didn’t use exchange for this in order to accommodate outside users.
  • Used InfoPath lists and workflows to replace paper processes. Kept them simple but effective. Although not used in this case, he recommends Nintex for more advanced needs.
  • Self-service help desk: printer/app installs, FAQs. Showed faces of team.
    • PageViewer web part and point it to \\servername to show all printers and put instructions on right to get those installed directly out of SP
    • Kept running FAQ to show common solutions
  • IT Administration site: ticket system used issue tracking list highly customized with InfoPath list forms.
    • Inventory lists in IT admin, also DHCP lease reports using powershell to dump that information.
    • List for user requests. WF for approvals, then Powershell took care of approved memberships directly in AD.
    • Used a list for password resets. Powershell would set password to generic password as requested (scheduled task every 5 min)
    • Powershell script to create team sites through list requests
  • SP will never be used to broadcast the Olympics but very effective to manage those teams
  • You must understand your users and build to what users really want/need
    • Don’t overwork, don’t over brand – It just clutters.
    • Don’t over deliver or over train.

Key Insights:

  • Keeping global navigation centralized in a single location (without including everything) and providing contextual navigation as needed can keep things simple from a management perspective while still allowing things to be intuitive.
    • Ensuring all navigation needed is exposed through the Quick Launch eliminates the need for All Site Content (except for Admin) and ensures your sites are laid out well
    • As long as you allow users to easily return to the global navigation from any sub site (Using the site icon is very intuitive) there is no need to clutter every site with complicated menu trees.
  • Request lists and Scheduled Tasks running Powershell Scripts can be used to create easy to manage but very powerful automation.
  • Removal of “I like it” and notes icons is a good idea
  • Embedding instructions directly on a given site/list using OOTB editing tools can increase usability dramatically
  • InfoPath List forms can go a long way towards improving list usability by making things appear more intuitive and providing in line instructions.
  • There are so many cool things in SP it can be easy to forget all the amazing things you can do using simple OOTB functionality. It is far too tempting to over deliver and over share when end users really only want to get their job done in the simplest way possible.

Overall Impression:

This was the best session I attended and made the entire conference worthwhile. It is unfortunately rare that you can see SP solutions in the context of an entire site. Seeing how SP was used to help manage one of the largest and most daring projects I can imagine was both inspiring and reassuring.

Besides the several tips of things they did (many of which will soon be showing up in our environment), he was able to confirm several things we were already doing that we were a little unsure about. Even better was his focus on simplifying things. I get so excited about SP features that sometimes I overuse them or forget the real power of simple lists. It was a fantastic reminder that we are often over delivering and therefore complicating things that just make SP scary and hard to use for end users.


Convention Summary

DevConnections 2012 was great. I had a great time in Vegas and I brought home several insights that have immediate practical value. Really, there’s not much more you can ask for in a technical convention.


Creating a PnP TemplateProviderExtension

$
0
0
Applies To: OfficeDev PnP, SharePoint, PowerShell

The SharePoint PnP Remote Provisioning engine is awesome. With just a couple of lines of code or some quick PowerShell you can have a deployable “template” for your SharePoint site (on-premises or O365). OfficeDev PnP offers much more, but it’s the provisioning aspect of things we’re going to talk about today.

Specifically, we’re going to talk about extending the process through the new ITemplateProviderExtension interface. In the August 2016 release the PnP team released the ability to create your own provider extensions and incorporate them directly in the retrieval and application of your PnP templates (Read the announcement here, see an example here).

These new extensions allow you to stick your custom logic directly into the generation of templates and the application of templates. This allows you to apply special tweaks, adjust output, generate additional objects/calls, etc. There are 4 entry points (see the interface below) that give you a lot of flexibility.

The Project

An extension is just a class that implements the ITemplateProviderExtension (more about this in a bit). If you are interfacing with the provisioning engine using .NET directly then you can just add the class to your project. More likely, however, you’ll want to add it as a Class Library (this is true for calling it through PowerShell as well).

In Visual Studio, add a new project of type Class Library (File > New > Project and select Class Library from the list of templates, give it a name, and click OK).

You’ll need to add the SharePoint PnP Core library NuGet package to your project. Right-Click on your project in Solution Explorer and choose Manage NuGet Packages… In the NuGet Package Manager click on Online in the left pane and type PnP into the Search Online box in the upper-right. From the results pick the SharePoint PnP Core library that matches your targeted version and click Install (I’m using SharePoint PnP Core library for SharePoint 2013 since I am targeting On-Premises SharePoint 2013):

PnP Package

This will take just a minute or so to copy everything into your project. You’ll probably be promoted to accept some licenses (just click accept). Once this is done, you can click Close.

The Interface

In your project you have a few files like Class1.cs, SharePointContext.cs and TokenHelper.cs. You can leave all of these (they won’t hurt anything). Right-click on Class1.cs and choose Rename. Enter the name of your extension. Visual Studio will also prompt you to rename the references for Class1 – Click Yes.

To implement the interface, you’ll want to slap a using statement up on top of your extension class for OfficeDevPnP.Core.Framework.Provisioning.Providers then implement the ITemplateProviderExtension like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OfficeDevPnP.Core.Framework.Provisioning.Providers;

namespace MyExtension
{
    public class CommentIt : ITemplateProviderExtension
    {
    }
}

Right-Click on ITemplateProviderExtension and select Implement Interface > Implement Interface to have the class stubbed out for you:

ImplementInterface

So what the heck is all this? Let’s go through the methods and talk about what you’re going to want to use.

Entry Points

Your template provider extension can intercept the template at 4 different entry points and then do whatever it is you want to do. I find the name of the entry points a little difficult to follow, but here’s where they’re called within the life cycle of the template:

  • From SharePoint to Template (Get-SPOProvisioningTemplate)
    • Template object generated from SharePoint
      • PreProcessSaveTemplate
    • Template serialized into XML
      • PostProcessSaveTemplate
    • Template saved to file system

 

  • From Template to SharePoint (Apply-SPOProvisioningTemplate)
    • Template loaded from file system as XML
      • PreProcessGetTemplate
    • Template deserialized from XML to Template object
      • PostProcessGetTemplate
    • Template object applied to SharePoint

And here’s a reference chart:

Template Is
Action Template Object XML Stream
From SP to Template (Save) PreProcessSaveTemplate PostProcessSaveTemplate
Applying Template (Apply) PostProcessGetTemplate PreProcessGetTemplate

Supports Properties

The Supports properties indicate to the provisioning engine which entry points your extension supports (where you want to inject your logic). You’ll need to edit each of these to remove the NotImplementedException and to return true when you want to inject during that point and false when you don’t.

For my extension, I just want to tweak the XML when someone is saving the template from SharePoint so here’s what mine look like:

public bool SupportsGetTemplatePostProcessing
{
    get { return (false); }
}

public bool SupportsGetTemplatePreProcessing
{
    get { return (false); }
}

public bool SupportsSaveTemplatePostProcessing
{
    get { return (true); }
}

public bool SupportsSaveTemplatePreProcessing
{
    get { return (false); }
}

Initialize

The Initialize method is where you can pass any settings and do any setup. For my extension, I am just passing a string that I will inserting into the template XML:

private string _comment;
public void Initialize(object settings)
{
    _comment = settings as string;
}

Processing Methods

You only need to implement the methods where you indicated you were supporting them in the Supports properties. You can leave the rest with the default NotImplementedException in place.

For this example, I just want to tweak the XML when someone is saving the template from SharePoint so I returned true for the SupportsSaveTemplatePostProcessing property which means I need to implement the PostProcessSaveTemplate method. For what I’m doing, you’ll need a few more using statements:

using System.IO;
using System.Xml;
using OfficeDevPnP.Core.Framework.Provisioning.Providers.Xml;

Here’s are my methods:

public OfficeDevPnP.Core.Framework.Provisioning.Model.ProvisioningTemplate PostProcessGetTemplate(OfficeDevPnP.Core.Framework.Provisioning.Model.ProvisioningTemplate template)
{
    throw new NotImplementedException();
}

public System.IO.Stream PostProcessSaveTemplate(System.IO.Stream stream)
{
    MemoryStream result = new MemoryStream();

    //Load up the Template Stream to an XmlDocument so that we can manipulate it directly
    XmlDocument doc = new XmlDocument();
    doc.Load(stream);
    XmlNamespaceManager nspMgr = new XmlNamespaceManager(doc.NameTable);
    nspMgr.AddNamespace("pnp", XMLConstants.PROVISIONING_SCHEMA_NAMESPACE_2016_05);

    XmlNode root = doc.SelectSingleNode("//pnp:Provisioning", nspMgr);
    XmlNode commentNode = doc.CreateComment(_comment);
    root.PrependChild(commentNode);

    //Put it back into stream form for other provider extensions to have a go and to finish processing
    doc.Save(result);
    result.Position = 0;

    return (result);
}

public System.IO.Stream PreProcessGetTemplate(System.IO.Stream stream)
{
    throw new NotImplementedException();
}

public OfficeDevPnP.Core.Framework.Provisioning.Model.ProvisioningTemplate PreProcessSaveTemplate(OfficeDevPnP.Core.Framework.Provisioning.Model.ProvisioningTemplate template)
{
    throw new NotImplementedException();
}

This is a pretty silly example, but here’s what the code above is doing in the PostProcessSaveTemplate method:

  • Line 28, The method expects us to return the transformed XML steam when we’re done making our tweaks, so just getting it ready
  • Lines 31-34, We can use the native XmlDocument objects to interact with the XML Stream. We just load it into a document and account for the pnp namespace.
  • Line 36, We find the root node of the XML Template using xpath and the namespace
  • Line 37, We generate a new XML Comment using the string passed into our Initialize method
  • Line 38, We jam the comment into the root node so it shows up right at the top
  • Lines 41-44, We save the modified XmlDocument to our result stream, reset it, then pass it along its way

Using Your Extension

Great, so now we’ve got an extension! How do we use this thing? In .NET it’s as simple as initializing our extension class and passing it into the XMLTemplateProvider’s SaveAs method (see the announcement for an example).

In PowerShell, we can write a script to load the extension from our dll and provide it in the TemplateProviderExtensions argument to the Get-SPOProvisioningTemplate or Apply-SPOProvisioningTemplate cmdlets.

Here’s an example of a PowerShell script that uses my custom CommentIt extension (Be sure to heck your dll location):

[CmdletBinding()]
param
(
    [Parameter(Mandatory = $true, HelpMessage="Enter the URL of the target site, e.g. 'https://intranet.mydomain.com/sites/targetSite'")]
    [String]
    $TargetSiteUrl,

    [Parameter(Mandatory = $false, HelpMessage="Enter the filepath for the template, e.q. Folder\File.xml or Folder\File.pnp")]
    [String]
    $FilePath,

    [Parameter(Mandatory = $true, HelpMessage="Enter the comment to add!")]
    [String]
    $TemplateComment,

    [Parameter(Mandatory = $false, HelpMessage="Optional administration credentials")]
    [PSCredential]
    $Credentials
)

if(!$FilePath)
{
    $FilePath = "Extractions\site.xml"
}

if($Credentials -eq $null)
{
	$Credentials = Get-Credential -Message "Enter Admin Credentials"
}

Write-Host -ForegroundColor Yellow "Target Site URL: $targetSiteUrl"

try
{
    Connect-SPOnline $TargetSiteUrl -Credentials $Credentials -ErrorAction Stop

    [System.Reflection.Assembly]::LoadFrom("MyExtension\bin\Debug\MyExtension.dll") | Out-Null
    $commentIt = New-Object MyExtension.CommentIt
    $commentIt.Initialize($TemplateComment)

    Get-SPOProvisioningTemplate -Out $FilePath -Handlers Lists,Fields,ContentTypes,CustomActions -TemplateProviderExtensions $commentIt

    Disconnect-SPOnline

}
catch
{
    Write-Host -ForegroundColor Red "Exception occurred!"
    Write-Host -ForegroundColor Red "Exception Type: $($_.Exception.GetType().FullName)"
    Write-Host -ForegroundColor Red "Exception Message: $($_.Exception.Message)"
}

Please note, that you’ll need the PnP PowerShell Cmdlets installed for this to work. Instructions can be found here. I am using the 2013 On-Premise version but this script will work with whatever version you’re using.

Here’s what’s happening in this script:

  • Lines 1-29, Just setting up the parameters for the script. Nothing too special here
  • Line 31, Always nice to remind the user of important details
  • Line 35, Connect to SharePoint with a single line – wowee!
  • Line 37, Load up your dll from the file system (You can provide a full or relative path here). The pipe to Out-Null just keeps us from printing dll information to the console which would be strange to an end user
  • Line 38, Get your extension class as an object using the namespace from your dll
  • Line 39, Call the Initialize method of the extension. In this case we are passing in the comment received as a parameter to the script
  • Line 41, This is a standard call to Get-SPOProvisioningTemplate with the exception that we are specifying our custom extension in the TemplateProviderExtensions parameter
  • Line 43, Close up that connection

If we take a look at the XML file generated by our template (With a TemplateComment parameter of Look at this sweet comment!), we can see:

Comment.PNG

Aw yeah, boyo!

Debugging Your Extension

Generally, you’re going to be doing something more complicated than that and you’ll probably want to debug the thing. If you are calling your extension in .NET within Visual Studio then things are pretty much as you’d expect – Set your breakpoints and run the thing. PowerShell is a little less obvious.

To debug your extension in the script above, you just need to see your breakpoints within the extension (say on the Initialize method). Then use the Debug > Attach to Process command within Visual Studio. Scroll through the processes until you find where your PowerShell script is running. I generally use the Windows PowerShell ISE to edit my scripts and that shows up as powershell_ise.exe. Choose it then click Attach:

AttachToProcess.PNG

Now when you run your script, your breakpoints should be hit. Fun Note, you’ll need to close and open the powershell window in order to release the dll when you want to make adjustments and build it.

Now you’re ready to take advantage of this incredibly powerful extension point! You can find the full code for this sample extension here. Have fun!


Viewing all 19 articles
Browse latest View live