Im runing a nancyfx with owin on centos 6.5 with mono 5.10.0.140, I change the default ViewLocationProvider to ResourceViewLocationProvider for the default ViewLocationProvider causes memory leak of somekind after running for days, and the ResourceViewLocationProvider dont have the same problem. I would like to hot update Views just like what we can do with a default ViewLocationProvider, but it seems impossibe when googling around.
I did find a partial solution though, by implenting a custom IViewLocator and a IViewCache, I did achieve someking of hot update. But It didn`t feel right aside from those ugly static class
//Here is what I did in the custom IViewLocator
//...class definition fallback viewlocator and other staffs
private static ConcurrentDictionary<string, ViewLocationResult> _cachedViewLocationResults;
//..other code
public ViewLocationResult LocateView(string viewName, NancyContext context)
{
//...lock and others
if (_cachedViewLocationResults != null && _cachedViewLocationResults.ContainsKey(viewName))
{
return _cachedViewLocationResults[viewName];
}
//...lock and others
return fallbackViewLocator.LocateView(viewName, context);
}
//...other class
//here is how I update Views
public static void UpdateCachedView(IDictionary<string, ViewLocationResult> replacements)
{
lock (CacheLock)
{
if(_cachedViewLocationResults == null)_cachedViewLocationResults = new ConcurrentDictionary<string, ViewLocationResult>();
foreach (var replace in replacements)
{
_cachedViewLocationResults.AddOrUpdate(replace.Key, x=>replacements[x], (x,y)=>y);
}
}
}
//END OF IViewLocator
//here is what I did in the custom IViewCache
//another static for ViewCache to tell if the view has been updated
public static List<ViewLocationResult> Exceptions { get; private set; }
//...some other code
//here is how I ignore the old cache
public TCompiledView GetOrAdd<TCompiledView>(ViewLocationResult viewLocationResult, Func<ViewLocationResult, TCompiledView> valueFactory)
{
if (Exceptions.Any(x=>x.Name == viewLocationResult.Name && x.Location == viewLocationResult.Location && x.Extension == viewLocationResult.Extension))
{
object old;
this.cache.TryRemove(viewLocationResult, out old);
Exceptions.Remove(viewLocationResult);
}
return (TCompiledView)this.cache.GetOrAdd(viewLocationResult, x => valueFactory(x));
}
With those implentions and a little bit of settings on the bootstrapper plus a router for some mysql update, I can update the View the way I want, but here is the problem:
1. now I have to manually map all the Location,Name,Extension for the ViewLocationResult to use and there are too many of them (243...), I would like to use the some sort of built-in function to identify the changes, something like the IsStale function of the ViewLocationResult, but I didnt know which and how...
2. those static class are ugly and I think it could be problematic but I didnt know a better way to replace them.
Could some one kindly give me a hint, thank in advance.
Well, I finally figure out how to do this myself, just in case anyone else want to use the same method as I do, Here is how you update your view in memory:
Make a interface
public interface INewViewLocationResultProvider
{
bool UseCachedView { get; set; }
ViewLocationResult GetNewerVersion(string viewName, NancyContext context);
void UpdateCachedView(IDictionary<string, ViewLocationResult> replacements);
}
Make a new ViewLocationResultProvider
public class ConcurrentNewViewLocationResultProvider : INewViewLocationResultProvider
{
private Dictionary<string, ViewLocationResult> _cachedViewLocationResults;
private readonly object _cacheLock = new object();
public bool UseCachedView { get; set; }
public ConcurrentNewViewLocationResultProvider()
{
lock (_cacheLock)
{
if(_cachedViewLocationResults == null)_cachedViewLocationResults = new Dictionary<string, ViewLocationResult>();
}
}
public ViewLocationResult GetNewerVersion(string viewName, NancyContext context)
{
if (UseCachedView)
{
if (Monitor.TryEnter(_cacheLock, TimeSpan.FromMilliseconds(20)))
{
try
{
if (_cachedViewLocationResults != null && _cachedViewLocationResults.ContainsKey(viewName))
{
return _cachedViewLocationResults[viewName];
}
}
finally
{
Monitor.Exit(_cacheLock);
}
}
}
return null;
}
public void UpdateCachedView(IDictionary<string, ViewLocationResult> replacements)
{
lock (_cacheLock)
{
if(_cachedViewLocationResults == null)_cachedViewLocationResults = new Dictionary<string, ViewLocationResult>();
foreach (var replace in replacements)
{
if (_cachedViewLocationResults.ContainsKey(replace.Key))
{
_cachedViewLocationResults[replace.Key] = replace.Value;
}
else
{
_cachedViewLocationResults.Add(replace.Key,replace.Value);
}
}
}
}
}
In your Bootstrapper,register the new ViewLocationResultProvider with tinyIoc or equivalent
container.Register<INewViewLocationResultProvider, ConcurrentNewViewLocationResultProvider>().AsSingleton();
Make a derived class from ViewLocationResult
public class OneTimeUsedViewLocationResult : ViewLocationResult
{
private bool _used = false;
public OneTimeUsedViewLocationResult(string location, string name, string extension, Func<TextReader> contents)
: base(location, name, extension, contents)
{
}
public override bool IsStale()
{
if (_used) return false;
_used = true;
return true;
}
}
And a new IViewLocator:
public class CachedViewLocator : IViewLocator
{
private readonly INewViewLocationResultProvider _newVersion;
private readonly DefaultViewLocator _fallbackViewLocator;
public CachedViewLocator(IViewLocationProvider viewLocationProvider, IEnumerable<IViewEngine> viewEngines, INewViewLocationResultProvider newVersion)
{
_fallbackViewLocator = new DefaultViewLocator(viewLocationProvider, viewEngines);
_newVersion = newVersion;
}
public ViewLocationResult LocateView(string viewName, NancyContext context)
{
if (_newVersion.UseCachedView)
{
var result = _newVersion.GetNewerVersion(viewName, context);
if (result != null) return result;
}
return _fallbackViewLocator.LocateView(viewName, context);
}
public IEnumerable<ViewLocationResult> GetAllCurrentlyDiscoveredViews()
{
return _fallbackViewLocator.GetAllCurrentlyDiscoveredViews();
}
}
}
Tell nancy about the new ViewLocator
protected override NancyInternalConfiguration InternalConfiguration
{
get
{
return NancyInternalConfiguration.WithOverrides
(
nic =>
{
nic.ViewLocationProvider = typeof(ResourceViewLocationProvider);//use this or your equivalent
nic.ViewLocator = typeof(CachedViewLocator);
}
);
}
}
Then you can update it through a API like this:
public class YourModule : NancyModule
{
public YourModule(INewViewLocationResultProvider provider)
{
Get["/yourupdateinterface"] = param =>
{
if(!provider.UseCachedView) return HttpStatusCode.BadRequest;//in case you turn off the hot update
//you can serialize your OneTimeUsedViewLocationResult with Newtonsoft.Json and store those views in any database, like mysql, redis, and load them here
//data mock up
TextReader tr = new StringReader(Resources.TextMain);
var vlr = new OneTimeUsedViewLocationResult("","index","cshtml",()=>tr);
var dir = new Dictionary<string, ViewLocationResult> {{"index",vlr}};
//mock up ends
provider.UpdateCachedView(dir);
return HttpStatusCode.OK;
}
}
}
Note: Those code above doesn't solve the manually map all the Location,Name,Extension for the ViewLocationResult thing menthions in my question, but since I endup build a view editor for my colleges to upload their views, I don't need to solve it anymore.
I have this custom WebViewClient where every time a Page finish loading I want to check the sessionStorage for a value using javascript injection to read the storage.
The problem is that callbackobj Value is always (null). What am I doing wrong ?
public class CustomWebViewClient : WebViewClient
{
public override void OnPageFinished(WebView view, string url)
{
string script = "window.sessionStorage.getItem('someKey');";
var callbackobj = InjectJS(view, script);
base.OnPageFinished(view, url);
}
private JsValue InjectJS(WebView view, string script)
{
var valueCallback = new JsValue();
view.EvaluateJavascript(script, valueCallback);
return valueCallback;
}
public class JsValue : Java.Lang.Object, IValueCallback
{
public string Value { get; set; }
public void OnReceiveValue(Java.Lang.Object value)
{
this.Value = value.ToString() ?? throw new ArgumentNullException(nameof(value));
}
}
}
I have also a custom renderer implemented from here where I attach the new CustomWebClient.
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
var webView = new Android.Webkit.WebView(_context);
webView.SetWebViewClient(new CustomWebViewClient());
webView.Settings.JavaScriptEnabled = true;
webView.Settings.DomStorageEnabled = true;
SetNativeControl(webView);
}
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if (e.NewElement != null)
{
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(Element.Uri);
}
}
EDIT: It doesn't even work with simple script as returning document.body, not only storage accessing. The problem must be more general.
I've played with it and I figured it out.
I don't need jsBridge when using EvaluateJavaScript.
When the script is changed to
string script = "javascript:(function() { return sessionStorage.getItem('someKey'); })()
it works.
Also I changed conversion to be:
public void OnReceiveValue(Java.Lang.Object value)
{
this.Value = ((Java.Lang.String)value).ToString() ?? throw new ArgumentNullException(nameof(value));
}
EDIT : If there is also a need for awaiting the JS to execute which I needed in this scenario this answer did the work https://forums.xamarin.com/discussion/comment/283373/#Comment_283373
We are using HttpSessionStateBase to store messages in a set up similar to this working example:
public class HttpSessionMessageDisplayFetch : IMessageDisplayFetch
{
protected HttpSessionStateBase _session;
private IList<ICoreMessage> messages
{
get
{
if (_session[EchoCoreConstants.MESSAGE_KEY] == null)
_session[EchoCoreConstants.MESSAGE_KEY] = new List<ICoreMessage>();
return _session[EchoCoreConstants.MESSAGE_KEY] as IList<ICoreMessage>;
}
}
public HttpSessionMessageDisplayFetch()
{
if (HttpContext.Current != null)
_session = new HttpSessionStateWrapper(HttpContext.Current.Session);
}
public void AddMessage(ICoreMessage message)
{
if (message != null)
messages.Add(message);
}
public IEnumerable<IResultPresentation> FlushMessagesAsPresentations(IResultFormatter formatter)
{
var mToReturn = messages.Select(m => m.GetPresentation(formatter)).ToList();
messages.Clear();
return mToReturn;
}
}
When we pass in a QualityExplicitlySetMessage (which inherits from ICoreMessage, see below) it is saved correctly to messages.
This is how the object looks after being inserted into the messages list, at the end of AddMessage(ICoreMessage message) above.
But when we come to access it after changing controllers the inherited member's properties are null, which causes a variety of null reference exceptions.
This is how the object now looks after we call FlushMessagesAsPresentations. I've commented out var mToReturn... as this tries to access one of these null ref properties.
I'd like to ask the following:
Why is the HttpSessionStateBase failing to capture these values taken
by the inherited type?
Is this an issue in saving to the HttpSession or in retrieving?
Is this anything to do with, as I suspect, inheritance?
Or is the fact I'm potentially calling a new controller that dependency injects the HttpSessionMessageDisplayFetch causing an issue?
I'm a first-time poster so please let me know if I'm making any kind of faux pas - Super keen to learn! Any input is very welcome.
Some potentially useful code snippets:
QualityExplicitlySetMessage
public class QualityExplicitlySetMessage : QualityChangeMessage
{
public QualityExplicitlySetMessage(IQPossession before, IQPossession after, IQEffect qEffect)
: base(before, after, qEffect)
{
IsSetToExactly = true;
}
}
QualityChangeMessage - Working example
public abstract class QualityChangeMessage : CoreMessage, IQualityChangeMessage
{
protected PossessionChange Change;
public PossessionChange GetPossessionChange()
{
return Change;
}
protected QualityChangeMessage(IQPossession before, IQPossession after, IQEffect qEffect)
{
Change = new PossessionChange(before, after, qEffect);
StoreQualityInfo(qEffect.AssociatedQuality);
}
public override IResultPresentation GetPresentation(IResultFormatter formatter)
{
return formatter.GetQualityResult(this);
}
#region IQualityChangeMessage implementation
public int LevelBefore
{
get { return Change.Before.Level; }
}
//... And so on with values dependent on the Change property.
}
CoreMessage - Working example
public abstract class CoreMessage : ICoreMessage
{
public string MessageType
{
get { return GetType().ToString(); }
}
public string ImageTooltip
{
get { return _imagetooltip; }
set { _imagetooltip = value; }
}
public string Image
{
get { return _image; }
set { _image = value; }
}
public int? RelevantQualityId { get; set; }
protected void StoreQualityInfo(Quality q)
{
PyramidNumberIncreaseLimit = q.PyramidNumberIncreaseLimit;
RelevantQualityId = q.Id;
RelevantQualityName = q.Name;
ImageTooltip = "<strong>" + q.Name + "</strong><br/>" + q.Description + "<br>" +
q.EnhancementsDescription;
Image = q.Image;
}
public virtual IResultPresentation GetPresentation(IResultFormatter formatter)
{
return formatter.GetResult(this);
}
}
UserController - Working example.
public partial class UserController : Controller
{
private readonly IMessageDisplayFetch _messageDisplayFetch;
public UserController(IMessageDisplayFetch messageDisplayFetch)
{
_messageDisplayFetch = messageDisplayFetch;
}
public virtual ActionResult MessagesForStoryletWindow()
{
var activeChar = _us.CurrentCharacter();
IEnumerable<IResultPresentation> messages;
messages = _messageDisplayFetch.FlushMessagesAsPresentations(_storyFormatter);
var vd = new MessagesViewData(messages)
{
Character = new CharacterViewData(activeChar),
};
return View(Views.Messages, vd);
}
}
I am using RavenDB in my mvc4 project when store an object of class to RavenDB it works fine but when performing select operation it throws an error:
Object reference not set to an instance of an object
on all queries
RavenbaseController.cs
public class RavenBaseController : Controller
{
public IDocumentSession RavenSession { get; protected set; }
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
RavenSession = MvcApplication.Store.OpenSession("ravendbtesting");
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.IsChildAction)
return;
using (RavenSession)
{
if (filterContext.Exception != null)
return;
if (RavenSession != null)
RavenSession.SaveChanges();
}
}
}
Activation.cs
public class Activation : RavenBaseController
{
public string tokenid { get; set; }
public bool validate(string tid)
{
var query = from u in RavenSession.Query<Register>() where u.TokenId == tid select u;
foreach (var v in query)
{
v.IsApproved = true;
}
RavenSession.SaveChanges();
return true;
}
}
Queries tried:
var results = from u in RavenSession.Query<Register>()
where u.TokenId == tid
select u;
var query= RavenSession.Query<Register>()
.Where(x => x.TokenId == tid)
.ToList();
I can't understand why it is not working i am new to RavenDB
Update
All the queries work fine if run from controller but if run from class file, the error "Object reference not set to an instance of an object" occurs
If you try to instanciate var ctrl = new Activation(); and do ctrl.validate(x); it won't work as OnActionExecuting didn't run. That function is called automatically by the MVC framework when serving a request, not when manually testing.
Since MVC instantiates a new controller on each requests anyway, you should move the RavenSession initialization to the constructor since you do not seem to use any info in the request context:
public class RavenBaseController : Controller
{
public IDocumentSession RavenSession { get; protected set; }
public RavenBaseController()
{
RavenSession = MvcApplication.Store.OpenSession("ravendbtesting");
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.IsChildAction)
return;
using (RavenSession)
{
if (filterContext.Exception != null)
return;
if (RavenSession != null)
RavenSession.SaveChanges();
}
}
}
The even better way is to use Dependency Injection to pass the session as a constructor parameter, but the above should work for you.
Our MvcSitemap has some DynamicNodeProviders implemented.
We want these to be unique per session. But it appears they are unique per user.
So if a user logs into two different browsers, or computers, they currently share the same sitemap.
We do not want this.
But I can't seem to figure out how to get it to use the User/Session combination for uniqueness.
Is there a way to make this work?
Option 1:
Implement your own ICacheProvider based on session state and inject it using DI.
using System;
using System.Collections.Generic;
using MvcSiteMapProvider.Web.Mvc;
using MvcSiteMapProvider.Caching;
using System.Web;
public class SessionStateCacheProvider<T>
: ICacheProvider<T>
{
public SessionStateCacheProvider(
IMvcContextFactory mvcContextFactory
)
{
if (mvcContextFactory == null)
throw new ArgumentNullException("mvcContextFactory");
this.mvcContextFactory = mvcContextFactory;
}
private readonly IMvcContextFactory mvcContextFactory;
protected HttpContextBase Context
{
get
{
return this.mvcContextFactory.CreateHttpContext();
}
}
#region ICacheProvider<ISiteMap> Members
public bool Contains(string key)
{
return (Context.Session[key] != null);
}
public Caching.LazyLock Get(string key)
{
return (LazyLock)Context.Session[key];
}
public bool TryGetValue(string key, out Caching.LazyLock value)
{
value = this.Get(key);
if (value != null)
{
return true;
}
return false;
}
public void Add(string key, LazyLock item, ICacheDetails cacheDetails)
{
// NOTE: cacheDetails is normally used to set the timeout - you might
// need to roll your own method for doing that.
Context.Session[key] = item;
}
public void Remove(string key)
{
Context.Session.Remove(key);
}
public event EventHandler<MicroCacheItemRemovedEventArgs<T>> ItemRemoved;
#endregion
// NOTE: Normally this is called by a callback from the cache when an item exprires.
// It is required to ensure there is no memory leak because a sitemap has circular references
// that need to be broken explicitly. You need to work out how to call this when the user's session
// expires.
protected virtual void OnCacheItemRemoved(MicroCacheItemRemovedEventArgs<T> e)
{
if (this.ItemRemoved != null)
{
ItemRemoved(this, e);
}
}
}
Then inject it like this (StructureMap example shown):
// Setup cache
SmartInstance<CacheDetails> cacheDetails;
this.For<ICacheProvider<ISiteMap>>().Use<SessionStateCacheProvider<ISiteMap>>();
var cacheDependency =
this.For<ICacheDependency>().Use<NullCacheDependency>();
cacheDetails =
this.For<ICacheDetails>().Use<CacheDetails>()
.Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
.Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
.Ctor<ICacheDependency>().Is(cacheDependency);
Option 2:
Append the user name to the siteMapCacheKey in a custom ISiteMapCacheKeyGenerator, and inject it via DI:
public class SessionBasedSiteMapCacheKeyGenerator
: ISiteMapCacheKeyGenerator
{
public UserBasedSiteMapCacheKeyGenerator(
IMvcContextFactory mvcContextFactory
)
{
if (mvcContextFactory == null)
throw new ArgumentNullException("mvcContextFactory");
this.mvcContextFactory = mvcContextFactory;
}
protected readonly IMvcContextFactory mvcContextFactory;
#region ISiteMapCacheKeyGenerator Members
public virtual string GenerateKey()
{
var context = mvcContextFactory.CreateHttpContext();
var builder = new StringBuilder();
builder.Append("sitemap://");
builder.Append(context.Request.Url.DnsSafeHost);
builder.Append("/?sessionId=");
builder.Append(context.Session.SessionID);
return builder.ToString();
}
#endregion
}
Inject it like this (StructureMap example):
this.For<ISiteMapCacheKeyGenerator>().Use<SessionBasedSiteMapCacheKeyGenerator>();
Note that using an external DI container is required.
Please see my open question here and explain to me why you would want to do this on GitHub, as it renders most of the features useless: https://github.com/maartenba/MvcSiteMapProvider/issues/16#issuecomment-22229604