RSS

Tag Archives: XML

Open a Link in SharePoint 2010′s Modal Dialog

Applies To: SharePoint 2010

Recently I’ve been customizing the XSLT of some of my XsltListViewWebParts. Getting all of that to work is worth another post in itself, but I wanted to talk briefly about a small frustration I had. I was customizing an announcement’s list part and I stripped out most of the nearly 1700 lines of XSLT used by default. However, one of the things I liked was being able to open the announcement in the modal dialog (sometimes called the Lightbox or the popup window):

Some searching through the autogenerated XSL for my view, I came across this section in the LinkTitleNoMenu.LinkTitle template:

<a onfocus="OnLink(this)" href="{$FORM_DISPLAY}&amp;ID={$ID}&amp;ContentTypeID={$thisNode/@ContentTypeId}" onclick="EditLink2(this,{$ViewCounter});return false;" target="_self">
	<xsl:call-template name="LinkTitleValue.LinkTitle">
		<xsl:with-param name="thisNode" select="$thisNode"/>
		<xsl:with-param name="ShowAccessibleIcon" select="$ShowAccessibleIcon"/>
	</xsl:call-template>
</a>

I’m going to dissect what’s happening in terms of XSL for the next couple of paragraphs. If you’re just looking for the format needed, skip to the Link Format section.

Basically this is the link that gets generated inside the view’s table. The call-template element is used to fill the contents (link text), but I already had that covered and am mostly just interested in the formatting of the link to do the modal dialog magic.

Some quick experimentation shows that the onfocus call was not needed for the popup (This is what causes the menu to display and the box around the row in a standard view). Also not needed is the target=”_self” since this is equivalent to leaving the target attribute out entirely. There are really just 2 key items:

HREF

This is the URL to display in the modal dialog. In this case, it’s generated using a number of variables defined automatically. The $FORM_DISPLAY is the absolute path to the item display page. The $ID is generated using a simple Template call (we’ll come back to this). and the $thisNode/@ContentTypeId is pulling the ContentTypeId attribute from the current Row element in the $AllRows variable populated by the dsQueryResponse XML. For now, all you need to know is that it is automatically finding the display form URL and populating the necessary ID and ContentTypeId query strings for the specific item URL.

OnClick

This calls the EditLink2 javascript method defined in Core.js. This extracts the link with the webpart’s ID ($ViewCounter) and shows it in the modal dialog. Then it returns false to prevent the browser from following the link like normal.

Trying to implement this exactly in my code wasn’t too hard. Unfortunately, it wouldn’t load in the popup and always just opened the page directly. Doing some searching, I came across a quick explanation and solution on technet. The EditLink2 function attempts to use a window object referenced by my webpart’s id ($ViewCounter). Whatever code sets this all up wasn’t firing in my XSL causing the window reference to be NULL and making the function default to just opening the link. Instead of tracking it down somewhere in the default generation, I did something similar to the proposed solution on technet.

Link Format

Ultimately my goal was to have a link generated using this format:

<a href="http://mysharepoint.com/sites/thesite/_layouts/listform.aspx?PageType=4&amp;ListId={SomeGUID}&amp;ID=SomeID&amp;ContentTypeID=SomeContentTypeID" onclick="ShowPopupDialog(GetGotoLinkUrl(this));return false;">Click Me</a>

So, I’m using the same link generation (but this could be any link). The real difference is that instead of calling EditLink2 I’m calling ShowPopupDialog. For the URL, I’m using a technique found in the EditLink2 method of calling GetGotoLinkUrl which extracts the URL from the link element.

XSL Implementation

To get this to work in XSL, you can do something similar to this:

<xsl:for-each select="$AllRows">
	<xsl:variable name="thisNode" select="."/>
	<xsl:variable name="link">
		<xsl:value-of select="$FORM_DISPLAY" />
		<xsl:text>&amp;ID=</xsl:text>
		<xsl:call-template name="ResolveId">
			<xsl:with-param name="thisNode" select ="$thisNode"/>
		</xsl:call-template>
		<xsl:text>&amp;ContentTypeID=</xsl:text>
		<xsl:value-of select="$thisNode/@ContentTypeId"/>
	</xsl:variable>

	<a onclick="ShowPopupDialog(GetGotoLinkUrl(this));return false;">
		<xsl:attribute name="href">
			<xsl:value-of select="$link"/>
		</xsl:attribute>
		<xsl:text>View Announcement</xsl:text>
	</a>
</xsl:for-each>

In the above XSL, we’re looping through each row returned by your view’s CAML query. We setup a link variable that builds the full HREF attribute in lines 3-11. The thing to note is the call to the ResolveId template to pull the item’s ID from the row. This is a standard template that will automatically be referenced as long as you keep the standard includes (main.xsl and internal.xsl).

Then we generate the actual html link in lines 13-17 using the $link variable we created above. This could be consolidated some, but hopefully it’s relatively easy to follow in this format.

That’s it! Now you can generate those links using XSL or follow the link format to make them on your own (like in a content editor web part).

 
3 Comments

Posted by on July 18, 2012 in List/Library Settings, Web Parts, XSLT

 

Tags: , , , , , , , , , , , , , ,

Make Your Cisco IP Phone Ring Using .NET

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
 
2 Comments

Posted by on June 20, 2012 in .NET

 

Tags: , , , , , , , , , , , , , , ,

Using Powershell to Document SharePoint 2010 Farm Configuration

Applies To: SharePoint 2010

Business continuity (Disaster Recovery) is an important topic for SharePoint 2010 and Microsoft has provided a helpful book available online here: Business Continuity Management for Microsoft SharePoint Server 2010. The book is full of steps and options for backing up and restoring your farm.

There is an interesting powershell script starting on page 132 that takes advantage of the Export-Clixml powershell cmdlet to cycle through your farm’s configuration and write everything to XML files. The resulting XML isn’t super user friendly, but it is human readable. Even cooler is that you can use the Import-Clixml cmdlet to instantiate those objects back in powershell later.

Obviously an actual Farm backup (configuration-only) is more helpful for restoration of your settings, but these XML files can be very useful. For instance, if you don’t want to restore all your configuration but just want to document it somewhere so you can either reference it in whole or in part, this is a great solution. Even better is using it as a guide to pick and choose the various commands when you’re trying to find some information on the fly.

Unfortunately, the script is all jumbled together and split across multiple pages making the included suggestion of copying and pasting into a text document more complicated than needed. The newlines are all in the wrong places, the comments run together and overwrite commands, page numbers show up, etc. So, I went through the resulting file and formatted it correctly. You can use the copy toolbar to get it directly:

Read the rest of this entry »

 
2 Comments

Posted by on June 15, 2012 in Powershell, SharePoint

 

Tags: , , , , , , , , ,

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

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.

 
3 Comments

Posted by on May 7, 2012 in .NET, PDFdocIcon, SharePoint

 

Tags: , , , , , , , , , ,

Automatically Setting Up PDF Icon Mapping in SharePoint 2010

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.

 
5 Comments

Posted by on May 3, 2012 in .NET, PDFdocIcon, SharePoint

 

Tags: , , , , , ,

Change Your Formatted XML’s Encoding

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>
 
2 Comments

Posted by on May 2, 2012 in .NET

 

Tags: , , , , , , , , , , , , ,

Prettify Your XML in .NET

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.

 
1 Comment

Posted by on May 1, 2012 in .NET

 

Tags: , , , , , , , , , , , ,

Batch Updates with SharePoint Web Services

Applies To: SharePoint

One of the biggest irritations to me coming from a SQL background is trying to do SQL like things in SharePoint 2007. One of those irritations is performing a simple update through the web services.  In SQL, I could write something like:

UPDATE ListTable
    SET FieldName = FieldValue
    WHERE QueryFieldName = QueryFieldValue

In the generic T-SQL above, any row where the QueryFieldName column had a value equal to the QueryFieldValue would have it’s FieldName Column set to FieldValue. Pretty straightforward for anyone with a database background.

With the SharePoint Web Services, however, there isn’t a simple UPDATE command like this. Instead of the one simple command above, we have to make 2 calls to the Web Service and provide some complicated XML parameters. This can be a big hassle the first time you do this, so to hopefully save you some headaches, I’ll provide most of the code required below.

I won’t be showing you how to setup whatever project you are using or how to connect to the web service. The code is in C# and I will assume your service reference (Lists) is called myService.  So let’s get started!

Helper Functions:

I will be using a few helper functions to make generating the XML parameters easier, I’ll go ahead and list these here:

private void AddAttribute(XmlDocument x, ref XmlNode node,
          string attributename, string attributevalue,
          string AttributeNameSpace = "")
{
    XmlNode attnode =
              x.CreateNode(XmlNodeType.Attribute, attributename,
              AttributeNameSpace);
    attnode.Value = attributevalue;
    node.Attributes.Append(attnode);
}

private XmlNode CreateUpdateNode(XmlDocument x, int ID, string RowID)
{
    XmlNode node = x.CreateNode(XmlNodeType.Element, "Method", null);
    AddAttribute(x, node, "ID", ID);
    AddAttribute(x, node, "Cmd", "Update");
    node.AppendChild(CreateFieldNode(x, "ID", RowID));
    return node;
}

private XmlNode CreateFieldNode(XmlDocument x, string Name, string Value)
{
    XmlNode node = x.CreateNode(XmlNodeType.Element, "Field", null);
    AddAttribute(x, node, "Name", Name);
    node.InnerText = Value;
    return node;
}

Retrieve the Items to be Updated:

This is equivalent to the WHERE clause of the example SQL query. You will need to use the GetListItems method of the Lists service.

This method uses the List GUID to reference the target list, I’ll leave it to you to retrieve it and will assume it in the variable ListGUID. You will also need to replace the QueryFieldName, QueryFieldType, and QueryFieldValue variables with appropriate values.

XmlDocument doc = new XmlDocument();
XmlNode queryNode = doc.CreateNode(XmlNodeType.Element, "Query", null);
queryNode.InnerXml =
          string.Format("<Where><Eq><FieldRef Name='{0}' />
                         <Value Type='{1}'>{2}</Value></Eq></Where>",
          QueryFieldName, QueryFieldType, QueryFieldValue);

XmlNode viewNode = doc.CreateNode(XmlNodeType.Element, "ViewFields", null);
viewNode.InnerXml = "<FieldRef Name='ID'/>";

XmlNode resultsNode = myService.GetListItems(ListGUID, null, queryNode,
          viewNode, null,
          doc.CreateNode(XmlNodeType.Element, "QueryOptions", null), null);

 Parse the IDs from the Results:

XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("z", "#RowsetSchema");
XmlNodeList rowNodes = resultsNode.SelectNodes(".//z:row", nsmgr);
List<int> IDs = new List<int>();
foreach (XmlNode rowNode in rowNodes) {
    XmlAttribute result = rowNode.Attributes("ows_ID");
    IDs.Add(int.Parse(result.Value));
}

Perform the Update:

This is equivalent to the SET portion of the example SQL query. You will need to use the UpdateListItems method of the Lists service.

Again, this method uses the List GUID to reference the target list, I’ll leave it to you to retrieve it and will assume it in the variable ListGUID. You will also need to replace the FieldName and FieldValue variables with appropriate values.

<pre>XmlDocument doc2 = new XmlDocument();
XmlNode node = doc2.CreateNode(XmlNodeType.Element, "Batch", null);
AddAttribute(doc2, node, "OnError", "Continue");

int itemCount = 1;
foreach (int i in IDs) {
    XmlNode updateNode = CreateUpdateNode(doc2, itemCount, i);
    updateNode.AppendChild(CreateFieldNode(doc2, FieldName, FieldValue));
    //Append More children here to update multiple fields
    node.AppendChild(updateNode);
    itemCount += 1;
}

myService.UpdateListItems(ListGUID, node);

That’s it. To review, we created a CAML query and retrieved the IDs of the matching rows using the GetListItems method. We parsed the XML result and put all of those IDs in a List<int>. We then added each row ID to a batch update XML parameter which we used in the UpdateListItems method.

As you can see, this is way more complicated and not nearly as obvious as a SQL UPDATE command, but you can take the above code and create a pretty generic wrapper to make your web service updates nearly as easy. Let me know how it works out for you or if you have any questions!

Originally Published on WireBear.com/blog on February 11, 2011
 
Leave a comment

Posted by on February 22, 2012 in SharePoint, Web Services

 

Tags: , , , , ,

 
Follow

Get every new post delivered to your Inbox.