1
votes

I am trying to load an XSLT file in a Sharepoint MOSS 2007 custom webpart, but I am getting a 401 error while trying to access an XSLT file. I tried providing default credentials but it is empty upon inspection during debugging.

Does anyone know how I can load the XSLT file from a custom webpart? Thanks in advance.

    XmlUrlResolver resolver = new XmlUrlResolver();
    resolver.Credentials = CredentialCache.DefaultNetworkCredentials;

    XsltSettings settings = new XsltSettings(true, true);  

    XslCompiledTransform oXSLTranform = new XslCompiledTransform();

    string siteUrl = SPContext.Current.Site.Url;
    if (siteUrl.EndsWith("/"))
        siteUrl = siteUrl.Remove(siteUrl.LastIndexOf("/"));

    siteUrl += "/Style Library/XSL Style Sheets/THM1News.xslt";

    oXSLTranform.Load(siteUrl ,settings, resolver);

This code returns a 401 error:

The remote server returned an error: (401) Unauthorized.

[WebException: The remote server returned an error: (401) Unauthorized.] System.Net.HttpWebRequest.GetResponse() +5313085 System.Xml.XmlDownloadManager.GetNonFileStream(Uri uri, ICredentials credentials) +69 System.Xml.XmlDownloadManager.GetStream(Uri uri, ICredentials credentials) +3929007 System.Xml.XmlUrlResolver.GetEntity(Uri absoluteUri, String role, Type ofObjectToReturn) +54 System.Xml.Xsl.Xslt.XsltLoader.CreateReader(Uri uri, XmlResolver xmlResolver) +26 System.Xml.Xsl.Xslt.XsltLoader.Load(Compiler compiler, Object stylesheet, XmlResolver xmlResolver) +315 System.Xml.Xsl.Xslt.Compiler.Compile(Object stylesheet, XmlResolver xmlResolver, QilExpression& qil) +41 System.Xml.Xsl.XslCompiledTransform.CompileXsltToQil(Object stylesheet, XsltSettings settings, XmlResolver stylesheetResolver) +59 System.Xml.Xsl.XslCompiledTransform.LoadInternal(Object stylesheet, XsltSettings settings, XmlResolver stylesheetResolver) +66 System.Xml.Xsl.XslCompiledTransform.Load(String stylesheetUri, XsltSettings settings, XmlResolver stylesheetResolver) +38 NewsGallery.AjaxNewsWebPart.AjaxNewsControl.RenderContents(HtmlTextWriter output) +403 System.Web.UI.WebControls.WebControl.Render(HtmlTextWriter writer) +32 System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27 System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +99 System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25 System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +134 System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +19 System.Web.UI.WebControls.WebControl.RenderContents(HtmlTextWriter writer) +10 System.Web.UI.WebControls.WebControl.Render(HtmlTextWriter writer) +32 Microsoft.SharePoint.WebPartPages.WebPart.RenderWebPart(HtmlTextWriter output) +36 Microsoft.SharePoint.WebPartPages.WebPart.RenderWebPartInternal(HtmlTextWriter writer) +139 Microsoft.SharePoint.WebPartPages.WebPart.Render(HtmlTextWriter writer) +93 System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +27 System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +99 System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25 Microsoft.SharePoint.WebPartPages.SPChrome.RenderPartContents(HtmlTextWriter output, WebPart part) +66

2

2 Answers

1
votes

I came across the same issue a while ago. This might not help you out seeing that it's been a while since you posted but it might help someone else.

I'm assuming that you've already eliminated the obvious situations that'll cause this 401 error - namely, the xslt in style library not having a published major version or accessing the webpart page that pulls the xslt under a user account that does not have access to the style library in the first place. I'm also assuming that you have a farm environment with atleast one web front end and a separate db server to host the content db, the web application is NOT enabled for anonymous access and the authentication scheme is integrated windows with NTLM (not kerberos) authorization and impersonation is enabled.

What we need to understand here is that when you call oXSLTranform.Load(xsltUrl) from within the webpart code, the code which is running under the credentials of the impersonated user is actually formulating an out-of-band request (request not coming directly from browser) for the file which resides in a database and not physically on the web server. If you look at the IIS log on the web front end where you make the request, you'll see the normal 401.2 followed by 401.1 (normal NTLM handshake request-response sequence) but the next response will also be a 401.1 instead of the expected 200.0 because the request was made with cs-username header which is empty (instead of the expected impersonated user name). It is empty because the security context of this out-of-band request cannot be delegated (since kerberos is not enabled) and since anonymous access is also not enabled for the web application, the request for the file is denied with a 401 status. You'll need to pass an explicit network credential object (as in new NetworkCredential(username, password, domain)) to your resolver to make this work and that's obviously a no go.

Here's how I solved it...

string siteUrl = SPContext.Current.Site.Url;
SPWeb rootWeb = SPContext.Current.Site.RootWeb;

if (siteUrl.EndsWith("/")) 
    siteUrl = siteUrl.Remove(siteUrl.LastIndexOf("/"));

string xmlFileUrl = siteUrl + "/data.xml";
string xsltFileUrl = siteUrl + "/transform.xslt";

if (rootWeb != null)
{
    var xmlDoc = new XmlDocument();
    var xslDoc = new XmlDocument();

    SPFile xmlDataFile = rootWeb.GetFile(xmlFileUrl);  // since you are
    // using the SP OM to extract the file from the doc library item you
    // are going under the impersonated user credential. No need to elevate
    // permissions

    if (xmlDataFile != null)
    {
        Stream xmlDataStream = xmlDataFile.OpenBinaryStream();
        xmlDoc.Load(xmlDataStream);
        xmlDataStream.Close();
    }

    SPFile xsltTransformFile = rootWeb.GetFile(xsltFileUrl);

    if (xsltTransformFile != null)
    {
        Stream xsltStream = xsltTransformFile.OpenBinaryStream();
        xslDoc.Load(xsltStream);
        xsltStream.Close();
    }

    // You now have your xmlDoc and xslDoc you can run your transform
    // without having to provide a resolver
    TransformXml(xmlDoc.outerXml, xslDoc.outerXml, false);
}

private string TransformXml(string xml, string xslt, bool bDebug)
{
    StringReader xsltInput = new StringReader(xslt);
    StringReader xmlInput = new StringReader(xml);
    XmlTextReader xsltReader = new XmlTextReader(xsltInput);
    XmlTextReader xmlReader = new XmlTextReader(xmlInput);

    // Create required writer for output   
    StringWriter stringWriter = new StringWriter();
    XmlTextWriter transformedXml = new XmlTextWriter(stringWriter);

    // Create a XslCompiledTransform to perform transformation   
    XslCompiledTransform xsltTransform = new XslCompiledTransform(bDebug);            
    xsltTransform.Load(xsltReader);
    xsltTransform.Transform(xmlReader, transformedXml);
}
1
votes

I used this methodology instead:

StringReader xmlReader = new StringReader(rawXML);
XPathDocument xmlDoc = new XPathDocument(xmlReader);

XslCompiledTransform myXslTransformer = new XslCompiledTransform();
XmlUrlResolver xmlResolver = new XmlUrlResolver();
xmlResolver.Credentials = CredentialCache.DefaultCredentials;
XsltSettings settings = new XsltSettings(true, true);

// Load XSL
SPSecurity.RunWithElevatedPrivileges(delegate()
{
    myXslTransformer.Load(XslPath, settings, xmlResolver);
});


// Create the output stream
StringWriter sWriter = new StringWriter();
XmlTextWriter writer = new XmlTextWriter(sWriter);

// Transform
myXslTransformer.Transform(xmlDoc, null, writer);

writer.Close();

string transformedXML = sWriter.ToString();

And it worked.