I recently upgraded a SharePoint 2010 custom solution to SharePoint 2013. One of the issues I came across was that on certain custom pages the images no longer displayed correctly. Instead of displaying an image, the page or control would be blank or the user would be prompted to download a file. The same code worked on a SharePoint 2010 farm and I could not find any obvious exceptions in the SharePoint logs.
In order to illustrate the problem (and solution) I created the following simple custom project:
My example project contains a SharePoint 2010 Visual Web Part which contains one button. The button uses JQuery to show a custom page from the _Layouts folder. The custom page contains code-behind which will display an image from the current web “Images” library.
I tested the solution in Visual Studio 2010 and all worked fine. When the user click on the button, the custom page is shown and it renders the image as expected.
However when I build and deploy the same project in SharePoint 2013 the custom page does not show the image, instead the user is prompted to download a file.
My colleague Hennie van Wyk quickly discovered that there is a difference between the SharePoint 2010 and SharePoint 2013 Web App’s HTTP Response Headers in IIS.
If you open IIS Manager and go to the HTTP Response Headers of the relevant SharePoint Web App you will see that in SharePoint 2013 two new host headers are introduced.
They are “X-Content-Type-Options” with a value “nosniff” and “X-MS-InvokeApp” with a value “1; RequireReadOnly”.
We further discovered that if we delete the X-Content-Type-Options response header the custom code worked and the images displayed on the custom page. I do not want to delete or disable standard SharePoint configuration and I certainly cannot expect my customers to do the same so I decided to investigate the possibility of changing the custom code so that it renders the new image regardless of the “X-Content-Type-Options” response header.
So, what is the X-Content-Type-Options response header?
I found a very helpful blog post by Mark Jones in which he explains that the X-Content-Type-Options response header with a value of “nosniff” is a directive from IIS that tells the browser to not sniff the MIME type. When this is disabled IE won't try to automatically determine what your content is. E.g. HTML, PNG, CSS, script, etc...
This means that it’s up to the developer to tell the browser what your content is. And, if what you are sending down isn't in IE’s allowable list it is going refuse to execute your content, hence the blank areas of the screen.
If the "nosniff" directive is received on a response retrieved by a script reference, Internet Explorer will not load the "script" file unless the MIME type matches one of the values”.
I also found the following great article written by Charles Torvalds.
Next step, let’s try to fix our custom code…
In the example SharePoint 2010 project which I developed for this blog post I had the following code in the Page_Load event of the custom page. This illustrates that when the page loads the code will retrieve the first item from the images library and render the image.
protected void Page_Load(object sender, EventArgs e)
{
SPList imagelist = SPContext.Current.Web.Lists.TryGetList("Images");
if (imagelist != null)
{
SPFile oFile = imagelist.Items[0].File;
byte[] img = oFile.OpenBinary();
Response.Clear();
Response.AddHeader("Content-Length", img.Length.ToString());
Response.ContentType = "application/octet-stream";
Response.BinaryWrite(img);
Response.End();
}
}
Note that the following line of code sets the response MIME type to “application/octet-stream”. Response.ContentType = "application/octet-stream";
The code worked fine in a SharePoint 2010 web application because in SharePoint 2010 there is no default X-Content-Type-Options response header with a “nosniff” value.
I changed the code to rather specify a Mime type of “image/png” and I deployed it to my SharePoint 2013 web application and everything worked fine.
protected void Page_Load(object sender, EventArgs e)
{
SPList imagelist = SPContext.Current.Web.Lists.TryGetList("Images");
if (imagelist != null)
{
SPFile oFile = imagelist.Items[0].File;
byte[] img = oFile.OpenBinary();
Response.Clear();
Response.AddHeader("Content-Length", img.Length.ToString());
Response.ContentType = "image/png";
Response.BinaryWrite(img);
Response.End();
}
}
This might be a very simple example of a potential serious challenge when you upgrade SharePoint 2007 or SharePoint 2010 solutions to SharePoint 2013.
In his blog post Mark Jones also mention examples where he found Javascript with a MIME type of “text/blank” which should have been “application/javascript” and in other cases JSON which was sent down as “application/json” when it should have been “application/javascript”. I can only imagine that there are hundreds of similar cases.
This might be a very simple example of a potential serious challenge when you upgrade SharePoint 2007 or SharePoint 2010 solutions to SharePoint 2013.
3 comments:
Thanks for sharing this, Excellent and insightful article. Thank you!!! You share very informative information on Upgrade to SharePoint 2013...
Fantastic Blog..!!
Thanks for sharing such a good information
We offer SHAREPOINT ONLINE TRAINING
This is a pretty interesting blog post that deserves to be read. I'm really grateful for giving the opportunity to read an informative article like this! I really appreciate this post thank you for sharing this type of posts.
DedicatedHosting4u.com
Post a Comment