I’ve been playing around with some of my custom code in SharePoint 2013. One of the issues I’ve been noticing is when I add any of my custom web parts to a page, the minimal download strategy (MDS) would failover to the normal asp.net webforms page. You can tell the difference by looking at the url. A url that looks like this:
/_layouts/15/start.aspx#/SitePages/Home.aspx
is using MDS. Notice the actual page is start.aspx and then the page it is ajax loading (MDS) is /sitepages/home.aspx (the part after the hash(#)). Whereas a normal asp.net webforms page url would look like this:
/SitePages/Home.aspx
They both look the same but when using MDS you get an added benefit of less being downloaded on each click and also smoother and snappier page changes.
Gotcha #1 – Decorate your assembly or class with MDSCompliant(true). If your MDS isn’t working and you see this message in your ULS:
MDSLog: MDSFailover: A control was discovered which does not comply with MDS
then you will need to add the attribute (http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.webcontrols.mdscompliantattribute.aspx). Here is an example of adding it to the class:
[MdsCompliant(true)] public class MyWebPart : WebPart { //...Code }
And here is an example of adding it to an assembly (assemblyinfo.cs):
[assembly: MdsCompliant(true)]
Every control that is loaded on the page needs this attribute. So if you are loading other controls in your web part, you’ll need to make sure they also have this attribute.
Gotcha #2 – Declarative user controls typically used for delegate controls need a codebehind and the attribute set as well. I use a lot of delegate controls and then use features to swap out user controls in the delegate controls to add / remove functionality on the site. Typically my user controls didn’t have a codebehind and would just add webparts, html or other controls in the markup. The issue is if you want these controls to be able to be swapped out using MDS, then you will need to add a codebehind to the user control and decorate it with the MdsCompliant attribute.
So a normal user control like this:
<%@ Control Language="C#" AutoEventWireup="true" %> <%@ Register TagPrefix="MyWebParts" Namespace="MyWebParts"%> <MyWebParts:WebPart runat="server" Title="WebPart"></MyWebParts:WebPart>
would need to be converted to this:
<%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %> <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MyControl.ascx.cs" Inherits="MyControl" %> <%@ Register TagPrefix="MyWebParts" Namespace="MyWebParts"%> <MyWebParts:WebPart runat="server" Title="WebPart"></MyWebParts:WebPart>
and with the following codebehind:
[MdsCompliant(true)] public partial class MyControl : UserControl { protected void Page_Load(object sender, EventArgs e) { } }
I couldn’t figure out a way to decorate the usercontrol without using a code behind. If anyone else knows how to do this, please comment or contact me with the info. Thanks!
Gotcha #3 – Inline scripts are not allowed and will need to be added using the SPPageContentManager. If you receive any of the following messages in your ULS logs you will need to look at your content.
MDSFailover: document.write MDSFailover: document.writeln MDSFailover: Unexpected tag in head MDSFailover: script in markup
The first two are obvious; you can’t have any document.write or document.writeln’s in your html. The third is a little less obvious. According to a MS source, in the head these tags are not allowed:
- Meta refresh tag
- link tag for a stylesheet (text/css)
- script tag
- style tag
- title tag
- base tag
The fourth was a big kicker for me. I had made the decision a few years ago to switch almost all of my webparts over to being XSLT rendered. That means I had a lot of inline javascript and css in my xslt. Luckily, I had previously also created a special webpart which could ajax load other webparts using an update panel and had solved the inline script issue before. When I was writing my special ajax loading webpart before I found this page http://www.codeproject.com/Articles/21962/AJAX-UpdatePanel-With-Inline-Client-Scripts-Parser which showed how to extend the ootb update panel to automatically find inline scripts and register them to work correctly using ajax. I was able to slightly modify this code to work for my XSLT rendered webparts.
public bool IsAnAjaxDeltaRequest { get { return false == String.IsNullOrEmpty(Context.Request.QueryString["AjaxDelta"]); } } protected override void Render(HtmlTextWriter output) { base.Render(output); string html = GetHtml(); if (IsAnAjaxDeltaRequest) { html = RegisterAndRemoveInlineClientScripts(this, this.GetType(), html); } output.Write(html); } public static readonly Regex REGEX_CLIENTSCRIPTS = new Regex( "<script\\s((?<aname>[-\\w]+)=[\"'](?<avalue>.*?)[\"']\\s?)*\\s*>(?<script>.*?)</script>", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); public static string RegisterAndRemoveInlineClientScripts(Control control, Type type, string htmlsource) { if (htmlsource.IndexOf("<script", StringComparison.CurrentCultureIgnoreCase) > -1) { MatchCollection matches = REGEX_CLIENTSCRIPTS.Matches(htmlsource); if (matches.Count > 0) { for (int i = 0; i < matches.Count; i++) { string script = matches[i].Groups["script"].Value; string scriptID = script.GetHashCode().ToString(); string scriptSrc = ""; CaptureCollection aname = matches[i].Groups["aname"].Captures; CaptureCollection avalue = matches[i].Groups["avalue"].Captures; for (int u = 0; u < aname.Count; u++) { if (aname[u].Value.IndexOf("src", StringComparison.CurrentCultureIgnoreCase) == 0) { scriptSrc = avalue[u].Value; break; } } if (scriptSrc.Length > 0) { SPPageContentManager.RegisterClientScriptInclude(control, type, scriptID, scriptSrc); } else { SPPageContentManager.RegisterClientScriptBlock(control, type, scriptID, script); } htmlsource = htmlsource.Replace(matches[i].Value, ""); } } } return htmlsource; }
Since this code will automatically register any script references it finds, make sure that the paths to your scripts are correct, otherwise MDS will silently failover without any ULS messages.
Update 5/3/2013:
I have found another potential MDS error message in ULS:
MDSLog: Master page version mismatch occurred in MDS
or in the ajax response if using fiddler:
versionMismatch
I was able to resolve this by browsing to another site and then back to the original site with the issue. Weird one, if anyone knows more about this error, please contact me or comment below.