I am working on Xamarin Android Application.I am using MvvmCross pattern for ViewModels.
Now,I want to pass data from one ViewModel to another viewmodel but don't want to navigate to that ViewModel. Instead of navigating to that ViewModel I want to navigate to another ViewModel.
e.g: I have three ViewModels V1,V2 and V3.Now I want to pass data from V1 to V2 but want to navigate to V3.
Is that possible ?
Look into the MvvmCross Messenger to do this: https://github.com/MvvmCross/MvvmCross-Plugins/tree/master/Messenger
You need to subscribe for something on your viewmodel:
public class LocationViewModel
: MvxViewModel
{
private readonly MvxSubscriptionToken _token;
public LocationViewModel(IMvxMessenger messenger)
{
_token = messenger.Subscribe<LocationMessage>(OnLocationMessage);
}
private void OnLocationMessage(LocationMessage locationMessage)
{
Lat = locationMessage.Lat;
Lng = locationMessage.Lng;
}
// remainder of ViewModel
}
An alternative to the mentioned messenger plugin I would suggest to save the data you need to share in a "service", being the service a singleton class managed by MvvmCross:
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
In your view models you can use that singleton just by adding it to the constructor:
public WhateverViewModel(IService service)
The service will be singleton so your data will persists over the application live cycle.
So, in one of your view models you could do:
service.SharedData = new SharedData();
In another view model:
this.data = service.SharedData
The easiest way to do this is using the Messenger Plugin from MvvmCross.You can subscribe a certain kind of message in V2 and publish that message in V1 and then navigate to V3 in a seperate step.
// subscribing to a certain message type
this.logoutToken = this.messenger.Subscribe<LogoutMessage>(this.HandleLogoutMessage);
// Creating and sending a message
var logoutMessage = new LogoutMessage(this, "You have been logged out.");
this.messenger.Publish(logoutMessage);
Note: It is important to assign the MessageToken to a member variable (like in the other answer), because otherwise it will get cleaned up by the garbage collector.
Related
Scenario: I have view, view model and model for PickList and PickLine. The PickListViewModel contains an ObservableCollection<PickLineViewModel> and the PickList model contains a List<PickLine>. My PickList page contains a ListView which is bound to the ObservableCollection<PickLineViewModel> and if a line is tapped NavigateAsync is called to navigate to the tapped PickLine.
Normally, when I call NavigateAsync Prism navigates to the page, locates the view model, creates an instance of it and binds this instance to the view. But in this case the view model instance that should be bound to the page is already existant (as an element of my ObservableCollection) and I don't want the Prism ViewModelLocator to create a new instance, due to the fact that it then would have to get data from web service and I try to keep the number of web service calls as low as possible.
Also I can't use models in the ObservableCollection because the view model contains properties which are only used for UI purposes so these properties should definitely not be part of the model, but the UI properties I'm talking about are needed in the PickList page and in the PickLine page.
Tl;dr: Is there any way in Prism.Forms to provide a view model instance
on navigating to a page that will than be bound to it?
Your problem is that you are confusing what a Model is and what a ViewModel is. In this case what you should have is:
PickLine (Model)
PickLineView (View)
PickLineViewModel (ViewModel)
PickLineListView (View)
PickLineListViewModel (ViewModel)
Containing your ObservableCollection of PickLine not PickLineViewModel
Without seeing your precise code, I'm going to take a guess from experience here that your code probably looks something like the following in principal:
public ObservableCollection<PickLineViewModel> Items { get; set; }
public DelegateCommand<PickLineViewModel> ItemTappedCommand { get; }
public void OnNavigatedTo(INavigationAware parameters)
{
var picks = _dataService.GetPickLines();
Items = new ObservableCollection<PickLineViewModel>(
picks.Select(x => new PickLineViewModel
{
Model = x,
ItemTappedCommand = ItemTappedCommand
});
}
I see code like the above a lot from people who confuse the difference between a Model and ViewModel, and often from people who don't understand how to properly bind back to the ViewModel from something like a Cell in a ListView.
What you should have instead is code that is more like:
public ObservableCollection<PickLine> Items { get; set; }
public DelegateCommand<PickLine> ItemTappedCommand { get; }
public void OnNavigatedTo(INavigationAware parameters)
{
var picks = _dataService.GetPickLines();
Items = new ObservableCollection<PickLine>(picks);
}
Is there any way in Prism.Forms to provide a view model instance on navigating to a page that will than be bound to it?
No, as far as I know, Prism Forms does not provide view model-first navigation (as opposed to Prism on WPF which does).
You should be able to work around this, though, by passing your existing view model as parameter and making the ViewModelLocator-created view model an "empty shell" that redirects everything to the actual view model.
I have 2 questions.
First=> How can i pass value between 2 ViewModels ?
For example, I m adding data and showing it into the MainPAGE, and simultaneously I want to show the same data ( Observable Collection ) in the ChildPAGE too. Inside the ChildPAGE Xaml I assigned the BindingContext to ViewModel and I assigned listview’s data source to that Observable Collection .But I couldn’t make it work . I tried some examples, but I couldn’t manage it to work. If the data load in the ChildPAGE’s constructor then it works else doesn’t work. I thought , I would improve the performance with using one ObservableCollection, but I think, the mechanism in MVVM is different.
So how can I use one ObservableCollection in 2 pages.
Second => How can I pass data between ViewModels without using the constructor.
Example: I Have 2 Pages ( MainPage and ChidPage) and 2 ViewModels ( MainVM and ChildVM ) .
Situation => If I would pass data from MainPage to ChildPage , I would send data within the constructor .But I want to get data from Childpage to MainPage . So PopAsync doesn’t have a constructor. I also tried EventHandler but it doesn’t work.
Is the only solution is Messaging center? Or what do you advice for better performance? Also does the MessagingCenter reduces the performance because of high usage of RAM?
NOTE: ( I want to learn the mvvm architecture, so I don’t want to use other MVVM Frameworks. I want to get the idea of MVVM and C# comprehensively.)
Thanks in advance
You could create a model class that both view models depend on. The model itself would hold the observable collection, while the view models reference the observable collection. This is a good way to share data across various view models throughout the life of your app. Usually you will want to make the model a singleton to ensure that it really is the same data.
If you don't want to use messaging center, you can use an MVVM framework which comes with other benefits. I use Prism to simplify navigation and you can pass navigation parameters along.
Finally, and this is not the best option usually, you may maintain data in the App object. You can set values in the Properties collection. This is not advisable for complex objects.
EDIT
First you would have some data transfer object which the ObservableCollection would contain, unless you're just holding integers or something.
public class MyDTO
{
//fields for Data Transfer Object
}
DTO's are often among your models, but sometimes you need a composite class to hold DTO's and collections of DTO's
Here is a simple Model that would contain your ObservableCollection
public class MyCollectionModel
{
#region Singleton Pattern
private MyCollectionModel()
{
}
public static MyCollectionModel Instance { get; } = new MyCollectionModel();
#endregion
private ObservableCollection<MyDTO> _dtos;
public ObservableCollection<MyDTO> MyObservableCollection
{
get { return _dtos; }
set { _dtos = value; }
}
}
Note that this implements the Singleton pattern. It could even implment INotifyPropertyChanged as well.
Next your view models, imagine a MyVM1 and MyVM2, would reference the ObservableCollection in your Model with something like this.
public class MyVM1 : INotifyPropertyChanged // Do the same with MyVM2, which would be the binding context for view 2
{
private MyCollectionModel _model;
public MyVM1 ()
{
_model = MyCollectionModel.Instance;
}
private ObservableCollection<MyDTO> myVar;
public ObservableCollection<MyDTO> MyProperty //Bind to this in your View1
{
get
{
return _model.MyObservableCollection;
}
set
{
myVar = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I'm leaving quite a few things out here, and don't really have time to build an entire project just to test. I hope this helps.
You normally use await navService.PushAsync(new MyPage2(dataToShare)); to pass data between pages/VMs. This involves using constructors. Here is a simple example of it: https://stackoverflow.com/a/47873920/1508398
Since you don't want to use constructors, you may load the data on your child page from your service. Alternatively, you may cache the data on the local storage (client-side) and simply load it from there in your child/main page.
Following code converts the ViewModel query to model and then converts the returned result back to ViewModel as PageResult. All this works fine but when I try to use include as part of my default query(or even with the latest version as part of querycontext) then OData formatter plays funny and doesn't include child elements. I have debugged and confirmed that it actually contains child elements. This only happens for controllers that I extended from ODataController(so basically for the ones that are extended from ApiController all works fine but i need results in OData format).
Please note that I have also tried with the latest nightly build(Microsoft.Data.OData 5.5.0.0) and still it doesn't work for me.
Any help would highly be appreciated.
public class ProductsController : ODataController
{
APPContext context = new APPContext();
public PageResult<ProductViewModel> Get(ODataQueryOptions QueryOptions)
{
EdmModel model = new EdmModel();
ODataQueryContext queryContext = new ODataQueryContext(model.GetEdmModel(), typeof(Product));
var mappedQuery = new ODataQueryOptions(queryContext, QueryOptions.Request);
var results = new List<ProductViewModel>();
foreach (var result in mappedQuery.ApplyTo(this.context.Serials.Include("Status").Include("Category")))
{
AutoMapper.Mapper.CreateMap(result.GetType(), typeof(ProductViewModel));
results.Add(AutoMapper.Mapper.Map<ProductViewModel>(result));
}
PageResult<ProductViewModel> pr = new PageResult<ProductViewModel>(results.AsEnumerable<ProductViewModel>(), mappedQuery.Request.GetNextPageLink(), mappedQuery.Request.GetInlineCount());
return pr;
}
}
In OData related entities are represented as navigation links. So, if you have a customers feed, the related orders for each customer will not be part of the customers feed. Instead, they would be represented as navigation links. You can explicitly tell the OData service to expand the related entities using the $expand query option. So, if you want the related orders for each customer to be expanded, you should ask for the url ~/Customers?$expand=Orders.
I'm building an MVC3 application using the Entity Framework. In the application my controllers talk to a service layer. One of the controllers is a TournamentController which uses the TournamentService. This service has some standard methods such as CreateTournament, UpdateTournament etc.
When I want to insert a new tournament I want the view to have a dropdownlist of possible sports for which the tournament can be organised. So in the TournamentController's Create method I fill the ViewBag.Sports with a list of possible sports. Now to obtain this list of sports I use _tournamentService.GetAllSports(). In this GetAllSports() method of the TournamentService I want to create an instance of the SportService so I can 'forward' the question to the right service.
All services use dependency injection in the constructor to inject their own repository, like so:
private ITournamentRepository _repo;
public TournamentService(ITournamentRepository repo) {
_repo = repo;
}
My GetAllSports() method in the TournamentService looks like this:
public IEnumerable<Sport> GetAllSports() {
ISportService sportService = new SportService();
return sportService.GetSports();
}
The problem is that by calling the new SportService() it expects me to hand it an ISportRepository like in the TournamentService, where ninject creates the TournamentRepository. Now I could do the following:
public IEnumerable<Sport> GetAllSports() {
ISportService sportService = new SportService(new SportRepository());
return sportService.GetSports();
}
But the problem with that is that each repository expects an IContext, which is normally handled by ninject as well. Furthermore, I don't want two separate contexts to be instantiated.
A possible solution I found myself is to do this:
private ITournamentRepository _repo;
private ISportService _sportService;
public TournamentService(ITournamentRepository repo, ISportService sportService) {
_repo = repo;
_sportService = sportService
}
But there's only one method in my TournamentService class that would actually use the _sportService so I figure this is a bit overkill to make it a class attribute.
Keep it simple, inject ISportService to your controller and call sportService.GetSports() directly from the controller!
Your last solution valid. Inject the necessary service as part of the constructor.
And if you're worried about multiple contexts, don't let two separate contexts be created then.
In your Ninject binding:
Bind<ITournamentRepository>().To<TournamentRepository>().InRequestScope();
See https://github.com/ninject/Ninject.Web.Common/wiki/InRequestScope
The last piece is the important part. It will only create one instance of TournamentRepository for the current request. Any other requesters of TournamentRepository will get this instance.
If you're already doing this, you're set, otherwise, just add InRequestScope and you're done. (keeping in mind you'll need a reference to Ninject.Web.Common)
Hope that helps.
EDIT:
Remo is correct, I wouldn't call this from a service either. Just call for your lookup data from the controller and populate your view model. The InRequestScope advice still holds.
I understood how to communicate between Web, Worker role and the flow in MVC architecture.
My question is, after I query the data from a table in web role, how can the controller in MVC get this data to diplay in the view?
I tried using a global static variable in webrole, where the data gets populated, but when I access the static variable from the controller, it only returned 'null'. Why am I getting a null?
Thanks.
ok, in case you use the storage client, the implementation would be like:
Create your Model:
public class MyEntity : Microsoft.WindowsAzure.StorageClient.TableServiceEntity
{
public MyEntity()
{
PartitionKey = DateTime.UtcNow.ToString("MMddyyyy");
RowKey = string.Format("{0:10}_{1}",
DateTime.MaxValue.Ticks - DateTime.Now.Ticks, Guid.NewGuid());
}
// Define the properties.
public string Title { get; set; }
public string Name { get; set; }
}
}
2. Define your context class:
public class MyDataContext : TableServiceContext
{
public MyDataContext(string baseAddress,
StorageCredentials credentials)
: base(baseAddress, credentials)
{ }
public IQueryable GetMyEntity
{
get
{
return this.CreateQuery("MyTableName");
}
}
}
Implement your controller action method:
public ActionResult Index()
{
var context = new MyDataContext(storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
var results = from g in context.GetMyEntity
where g.PartitionKey ==
DateTime.UtcNow.ToString("MMddyyyy")
select g;
return View(results.FirstOrDefault());
}
this is reference code only, which is very ugly and will hardly work as it is, but it still provides an example of how you can query the table storage in your MVC project.
are we talking about an application whose MVC part is hosted in a worker role and which gets data from a web role which is querying the table storage? Or are we talking about a ASP.NET MVC application here which is hosted in a web role?
static variables is not a good idea at all because of concurrency issues.
in case of scenario 1, how do you communicate with a web role? via web service call directly?
you cold simply call the service from your controller or delegate the call to another layer and then put this data in your model which is then displayed by the corresponding view.
have you tried debugging this application locally using the [azure local dev env][1]
[1]: http://blogs.msdn.com/b/morebits/archive/2010/12/01/using-windows-azure-development-environment-essentials.aspx ? or do you use the real azure infrastructure? Are you sure you are getting the data from your query? maybe the query is wrong? have you observed any exceptions?
we need more information here to be able to help you