Wednesday, September 30, 2009

Tech Ed 2009 - Visual Studio 2008 IDE Tips [DEV301]

I was watching Tech Ed Online - Visual Studio 2008 IDE Tips [DEV301] and thought I'd record the tips I was interested in.

Extracted from http://blogs.msdn.com/saraford/archive/2009/09/09/teched-australia-25-visual-studio-2008-ide-tips.aspx

5. You can use a reg hack for customizing search results
HKCU\Software\Microsoft\VisualStudio\9.0\Find String Find=$f$e($l,$c):$t\r\n

6. How not to accidentally copy a blank line
Tools – Options – Text Editor – All Languages – General, Uncheck Apply cut or copy to blank lines

22. You can use tracepoints to log stuff in your code
Right-click in indicator margin, select breakpoints, select Insert Tracepoint

Bidirectional data connection between Sitecore (CMS) and Salesforce (CRM)

I've been working a product called (S4S) that provides a number of ways to integrate Sitecore and Salesforce. This parallels the Sitecore (Microsoft) CRM Membership Provider but with Salesforce rather than Microsoft CRM.

One part (the Security Connector) provides custom ASP.NET Security Providers (Membership, Role and Profile) that uses the Salesforce Partner API to expose Contacts and Accounts as Users and Roles in Sitecore respectively. This is useful for companies who want to provide extranet logins in Sitecore for their Salesforce contacts. Using the standard ASP.NET providers makes the integration seemless for sitecore admins. I.e. You can use Salesforce derived users and roles within Sitecore in the same way you would use a native user and/or role. You use the same admin tools from the Sitecore Shell. The profile connection can be used to expose Salesforce Contact fields to Sitecore.

Some other goodness (the Data connector) makes bidirectional data transfer easier. At its core it is a entity based .NET wrapper around the Salesforce partner and metadata API's. This makes strongly typed CRUD (Create, Retrieve, Update, Delete) operations really simple. Internally we handles compressing the outbound and inbound API calls and caching the meta data to improve performance. There is also query building to produce dynamic SOQL. I've done some quick demos with standard ASP.NET GridViews and FormViews round tripping Salesforce data through Sitecore (remember to set typesThatShouldNotBeExpanded in the web.config first!). The is also a Generic entity service that can accommodate any Salesforce object. This is useful if the field definition is unknown or likely to change.

Finally there is the Report and Dashboard Connector that provides .NET API access to Salesforce Reports and Dashboards. With reports you can pull out the HTML or have it converted to a DataSet. By surfacing the graphs and charts from Salesforce rather than using static content keeps everything up to date with changes in the Salesforce data.

Tuesday, September 29, 2009

Sitecore CMS 6 - IndexOutOfRangeException from Sitecore.Shell.Applications.Security.UserManager

Something odd has occurred to my Sitecore 6 install. I've started getting the following exception from the Security Tools > User Manager:

Exception: System.IndexOutOfRangeException
Message: Index was outside the bounds of the array.
Source: ComponentArt.Web.UI
   at ComponentArt.Web.UI.GridItem.set_Item(Object obj, Object value)
   at Sitecore.Shell.Applications.Security.UserManager.UserManager.Users_ItemDataBound(Object sender, GridItemDataBoundEventArgs e)
   at ComponentArt.Web.UI.Grid.ItemDataBoundEventHandler.Invoke(Object sender, GridItemDataBoundEventArgs e)
   at ComponentArt.Web.UI.Grid.LoadGridItems(GridItemCollection arItems, Object[] arObjects, Int32 iLevel)
   at ComponentArt.Web.UI.Grid.DataBindToEnumerable(IEnumerable arList)
   at ComponentArt.Web.UI.Grid.DataBind()
   at Sitecore.Web.UI.Grids.ComponentArtGridHandler`1.DataBind()
   at Sitecore.Web.UI.Grids.ComponentArtGridHandler`1.InitializeGrid(Boolean dataBind)
   at Sitecore.Web.UI.Grids.ComponentArtGridHandler`1..ctor(Grid grid, IGridSource`1 source, Boolean dataBind)
   at Sitecore.Web.UI.Grids.ComponentArtGridHandler`1.Manage(Grid grid, IGridSource`1 source, Boolean dataBind)
   at Sitecore.Shell.Applications.Security.UserManager.UserManager.OnLoad(EventArgs e)
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

Using reflector I found the code in question:

private void Users_ItemDataBound(object sender, GridItemDataBoundEventArgs e)
{
    e.Item["Profile.PortraitFullPath"] = Themes.MapTheme((e.DataItem as User).Profile.Portrait);
}

There appears to be an issue with the Portrait profile profile property not existing for the GridItem.


Turns out the issue was caused by the aspx files under \sitecore\ in the web site being out of sync with the Sitecore DLLS. In particular, I had Sitecore.Client.dll (6.0.1.2906) and an out of date version of \sitecore\shell\Applications\Security\UserManager\UserManager.aspx. I believe this can about by trying to keep Sitecore under TFS source control. At some point an update package was installed but the changes to the sitecore folder never found their way into source control.

Monday, September 28, 2009

Adding FLV files to the TFS build output

I have several flash videos that I need to include in an ASP.NET web site. From the build servers perspective they should be static content.

For some reason the TFS build server wasn't copying them over to the Binary drop location as part of the _CopyWebApplication target but the SWF files were.

Turned out to be a simple fix. The SWF files were set as "Build Action" "Content" while the FLV files were "Build Action" "None". Changing them to content fixed the problem.


On a side note. I needed to add the MIME type for FLV files to IIS.
.flv   video/x-flv

Friday, September 25, 2009

Arggg - websites with broken email address validation

Gmail offers a handy feature for adding email address aliases by appending a '+' and a term to the end of your email address. See Using an address alias and Wikipedia:Sub-addressing

The issue I keep running into is incorrect email address validation preventing the use of characters that should be valid according to RFC 5322 and RFC 3696.

What annoys me most is that there often isn't an easy way to tell a site that there is a bug. Some problematic sites I've come across:

From some quick Googling it may be that RegEx just won't cut it for email address validation.

Monday, September 21, 2009

Finding missing SATA hard drive after upgrading to Windows 7

I recently upgraded to Windows 7 buy adding an additional SATA hard drive and doing a fresh install. After logging in I found the older existing drive wasn't appearing in Windows 7.

I found I needed to bring up Disk manager (diskmgmt.msc) and map it to a drive letter.

Lake Wanaka - Island within an island within an island (Mou Waho Island)

Caught a bit of South on TV last night and thought I'd find the location they found with a small island sitting inside a larger island in Lake Wanaka. Turns out the island is called Mou Waho Island and the smaller "island" is in the "Arethusa Pool".


View Larger Map

Thursday, September 17, 2009

Microsoft launches hosted JavaScript libraries

Microsoft has launched the Microsoft Ajax CDN (Content Delivery Network) service that provides caching support for AJAX libraries. In addition to being an alternative to the Google AJAX Libraries API it looks like it will add support for the ASP.NET JavaScript libraries.

See Also:

Visual Studio Macros to clean up HTML tables from Microsoft Word

The following Visual Studio Macros can be used to strip an HTML table copied from word down to it's core tags.
It's a little(very) rough around the edges but it seems to get the job done.

Sub CleanWordTable()

        FlattenStyles()

        DTE.ExecuteCommand("Edit.Replace")
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.FindWhat = "<o:p></o:p>"
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.Execute()

        DTE.ExecuteCommand("Edit.Replace")
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.FindWhat = "class=""MsoNormal"""
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.Execute()

        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.FindWhat = "\<span.@\>"
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxRegExpr
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.Execute()

        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.FindWhat = "\</span\>"
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxRegExpr
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.Execute()

        'DTE.ExecuteCommand("Edit.FormatSelection")

        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.FindWhat = "style="".@"""
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxRegExpr
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.Execute()

        ''DTE.ExecuteCommand("Edit.FormatSelection")

        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.FindWhat = "style="".@(\n.@)"""
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxRegExpr
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.Execute()

        DTE.ExecuteCommand("Edit.FormatSelection")

        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.ReplaceWith = "<td>"
        DTE.Find.FindWhat = "\<td.@\>"
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxRegExpr
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Execute()

        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.FindWhat = "align=""right"""
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxRegExpr
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Execute()

        DTE.ExecuteCommand("Edit.FormatSelection")

        DTE.Find.FindWhat = "<p>"
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then
            Throw New System.Exception("vsFindResultNotFound")
        End If

        DTE.Find.FindWhat = "</p>"
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then
            Throw New System.Exception("vsFindResultNotFound")
        End If

        DTE.Find.FindWhat = "</b>"
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then
            Throw New System.Exception("vsFindResultNotFound")
        End If

        DTE.Find.FindWhat = "<b>"
        DTE.Find.ReplaceWith = ""
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral
        DTE.Find.ResultsLocation = vsFindResultsLocation.vsFindResultsNone
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        If (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then
            Throw New System.Exception("vsFindResultNotFound")
        End If

        DTE.ExecuteCommand("Edit.FormatSelection")
    End Sub

    Sub FlattenStyles()
        ' Recursively Flatten style tags that span multiple lines
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocumentSelection
        DTE.Find.Action = vsFindAction.vsFindActionReplaceAll
        DTE.Find.FindWhat = "{style=""[^""]@}\n"
        DTE.Find.ReplaceWith = "\1"
        DTE.Find.Target = vsFindTarget.vsFindTargetCurrentDocument
        DTE.Find.MatchCase = True
        DTE.Find.MatchWholeWord = False
        DTE.Find.Backwards = False
        DTE.Find.MatchInHiddenText = True
        DTE.Find.PatternSyntax = vsFindPatternSyntax.vsFindPatternSyntaxRegExpr
        If Not (DTE.Find.Execute() = vsFindResult.vsFindResultNotFound) Then
            FlattenStyles()
        End If
    End Sub

Friday, September 11, 2009

Salesforce WebServices SOAP Compression - Response is not well-formed XML. - '', hexadecimal value 0x1F, is an invalid character

I've been getting the following error when trying to login to the Salesforce Partner API with invalid credentials and SOAP Compression enabled. Valid credentials and subsequent API calls have been working fine.

System.InvalidOperationException: Response is not well-formed XML.
--->  System.Xml.XmlException: '', hexadecimal value 0x1F, is an invalid character.
Line 1, position 1..

The exception was coming out of the following method from the GzipWebRequest as defined here:

    public override WebResponse GetResponse()
    {
        return new GzipWebResponse(wr.GetResponse ());
    }

It turns out this is truly ancient .NET 1.1 code that is no longer applicable for .NET 2.0 and later projects. This has been the case for some time, I've just never gone into this part of the code base as it hasn't been failing until now.

Rather than manually accepting and decompressing the response stream we now just set EnableDecompression to true when inheriting the generated HttpWebClientProtocol (the Generated Proxy Client).

There is still the need to manually compress the outbound request. Improvements can be made here too by ditching the third party ICSharpCode.SharpZipLib.dll and using the built in System.IO.Compression.GZipStream class. There isn't anything wrong with the SharpZipLib but it's one less DLL to cart around.

See also:

Privacy Preferences Project (P3P) header for setting session cookies when nested in an iframe

This can be setup in IIS as CP="CAO DSP IVAa IVDa OUR BUS UNI OTC". Alternatively, I've added the following code to the Page_Load method of the master page.

        string p3pHeader = "CP=\"CAO DSP IVAa IVDa OUR BUS UNI OTC\"";
        if (!string.IsNullOrEmpty(p3pHeader))
        {
            //P3P headers will typically be set in IIS. This is more useful when running locally from Visual Studio.
            HttpContext.Current.Response.AddHeader("p3p", p3pHeader);
        }

Wednesday, September 9, 2009

WCF - Custom tool error: Failed to generate code for the service reference

Error 240 Custom tool error: Failed to generate code for the service reference 'FooService'.  
Please check other error and warning messages for details. 
C:\Development\SolutionName\ProjectName\Service References\FooService\Reference.svcmap
  • Try "Update Service Reference"
  • Try "Configure Service Reference" and unchecking "Reuse types in referenced assemblies"
  • Try searching through the generated files (.disco, .wsdl, .xsd) for path references that differ from the address set in the step above.
  • Try dropping the entire service reference and adding it again.

Tuesday, September 8, 2009

TF20017: The area or iteration provided for field 'Iteration Path' could not be found.

When attempting to update a TFS work item I got the following error prompt:

To add the new iteration from the menu: Team > Team Project Settings > Areas and Iterations...

Then jump to the Iteration tab and use the "Add a child node" button.

Refreshing the TFS cached iteration list

  1. Close Visual Studio to release any file locks in the cache.
  2. Delete all the files in:
    C:\Documents and Settings\[USER]\Local Settings\Application Data\Microsoft\Team Foundation\2.0\Cache
    or
    C:\Users\[USER]\AppData\Local\Microsoft\Team Foundation\2.0\Cache
    depending on your OS.