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.
Good explanation. Saved my day. Thanks a lot Alexey.
ReplyDelete