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¶meter2=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)?
Related
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 a config service which wraps the baked-in assembly settings, but I'd also like to override these on the command line.
Currently this code is working fine:
public interface ISettings
{
string Url { get; }
}
public class OperationalSettings : ISettings
{
public string Url { get { return ServiceSettings.Default.Url; } }
}
public class CommandLineModel
{
public string Url;
}
public class CommandLineSettings : ISettings
{
private readonly CommandLineModel _model;
public CommandLineSettings(string serialisedSettings)
{
_model = JsonConvert.DeserializeObject<CommandLineModel>(serialisedSettings);
}
public string Url { get { return _model.Url; } }
}
public class ConfigService
{
private readonly ISettings _settings;
public ConfigService(ISettings settings)
{
_settings = settings;
}
public ISettings settings { get { return _settings; } }
}
Then the test driver code:
class Program
{
static void Main(string[] args)
{
ISettings opSettings = new OperationalSettings();
var commandLineTest = "{Url:'http://overridenurl.com'}";
ISettings commandSettings = new CommandLineSettings(commandLineTest);
var configService = new ConfigService(opSettings);
var configServiceUsingCmdOpts = new ConfigService(commandSettings);
}
}
So with this I can override settings using the command line string. However, what I don't like is that if I have a new settings, I now need to add this in 4 places:
The interface
The concrete implementation of the settings wrapper (OperationalSettings)
The command line model for deserialisation
The command line settings implementation that wraps the deserialised model.
This seems to suffer from scalability once I add more properties. Is there a more efficient way to achieve this without so many code changes?
You might take a look at DynamicObject
public class CommandLineModelDictionary : DynamicObject
{
// The inner dictionary.
Dictionary<string, object> dictionary
= new Dictionary<string, object>();
// This property returns the number of elements
// in the inner dictionary.
public int Count
{
get
{
return dictionary.Count;
}
}
// If you try to get a value of a property
// not defined in the class, this method is called.
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
string name = binder.Name.ToLower();
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
// If you try to set a value of a property that is
// not defined in the class, this method is called.
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
dictionary[binder.Name.ToLower()] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
}
Otherwise If you have a simple scenario in which you need an object that can only add and remove members at run time but that does not need to define specific operations and does not have static members, use the ExpandoObject class
here how to use
class Program
{
public static dynamic Dyn { get; set; }
static void Main(string[] args)
{
dynamic model= new CommandLineModelDictionary();
model.Prop1 = "Foo";
model.Prop2 = "toto";
Console.WriteLine(model.Prop1);
Console.WriteLine(model.Prop2);
//otherwise you can use
dynamic dynModel = new ExpandoObject();
dynModelop1 = "Test1";
dynModel2 = "Test2";
Console.WriteLine(dynModel.Prop1);
Console.WriteLine(dynModel.Prop2);
}
}
I am trying to implement a simple caching mechanism in a windows phone 8.1 API that I am creating. I have chosen a Windows Phone Portable Class Library template in visual studio.
Refrence : http://channel9.msdn.com/Series/Windows-Phone-8-1-Development-for-Absolute-Beginners/Part-22-Storing-and-Retrieving-Serialized-Data
The cache class looks something like this,
[DataContract]
class cache
{
private const string JSONFILENAME = "data.json";
[DataMember]
Dictionary<Int32, item> cDictionary;
[DataMember]
int _maxSize;
public int MaxSize
{
get { return _maxSize; }
set { _maxSize = value; }
}
public cache(int maxSize){
cDictionary = new Dictionary<int, item>();
_maxSize = maxSize;
}
public void push(Int32 id, item obj)
{
if (!cDictionary.ContainsKey(id)) {
cDictionary.Add(id, obj);
}
}
internal static async Task<cache> Load()
{
cache obj = null;
try
{
var jsonSerializer = new DataContractJsonSerializer(typeof(cache));
using (var myStream = await ApplicationData.Current.LocalFolder.OpenStreamForReadAsync(JSONFILENAME))
{
obj = (cache)jsonSerializer.ReadObject(myStream);
}
}
catch (FileNotFoundException)
{
obj = null;
}
return obj;
}
internal static async void Save(cache obj)
{
var serializer = new DataContractJsonSerializer(typeof(cache));
using (var stream = await ApplicationData.Current.LocalFolder.OpenStreamForWriteAsync(
JSONFILENAME,
CreationCollisionOption.ReplaceExisting))
{
serializer.WriteObject(stream, obj);
}
}}
The item class whose objects go into the dictionary looks like this,
[DataContract]
class item
{
[DataMember]
string _fName;
public string FName
{
get { return _fName; }
set { _fName = value; }
}
[DataMember]
string _lName;
public string LName
{
get { return _lName; }
set { _lName = value; }
}
[DataMember]
int _id;
public int Id
{
get { return _id; }
set { _id = value; }
}
public item(int id, string fName, string lName)
{
this.Id = id;
this.FName = fName;
this.LName = lName;
}
}
The idea is : The end user creates an instance of the api and calls a method doSomething(). The method first looks in the cache (not shown in the example) if found, returns the Item object back, or else, gets the item object from a web service(not shown) and then push it to cache.
public class api
{
cache tCache;
string apiKey;
public laas(string apiKey)
{
this.apiKey = apiKey;
this.tCache = new cache(100);
}
public async void Initialize(api obj)
{
//If cache exists
obj.tCache = await cache.Load();
if (obj.tCache == null)
{
obj.tCache = new cache(100);
}
}
public void doSomething(string id)
{
tCache.push(id.GetHashCode(),new item(1,"xxxx","xxx"));
cache.Save(tCache);
}
}
I wanted to initialize/load the cache in the constructor of the api class, but since ApplicationData.Current.LocalFolder provide only async methods to read and write data from persistent storage, I created a separate static async class Initiialize() that would load the cache, since making an async constructor makes no sense.
Problem: the statement tCache.push(id.GetHashCode(),new item(1,"xxxx","xxx")); in the doSomething() throws null reference exceptions. This could possibilly be happening because the tCache hasn't been loaded/initialized yet due to the async operation.
I had tried obj.tCache = await cache.Load().Result to wait for the loading to complete, but that hangs my application. (http://msdn.microsoft.com/en-us/magazine/jj991977.aspx)
Could you please point me in the right directions here? Is my diagnonis right? Is there a better way to do it? Any pointer is appreciated.
Thanks!
What is probably happening is that you're calling Initialize but not awaiting it, because it is async void.
What you need to do is change:
public async void Initialize(api obj)
To:
public async Task Initialize(api obj)
Then, you'll need to await Initialize(obj) which will ensure that caches completion before use.
Note that async void is ment only for top level event handlers and shouldn't be used otherwise.
Also, the reason Task.Result hangs your application is because it is causing a deadlock, which is related to the way async marshals your synchronization context between calls.
I am creating a project data pipeline and I need to return different types from a single class
in this class I have a number of Dictionaries that hold and separate the elements/content i want to load up but i need a way to return them with a single string... as i am not to familiar with these Type functions i am lost as to how to return the content properly
I need this in a separate class so i can do a XML serialization later
Here is what I have now
DataClass contents;
public T ReturnType<T>(string asset)
{
if(typeof(T) == typeof(int))
{
return contents.Integers[Asset];
}
if(typeof(T) == typeof(float))
{
return contents.Floats[Asset];
}
if(typeof(T) == typeof(double))
{
return contents.Doubles[Asset];
}
return default(T);
}
it will allow me to use a base Object class to parse the content but i dont want anything to get lost in transit so i am weary in using this method
my question is how to return one of the different objects of a certain types within the class that i am using for serialization with a function like that
If i wanted to use the previous function to grab content within the class eg
public Object someobject;
//button event handler to change the current object
//preferably this would be changed depending on the object i would be calling
//but this should do for showing how it is supposed to work
public void ChangeCurrentObject(event e)
{
someobject = (Object)ReturnType<Object>("23rdObject");
}
it sends a string to the function called 'ReturnType' and returns an object ie(int, float,etc) within there own respective dictionary
The generics in this case will only help you not to write diferent method for every asset type. You can also use this aproach to make it more modular.
static class Assets
{
public interface IAssetHandler<out T>
{
T GetAsset(string name);
}
private static readonly Dictionary<Type,object> _handlers=new Dictionary<Type, object>();
public static T GetAsset<T>(string name)
{
object assetHandler;
if(!_handlers.TryGetValue(typeof(T),out assetHandler))
{
throw new Exception("No handler for that type of asset");
}
return (assetHandler as IAssetHandler<T>).GetAsset(name);
}
public static void RegisterAssetHandler<T>(IAssetHandler<T> handler)
{
_handlers[typeof (T)] = handler;
}
}
public class IntAssetsHandler:Assets.IAssetHandler<int>
{
#region Implementation of IAssetHandler<out int>
public int GetAsset(string name)
{
return 0;
}
#endregion
}
static void Main(string[] args)
{
Assets.RegisterAssetHandler(new IntAssetsHandler());
Console.WriteLine(Assets.GetAsset<int>("test"));
}
You could use external class, set the properties types as you wish, then use it in your function.
public class MultipleOpjects
{
public List<string> ObjectOne { get; set; }
public List<object> ObjectTwo { get; set; }
public object ObjectThree { get; set; }
}
public MultipleOpjects GetAnything()
{
MultipleOpjects Vrble = new MultipleOpjects();
Vrble.ObjectOne = SomeThing1;
Vrble.ObjectTwo = SomeThing2;
Vrble.ObjectThree = SomeThing3;
return Vrble;
}
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);