Пользовательский не найденный маршрут срабатывает только один раз - PullRequest
1 голос
/ 18 сентября 2011

Мне не нравится публиковать десятки строк кода и полагать, что сообщество в целом заинтересовано в том, чтобы распутать мой беспорядок.В этом случае я использовал все, что я могу найти, чтобы искать в Google, прослеживаемый через Glimpse и Firebug / Fiddler, и что мне иногда остается, это рабочее поведение, которое особенно раздражает при отладке.Итак, я зову на помощь.

Вот суть: у меня есть серия классов, которые обрабатывают маршруты MVC, которые в противном случае не были бы найдены (и сгенерировали бы ошибку 404) благодаря @AndrewDavey.Я пытаюсь перехватить 404 и показать управляемый данными контент там, где он есть.Все работает, пока я не обновлю страницу.Запрос работает при первой загрузке, но после этого никогда не запускается.

Если вам скучно или у вас зуд, весь блок кода находится ниже.

Настройка идет следующим образом:

  • Добавить WebActivator через NuGet
  • В папке AppStart добавьте файл cs с кодом ниже
  • ДобавитьСтрока подключения «PageContext» к вашему web.config
  • Запустите приложение, на экране MVC по умолчанию отобразится
  • Теперь добавьте «/ abc» в конец URL (т. Е. http://localhost/abc)
  • Будет отображено представление cshtml, хранящееся в базе данных.
  • Измените разметку представления в базе данных и перезагрузите страницу. Обратите внимание, что в вашем браузере нет изменений.

маршрут / abc предполагает, что у вас есть запись в базе данных со следующим

  • Путь: "~ / abc / index.cshtml"

  • Просмотр: "@{ Layout = null;}<!doctype html><html><head><title>abc</title></head><body><h2>About</h2></body></html>"

Я понятия не имею, почему первый запрос работает, а последующие запросы не достигают точек остановаи обслуживать устаревшее содержимое.

Мои подозрения:

  • Некоторое вуду с VirtualFile
  • Что-то кешируется (но где?)
  • Неправильно настроенный обработчик

Спасибо за помощь - вот код (так как я постыдно заправил свой хвост за публикацию такого большого количества кода).

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Entity;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.Hosting;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.SessionState;
using Microsoft.Web.Infrastructure.DynamicModuleHelper;
using SomeCms;

[assembly: WebActivator.PreApplicationStartMethod(typeof(Sample.Web.App_Start.cms), "PreStart")]

namespace Sample.Web.App_Start
{
    public static class cms
    {
        public static void PreStart()
        {
            DynamicModuleUtility.RegisterModule(typeof(InstallerModule));
        }
    }
}

namespace SomeCms
{
    class ActionInvokerWrapper : IActionInvoker
    {
        readonly IActionInvoker actionInvoker;

        public ActionInvokerWrapper(IActionInvoker actionInvoker)
        {
            this.actionInvoker = actionInvoker;
        }

        public bool InvokeAction(ControllerContext controllerContext, string actionName)
        {
            if (actionInvoker.InvokeAction(controllerContext, actionName))
            {
                return true;
            }

            // No action method was found.
            var controller = new CmsContentController();
            controller.ExecuteCmsContent(controllerContext.RequestContext);

            return true;
        }
    }

    class ControllerFactoryWrapper : IControllerFactory
    {
        readonly IControllerFactory factory;

        public ControllerFactoryWrapper(IControllerFactory factory)
        {
            this.factory = factory;
        }

        public IController CreateController(RequestContext requestContext, string controllerName)
        {
            try
            {
                var controller = factory.CreateController(requestContext, controllerName);
                WrapControllerActionInvoker(controller);
                return controller;
            }
            catch (HttpException ex)
            {
                if (ex.GetHttpCode() == 404)
                {
                    return new CmsContentController();
                }

                throw;
            }
        }

        static void WrapControllerActionInvoker(IController controller)
        {
            var controllerWithInvoker = controller as Controller;
            if (controllerWithInvoker != null)
            {
                controllerWithInvoker.ActionInvoker = new ActionInvokerWrapper(controllerWithInvoker.ActionInvoker);
            }
        }

        public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
        {
            return factory.GetControllerSessionBehavior(requestContext, controllerName);
        }

        public void ReleaseController(IController controller)
        {
            factory.ReleaseController(controller);
        }
    }

    class InstallerModule : IHttpModule
    {
        static bool installed;
        static readonly object installerLock = new object();

        public void Init(HttpApplication application)
        {
            if (installed)
            {
                return;
            }

            lock (installerLock)
            {
                if (installed)
                {
                    return;
                }

                Install();
                installed = true;
            }
        }

        static void Install()
        {
            Database.SetInitializer(new CreateDatabaseIfNotExists<PageContext>());
            HostingEnvironment.RegisterVirtualPathProvider(new ExampleVirtualPathProvider());
            WrapControllerBuilder();
            AddNotFoundRoute();
            AddCatchAllRoute();
        }

        static void WrapControllerBuilder()
        {
            ControllerBuilder.Current.SetControllerFactory(new ControllerFactoryWrapper(ControllerBuilder.Current.GetControllerFactory()));
        }

        static void AddNotFoundRoute()
        {
            // To allow IIS to execute "/cmscontent" when requesting something which is disallowed,
            // such as /bin or /add_data.
            RouteTable.Routes.MapRoute(
                "CmsContent",
                "cmscontent",
                new { controller = "CmsContent", action = "CmsContent" }
            );
        }

        static void AddCatchAllRoute()
        {
            RouteTable.Routes.MapRoute(
                "CmsContent-Catch-All",
                "{*any}",
                new { controller = "CmsContent", action = "CmsContent" }
            );
        }

        public void Dispose() { }
    }

    public class CmsContentController : IController
    {
        public void Execute(RequestContext requestContext)
        {
            ExecuteCmsContent(requestContext);
        }

        public void ExecuteCmsContent(RequestContext requestContext)
        {
            //new CmsContentViewResult().ExecuteResult(new ControllerContext(requestContext, new FakeController()));
            new CmsContentViewResult().ExecuteResult(new ControllerContext(requestContext, new FakeController()));
        }

        // ControllerContext requires an object that derives from ControllerBase.
        // NotFoundController does not do this.
        // So the easiest workaround is this FakeController.
        class FakeController : Controller { }
    }

    public class CmsContentHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            var routeData = new RouteData();
            routeData.Values.Add("controller", "CmsContent");
            var controllerContext = new ControllerContext(new HttpContextWrapper(context), routeData, new FakeController());
            var cmsContentViewResult = new CmsContentViewResult();
            cmsContentViewResult.ExecuteResult(controllerContext);
        }

        public bool IsReusable
        {
            get { return false; }
        }

        // ControllerContext requires an object that derives from ControllerBase.
        class FakeController : Controller { }
    }

    public class CmsContentViewResult : ViewResult
    {
        public CmsContentViewResult()
        {
            ViewName = "index";
        }

        public override void ExecuteResult(ControllerContext context)
        {
            var request = context.HttpContext.Request;
            if (request != null && request.Url != null)
            {
                var url = request.Url.OriginalString;

                ViewData["RequestedUrl"] = url;
                ViewData["ReferrerUrl"] = (request.UrlReferrer != null && request.UrlReferrer.OriginalString != url)
                                              ? request.UrlReferrer.OriginalString
                                              : null;
            }

            base.ExecuteResult(context);
        }
    }

    public class ExampleVirtualPathProvider : VirtualPathProvider
    {
        private readonly List<SimpleVirtualFile> virtualFiles = new List<SimpleVirtualFile>();

        public ExampleVirtualPathProvider()
        {
            var context = new PageContext();
            var pages = context.Pages.ToList();

            foreach (var page in pages)
            {
                virtualFiles.Add(new SimpleVirtualFile(page.Path));

            }
        }

        public override bool FileExists(string virtualPath)
        {
            var files = (from f in virtualFiles
                         where f.VirtualPath.Equals(virtualPath, StringComparison.InvariantCultureIgnoreCase) ||
                               f.RelativePath.Equals(virtualPath, StringComparison.InvariantCultureIgnoreCase)
                         select f)
                         .ToList();

            return files.Count > 0 || base.FileExists(virtualPath);
        }

        private class SimpleVirtualFile : VirtualFile
        {
            public SimpleVirtualFile(string filename) : base(filename)
            {
                RelativePath = filename;
            }

            public override Stream Open()
            {
                var context = new PageContext();
                var page = context.Pages.FirstOrDefault(p => p.Path == RelativePath);

                return new MemoryStream(Encoding.ASCII.GetBytes(page.View), false);
            }

            public string RelativePath { get; private set; }
        }

        private class SimpleVirtualDirectory : VirtualDirectory
        {
            public SimpleVirtualDirectory(string virtualPath)
                : base(virtualPath)
            {

            }

            public override IEnumerable Directories
            {
                get { return null; }
            }

            public override IEnumerable Files
            {
                get
                {
                    return null;
                }
            }

            public override IEnumerable Children
            {
                get { return null; }
            }
        }

        public override VirtualFile GetFile(string virtualPath)
        {
            var files = (from f in virtualFiles
                         where f.VirtualPath.Equals(virtualPath, StringComparison.InvariantCultureIgnoreCase) ||
                               f.RelativePath.Equals(virtualPath, StringComparison.InvariantCultureIgnoreCase)
                         select f).ToList();
            return files.Count > 0
                ? files[0]
                : base.GetFile(virtualPath);
        }

        public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
        {
            return IsPathVirtual(virtualPath) ? null : base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
        }

        private bool IsPathVirtual(string virtualPath)
        {
            var checkPath = VirtualPathUtility.ToAppRelative(virtualPath);
            return
                virtualFiles.Any(f => checkPath.StartsWith(virtualPath, StringComparison.InvariantCultureIgnoreCase)) ||
                virtualFiles.Any(f => checkPath.Replace("~", "").StartsWith(virtualPath, StringComparison.InvariantCultureIgnoreCase));
        }

        public override bool DirectoryExists(string virtualDir)
        {
            return IsPathVirtual(virtualDir) || Previous.DirectoryExists(virtualDir);
        }

        public override VirtualDirectory GetDirectory(string virtualDir)
        {
            return IsPathVirtual(virtualDir)
                ? new SimpleVirtualDirectory(virtualDir)
                : Previous.GetDirectory(virtualDir);
        }
    }

    public class ContentPage
    {
        public int Id { get; set; }
        public string Path { get; set; }
        public string View { get; set; }
    }

    public class PageContext : DbContext
    {
        public DbSet<ContentPage> Pages { get; set; }
    }
}

1 Ответ

2 голосов
/ 19 сентября 2011

Этот вопрос не является проблемой.Мой контроль над зависимостью кеша в провайдере виртуальных путей возвращает ноль для виртуальных путей.Таким образом, представление кэшируется на неопределенный срок.

Решение состоит в том, чтобы использовать настраиваемый поставщик зависимостей кэша, срок действия которого истекает немедленно.

public class NoCacheDependency : CacheDependency
{
    public NoCacheDependency()
    {
        NotifyDependencyChanged(this, EventArgs.Empty);
    }
}

public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
{
     return IsPathVirtual(virtualPath) ? new NoCacheDependency() : base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
}
...