If there is ViewBag for ViewData, why is there no TempBag for TempData? - asp.net-mvc-3

Why is there no dynamic dictionary object for TempData as there is for ViewData?

There isn't because no-one ever bothered to implement it. But that would be trivially easy to do. For example as an extension method (unfortunately extension properties are not yet supported in .NET so you cannot quite get the syntax you might have hoped for):
public class DynamicTempDataDictionary : DynamicObject
{
public DynamicTempDataDictionary(TempDataDictionary tempData)
{
_tempData = tempData;
}
private readonly TempDataDictionary _tempData;
public override IEnumerable<string> GetDynamicMemberNames()
{
return _tempData.Keys;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = _tempData[binder.Name];
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_tempData[binder.Name] = value;
return true;
}
}
public static class ControllerExtensions
{
public static dynamic TempBag(this ControllerBase controller)
{
return new DynamicTempDataDictionary(controller.TempData);
}
}
and then:
public ActionResult Index()
{
this.TempBag().Hello = "abc";
return RedirectToAction("Foo");
}
The question is: why would you need that and how is it better/safer than:
public ActionResult Index()
{
TempData["Hello"] = "abc";
return RedirectToAction("Foo");
}

Related

ModelState to check all parameters in Web Api

This is my action the ModelState checks only the bookId parameter. The other one even if it is null, no error is raised.
Is there any way to make it check the ModelState of all parameters?
[HttpPut]
[Route("{bookId}")]
public IHttpActionResult Edit([FromBody] EditBookBindingModel model, int bookId)
{
if (!this.service.ExistsBook(bookId))
{
return this.NotFound();
}
if (!this.ModelState.IsValid)
{
return this.StatusCode(HttpStatusCode.BadRequest);
}
this.service.EditBook(bookId, model);
return this.Ok();
}
You could define an ActionFilterAttribute that protects you from null arguments:
public class CheckModelForNullAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.ContainsValue(null))
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "The argument cannot be null");
}
}
Then use this:
[HttpPut]
[Route("{bookId}")]
[CheckModelForNull]
public IHttpActionResult Edit([FromBody] EditBookBindingModel model, int bookId)
{
// model canĀ“t be null now
...
I wrote a custom filter so DataAnnotations works with parameters also.
Here is the filter.
public class ModelValidationFilter : FilterAttribute, IActionFilter, IFilter
{
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext,
CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
if (parameters.Any())
{
var validationParams = parameters.Where(x => x.GetCustomAttributes<ValidationAttribute>().Any());
if (validationParams.Any())
{
foreach (var item in validationParams)
{
var val = actionContext.ActionArguments[item.ParameterName];
foreach (var attr in item.GetCustomAttributes<ValidationAttribute>())
{
if (!attr.IsValid(val))
{
actionContext.ModelState.AddModelError(item.ParameterName, attr.FormatErrorMessage(item.ParameterName));
}
}
}
}
if (!actionContext.ModelState.IsValid)
{
return Task.FromResult(actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState));
}
}
return continuation();
}
}
Usage (I have'nt tested completely.):
add it to global filters.
config.Filters.Add(new ModelValidationFilter());
public Student Post([Required] addStudentDTO)
{
//Your logic
}
public Student Patch([FromBody,Required] addStudentDTO, [Required,EmailAddress]string emailAddress])
{
//Your logic
}

HttpRouteBuilder - Where did it go and Why?

I upgraded my nuget package for the Web API 2 from the RC1 to 5.0.0, and was dumbfounded to find that HttpRouteBuilder, which used to be accessible, was made internal. Along with that, there is no longer an overload for HttpConfiguration.MapHttpAttributeRoutes that takes HttpRouteBuilder as an argument. Why?
I was using that, and it solves a major problem in my project. What do I use instead?
Background:
I am writing a server that uses Attribute Routing for Web API 2. I implemented a class that inherited from HttpRouteBuilder so that I could inject a couple extra path segments to every URI. For example, if the default route builder ended up creating a route for //myserver/user/update, my route builder would modify that route to //myserver/{instance}/user/update. I wanted this done automatically so that I didn't have to stick that in every single of my hundreds of HttpGet, HttpPost, etc. attributes. So now how do I handle that with this major change?
That internalling broke something I was working on as well.
A change set made on August 21st 2013 made this api alteration to fix this issue. According to that issue the only reason functionality was removed was to make Web Api closer to MVC's api. Not a particularly good justification in my opinion.
To resolve my issues I implemented a custom IHttpActionSelector derived from ApiControllerActionSelector. I hope it is not going to be my final solution since it really is far too much code for a simple thing. This approach should work for your problem too.
In my project each route needs to be modified according to which assembly it was found in. In following simplified code every route is prefixed with /Api (before a controller's RoutePrefixAttribute if present).
The actual IHttpActionSelector:
public class PrefixWithApiControllerActionSelector : WrappingApiControllerActionSelector {
protected override HttpActionDescriptor WrapHttpActionDescriptor(HttpActionDescriptor actionDescriptor) {
if (actionDescriptor is ReflectedHttpActionDescriptor)
return new PrefixWithApiReflectedHttpActionDescriptor((ReflectedHttpActionDescriptor)actionDescriptor);
return actionDescriptor;
}
}
public abstract class WrappingApiControllerActionSelector : ApiControllerActionSelector {
protected abstract HttpActionDescriptor WrapHttpActionDescriptor(HttpActionDescriptor actionDescriptor);
public override ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor) {
return base.GetActionMapping(controllerDescriptor).SelectMany(grouping => {
return grouping.Select(actionDescriptor => new KeyValuePair<string, HttpActionDescriptor>(grouping.Key, WrapHttpActionDescriptor(actionDescriptor)));
}).ToLookup(_ => _.Key, _ => _.Value);
}
}
The part that changes the route:
public class PrefixWithApiHttpRouteInfoProvider : WrappedHttpRouteInfoProvider {
public PrefixWithApiHttpRouteInfoProvider(IHttpRouteInfoProvider infoProvider, HttpControllerDescriptor controllerDescriptor) : base(infoProvider, controllerDescriptor) { }
public override string Template {
get {
var parts = new List<string>();
parts.Add("Api");
var prefix = ControllerDescriptor.GetCustomAttributes<RoutePrefixAttribute>().FirstOrDefault();
if (prefix != null && !string.IsNullOrEmpty(prefix.Prefix)) {
parts.Add(prefix.Prefix);
}
if (!string.IsNullOrEmpty(InfoProvider.Template)) {
parts.Add(InfoProvider.Template);
}
var route = "~/" + string.Join("/", parts);
if (route.Length > 2 && route.EndsWith("/", StringComparison.Ordinal)) {
route = route.Substring(0, route.Length - 1);
}
return route;
}
}
}
public abstract class WrappedHttpRouteInfoProvider : IHttpRouteInfoProvider {
private readonly IHttpRouteInfoProvider _infoProvider;
private readonly HttpControllerDescriptor _controllerDescriptor;
protected WrappedHttpRouteInfoProvider(IHttpRouteInfoProvider infoProvider, HttpControllerDescriptor controllerDescriptor) {
_infoProvider = infoProvider;
_controllerDescriptor = controllerDescriptor;
}
public virtual string Name {
get { return InfoProvider.Name; }
}
public virtual string Template {
get { return _infoProvider.Template; }
}
public virtual int Order {
get { return InfoProvider.Order; }
}
protected HttpControllerDescriptor ControllerDescriptor {
get { return _controllerDescriptor; }
}
protected IHttpRouteInfoProvider InfoProvider {
get { return _infoProvider; }
}
}
The glue:
public class PrefixWithApiReflectedHttpActionDescriptor : WrappedReflectedHttpActionDescriptor {
public PrefixWithApiReflectedHttpActionDescriptor(ReflectedHttpActionDescriptor descriptor) : base(descriptor) {}
public override Collection<T> GetCustomAttributes<T>(bool inherit) {
if (typeof(T) == typeof(IHttpRouteInfoProvider)) {
var attributes = Descriptor.GetCustomAttributes<T>(inherit).Cast<IHttpRouteInfoProvider>().Select(_ => new PrefixWithApiHttpRouteInfoProvider(_, Descriptor.ControllerDescriptor));
return new Collection<T>(attributes.Cast<T>().ToList());
}
return Descriptor.GetCustomAttributes<T>(inherit);
}
public override Collection<T> GetCustomAttributes<T>() {
if (typeof(T) == typeof(IHttpRouteInfoProvider)) {
var attributes = Descriptor.GetCustomAttributes<T>().Cast<IHttpRouteInfoProvider>().Select(_ => new PrefixWithApiHttpRouteInfoProvider(_, Descriptor.ControllerDescriptor));
return new Collection<T>(attributes.Cast<T>().ToList());
}
return Descriptor.GetCustomAttributes<T>();
}
}
public abstract class WrappedReflectedHttpActionDescriptor : ReflectedHttpActionDescriptor {
private readonly ReflectedHttpActionDescriptor _descriptor;
protected WrappedReflectedHttpActionDescriptor(ReflectedHttpActionDescriptor descriptor) : base(descriptor.ControllerDescriptor, descriptor.MethodInfo) {
_descriptor = descriptor;
}
public override HttpActionBinding ActionBinding {
get { return Descriptor.ActionBinding; }
set { Descriptor.ActionBinding = value; }
}
public override Collection<T> GetCustomAttributes<T>(bool inherit) {
return Descriptor.GetCustomAttributes<T>(inherit);
}
public override Collection<T> GetCustomAttributes<T>() {
return Descriptor.GetCustomAttributes<T>();
}
public override Collection<System.Web.Http.Filters.FilterInfo> GetFilterPipeline() {
return Descriptor.GetFilterPipeline();
}
public override Collection<System.Web.Http.Filters.IFilter> GetFilters() {
return Descriptor.GetFilters();
}
public override System.Collections.Concurrent.ConcurrentDictionary<object, object> Properties {
get { return Descriptor.Properties; }
}
public override IActionResultConverter ResultConverter {
get { return Descriptor.ResultConverter; }
}
public override Collection<HttpMethod> SupportedHttpMethods {
get { return Descriptor.SupportedHttpMethods; }
}
public override Collection<HttpParameterDescriptor> GetParameters() {
return Descriptor.GetParameters();
}
public override Task<object> ExecuteAsync(HttpControllerContext controllerContext, IDictionary<string, object> arguments, CancellationToken cancellationToken) {
return Descriptor.ExecuteAsync(controllerContext, arguments, cancellationToken);
}
public override string ActionName {
get { return Descriptor.ActionName; }
}
public override Type ReturnType {
get { return Descriptor.ReturnType; }
}
protected ReflectedHttpActionDescriptor Descriptor {
get { return _descriptor; }
}
}
To use this functionality just substitute the IHttpActionSelector service with PrefixWithApiControllerActionSelector in the config.
If you find a cleaner way of doing things please post your solution!

CS0103: The name 'ViewBag' does not exist in the current context [duplicate]

I'm pulling a razor view's markup from the database, as detailed in this question:
ASP.NET MVC load Razor view from database
I can pull the view, but it fails on execution because ViewBag is not recognized.
CS0103: The name 'ViewBag' does not exist in the current context
Any suggestions?
Here's the source:
global:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new BearForce.Web.Core.DbPathProvider());
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
my path provider:
namespace BearForce.Web.Core
{
public class DbPathProvider : VirtualPathProvider
{
public DbPathProvider()
: base()
{
}
public override bool FileExists(string virtualPath)
{
var repo = new Repository();
var viewPage = repo.GetView(240, virtualPath);
if (base.FileExists(virtualPath))
{
return true;
}
if (viewPage != null)
{
return true;
}
return false;
}
public override VirtualFile GetFile(string virtualPath)
{
if (base.FileExists(virtualPath))
{
return base.GetFile(virtualPath);
}
var repo = new Repository();
var result = repo.GetView(240, virtualPath);
var vf = new DbVirtualFile(virtualPath, result.Markup);
return vf;
}
}
}
my Virtual File:
public class DbVirtualFile : System.Web.Hosting.VirtualFile
{
string _fileContents = string.Empty;
public DbVirtualFile(string path, string fileContents)
: base(path)
{
_fileContents = fileContents;
}
public override System.IO.Stream Open()
{
return new System.IO.MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(_fileContents));
}
}
My Controller:
public ActionResult Index()
{
ViewBag.Title = "aaah!!! Muppets!!! Help!!!!!";
return View();
}
Obviously, this is a proof of concept, so the names are all silly and the code sloppy as hell...
For future people who get this error, you can get this exact error if your web.config files are missing from your Views and your root project folder.
You should make sure that the view you are returning corresponds to a razor view. Here's a simplified working example:
public class CustomPathProvider : VirtualPathProvider
{
private class CustomVirtualFile : VirtualFile
{
public CustomVirtualFile(string path)
: base(path)
{ }
public override Stream Open()
{
return new MemoryStream(Encoding.UTF8.GetBytes("Hello #ViewBag.Name"));
}
}
public override bool FileExists(string virtualPath)
{
// This is very important: make sure that here you
// are returning true only for Razor view pages or
// you won't have ViewBag.
// In this oversimplified example we support
// the index view for the home controller
return virtualPath == "/Views/Home/Index.cshtml";
}
public override VirtualFile GetFile(string virtualPath)
{
return new CustomVirtualFile(virtualPath);
}
}
which would be registered in Application_Start:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
HostingEnvironment.RegisterVirtualPathProvider(new CustomPathProvider());
}
and finally a sample controller:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Name = "John";
return View();
}
}
And a final very important remark if you are implementing a custom VirtualPathProvider:
This doesn't work if your web application is precompiled. So if you are using precompilation (things like Publish... or Web Deployment Projects) your custom virtual path provider will never be used.

Multiple attributes of same type fail when using Ninject's BindFilter<T>

As part of a identity-enabled authorization system, I'd like to use IAuhtorizationFilter and Attributes to restrict access to action methods in my controllers. I've got things working very well, partly due to help from the following resources:
Ninject Binding Attribute to Filter with Constructor Arguments
https://github.com/ninject/ninject.web.mvc/wiki/Filter-configurations
Custom Authorization MVC 3 and Ninject IoC
https://github.com/ninject/ninject.web.mvc/wiki/Dependency-injection-for-filters
However, when I try to decorate an action method with more than one of my attributes, I get an exception as follows (sorry for the formatting):
[InvalidOperationException: Sequence contains more than one element]
System.Linq.Enumerable.Single(IEnumerable`1 source) +2691369
Ninject.Web.Mvc.FilterBindingSyntax.c__DisplayClass15`1.b__14(IContext ctx, ControllerContext controllerContext, ActionDescriptor actionDescriptor) in c:\Projects\Ninject\Maintenance2.2\ninject.web.mvc\mvc3\src\Ninject.Web.Mvc\FilterBindingSyntax\FilterFilterBindingBuilder.cs:379
Ninject.Web.Mvc.FilterBindingSyntax.c__DisplayClass12.b__11(IContext ctx) in c:\Projects\Ninject\Maintenance2.2\ninject.web.mvc\mvc3\src\Ninject.Web.Mvc\FilterBindingSyntax\FilterFilterBindingBuilder.cs:358
Ninject.Parameters.c__DisplayClass6.b__4(IContext ctx, ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Parameters\Parameter.cs:60
Ninject.Parameters.Parameter.GetValue(IContext context, ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Parameters\Parameter.cs:88
Ninject.Activation.Providers.StandardProvider.GetValue(IContext context, ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Activation\Providers\StandardProvider.cs:97
Ninject.Activation.Providers.c__DisplayClass2.b__1(ITarget target) in c:\Projects\Ninject\Maintenance2.2\ninject\src\Ninject\Activation\Providers\StandardProvider.cs:81
...
Here is a very simplified version of my code that demonstrates the problem in an MVC3 app:
Attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class SampleAttribute : Attribute
{
private Guid typeId;
public bool IsAllowed { get; set; }
public SampleAttribute(bool IsAllowed)
{
this.IsAllowed = IsAllowed;
this.typeId = new Guid();
}
public override object TypeId
{
get
{
return (object)typeId;
}
}
}
Filter:
public class SampleFilter : IAuthorizationFilter, IMvcFilter
{
private bool isAllowed;
public bool AllowMultiple
{
get { return true; }
}
public int Order
{
get { return 0; }
}
public SampleFilter(bool isAllowed)
{
this.isAllowed = isAllowed;
}
public void OnAuthorization(AuthorizationContext filterContext)
{
if (!isAllowed)
throw new Exception("unauthorized");
}
}
Controller:
public class HomeController : Controller
{
[Sample(true)]
[Sample(false)]
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
}
The controller method above works as expected if one or the other of the Sample attributes on the Index() method is removed. Having both in place, generates the exception. I realize that in this simplified example, there isn't a situation that would call for both attributes, but it's simply for illustration.
What am I missing?
This is a known issue of Ninject 2.2. Please use 3.0 instead.
https://github.com/ninject/ninject.web.mvc/blob/master/mvc3/ReleaseNotes.txt

Custom Not-Found Route Fires Only Once

I tend to dislike posting dozens of lines of code and assuming the community at large is interested in untangling my mess. In this case I've exercised everything I can think to search on Google, traced through Glimpse, and Firebug/Fiddler, and what I'm left with is an occasionally working behavior, which is particularly annoying to debug. So, I'm calling out for help.
Here's the gist: I've got a series of classes that handle MVC routes that are otherwise not found (and would produce a 404 error) thanks to #AndrewDavey. I'm attempting to intercept the 404 and show data-driven content where any exists. It all works until I refresh the page. The request works on the first load, but it never fires again after that.
If you're bored or have an itch, the entire code block is below.
Setup goes like this:
Add WebActivator via NuGet
In your AppStart folder add a cs file with the code below
Add a "PageContext" connection string to your web.config
Run the app, the default MVC screen shows up
Now add "/abc" to the end of the url (i.e http://localhost/abc)
A cshtml view, stored in the database, will render.
Change the view's markup in the database and reload the page. Notice no change in your browser.
the /abc route assumes you have a record in the database with the following
Path: "~/abc/index.cshtml"
View: "#{ Layout = null;}<!doctype html><html><head><title>abc</title></head><body><h2>About</h2></body></html>"
I've got no idea why the first request works and subsequent requests don't hit break points and serve up stale content.
My suspicions are:
Some voodoo with the VirtualFile
Something cached (but where?)
A misconfigured handler
Thanks for the help - here's the code (as I shamefully tuck my tail for posting this much code).
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; }
}
}
This question turns out to be a non-issue. My oversight of the cache dependency in the virtual path provider is returning null for virtual paths. As such, the view is cached indefinitely.
The solution is to use a custom cache dependency provider that expires immediately.
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);
}

Resources