I think I figured out why SharePoint sends 403 error code. SharePoint runs often under the client credentials. But those credentials doesn't work when accessing a network share because Kerberos delegation is not properly enabled and configured. I'm not going to go with Kerberos, It's painful and the virtual directory approach is a kludge as Greg pointed out.
I'm taking another route. A SharePoint Solution (wsp) with a generic handler, a list definition and a list instance.
The list is called ExternalFiles
and has the following columns:
- Title
- Path
- HttpContentType
A list item looks like this:
+---------+---------------------------------+----------------+
| Title | Path | HttpContentType|
+---------+---------------------------------+----------------+
| MyVideo | \\FileServer\Videos\MyVideo.mp4 | video/mp4 |
+---------+---------------------------------+----------------+
The following is generic handler DownloadExternalFile.ashx
@ Assembly Name="ExternalFiles, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2468c1409572afad" %>
<%@ WebHandler Language="C#" CodeBehind="DownloadExternalFile.ashx.cs" Class="ExternalFiles.DownloadExternalFile" %>
And this is DownloadExternalFile.ashx.cs
using Microsoft.SharePoint;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
namespace ExternalFiles
{
public class DownloadExternalFile : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
var web = SPContext.Current.Web;
var webServerRelativeUrl = web.ServerRelativeUrl.TrimEnd('/');
var list = web.GetList(webServerRelativeUrl + "/Lists/ExternalFiles");
var listItem = list.Items.GetItemById(int.Parse(context.Request.QueryString["Id"]));
context.Response.Buffer = false;
var httpContentType = (string)listItem["HttpContentType"];
if (string.IsNullOrEmpty(httpContentType))
{
context.Response.ContentType = "application/octet-stream";
}
else
{
context.Response.ContentType = httpContentType;
}
var filePath = (string)listItem["Path"];
context.Response.AddHeader("content-disposition", "attachment;filename=" + Path.GetFileName(filePath));
SPSecurity.RunWithElevatedPrivileges(() =>
{
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
stream.CopyTo(context.Response.OutputStream);
}
});
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
The handler takes the list item id from the query string, reads the list item from the ExternalFiles list and streams the file content to the browser.
To show a video on a page you just need an HTML tag like the following:
<video src="http://mysharepointserver/sites/somesite/_layouts/15/ExternalFiles/DownloadExternalFile.ashx?Id=1" controls="controls"></video>