Thursday, January 15, 2009

How to: Host ASP.Net MVC site under a SharePoint.


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:

  1. In your MVC application create a class for your custom view engine (CustomViewEngine.cs)

  2. 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);
     }



  3. Give strong name to your MVC application and put the dll in GAC.

  4. Create virtual directory under your SharePoint site.

  5. Point virtual directory to your MVC application.

  6. Copy httpHandlers, httpModules, assemblies from MVC configuration file to SP configuration file.

  7. Remove authentication, sessionState, customErrors from MVC configuration file.

  8. 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.


  9. 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);
  }
 }

4 comments:

  1. Hi techabhi

    This is in response to your comment here http://www.codeplex.com/SharePointMVC/wiki/comments/archive/view?title=Home

    Using your approach is it possible to use different rendering engine? (nhaml, nvelocity, spark etc)

    Also would it be possible for you to post a working solution for download? It would make it easier to have a look into the code.

    Regards

    Simon

    ReplyDelete
  2. Hi Simon,
    I will upload the project at code plex during weekend and will let you know. (For now if you want, you can follow above steps and do execute it accordingly then you are done.)

    Regarding different rendering engine, I am not sure.. need to check out this.

    There could one more approach i.e. instead of writing custom view engine.. we can write sucton virtualpath provider. I am trying approach as well and will get back to you soon on this.

    ReplyDelete
  3. SD

    I would like to have a more detailed conversation.
    Can you shoot me your email address through my codeplex contact page
    http://www.codeplex.com/site/users/view/simoncropp

    ReplyDelete

  4. استقرار في معا بداية عام 2020 حيث اصبح اسعار الخامات تترواح بين 85 الى 100 جنية على ارضة اما اسعار التوريد والتركيب تتراوح بين 180 الى 200 جنية بالسقالات سكاي هوم للديكور افضل شركة توريد وتركيب في مصر

    سعر متر حجر هاشمى وش جبل
    اسعار تركيب حجر هاشمى وش جبل
    سعر متر حجر هاشمى هيصم
    اسعار حجر هاشمى هيصم
    اسعار تركيب حجر هاشمى
    سعر متر حجر هاشمى
    اسعار حجر هاشمى
    اسعار تركيب حجر هاشمى هيصم
    اسعار حجر هاشمى هيصم
    <a href="https://www.pinterest.com/kayanstones/01020010444-%D8%A7%D8%B3%D8%B9%D8%A7%D8%B1-%D8%AD%D8%AC%D8%B1-%D9%87%D8%A7%D8%B4%D9%85%D9%89-%D9%86%D8%A7%D8%B9%D9%85/

    ReplyDelete