how to perform a runtime view update when using ResourceViewLocationProvider - c#

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.

Related

HttpSessionStateBase losing property values of inherited type

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);
}
}

How do I alter the RenderingContext in a pipeline?

I need a pipeline to intercept the building of the sitecore RenderingContext, specifically I need to alter the RenderingContext.Current.Rendering.DataSource attribute on the fly.
I need to do this because I've added a variable into my Datasource in sitecore. I was manipulating this in the controller but when I turn on the experience editor it falls over before it even hit's my controller. I'm guessing somehting higher up needs the datasource to be valid.
After a bit of digging about I discovered this pipeline:
namespace Sitecore.Mvc.Pipelines.Response.RenderRendering
{
public class EnterRenderingContext : RenderRenderingProcessor
{
public override void Process(RenderRenderingArgs args)
{
Assert.ArgumentNotNull(args, "args");
if (args.Rendered)
{
return;
}
this.EnterContext(args.Rendering, args);
}
protected virtual void EnterContext(Rendering rendering, RenderRenderingArgs args)
{
IDisposable item = RenderingContext.EnterContext(rendering);
args.Disposables.Add(item);
}
}
}
Reflected out of Sitecore.Mvc.dll
I can now replace this pipeline with my own and change the values of the RenderingContext before they're built:
public class RedrowEnterRenderingContext : Sitecore.Mvc.Pipelines.Response.RenderRendering.EnterRenderingContext
{
private const string _developmentKeyword = "$development";
private IDevelopmentQueryServiceV2 _coUkDevelopmentQueryService = ServiceLocator.Current.GetInstance<IDevelopmentQueryServiceV2>();
protected override void EnterContext(Rendering rendering, RenderRenderingArgs args)
{
//Make your changes to the items that are used to build the context here
if (args.PageContext != null &&
args.PageContext.Item != null &&
args.Rendering.DataSource.Contains(_developmentKeyword) &&
args.PageContext.Item.TemplateID.Guid == TemplateIdConst.V2Development)
{
args.Rendering.DataSource = args.Rendering.DataSource.Replace(_developmentKeyword,
args.PageContext.Item.Paths.Path);
}
//build the context using the existing functionality
base.EnterContext(rendering, args);
}
}
I'm manipulating the datasource in a specific scenario but this code could be adapted to do many jobs.
you register this thus:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<pipelines>
<mvc.renderRendering>
<processor type="Namespace.MyEnterRenderingContext, DLLName"
patch:instead="*[#type='Sitecore.Mvc.Pipelines.Response.RenderRendering.EnterRenderingContext, Sitecore.Mvc']"/>
</mvc.renderRendering>
</pipelines>
</sitecore>
</configuration>
One issue with this is that it shows up in the BrokenLinkValidator. You can override this though and create your own:
[Serializable]
public class MyBrokenLinksValidator : BrokenLinkValidator
{
public RedrowBrokenLinksValidator() : base()
{
}
public RedrowBrokenLinksValidator(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context)
{
}
protected override ValidatorResult Evaluate()
{
ValidatorResult returnVal = base.Evaluate();
if (returnVal != ValidatorResult.Valid)
{
Item obj = base.GetItem();
ItemLink[] brokenLinks = obj.Links.GetBrokenLinks(false);
//are all the broken links basically because they are contextual?
if (brokenLinks.All(a => a.TargetPath.Contains("$development")))
{
foreach (ItemLink brokenLink in brokenLinks)
{
Database database = Sitecore.Configuration.Factory.GetDatabase("master");
//try again but replacing the varible with a context
var secondTryPath = brokenLink.TargetPath.Replace(
"$development", obj.Paths.Path);
Item secondTryItem = database.GetItem(secondTryPath);
if (secondTryItem == null)
return returnVal;
}
//if we've got here then all the links are valid when adding the context
return ValidatorResult.Valid;
}
}
return returnVal;
}
}

Sharing session data between frames and support load/restore state

Let's say that I have a list in my main frame in a windows store app with session data. When I click an item a new frame is open where I could edit the data.
How do I properly share session data between the frames, and how do I save and restore session data so that the references between the objects are intact?
I know that I could send an object as parameter when a new frame is created. I also know how to save/restore session data. I just don't how to solve this :).
You can use NavigationService to send data to new frames as mentioned in the option below. it is more like sending keyvalur pairs as uri parameters:
NavigationService.Navigate(new Uri("/Page1.xaml?parameter1=p1&parameter2=p2", UriKind.Relative));
To get value:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
string parameterValue = NavigationContext.QueryString["parameter"];
}
http://www.geekchamp.com/tips/how-to-pass-data-between-pages-in-windows-phone-alternatives
Also if you want to share complex objects you might need to create nested viewmodels but if you don't have luxury to nest viewmodels due to nature of views then you might need to create a static app cache to use a medium to persist/share object among frames.
Storing global references to objects isn't all that tricky. You can just have a static holder that does or doesn't have the responsibility to persist them. Oh, something like this:
public interface IViewModel { }
public class ViewModelOne : IViewModel { }
public class ViewModelTwo : IViewModel { }
public class ViewModelThree : IViewModel { }
public static class GlobalObjects
{
private static ViewModelOne viewModelOne = null;
public static ViewModelOne ViewModelOne
{
get { return Get<ViewModelOne>(ref viewModelOne); }
set { Set(ref viewModelOne, value); }
}
private static T Get<T>(ref T storage) where T : IViewModel, new()
{
if (storage != null)
return storage;
try
{
var json = Load(typeof(T).ToString());
return storage = Deserialize<T>(json);
}
catch (Exception)
{
return new T();
}
}
private static void Set<T>(ref T storage, T value) where T : IViewModel
{
if (storage?.Equals(value))
return;
try
{
var json = Serialize(value);
Save(json, typeof(T).ToString());
}
catch (Exception)
{
Save(string.Empty, typeof(T).ToString());
}
}
private static void Save(string value, string key)
{
throw new NotImplementedException();
}
private static string Serialize(object obj)
{
throw new NotImplementedException();
}
private static string Load(string key)
{
throw new NotImplementedException();
}
private static T Deserialize<T>(string obj)
{
throw new NotImplementedException();
}
}
Best of luck!
// Jerry
After some inspiration from Shoaib Shaikh I decided to do a global repository. Please review this cause I barely know what I’m doing :-).
I have three classes. All classes use DataContract so it’s easy to serialize. First PersonViewModel which is pretty straight forward:
[DataContract()]
public class PersonViewModel : BaseViewModel
{
public PersonViewModel(string name)
{
Name = name;
}
#region Name property
[DataMember()]
private string _Name;
public string Name
{
get
{
return _Name;
}
set
{
SetPropertyValue(ref _Name, value, () => Name);
}
}
#endregion
}
Second is the PersonListViewModel . Each PersonViewModel is stored in a global hash table with a string id. This class taking care of the ID:s. By calling RefreshPersonCollection the person list is rebuild from the global objects. Quite clumsy, it’s better to get the persons on request but I’m too tired to solve that right now :-).
[DataContract()]
public class PersonListViewModel : BaseViewModel
{
[DataMember()]
private List<string> PersonIds = new List<string>();
public PersonListViewModel()
{
Persons = new ObservableCollection<PersonViewModel>();
CreateDefaultData();
}
public void CreateDefaultData()
{
for(int i=0; i<3; i++)
{
string personid = Guid.NewGuid().ToString();
string personname = "Person " + personid;
PersonViewModel person = new PersonViewModel(personname);
PersonIds.Add(personid);
Persons.Add(person);
SharedObjects.Instance.Objects[personid] = person;
}
}
public void RefreshPersonCollection()
{
Persons = new ObservableCollection<PersonViewModel>();
foreach (string personid in PersonIds)
{
Persons.Add((PersonViewModel)SharedObjects.Instance.Objects[personid]);
}
}
public ObservableCollection<PersonViewModel> Persons{ get; set; }
}
Third class is my global repository. Also quite straight forward I think. All PersonViewModels and all PersonListViewModel is stored in this repository.
[DataContract()]
public class SharedObjects
{
public static SharedObjects Instance;
public SharedObjects()
{
Objects = new Dictionary<string, object>();
}
public void Init()
{
Objects["mainviewmodel"] = new PersonListViewModel();
}
[DataMember()]
private Dictionary<string, Object> _Objects;
public Dictionary<string, Object> Objects
{
get { return _Objects; }
set { _Objects = value; }
}
}
In my Windows store application I have a SuspensionManager that I have modified it slightly to serialize and deserialize my global repository.
First change was to add my new types to known types to be serialized. I don’t like this, would prefer that the classes could do this themselves somehow (which is possible as far as I understand it).
static SuspensionManager()
{
_knownTypes.Add(typeof(SharedObjects));
_knownTypes.Add(typeof(PersonListViewModel));
_knownTypes.Add(typeof(PersonViewModel));
}
Second change is in SaveAsync to make sure the global data is saved. Just one line was added:
//I added this:
_sessionState["globalobjects"] = SharedObjects.Instance;
// Serialize the session state synchronously to avoid asynchronous access to shared
// state
MemoryStream sessionData = new MemoryStream();
DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes);
serializer.WriteObject(sessionData, _sessionState);
Third change is in RestoreAsync.
// Get the input stream for the SessionState file
StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(sessionStateFilename);
using (IInputStream inStream = await file.OpenSequentialReadAsync())
{
// Deserialize the Session State
DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes);
_sessionState = (Dictionary<string, object>)serializer.ReadObject(inStream.AsStreamForRead());
}
//I added this:
if (_sessionState.ContainsKey("globalobjects"))
SharedObjects.Instance = (SharedObjects) _sessionState["globalobjects"];
This feels manageable but any suggestions for improvements are appreciated :-). Will this work in Windows Phone as well (except for SuspensionManager, but I guess it's something similar on that platform)?

Modify existing WCF communication object

This is how I used to make method calls:
SvcHelper.Using<SomeWebServiceClient>(proxy =>
{
proxy.SomeMethod();
}
public class SvcHelper
{
public static void Using<TClient>(Action<TClient> action) where TClient : ICommunicationObject, IDisposable, new()
{
}
}
This is how I make method calls:
ChannelFactory<ISomethingWebService> cnFactory = new ChannelFactory<ISomethingWebService>("SomethingWebService");
ISomethingWebService client = cnFactory.CreateChannel();
using (new OperationContextScope((IContextChannel)client))
{
client.SomeMethod();
}
My question is: Instead of replacing every instance of my original method call approach; Is there a way to modify my SvcHelper and do the creation of the channel in the SvcHelper constructor and then simply pass the interface like the following:
SvcHelper.Using<ISomethingWebService>(client =>
{
client.SomeMethod();
}
Hope this makes sense and thanks in advance.
First, you don't want to create a new ChannelFactory<T> every call to the Using helper method. They are the most costly thing to construct in the WCF universe. So, at bare minimum, you will want to use a caching approach there.
Second, you don't want to tie yourself to "client" types at all anymore. Just work straight with the service contract interfaces.
Starting from what you've got, here's where I'd go based on how I've done this in the past:
public class SvcHelper
{
private static ConcurrentDictionary<ChannelFactoryCacheKey, ChannelFactory> ChannelFactories = new ConcurrentDictionary<ChannelFactoryCacheKey, ChannelFactory>();
public static void Using<TServiceContract>(Action<TServiceContract> action) where TServiceContract : class
{
SvcHelper.Using<TServiceContract>(action, "*");
}
public static void Using<TServiceContract>(Action<TServiceContract> action, string endpointConfigurationName) where TServiceContract : class
{
ChannelFactoryCacheKey cacheKey = new ChannelFactoryCacheKey(typeof(TServiceContract), endpointConfigurationName);
ChannelFactory<TServiceContract> channelFactory = (ChannelFactory<TServiceContract>)SvcHelper.ChannelFactories.GetOrAdd(
cacheKey,
missingCacheKey => new ChannelFactory<TServiceContract>(missingCacheKey.EndpointConfigurationName));
TServiceContract typedChannel = channelFactory.CreateChannel();
IClientChannel clientChannel = (IClientChannel)typedChannel;
try
{
using(new OperationContextScope((IContextChannel)typedChannel))
{
action(typedChannel);
}
}
finally
{
try
{
clientChannel.Close();
}
catch
{
clientChannel.Abort();
}
}
}
private sealed class ChannelFactoryCacheKey : IEquatable<ChannelFactoryCacheKey>
{
public ChannelFactoryCacheKey(Type channelType, string endpointConfigurationName)
{
this.channelType = channelType;
this.endpointConfigurationName = endpointConfigurationName;
}
private Type channelType;
public Type ChannelType
{
get
{
return this.channelType;
}
}
private string endpointConfigurationName;
public string EndpointConfigurationName
{
get
{
return this.endpointConfigurationName;
}
}
public bool Equals(ChannelFactoryCacheKey compareTo)
{
return object.ReferenceEquals(this, compareTo)
||
(compareTo != null
&&
this.channelType == compareTo.channelType
&&
this.endpointConfigurationName == compareTo.endpointConfigurationName);
}
public override bool Equals(object compareTo)
{
return this.Equals(compareTo as ChannelFactoryCacheKey);
}
public override int GetHashCode()
{
return this.channelType.GetHashCode() ^ this.endpointConfigurationName.GetHashCode();
}
}
}
This should work:
public class SvcHelper
{
public static void Using<TClient>(Action<TClient> action) where TClient : ICommunicationObject, IDisposable
{
ChannelFactory<TClient> cnFactory = new ChannelFactory<TClient>("SomethingWebService");
TClient client = cnFactory.CreateChannel();
using (new OperationContextScope((IContextChannel)client))
{
action(client);
}
}
}

How to improve the Pass an object when navigating to a new view in PRISM 4

I'm using Prism with IoC. The problem is to pass an object (like collections) through navigation. I was watching this post: How to Pass an object when navigating to a new view in PRISM 4
And this is the solution
I extract the hash code of the object and save it in a Dictionary, with the hash code as the key and the object as the value of the pair.
Then, I attach the hash code to the UriQuery.
After, I only have to get the hash code that comes from the Uri on the target view and use it to request the original object from the Dictionary.
Some example code:
Parameter repository class:
public class Parameters
{
private static Dictionary<int, object> paramList =
new Dictionary<int, object>();
public static void save(int hash, object value)
{
if (!paramList.ContainsKey(hash))
paramList.Add(hash, value);
}
public static object request(int hash)
{
return ((KeyValuePair<int, object>)paramList.
Where(x => x.Key == hash).FirstOrDefault()).Value;
}
}
The caller code:
UriQuery q = null;
Customer customer = new Customer();
q = new UriQuery();
Parameters.save(customer.GetHashCode(), customer);
q.Add("hash", customer.GetHashCode().ToString());
Uri viewUri = new Uri("MyView" + q.ToString(), UriKind.Relative);
regionManager.RequestNavigate(region, viewUri);
The target view code:
public partial class MyView : UserControl, INavigationAware
{
// some hidden code
public void OnNavigatedTo(NavigationContext navigationContext)
{
int hash = int.Parse(navigationContext.Parameters["hash"]);
Customer cust = (Customer)Parameters.request(hash);
}
}
That's it.
I'm not sure if this solution is the best to pass objects. I guess this maybe would be a service. Is a good way to do this or is there a better way to do it?
I posted an easier way. Mentioning it here for reference -
I would use the OnNavigatedTo and OnNavigatedFrom methods to pass on the objects using the NavigationContext.
First derive the viewmodel from INavigationAware interface -
public class MyViewModel : INavigationAware
{ ...
You can then implement OnNavigatedFrom and set the object you want to pass as navigation context as follows -
void INavigationAware.OnNavigatedFrom(NavigationContext navigationContext)
{
SharedData data = new SharedData();
...
navigationContext.NavigationService.Region.Context = data;
}
and when you want to receive the data, add the following piece of code in the second view model -
void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
{
if (navigationContext.NavigationService.Region.Context != null)
{
if (navigationContext.NavigationService.Region.Context is SharedData)
{
SharedData data = (SharedData)navigationContext.NavigationService.Region.Context;
...
}
}
}
I just started using Prism and this is one of the first limitations I ran into. I solved it a different way. I first created a class that inherits from Uri and implements IDictionary (plus some generic methods for easier access)
public class NavigationUri : Uri, IDictionary<Type, object>
{
private IDictionary<Type, object> _internalDictionary = new Dictionary<Type, object>();
public NavigationUri(string uriString) : base(uriString, UriKind.Relative)
{
}
public NavigationUri(string uriString, UriKind uriKind) : base(uriString, uriKind)
{
}
public void Add<T>(T value)
{
Add(typeof(T), value);
}
public void Add(Type key, object value)
{
_internalDictionary.Add(key, value);
}
public bool ContainsKey<T>()
{
return ContainsKey(typeof (T));
}
public bool ContainsKey(Type key)
{
return _internalDictionary.ContainsKey(key);
}
public ICollection<Type> Keys
{
get { return _internalDictionary.Keys; }
}
public bool Remove<T>()
{
return Remove(typeof (T));
}
public bool Remove(Type key)
{
return _internalDictionary.Remove(key);
}
public bool TryGetValue<T>(out object value)
{
return TryGetValue(typeof (T), out value);
}
public bool TryGetValue(Type key, out object value)
{
return _internalDictionary.TryGetValue(key, out value);
}
public ICollection<object> Values
{
get { return _internalDictionary.Values; }
}
public object this[Type key]
{
get { return _internalDictionary[key]; }
set { _internalDictionary[key] = value; }
}
public void Add(KeyValuePair<Type, object> item)
{
_internalDictionary.Add(item);
}
public void Clear()
{
_internalDictionary.Clear();
}
public bool Contains(KeyValuePair<Type, object> item)
{
return _internalDictionary.Contains(item);
}
public void CopyTo(KeyValuePair<Type, object>[] array, int arrayIndex)
{
_internalDictionary.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _internalDictionary.Count; }
}
public bool IsReadOnly
{
get { return _internalDictionary.IsReadOnly; }
}
public bool Remove(KeyValuePair<Type, object> item)
{
return _internalDictionary.Remove(item);
}
public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
{
return _internalDictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _internalDictionary.GetEnumerator();
}
}
Then I created a class that inherits from RegionNavigationContentLoader. On GetContractFromNavigationContext I store the passed in Uri so I can access it in the CreateNewRegionItem method. In that method I check to see if the Uri is the NavigationUri and if so I loop though adding all the dependency injection overrides. I'm using Unity but I assume the code could easy be converted to another IOC container.
public class BaseRegionNavigationContentLoader : RegionNavigationContentLoader
{
private Uri _uri;
private IServiceLocator _serviceLocator;
private IUnityContainer _unityContainer;
public BaseRegionNavigationContentLoader(IServiceLocator serviceLocator, IUnityContainer unityContainer) : base(serviceLocator)
{
_serviceLocator = serviceLocator;
_unityContainer = unityContainer;
}
protected override string GetContractFromNavigationContext(NavigationContext navigationContext)
{
_uri = navigationContext.Uri;
return base.GetContractFromNavigationContext(navigationContext);
}
protected override object CreateNewRegionItem(string candidateTargetContract)
{
object instance;
try
{
var uri = _uri as NavigationUri;
if (uri == null)
{
instance = _serviceLocator.GetInstance<object>(candidateTargetContract);
}
else
{
// Create injection overrides for all the types in the uri
var depoverride = new DependencyOverrides();
foreach (var supplant in uri)
{
depoverride.Add(supplant.Key, supplant.Value);
}
instance = _unityContainer.Resolve<object>(candidateTargetContract, depoverride);
}
}
catch (ActivationException exception)
{
throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture, "CannotCreateNavigationTarget", new object[] { candidateTargetContract }), exception);
}
return instance;
}
}
Now in the prism Bootstrapper you need to register the BaseRegionNavigationContentLoader as IRegionNavigationContentLoader in the ConfigureServiceLocator method. Make sure you mark it as TransientLifetimeManager so it gets newed up each time. The default registration for IRegionNavigationContentLoader is container controlled which makes it act like a singleton but we need a new one each time since we need to pass the uri from one method to the next in a property.
Now I can wright code like the following and still use constructor injection.
var uri = new NavigationUri("MessageBoxView");
uri.Add(messageBoxEventArgs);
regionManager.RequestNavigate(RegionNames.MainRegion, uri);

Categories