We had requirement to host simple asp.net site under SharePoint site. To host a simple asp.net website is SharePoint site is pretty straight forward. But as we were using ASP.Net MVC framework for our website, we faced few challenges as below:
- APS.Net MVC uses its own view engine i.e.VirtualPathProviderViewEngine, this view engine uses the hosting site VirtualPathProvider. So in our case it was using sharepoint’s VirtualPathProvider. SP’s virtual path provider was not able to understand the MVC view’s path.
To resolve this issue we created our own custom view engine dervied from MVC’s view engine. Now the problem was to tell the application to use our custom view engine. This is resolved by registering new view engines during Application_Start. - MVC uses its own http module and routing model (UrlRoutingModule), need to register this in the web.config of sharepoint site, so that all MVC views are handled by the MVC modules and handlers.
- Cannot have session state, authentication in web.config of MVC application. As the MVC is application set as a subsite (new virtual dir under SP site) and not a website it will use session and authentication of its parent site hence our MVC application web config cannot have these keys.
And finally we were successful to host asp.net MVC site under sp site.Below are the steps to run your asp.net MVC site under SP site.
Steps to host a MVC site under SP site:
- In your MVC application create a class for your custom view engine (CustomViewEngine.cs)
- Derive CustomViewEngine class from VirtualPathProviderViewEngine and implement this abstrct class. Major changes to the view engine class as below:
//Constructor of custom view engine
public CustomViewEngine()
{
// This is where we tell MVC where to look for our files.
// This says
// to look for a file at "Views/Controller/Action.html"
ViewLocationFormats = new[]
{
"~/MVCApp/Views/{1}/{0}.aspx",
"~/MVCApp/Views/{1}/{0}.ascx",
"~/MVCApp/Views/Shared/{0}.aspx",
"~/MVCApp/Views/Shared/{0}.ascx"
};
PartialViewLocationFormats = ViewLocationFormats;
//Add your master page location here..
base.MasterLocationFormats = base.MasterLocationFormats;
}
//Method that fetches the path from general name. This is private method used by GetPath() method of the view engine.
private string GetPathFromGeneralName(string[] locations, string name, string controllerName, ref string[] searchedLocations)
{
string result = String.Empty;
searchedLocations = new string[locations.Length];
for (int i = 0; i < locations.Length; i++)
{
string virtualPath = String.Format(CultureInfo.InvariantCulture, locations[i], name, controllerName);
searchedLocations = _emptyLocations;
result = virtualPath;
}
return result;
}
//Method that fetches the path from specific name. This is private method used by GetPath() method of the view engine.
private string GetPathFromSpecificName(string name, ref string[] searchedLocations)
{
string result = name;
return result;
}
//Override the methods in VirtualPathProviderViewEngine.
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return new WebFormView(partialPath, null);
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
return new WebFormView(viewPath, masterPath);
}
- Give strong name to your MVC application and put the dll in GAC.
- Create virtual directory under your SharePoint site.
- Point virtual directory to your MVC application.
- Copy httpHandlers, httpModules, assemblies from MVC configuration file to SP configuration file.
- Remove authentication, sessionState, customErrors from MVC configuration file.
- In global.asax of SP site add below code:
void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomViewEngine());
}
public static void RegisterRoutes(RouteCollection routes)
{
Route MyRoute = new Route("MVCApp/{controller}/{action}/{id}", new MvcRouteHandler());
MyRoute.Defaults = new RouteValueDictionary();
MyRoute.Defaults.Add("controller", "Home" );
MyRoute.Defaults.Add("action", "Index" );
MyRoute.Defaults.Add("id", "");
routes.Add(MyRoute);
}
MVCApp --> This is the name of your MVC virtual dir.
- Reset IIS and browse the application.
Appendix: Complete custom view engine class
public class CustomViewEngine : VirtualPathProviderViewEngine
{
private static readonly string[] _emptyLocations = new string[0];
public CustomViewEngine()
{
// This is where we tell MVC where to look for our files.
// to look for a file at "Views/Controller/Action.html"
ViewLocationFormats = new[] {
"~/MVCApp/Views/{1}/{0}.aspx",
"~/MVCApp/Views/{1}/{0}.ascx",
"~/MVCApp/Views/Shared/{0}.aspx",
"~/MVCApp/Views/Shared/{0}.ascx"
};
PartialViewLocationFormats = ViewLocationFormats;
//Add your master page location here..
base.MasterLocationFormats = base.MasterLocationFormats;
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(partialViewName))
{
throw new ArgumentException("Provide partial view name"
, "partialViewName");
}
string[] searched;
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string partialPath = GetPath(PartialViewLocationFormats, "PartialViewLocationFormats", partialViewName,controllerName, out searched);
if (String.IsNullOrEmpty(partialPath))
{
return new ViewEngineResult(searched);
}
return new ViewEngineResult(CreatePartialView(controllerContext, partialPath), this);
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(viewName))
{
throw new ArgumentException("View name is missing", "viewName");
}
string[] viewLocationsSearched;
string[] masterLocationsSearched;
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string viewPath = GetPath(ViewLocationFormats, "ViewLocationFormats", viewName, controllerName, out viewLocationsSearched);
string masterPath = GetPath(MasterLocationFormats, "MasterLocationFormats", masterName, controllerName, out masterLocationsSearched);
if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
}
return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
}
private string GetPath(string[] locations, string locationsPropertyName, string name, string controllerName, out string[] searchedLocations)
{
searchedLocations = _emptyLocations;
if (String.IsNullOrEmpty(name))
{
return String.Empty;
}
if (locations == null || locations.Length == 0)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICul ture, "Locations property name is missing", locationsPropertyName));
}
bool nameRepresentsPath = IsSpecificPath(name);
return (nameRepresentsPath) ?
GetPathFromSpecificName(name, ref searchedLocations) :
GetPathFromGeneralName(locations, name, controllerName, ref searchedLocations);
}
private string GetPathFromGeneralName(string[] locations, string name, string controllerName, ref string[] searchedLocations)
{
string result = String.Empty;
searchedLocations = new string[locations.Length];
for (int i = 0; i < locations.Length; i++)
{
string virtualPath = String.Format(CultureInfo.InvariantCulture, locations[i], name, controllerName);
searchedLocations = _emptyLocations;
result = virtualPath;
}
return result;
}
private string GetPathFromSpecificName(string name, ref string[] searchedLocations)
{
string result = name;
return result;
}
private static bool IsSpecificPath(string name)
{
char c = name[0];
return (c == '~' || c == '/');
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return new WebFormView(partialPath, null);
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
return new WebFormView(viewPath, masterPath);
}
}