Wednesday, October 30, 2013

Declaratively configure navigation settings in Sharepoint publishing sites

If you have custom web template based on OTB publishing site (CMSPUBLISHING) you can configure navigation settings declaratively by adding NavigationProperties feature (id = 541F5F57-C847-4e16-B59A-B31E90E6F9EA) to your onet.xml and defining properties, e.g.:

   1: <Feature ID="541F5F57-C847-4e16-B59A-B31E90E6F9EA">
   2:   <Properties xmlns="http://schemas.microsoft.com/sharepoint/">
   3:     <Property Key="InheritGlobalNavigation" Value="true"/>
   4:     <Property Key="IncludeSubSites" Value="true"/>
   5:     <Property Key="IncludePages" Value="false"/>
   6:     <Property Key="IncludeInGlobalNavigation" Value="false"/>
   7:     <Property Key="IncludeInCurrentNavigation" Value="false"/>
   8:   </Properties>
   9: </Feature>

What other properties are supported? In order to answer on this question let’s check NavigationFeatureHandler feature receiver (from Microsoft.SharePoint.Publishing assembly). In its FeatureActivated method it uses private method ApplyNavigationProperties which does actual job:

   1:  
   2: private static void ApplyNavigationProperties(PublishingWeb publishingWeb,
   3: SPFeaturePropertyCollection properties)
   4: {
   5:     if (!publishingWeb.Web.AllProperties.ContainsKey("NavigationPropertiesSet"))
   6:     {
   7:         CmsSecurityUtilities.RunWithWebCulture(publishingWeb.Web, delegate {
   8:             CommonUtilities.ConfirmNotCentralAdminWebApp(publishingWeb.Web);
   9:             for (int j = 0; j < properties.Count; j++)
  10:             {
  11:                 SPFeatureProperty property = properties[j] as SPFeatureProperty;
  12:                 switch (property.Name)
  13:                 {
  14:                     case "InheritGlobalNavigation":
  15:                         publishingWeb.Navigation.InheritGlobal =
  16:                             bool.Parse(property.Value);
  17:                         break;
  18:  
  19:                     case "InheritCurrentNavigation":
  20:                         publishingWeb.Navigation.InheritCurrent =
  21:                             bool.Parse(property.Value);
  22:                         break;
  23:  
  24:                     case "ShowSiblings":
  25:                         publishingWeb.Navigation.ShowSiblings =
  26:                             bool.Parse(property.Value);
  27:                         break;
  28:  
  29:                     case "IncludeSubSites":
  30:                     {
  31:                         bool flag = bool.Parse(property.Value);
  32:                         publishingWeb.Navigation.GlobalIncludeSubSites = flag;
  33:                         publishingWeb.Navigation.CurrentIncludeSubSites = flag;
  34:                         break;
  35:                     }
  36:                     case "IncludePages":
  37:                     {
  38:                         bool flag2 = bool.Parse(property.Value);
  39:                         publishingWeb.Navigation.GlobalIncludePages = flag2;
  40:                         publishingWeb.Navigation.CurrentIncludePages = flag2;
  41:                         break;
  42:                     }
  43:                     case "GlobalIncludeSubSites":
  44:                         publishingWeb.Navigation.GlobalIncludeSubSites =
  45:                             bool.Parse(property.Value);
  46:                         break;
  47:  
  48:                     case "GlobalIncludePages":
  49:                         publishingWeb.Navigation.GlobalIncludePages =
  50:                             bool.Parse(property.Value);
  51:                         break;
  52:  
  53:                     case "CurrentIncludeSubSites":
  54:                         publishingWeb.Navigation.CurrentIncludeSubSites =
  55:                             bool.Parse(property.Value);
  56:                         break;
  57:  
  58:                     case "CurrentIncludePages":
  59:                         publishingWeb.Navigation.CurrentIncludePages =
  60:                             bool.Parse(property.Value);
  61:                         break;
  62:  
  63:                     case "GlobalDynamicChildLimit":
  64:                         publishingWeb.Navigation.GlobalDynamicChildLimit =
  65:                             int.Parse(property.Value);
  66:                         break;
  67:  
  68:                     case "CurrentDynamicChildLimit":
  69:                         publishingWeb.Navigation.CurrentDynamicChildLimit =
  70:                             int.Parse(property.Value);
  71:                         break;
  72:  
  73:                     case "OrderingMethod":
  74:                         publishingWeb.Navigation.OrderingMethod =
  75: (OrderingMethod) Enum.Parse(typeof(OrderingMethod), property.Value);
  76:                         break;
  77:  
  78:                     case "AutomaticSortingMathod":
  79:                     case "AutomaticSortingMethod":
  80:                         publishingWeb.Navigation.AutomaticSortingMethod =
  81: (AutomaticSortingMethod) Enum.Parse(typeof(AutomaticSortingMethod), property.Value);
  82:                         break;
  83:  
  84:                     case "SortAscending":
  85:                         publishingWeb.Navigation.SortAscending =
  86:                             bool.Parse(property.Value);
  87:                         break;
  88:  
  89:                     case "IncludeInGlobalNavigation":
  90:                         publishingWeb.IncludeInGlobalNavigation =
  91:                             bool.Parse(property.Value);
  92:                         break;
  93:  
  94:                     case "IncludeInCurrentNavigation":
  95:                         publishingWeb.IncludeInCurrentNavigation =
  96:                             bool.Parse(property.Value);
  97:                         break;
  98:                 }
  99:             }
 100:             publishingWeb.SetBooleanProperty("NavigationPropertiesSet", true);
 101:             publishingWeb.Update();
 102:         });
 103:     }
 104: }

So here we can see all supported properties:

Supported navigation properties
InheritGlobalNavigation
InheritCurrentNavigation
ShowSiblings
IncludeSubSites
IncludePages
GlobalIncludeSubSites
GlobalIncludePages
CurrentIncludeSubSites
CurrentIncludePages
GlobalDynamicChildLimit
CurrentDynamicChildLimit
OrderingMethod
AutomaticSortingMathod/AutomaticSortingMethod
SortAscending
IncludeInGlobalNavigation
IncludeInCurrentNavigation

You can use these properties in your onet.xml files.

Friday, October 18, 2013

Extend WCF automatically generated DTO with partial classes

Suppose that you have some WCF service which has web method which returns instance of Foo class (this class should be marked with [DataContract] attribute and its members with [DataMember] attribute as you probably know) and VS generated proxy for this web service. VS also will generate all dependent classes necessary for successful compilation of the proxy, including mentioned Foo class. Now you need to extend it with partial class (there may be a lot of reasons), how to do it?

The answer depends on actual configuration of the service and client, but there is one thing which you should keep in mind. Let's assume that we have the following automatically generated Foo class on the client side:

   1: [Serializable]
   2: public class Foo
   3: {
   4:     [XmlElement]
   5:     public string Title { get; set; }
   6: }

We want to add additional property which will be used only on client side, so we create partial class for it like this:

   1: public partial class Foo
   2: {
   3:     public string Status { get; set; }
   4: }

With this approach we may get the following exception:

There was an error reflecting 'Foo'.
at System.Xml.Serialization.XmlReflectionImporter.ImportMembersMapping(XmlReflectionMember[] xmlReflectionMembers, String ns, Boolean hasWrapperElement, Boolean rpc, Boolean openModel, RecursionLimiter limiter)

In order to avoid it, add [XmlIgnore] attribute for your property:

   1: public partial class Foo
   2: {
   3:     [XmlIgnore]
   4:     public string Status { get; set; }
   5: }

You may also try to change auto property from example above to classic property with private field and add [NonSerializable] attribute to the field, but in this case added property won’t be serialized on the client side, which in turn may cause another problems.

Hope that this information will help someone.

Saturday, October 12, 2013

Problem with WebPartPageUserException in Sharepoint 2013 web parts

In Sharepoint 2013 web parts are created in different way comparing with previous versions. Now with .ascx and .ascx.cs files, which also were created before, there is new .ascx.g.cs file – automatically generated file which contains code for all elements added on .ascx file. I already wrote about one problem with new web parts here: Fix bug in Visual Studio 2012 with deleting generated ascx.g.cs files for web parts. In this post I will write about another problem, related with them. Your web part may work properly until you will add new element to ascx file. After that you may get the WebPartPageUserException:

The default namespace "http://schemas.microsoft.com/WebPart/v2" is a reserved namespace for base Web Part properties. Custom Web Part properties require a unique namespace (specified through an XmlElementAttribute on the property, or an XmlRootAttribute on the class).
   at Microsoft.SharePoint.WebPartPages.TypeCacheBuilder.CheckNamespace(String ns, PropertyInfo pi)
   at Microsoft.SharePoint.WebPartPages.TypeCacheBuilder.EnsureNamespace(PropertyInfo pi, XmlAttributes xmlAttrs)
   at Microsoft.SharePoint.WebPartPages.TypeCacheBuilder.CreateXmlSerializer(Storage storage, Boolean buildPersistedNames, Boolean shouldExcludeSpecialProperties, Hashtable& persistedNames, Hashtable& namespaces, Hashtable& dwpNamespaces)
   at Microsoft.SharePoint.WebPartPages.TypeCacheEntry.ThreadSafeCreateXmlSerializer(XmlSerializer& serializer, Storage storage, Boolean buildPersistedNames, Boolean shouldExcludeSpecialProperties)
   at Microsoft.SharePoint.WebPartPages.TypeCacheEntry.get_XmlSerializer()
   at Microsoft.SharePoint.WebPartPages.WebPart.ParseXml(XmlReader reader, Type type, String[] links, SPWeb spWeb)
   at Microsoft.SharePoint.WebPartPages.WebPart.AddParsedSubObject(Object obj)

I faced with this problem when added new <script> element with reference to javascript file on top of ascx. But it is not stable step to reproduce the problem. In the same project we had another web parts with added scripts already, which worked properly.

This problem comes from Microsoft.SharePoint.WebPartPages.WebPart, or more specifically from serializer used in this class. Because of some reason it “thinks” that we used reserved xml namespace "http://schemas.microsoft.com/WebPart/v2" for web part property in web part declaration in .webpart file, which is of course not true: in this case we don’t even have .webpart file with xml declaration. Instead we have ascx file and Sharepoint uses the same serializer, which it uses when deserialize web part from xml. The quick way to fix it is to change base class from Microsoft.SharePoint.WebPartPages.WebPart to System.Web.UI.WebControls.WebParts.WebPart. But if you want still use Microsoft.SharePoint.WebPartPages.WebPart because of some reason (in my practice I don’t remember the case when using of Microsoft.SharePoint.WebPartPages.WebPart gave advantage comparing with System.Web.UI.WebControls.WebParts.WebPart), you can use the following workaround.

First of all let’s see what code is added to .ascx.g.cs file when we add <script> tag into .ascx file:

   1: [global::System.ComponentModel.EditorBrowsableAttribute(global::System
   2: .ComponentModel.EditorBrowsableState.Never)]
   3: private void @__BuildControlTree(global::UPM.Internet.Raflatac.Web.UI.WebControls
   4: .WebParts.RibbonRecProduct.RibbonRecProduct @__ctrl) {
   5:     System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
   6:     @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl(
   7: "\r\n\r\n<div>\r\n    <script type=\"text/javascript\" src=\"/_layouts/15/test/js/test.js?r" +
   8:                 "el=1\"></script>\r\n</div>\r\n\r\n"));
   9:     global::System.Web.UI.HtmlControls.HtmlGenericControl @__ctrl1;
  10:     @__ctrl1 = this.@__BuildControldivSearch();
  11:     @__parser.AddParsedSubObject(@__ctrl1);
  12:     @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\r\n"));
  13:     global::System.Web.UI.HtmlControls.HtmlGenericControl @__ctrl2;
  14:     @__ctrl2 = this.@__BuildControldivResults();
  15:     @__parser.AddParsedSubObject(@__ctrl2);
  16:     @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\r\n"));
  17:     global::System.Web.UI.HtmlControls.HtmlGenericControl @__ctrl3;
  18:     @__ctrl3 = this.@__BuildControldivNoResults();
  19:     @__parser.AddParsedSubObject(@__ctrl3);
  20: }

As you can see script is added as LiteralControl on line 6. Let’s see now code of Microsoft.SharePoint.WebPartPages.WebPart.AddParsedSubObject() method, which is first method from stack trace from WebPart class:

   1: protected override void AddParsedSubObject(object obj)
   2: {
   3:     LiteralControl control = obj as LiteralControl;
   4:     if ((control != null) && !this._hasParsedLiteralControlDWP)
   5:     {
   6:         string text = control.Text;
   7:         if (text.Trim().Length > 0)
   8:         {
   9:             SPWeb web;
  10:             try
  11:             {
  12:                 web = Utility.CurrentWeb();
  13:             }
  14:             catch (InvalidOperationException)
  15:             {
  16:                 throw;
  17:             }
  18:             catch (Exception)
  19:             {
  20:                 throw new WebPartPageUserException(
  21: WebPartPageResource.GetString("InvalidWebPartChild"));
  22:             }
  23:             try
  24:             {
  25:                 Type type = base.GetType();
  26:                 EmbeddedXmlReader reader = new EmbeddedXmlReader(
  27: new StringReader(text), type, web);
  28:                 WebPart webPartFrom = ParseXml(reader, type, null, web);
  29:                 if (webPartFrom.UnknownXmlElements.Count > 0)
  30:                 {
  31:                     foreach (XmlElement element in webPartFrom.UnknownXmlElements)
  32:                     {
  33:                         if ((element.NamespaceURI == "http://schemas.microsoft.com/WebPart/v2")
  34: && ((string.Compare(element.Name, "Assembly",  true, CultureInfo.InvariantCulture) == 0)
  35: || (string.Compare(element.Name, "TypeName", true, CultureInfo.InvariantCulture) == 0)))
  36:                         {
  37:                             throw new WebPartPageUserException(
  38: WebPartPageResource.GetString("InvalidWebPartTag"));
  39:                         }
  40:                     }
  41:                 }
  42:                 this.InheritProperties(webPartFrom, reader.PropertyNames);
  43:                 this._hasParsedLiteralControlDWP = true;
  44:                 return;
  45:             }
  46:             catch (WebPartPageUserException)
  47:             {
  48:                 if ((SPContext.Current != null) && !SPContext.Current.IsDesignTime)
  49:                 {
  50:                     throw;
  51:                 }
  52:             }
  53:             catch (Exception)
  54:             {
  55:                 throw new WebPartPageUserException(
  56: WebPartPageResource.GetString("InvalidWebPartChild"));
  57:             }
  58:         }
  59:     }
  60:     base.AddParsedSubObject(obj);
  61: }

In the beginning of the method (see lines 3-4) it check whether the added control is LiteralControl and if yes, executes block of code, which looks like on the code of deserializing of web part from xml (WebPart webPartFrom = ParseXml(reader, type, null, web)) and then initialize properties of current web part from properties of deserialized web part (this.InheritProperties(webPartFrom, reader.PropertyNames)).

It sounds a little bit crazy, but above code looks like on the one more way of how we can initialize web part properties for web parts in Sharepoint. I.e. add xml declaration of web part (from .webpart file) directly to ascx file with properties initialization, and these values will be used for your web part. I tried to do it, but got the same WebPartPageUserException. Another (most likely) reason is that there is a bug in visual web parts in Sharepoint 2013. See below for more details.

At the moment based on the findings above we can apply the following workaround: enclose <script> element with <div runat=”server”></div> (runat=”server” is important, see below why). After this generated code will be changed:

   1: [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel
   2: .EditorBrowsableState.Never)]
   3: private global::System.Web.UI.HtmlControls.HtmlGenericControl @__BuildControl__control2() {
   4:     global::System.Web.UI.HtmlControls.HtmlGenericControl @__ctrl;
   5:     @__ctrl = new global::System.Web.UI.HtmlControls.HtmlGenericControl("div");
   6:     System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
   7:     @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl(
   8: "\r\n    <script type=\"text/javascript\" src= uts/15/test/js/test.js?rel=1\"></s" +
   9:                 "cript>\r\n"));
  10:     return @__ctrl;
  11: }

I.e. now it will use HtmlGenericControl instead of LiteralControl. If you would enclose it with <div></div> without runat=”server” it will still use LiteralControl and you will get the same error.

In order to complete the picture, let’s see what code is generated for another web part in the same project with <script> element, which works:

   1: private void @__Render__control1(System.Web.UI.HtmlTextWriter @__w,
   2: System.Web.UI.Control parameterContainer) {
   3:     @__w.Write(@"
   4:  
   5: <script type=""text/javascript"" src=""/_layouts/15/test/js/test.js""></script>
   6:  
   7: <div class=""..."">
   8: <p>");
   9: ...
  10: }

I.e. it uses completely different approach because of some reason: instead of adding text via LiteralControl or HtmlGenericControl, it writes it via HtmlTextWriter.

Let’s summarize open questions:

1. Why for one web part generated code adds text via HtmlTextWriter and it works, but for other via LiteralControl, which fails. Both web parts are in the same project, inherit the same Microsoft.SharePoint.WebPartPages.WebPart class and <script> element was added as first element in ascx file for both of them.

2. For what reason MS added ability to initialize web part properties from xml, added to ascx file. My first thought was that it was added for simplifying initialization of the properties for cloud environments (when you can’t provision .webpart file to the file system), but then I checked that this code exists in Sharepoint 2007 and 2010 (in Sharepoint 2007 it is a bit different, but uses the same idea). In order to say something we should get working example.

There may be some reasons for this behavior of course, but it also can be that this is just a bug in visual web parts code generation and it uses AddParsedSubObject() method where it should not use it (like in second example where it uses HtmlTextWriter). If you will have further information about this problem, please share it in comments.