Custom View Scope with SetPropertyActionListener - spring

I'm using Spring with a custom view scope to create view scope beans. This works fine until I tried to inject a property using setPropertyActionListener. This works fine if I change the bean to request scope.
This is my view scope impl:
public class ViewScope implements Scope {
public Object get(String name, ObjectFactory<?> objectFactory) {
if (FacesContext.getCurrentInstance().getViewRoot() != null) {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
} else {
return null;
}
}
public Object remove(String name) {
if (FacesContext.getCurrentInstance().getViewRoot() != null) {
return FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove(name);
} else {
return null;
}
}
public void registerDestructionCallback(String name, Runnable callback) {
// Do nothing
}
public Object resolveContextualObject(String key) {
return null;
}
public String getConversationId() {
return null;
}
}
and my JSF:
<h:commandLink action="confirm.xhtml">
<f:setPropertyActionListener target="#{quoteHolder.item}" value="#{quote}"/>
</h:commandLink>

Related

Activtii/OSGI, TaskListener not added as Activiti service to delegate cache

Hello I have a problem with osgi on servicemix.
It cannot bind or unbind service to delegate cache, when using TaskListener interface. JavaDelegate works fine in osgi with delegate expression.
Could this solution solve the problem or is there something else needed with BlueprintContextELResolver? Because the blueprint with the
service ref="something" interface="org.activiti.engine.delegate.TaskListener"/>
package org.activiti.osgi.blueprint;
/**
* #see org.activiti.spring.ApplicationContextElResolver
*/
public class BlueprintELResolver extends ELResolver {
private Map<String, JavaDelegate> delegateMap = new HashMap<String, JavaDelegate>();
private Map<String, TaskListener> taskListenerMap = new HashMap<String, TaskListener>();
private Map<String, ActivityBehavior> activityBehaviourMap = new HashMap<String, ActivityBehavior>();
public Object getValue(ELContext context, Object base, Object property) {
if (base == null) {
// according to javadoc, can only be a String
String key = (String) property;
LOGGER.info("Show string key: {}", key);
LOGGER.info("Show property: {}", property);
for (String name : delegateMap.keySet()) {
if (name.equalsIgnoreCase(key)) {
LOGGER.info("Show property JavaDelegate: {}", name);
context.setPropertyResolved(true);
return delegateMap.get(name);
}
}
for (String name : taskListenerMap.keySet()) {
if (name.equalsIgnoreCase(key)) {
LOGGER.info("Show property TaskListener: {}", name);
context.setPropertyResolved(true);
return taskListenerMap.get(name);
}
}
for (String name : activityBehaviourMap.keySet()) {
if (name.equalsIgnoreCase(key)) {
context.setPropertyResolved(true);
return activityBehaviourMap.get(name);
}
}
}
return null;
}
#SuppressWarnings("rawtypes")
public void bindService(JavaDelegate delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
delegateMap.put(name, delegate);
LOGGER.info("added Activiti service to delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void unbindService(JavaDelegate delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
if(delegateMap.containsKey(name)) {
delegateMap.remove(name);
}
LOGGER.info("removed Activiti service from delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void bindTaskListenerService(TaskListener delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
taskListenerMap.put(name, delegate);
LOGGER.info("added Activiti service to delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void unbindTaskListenerService(TaskListener delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
if(taskListenerMap.containsKey(name)) {
taskListenerMap.remove(name);
}
LOGGER.info("removed Activiti service from delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void bindActivityBehaviourService(ActivityBehavior delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
activityBehaviourMap.put(name, delegate);
LOGGER.info("added Activiti service to activity behaviour cache {}", name);
}
#SuppressWarnings("rawtypes")
public void unbindActivityBehaviourService(ActivityBehavior delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
if(activityBehaviourMap.containsKey(name)) {
activityBehaviourMap.remove(name);
}
LOGGER.info("removed Activiti service from activity behaviour cache {}", name);
}
public boolean isReadOnly(ELContext context, Object base, Object property) {
return true;
}
public void setValue(ELContext context, Object base, Object property,
Object value) {
}
public Class<?> getCommonPropertyType(ELContext context, Object arg) {
return Object.class;
}
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,
Object arg) {
return null;
}
public Class<?> getType(ELContext context, Object arg1, Object arg2) {
return Object.class;
}
}
After this I still get:
Exception while invoking TaskListener: Unknown property used in
expression: ${expression}
I have experienced a similar problem where the El resolver was not being fully initiated.
I resolved the problem by overloading the BlueprintExpressionManager when loading the configuration:
public class BlueprintExpressionManagerReordered extends ExpressionManager
{
#Override
protected ELResolver createElResolver(VariableScope variableScope){
CompositeELResolver elResolver = new CompositeELResolver();
elResolver.add(new VariableScopeElResolver(variableScope));
elResolver.add(new ArrayELResolver());
elResolver.add(new ListELResolver());
elResolver.add(new MapELResolver());
if (_blueprintContextELResolver != null) {
elResolver.add(_blueprintContextELResolver);
}
elResolver.add(_blueprintELResolver);
elResolver.add(new BeanELResolver());
return elResolver;
}
}
It is called during init of the process engine configuration:
public class ProcessEngineFactoryWithELResolverReordered extends ProcessEngineFactoryWithELResolver
{
private BlueprintELResolver _blueprintELResolver;
private BlueprintContextELResolver _blueprintContextELResolver;
public void init() throws Exception
{
super.init();
ProcessEngineConfigurationImpl configImpl = (ProcessEngineConfigurationImpl)getProcessEngineConfiguration();
final BlueprintExpressionManagerReordered mgr = new BlueprintExpressionManagerReordered();
configImpl.setExpressionManager(mgr);
if (configImpl.getActivityBehaviorFactory() instanceof DefaultActivityBehaviorFactory) {
((DefaultActivityBehaviorFactory)configImpl.getActivityBehaviorFactory()).setExpressionManager(mgr);
}
if (configImpl.getListenerFactory() instanceof DefaultListenerFactory) {
((DefaultListenerFactory)configImpl.getListenerFactory()).setExpressionManager(mgr);
}
}
And referenced in my context.xml
<bean id="processEngineFactory"
class="com.bp3.oss.custom.ProcessEngineFactoryWithELResolverReordered"
init-method="init"
destroy-method="destroy">
<property name="processEngineConfiguration"
ref="configuration"/>
<property name="bundle" ref="blueprintBundle"/>
<property name="blueprintELResolver"
ref="blueprintELResolver"/>
<property name="blueprintContextELResolver" ref="blueprintContextELResolver"/>
</bean>
Not sure if this will help, but may give you something to go after.

"The type IUnitOfWork does not have an accessible constructor" with Umbraco 6.1, UmbracoApiController (Web API) & Dependency Injection (Unity)

I am using Umbraco 6.1 with an UmbracoApiController which has a IUnitOfWork injected into it's constructor. To inject the dependencies, I am using Unity, like I have in the past with standard Web API projects. Normally, I set unity up in the Global.asax.cs. As Umbraco does not have this I have created my own UmbracoEvents handler, which inherits from IApplicationEventHandler, and has the methods:
OnApplicationInitialized
OnApplicationStarting
OnApplicationStarted
ConfigureApi
In the OnApplicationStarted method I set up my EF database, db initializer etc and call ConfigureApi to set up Unity. My OnApplication Started and ConfigureApi methods looks like this:
public void OnApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
_applicationContext = applicationContext;
_umbracoApplication = umbracoApplication;
_contentService = ApplicationContext.Current.Services.ContentService;
this.ConfigureApi(GlobalConfiguration.Configuration);
Database.SetInitializer(null);
PropertySearchContext db = new PropertySearchContext();
db.Database.Initialize(true);
}
private void ConfigureApi(HttpConfiguration config)
{
var unity = new UnityContainer();
unity.RegisterType<PropertiesApiController>();
unity.RegisterType<IUnitOfWork, UnitOfWork>(new HierarchicalLifetimeManager());
config.DependencyResolver = new IoCContainer(unity);
}
My Controller code:
public class PropertiesApiController : UmbracoApiController
{
private readonly IUnitOfWork _unitOfWork;
public PropertiesApiController(IUnitOfWork unitOfWork)
{
if(null == unitOfWork)
throw new ArgumentNullException();
_unitOfWork = unitOfWork;
}
public IEnumerable GetAllProperties()
{
return new[] {"Table", "Chair", "Desk", "Computer", "Beer fridge"};
}
}
My Scope Container/IoC Container code: (as per http://www.asp.net/web-api/overview/extensibility/using-the-web-api-dependency-resolver)
public class ScopeContainer : IDependencyScope
{
protected IUnityContainer container;
public ScopeContainer(IUnityContainer container)
{
if (container == null)
{
throw new ArgumentNullException("container");
}
this.container = container;
}
public object GetService(Type serviceType)
{
if (container.IsRegistered(serviceType))
{
return container.Resolve(serviceType);
}
else
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
if (container.IsRegistered(serviceType))
{
return container.ResolveAll(serviceType);
}
else
{
return new List<object>();
}
}
public void Dispose()
{
container.Dispose();
}
}
public class IoCContainer : ScopeContainer, IDependencyResolver
{
public IoCContainer(IUnityContainer container)
: base(container)
{
}
public IDependencyScope BeginScope()
{
var child = this.container.CreateChildContainer();
return new ScopeContainer(child);
}
}
My IUnitOfWork code:
public interface IUnitOfWork : IDisposable
{
GenericRepository<Office> OfficeRepository { get; }
GenericRepository<Property> PropertyRepository { get; }
void Save();
void Dispose(bool disposing);
void Dispose();
}
My UnitOfWork implementation:
public class UnitOfWork : IUnitOfWork
{
private readonly PropertySearchContext _context = new PropertySearchContext();
private GenericRepository<Office> _officeRepository;
private GenericRepository<Property> _propertyRepository;
public GenericRepository<Office> OfficeRepository
{
get
{
if (this._officeRepository == null)
{
this._officeRepository = new GenericRepository<Office>(_context);
}
return _officeRepository;
}
}
public GenericRepository<Property> PropertyRepository
{
get
{
if (this._propertyRepository == null)
{
this._propertyRepository = new GenericRepository<Property>(_context);
}
return _propertyRepository;
}
}
public void Save()
{
_context.SaveChanges();
}
private bool disposed = false;
public virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
I have used unity/DI with MVC4/WebAPI controllers and this implementation of UnitOfWork many times before without issue, so I'm thinking it's Umbraco specific.
I have also debugged the application and made sure that it hits OnApplicationStarted and that its parameters are not null.
The GetAllProperties method in the controller is just a test method to make sure it is all working fine, however, when I try and access this action I get the error:
"The type IUnitOfWork does not have an accessible constructor"
Does anyone have experience with using Umbraco 6.1 and it's UmbracoApiController with dependency injection/Unity?
Also, on an unrelated note, is there a way to return JSON instead of XML in the action? In Web API you would just define the formatter in the WebApi.config but there is none in Umbraco.
Thanks,
Justin
In case you haven't found a solution to your problem? Download this nuget package and right after building your unity container:
GlobalConfiguration.Configuration.DependencyResolver =
new Unity.WebApi.UnityDependencyResolver(Bootstrapper.Container);
Notice the namespace which is different than Unity.Mvc4.UnityDependencyResolver.

MVC3, Unity Framework and Per Session Lifetime Manager Issue

In a simple word I try to create Lifetime manager for Unity framework by using Http Session in my MVC3 project. My sample implementation of lifetime manager is:
public class UnityPerSessionLifetimeManager : LifetimeManager
{
private string sessionKey;
private HttpContext ctx;
public UnityPerSessionLifetimeManager(string sessionKey)
{
this.sessionKey = sessionKey;
this.ctx = HttpContext.Current;
}
public override object GetValue()
{
return this.ctx.Session[this.sessionKey];
}
public override void RemoveValue()
{
this.ctx.Items.Remove(this.sessionKey);
}
public override void SetValue(object newValue)
{
this.ctx.Session[this.sessionKey] = newValue;
}
}
In my global.asax.cs I replaced default controller factory with my own UnityControllerFactory
public class UnityControllerFactory : DefaultControllerFactory
{
private IUnityContainer container;
public UnityControllerFactory(IUnityContainer container)
{
this.container = container;
this.RegisterServices();
}
protected override IController GetControllerInstance(RequestContext context, Type controllerType)
{
if (controllerType != null)
{
return this.container.Resolve(controllerType) as IController;
}
return null;
}
private void RegisterServices()
{
this.container.RegisterType<IMyType, MyImpl>(new UnityPerSessionLifetimeManager("SomeKey"));
}
}
}
I set breakpoints on each function of UnityPerSessionLifetimeManager class, I noticed that when controller factory tries to solve dependencies of my controller, the HttpContext.Session is actually null, so the code fails retrieve from session or save to session.
Any idea why session is null at this stage?
My mistake, I should change code of UnityPerSessionLifetimeManager class to be
public class UnityPerSessionLifetimeManager : LifetimeManager
{
private string sessionKey;
public UnityPerSessionLifetimeManager(string sessionKey)
{
this.sessionKey = sessionKey;
}
public override object GetValue()
{
return HttpContext.Current.Session[this.sessionKey];
}
public override void RemoveValue()
{
HttpContext.Current.Session.Remove(this.sessionKey);
}
public override void SetValue(object newValue)
{
HttpContext.Current.Session[this.sessionKey] = newValue;
}
}
because when the constructor was called to register type, session state is not ready yet and I already assigned http context of that time to a variable. But in later Get/Set functions session state is ready.

A single instance of controller 'TestController' cannot be used to handle multiple requests

I have Some issues with the life time manager in unity, it uses the object like its singleton, but in the configuration I set it to "PerWebRequest".
The Error is:
A single instance of controller 'TestController' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request.
The PerWebRequest code:
public class UnityPerWebRequestLifetimeManager : LifetimeManager
{
private HttpContextBase _httpContext;
public UnityPerWebRequestLifetimeManager()
: this(new HttpContextWrapper(HttpContext.Current))
{
}
public UnityPerWebRequestLifetimeManager(HttpContextBase httpContext)
{
_httpContext = httpContext;
}
private IDictionary<UnityPerWebRequestLifetimeManager, object> BackingStore
{
get
{
_httpContext = (HttpContext.Current != null) ? new HttpContextWrapper(HttpContext.Current) : _httpContext;
return UnityPerWebRequestLifetimeModule.GetInstances(_httpContext);
}
}
private object Value
{
[DebuggerStepThrough]
get
{
IDictionary<UnityPerWebRequestLifetimeManager, object> backingStore = BackingStore;
return backingStore.ContainsKey(this) ? backingStore[this] : null;
}
[DebuggerStepThrough]
set
{
IDictionary<UnityPerWebRequestLifetimeManager, object> backingStore = BackingStore;
if (backingStore.ContainsKey(this))
{
object oldValue = backingStore[this];
if (!ReferenceEquals(value, oldValue))
{
IDisposable disposable = oldValue as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
if (value == null)
{
backingStore.Remove(this);
}
else
{
backingStore[this] = value;
}
}
}
else
{
if (value != null)
{
backingStore.Add(this, value);
}
}
}
}
[DebuggerStepThrough]
public override object GetValue()
{
return Value;
}
[DebuggerStepThrough]
public override void SetValue(object newValue)
{
Value = newValue;
}
[DebuggerStepThrough]
public override void RemoveValue()
{
Value = null;
}
}
The controller:
public class TestController : Controller
{
//
// GET: /Test/
public TestController()
{
}
public ActionResult Index()
{
return View();
}
public ActionResult RadioButtonList()
{
return View("TestControl");
}
}
The Controller Factory:
public class ControllerFactory : DefaultControllerFactory
{
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
return (controllerType == null) ? base.GetControllerInstance(requestContext, controllerType) : IoC.Resolve<IController>(controllerType);
}
}
And in one of the views I am trying to use it like this:
...
<% Html.RenderAction<TestController>(c => c.RadioButtonList()); %>
<% Html.RenderAction<TestController>(c => c.RadioButtonList()); %>
...
I don't know what wrong here?
Thanks.
Both unity controller requests are created within the same HTTP request/reply, hence you get the same instance. You need to switch so that the controllers have a Transient lifetime.
I would switch to DependencyResolver instead of using ControllerFactory since you are running MVC3.

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