【问题标题】:Why MVC 5 does not call GetFile Method of my VirtualPathProvider Class?为什么 MVC 5 不调用我的 VirtualPathProvider 类的 GetFile 方法?
【发布时间】:2014-07-19 18:41:39
【问题描述】:

我正在尝试从数据库加载 Razor View。

我关注 ASP.NET MVC and virtual viewsVirtualPathProvider in MVC 5 来做这件事。

我的代码:

虚拟路径提供者:

 public class DbPathProvider : VirtualPathProvider
  {
    public override bool FileExists(string virtualPath)
    {
        var page = FindPage(virtualPath);
        if (page == null)
        {
            return base.FileExists(virtualPath);
        }
        else
        {
            return true;
        }
    }

    public override VirtualFile GetFile(string virtualPath)
    {
        var page = FindPage(virtualPath);
        if (page == null)
        {
            return base.GetFile(virtualPath);
        }
        else
        {
            return new DbVirtualFile(virtualPath, page.PageData.ToArray());
        }
    }

    private SiteModel FindPage(string virtualPath)
    {
        var db = new DatabaseContext();
        var page = db.SiteModels.FirstOrDefault(x => x.SiteName == virtualPath);
        return page;
    }
}

虚拟文件

public class DbVirtualFile : VirtualFile
{
    private byte[] data;

    public DbVirtualFile(string virtualPath, byte[] data)
        : base(virtualPath)
    {
        this.data = data;
    }

    public override System.IO.Stream Open()
    {
        return new MemoryStream(data);
    }
}

全球.asax:

protected void Application_Start()
    {
        HostingEnvironment.RegisterVirtualPathProvider(new DbPathProvider());
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

    }

行动:

public ActionResult Display(string id)
    {
        var db = new DatabaseContext();
        var site = db.SiteModels.FirstOrDefault(x => x.PageName == id);
        if (site == null)
        {
            return RedirectToAction("Index", "Home");
        }
        ViewBag.Body = site.PageContent;
        return View(System.IO.Path.GetFileNameWithoutExtension(site.SiteName));
    }

数据:

案例一:

当 virtualPath 值为 "/Views/Home/Contact.cshtml" 时,FileExists 方法返回 true 并调用 GetFile 方法

案例2:

当 virtualPath 值为 "~/Home/Display/ce28bbb6-03cb-4bf4-8820-373890396a90" 时 FileExists 方法返回 trueGetFile 方法和Display Action 永远不会被调用。 结果是

HTTP 错误 404.0 - 未找到 您要查找的资源已被删除、名称已更改或暂时不可用。

我对动态视图一无所知。我刚刚读了那两篇文章并尝试实现它。

请告诉我哪里做错了。

我正在使用 MVC 5 和 .NET 4.5

【问题讨论】:

    标签: c# asp.net asp.net-mvc razor


    【解决方案1】:

    我应该能够帮助您。 有一些你没有考虑过的事情是好的,因为我也遇到了很多麻烦。

    我认为您在这里遇到的问题是知道一切触发的顺序。 这就是发生的事情:

    1. 首先调用 VirtualPathProvider FileExists 方法,然后 这是您在寻找页面的地方。我的方法和你的略有不同,这里是一个例子:

      public IList<Page> Pages
      {
          get
          {
              using (var uow = new UnitOfWork<SkipstoneContext>())
              {
                  var companyService = new CompanyService(uow);
                  var company = companyService.GetTenant();
      
                  if (company != null)
                  {
                      var pageService = new PageService(uow, company.Id);
      
                      return pageService.GetPublished();
                  }
              }
      
              return null;
          }
      }
      
      public override bool FileExists(string virtualPath)
      {
          if (IsVirtualPath(virtualPath))
          {
              if (FindPage(virtualPath) != null)
              {
                  var file = (PageVirtualFile)GetFile(virtualPath);
                  return file.Exists;
              }
          }
      
          return Previous.FileExists(virtualPath);
      }
      
      public override VirtualFile GetFile(string virtualPath)
      {
          if (IsVirtualPath(virtualPath))
          {
              var page = FindPage(virtualPath);
              if (page != null)
              {
                  var decodedString = Uri.UnescapeDataString(page.ViewData);
                  var bytes = Encoding.ASCII.GetBytes(decodedString);
      
                  return new PageVirtualFile(virtualPath, bytes);
              }
          }
      
          return Previous.GetFile(virtualPath);
      }
      
      public override System.Web.Caching.CacheDependency GetCacheDependency(string virtualPath, System.Collections.IEnumerable virtualPathDependencies, DateTime utcStart)
      {
          if (IsVirtualPath(virtualPath))
              return null;
      
          return Previous.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
      }
      
      public override string GetFileHash(string virtualPath, System.Collections.IEnumerable virtualPathDependencies)
      {
          if (IsVirtualPath(virtualPath))
              return Guid.NewGuid().ToString();
      
          return Previous.GetFileHash(virtualPath, virtualPathDependencies);
      }
      
      private Page FindPage(string virtualPath)
      {
          var virtualName = VirtualPathUtility.GetFileName(virtualPath);
          var virtualExtension = VirtualPathUtility.GetExtension(virtualPath);
      
          try
          {
              if (Pages != null)
              {
                  var id = Convert.ToInt32(virtualName.Replace(virtualExtension, ""));
                  var page = Pages.Where(model => model.Id == id && model.Extension.Equals(virtualExtension, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
      
                  return page;
              }
          }
          catch(Exception ex)
          {
              // Do nothing
          }
      
          return null;
      }
      
      private bool IsVirtualPath(string virtualPath)
      {
          var path = (VirtualPathUtility.GetDirectory(virtualPath) != "~/") ? VirtualPathUtility.RemoveTrailingSlash(VirtualPathUtility.GetDirectory(virtualPath)) : VirtualPathUtility.GetDirectory(virtualPath);
          if (path.Equals("~/Views/Routing", StringComparison.OrdinalIgnoreCase) || path.Equals("/Views/Routing", StringComparison.OrdinalIgnoreCase))
              return true;
          else
              return false;
      }
      
    2. 现在,如果这个页面是从数据库提供的,它就是一个虚拟页面,所以接下来发生的事情就是它调用你的 MvcHandler(在我的处理程序中我有这些函数)

      protected override IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
      {
          var vpp = new SkipstoneVirtualPathProvider(); // Create an instance of our VirtualPathProvider class
          var requestContext = ((MvcHandler)httpContext.Handler).RequestContext; // Get our request context
          var path = requestContext.HttpContext.Request.Url.AbsolutePath; // Get our requested path
          var pages = vpp.Pages; // Get all the published pages for this company
      
          if (pages != null && !string.IsNullOrEmpty(path)) // If we have any pages and we have a path
          {
              var page = this.MatchPage(pages, path);
              if (page != null) // If we find the page
              {
                  requestContext.RouteData.Values["controller"] = "Routing"; // Set the controller
                  requestContext.RouteData.Values["action"] = "Index"; // And the action
              }
          }
      
          return base.BeginProcessRequest(httpContext, callback, state);
      }
      
      private Page MatchPage(IList<Page> pages, string path)
      {
          if (path == "/" || !path.EndsWith("/"))
          {
              var page = pages.Where(model => model.Path.Equals(path, StringComparison.OrdinalIgnoreCase)).SingleOrDefault(); // Try to match the path first
              if (page == null) // If we have no page, then get the directory and see if that matches any page
              {
                  path = VirtualPathUtility.RemoveTrailingSlash(VirtualPathUtility.GetDirectory(path));
                  page = pages.Where(model => model.Path.Equals(path, StringComparison.OrdinalIgnoreCase)).SingleOrDefault();
              }
      
              return page; // return our page or null if no page exists
          }
      
          return null; // return null if anything fails
      }
      
    3. 以防万一,如果您尚未创建 MvcHandler,则必须像这样在 RouteConfig 中注册它:

          // default MVC route
          routes.MapRoute(
              name: "Default",
              url: "{controller}/{action}/{id}",
              defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
          ).RouteHandler = new SkipstoneRouteHandler();
      
    4. 在我的 MvcHandler 中,我得到我的页面,如果它与任何虚拟页面匹配,我将移动到 RoutingController

      public ActionResult Index()
      {
          using (var uow = new UnitOfWork<SkipstoneContext>())
          {
              var userId = User.Identity.GetUserId();
              var service = new PageService(uow, this.CompanyId);
              var userService = new UserService(uow, this.CompanyId);
              var user = userService.Get(userId);
      
              var fileName = service.View(Request.Url, user);
      
              if (!fileName.StartsWith(@"/") && !fileName.StartsWith("~/"))
                  return View(fileName); // Show the page
              else
                  return Redirect(fileName); // Redirect to our page
          }
      }
      

    为了您,我将展示 service.View 方法

        /// <summary>
        /// Views the CMS page
        /// </summary>
        /// <param name="uri">The current Request.Url</param>
        /// <param name="user">The current User</param>
        /// <returns>The filename of the requested page</returns>
        public string View(Uri uri, User user)
        {
            var path = uri.AbsolutePath; // Get our Requested Url
            var queryString = uri.Query;
            var pages = this.GetPublished();
            var page = pages.Where(model => model.Path.Equals(path, StringComparison.OrdinalIgnoreCase)).SingleOrDefault(); // Try to get the page
    
            if (page == null) // If our page is null
                page = pages.Where(model => model.Path.Equals(VirtualPathUtility.RemoveTrailingSlash(VirtualPathUtility.GetDirectory(path)))).SingleOrDefault(); // try to get the page based off the directory
    
            if (page == null) // If the page is still null, then it doesn't exist
                throw new HttpException(404, "This page has been deleted or removed."); // Throw the 404 error
    
            if (page.Restricted && user == null) // If our page is restricted and we are not logged in
                return "~/Account/LogOn?ReturnUrl=" + page.Path + queryString; // Redirect to the login page
    
            if (user != null && page.Restricted)
            {
                if (PageService.IsForbidden(page, user))
                    throw new HttpException(401, "You do not have permission to view this page."); // Throw 401 error
            }
    
            return Path.GetFileNameWithoutExtension(page.Id.ToString());
        }
    
    1. 然后我们回到我们的 VirtualPathProvider 并重新开始该过程,但这次虚拟路径将如下所示:"~/Views/Routing/1331.aspx"
    2. 如果您注意到,我的 FindPage 方法还会查找我也存储在数据库中的扩展匹配:
    3. 然后将调用 GetFile,只要你按照我的路径,你应该返回 VirtualFile

    我真的希望这会有所帮助:)

    【讨论】:

    • 你能评论一下为什么GetCacheDependency返回null,为什么GetFileHash返回new Guid.NewGuid()?
    • 当然,这是因为我正在为从数据库中提取的项目禁用缓存
    • 谢谢!在这里为我节省了很多时间......在我的情况下,还需要将routes.RouteExistingFiles = true; 添加到路由配置中,否则'404 not found'。
    • @r3plica 在这个例子中什么是 SkipstoneRouteHandler()?它有什么定义?
    • 只是我用于处理子域的自定义 RouteHandler。它与解决方案无关 :) 您实际上只需删除 .RouteHandler = new SkipstoneRouteHandler() 就可以了
    【解决方案2】:

    我有同样的问题。 如果您的 virtualPath 不是 *.cshtml 格式,则 Razor 视图引擎不会调用 GetFile。 您需要生成 {Guid}.cshtml 路径格式

    【讨论】:

    • 不应该这样。它实际上会为所有扩展类型触发。
    • 我同意,不应该是这种情况,但这是我看到的相同行为。
    【解决方案3】:

    警告:ASP.NET 4 (MVC5) 相关!

    尽管答案的数量和选择的一个,我会分享我的经验。

    FileExists(string virtualPath)
    

    被调用两次,一次使用应用程序相对路径(“~/bla-bla”),另一次使用绝对路径(“/bla-bla”)。在我的情况下,第二个(绝对路径)失败了。似乎在 ASP.NET 3 中这没问题,但是 ASP.NET 4 (MVC5) 也会检查它。并要求它返回true。结果

    GetFile(string virtualPath) 
    

    没有被调用。

    对于那些需要实现这种行为的人VirtualPathUtility 提供了ToAbsoluteToAppRelative 方便的方法。

    HTH

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-05
      • 2018-08-22
      • 1970-01-01
      • 1970-01-01
      • 2014-12-14
      • 2018-05-07
      • 2023-03-05
      • 1970-01-01
      相关资源
      最近更新 更多