I have two ViewModels, the first has a list of type ProductViewModel, this list of type ProductViewModel is backed by a list of type Product in my model.
public List<ProductViewModel> Products
{
get
{
return (from product in ProductManager.Products
select new ProductViewModel(product)).ToList();
}
}
My first ViewModel will add products to the ProductManager.Products list and then raise a PropertyChanged notification so that the UI is updated. (So far so good).
A navigation to a second page then occurs, this then accesses the ProductManager.Products, once these products are processed, the ProductManager.Products list is cleared (by the second ViewModel).
Upon navigating back to the first view, how would I then update the List Products binding?
I am using the ViewModelLocator as provided by MVVMLight and therefore do not have static access to the first ViewModel from the second.
My current workaround is to create a BaseView page, override the OnNavigatedTo method, raise an event in this override, which I can then bind a Command to in my first ViewModel so that I can then call RaisePropertyChanged.
public class BaseView : PhoneApplicationPage
{
public event RoutedEventHandler NavigatedTo;
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (NavigatedTo != null)
{
NavigatedTo(this, new RoutedEventArgs());
}
}
}
<i:EventTrigger EventName="NavigatedTo">
<cmdextras:EventToCommand Command="{Binding Path=PerformNavigatedTo}" />
</i:EventTrigger>
public ICommand PerformNavigatedTo
{
get
{
return new RelayCommand(
() =>
{
RaisePropertyChanged(() => Products);
RaisePropertyChanged(() => SecondaryPageName);
},
() => true);
}
}
In addition to my workaround above, I previously investigated implementing messaging between ViewModels. Unfortunately this wasn't working for me as I had forgotten to include the Token when calling the Register method. As soon as I sorted this out I was able to send a message from the second ViewModel to be received by the first and therefore call the RaisePropertyChanged method as required.
Also, as soon as I realised that SimpleIoC returned the same instance after multiple calls to GetInstance, this helped in my understanding.
Related
Three weeks ago I was trying to find a way to send message (or notification) to admin after any user make create or update, but ended up with nothing. I searched a lot and I did not find a clear solution, I am trying to understand Yii2 events, I found this link
http://www.yiiframework.com/wiki/329/real-time-display-of-server-push-data-using-server-sent-events-sse/
I think it is the key to solve my problem, but I am really stuck I don't know what to do, hope anyone can help me.
thanks
Consider using a behavior to handle this.
Assumptions
You have at least one model (possibly multiple) within your project.
You have a controller that contains at least two actions: actionCreate and actionUpdate.
An email is sent to an administrator whenever either of the aforementioned actions are called.
Events and Behaviours
When actionCreate is called a new record is inserted into the database through an instance of a model class that extends ActiveRecord. Similarly, when actionUpdate is called an existing record is fetched from the database, updated and saved back. An event (i.e: insert or update) is fired by the model (since model extends component and components are responsible for implementing events) on both of these occasions. Yii2 provides the ability to respond to these events using behaviours which "customize the normal code execution of the component”.
In short, this means you can bind custom code to any given event such that your code executes when the event is fired.
Proposed Solution
Now that we know a little something about events and behaviours, we could create a behaviour that executes some custom code whenever an insert or an update event is fired. This custom code could check the name of the action being called (is it called create or update?) in order to determine whether an email is required to be sent out.
The behaviour is useless on it’s own though, we would need to attach it to any models that should be triggering it.
Implementation of Solution
NotificationBehavior.php
<?php
namespace app\components;
use yii\base\Behavior;
use yii\db\ActiveRecord;
class NotificationBehavior extends Behavior
{
/**
* Binds functions 'afterInsert' and 'afterUpdate' to their respective events.
*/
public function events()
{
return [
ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert',
ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate',
];
}
/**
* This function will be executed when an EVENT_AFTER_INSERT is fired
*/
public function afterInsert($event)
{
// check the 'id' (name) of the action
if (Yii::$app->controller->action->id === 'create') {
// send email to administrator 'user performed insert'
}
}
/**
* This function will be executed when an EVENT_AFTER_UPDATE is fired
*/
public function afterUpdate($event)
{
if (Yii::$app->controller->action->id === 'update') {
// send email to administrator 'user performed update'
}
}
}
PostController.php
<?php
namespace app\controllers;
use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
class PostController extends Controller
{
/**
* Creates a new record
*/
public function actionCreate()
{
$model = new Post;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
/**
* Updates an existing record
*/
public function actionUpdate()
{
// ...
}
}
Post.php (model)
<?php
namespace app\models;
use app\components\NotificationBehavior;
use yii\db\ActiveRecord;
class Post extends ActiveRecord
{
/**
* specify any behaviours that should be tied to this model.
*/
public function behaviors()
{
return [
// anonymous behavior, behavior class name only
NotificationBehavior::className(),
];
}
}
I would also advise checking out Yii2's TimestampBehavior implementation for a more concrete example.
Do you have a model to "user"? If yes, then just override method afterSave (it fires exactly after making any changes in the model) like this:
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {
// your notification logic here
return true;
}
return false;
}
I'm using Castle Windsor, which generally rocks, however I want it to call a method on my component when it is created and seem to have hit a limitation with OnCreate. For exaxmple:
interface IService1
{
void op1();
}
interface IService2
{
void op2();
}
class MyComponent : IService1, IService2
{
public void Init() // not part of either service
{
}
public void op1()
{
}
public void op2()
{
}
}
// I want to call the component's Init() method when it's created, which isn't part of the service contract
container.Register(Component.For<IService1, IService2>().ImplementedBy<MyComponent>().OnCreate(s => s.Init()));
// I could call something from IService1
container.Register(Component.For<IService1, IService2>().ImplementedBy<MyComponent>().OnCreate(s => s.op1()));
// But I can't call anything from any of the other services
container.Register(Component.For<IService1, IService2>().ImplementedBy<MyComponent>().OnCreate(s => s.op2()));
The first registration won't compile, complaining that it "cannot resolve symbol Init" because the instance passed to the delegate is of type IService1. OnCreate seems a bit limited to me, as in the third case when there are multiple services exposed it only allows you to bind to the first one you declare. I'd have to swap IService1 and IService2 around in order to call op2, but that's just moving the problem around.
Why isn't the type passed in the delegate that of the component being registered? Then I'd be free to call whatever method I like. Is there a way around this? Assume I can't put the Init() code in the component's constructor.
Don't be constrained by the strongly typed nature of C#
Yes, the way the API is constructed it's based off of the first service of the component but you can always cast it down to its actual type (or a secondary service)
.OnCreate(s => ((MyComponent)s).Init())
Alternatively, implement Castle.Core.IInitializable or System.ComponentModel.ISupportInitialize (if you don't want your components to reference Windsor) and then you won't need .OnCreate() at all.
For future reference, here's the relevant documentation.
I have a class:
public class Application
{
....
public Deployment NewDeployment { get; set; }
....
}
I have an editor template for Deployment within the Application View folder.
The ApplicationViewModel has a SelectedApplication (of type Application), in my Index.cshtml where I use ApplicationViewModel as my Model, I have this call:
#using (Html.BeginForm("Create", "Deployment", new { #id = Model.SelectedId,
q = Model.Query }, FormMethod.Post, new { id = "form", role = "form" }))
{
#Html.EditorFor(m => m.SelectedApplication.NewDeployment)
}
Which then correctly renders out the control in my DisplayTemplates\Deployment.cshtml (though, it may just be pulling the display code and nothing in relation to the NewDeployment object's contents). All is well in the world until I go to submit. At this stage everything seems good. Controller looks like:
public class DeploymentController : Controller
{
[HttpPost]
public ActionResult Create(Deployment NewDeployment)
{
Deployment.CreateDeployment(NewDeployment);
return Redirect("/Application" + Request.Url.Query);
}
}
However, when it goes to DeploymentController -> Create, the object has nulls for values. If I move the NewDeployment object to ApplicationViewModel, it works fine with:
#Html.EditorFor(m => m.NewDeployment)
I looked at the output name/id which was basically SelectedApplication_NewDeployment, but unfortunately changing the Create signature to similar didn't improve the results. Is it possible to model bind to a child object and if so, how?
Your POST action should accept the same model your form is working with, i.e.:
[HttpPost]
public ActionResult Create(ApplicationViewModel model)
Then, you'll be able to get at the deployment the same way as you did in the view:
model.SelectedApplication.NewDeployment
It was technically an accident that using #Html.EditorFor(m => m.NewDeployment) worked. The only reason it did is because the action accepted a parameter named NewDeployment. If the parameter had been named anything else, like just deployment. It would have also failed.
Per Stephen Muecke's comment and with slight modifications, I was able to find how to correct it:
[HttpPost]
public ActionResult Create ([Bind(Prefix="SelectedApplication.NewDeployment")] Deployment deployment)
{
// do things
}
First View is a listview including some items.
eg. item0, item1, item2
When click a "new" button, the second view will be shown.
Then click a "save" button and input a name(eg. item3). The "item3" will be save in seconde viewmodel
After saving success. The first view will refresh and show "item3"
How to pass name "item3" from second viewmodel to first viewmodel for showing in first view?
Use the Messenger Plugin in MVVMCross. The First VM registers for a message. The 2nd VM Publishes(Notifies) the 1st one by publishing a Message of the Message type(class) registered. For example:
1st VM
In order to use the messenger plugin you need to register it first, using Injection as follows:
private readonly IMvxMessenger _messenger; // VM Instance var
private MvxSubscriptionToken myToken;
public FirstViewModel(IDataService dataService, IMvxMessenger messenger)
{
// Registers the DataService(SQLite Plugin) and the Messenger Service Plugin
_dataService = dataService;
_messenger = messenger;
// Suscribe to the Meessage and pass it the name of a method to be called when the 2nd VM(the Publisher), publishes(notifies) a MyMessage to all Subscribers...
myToken = _messenger.Subscribe<MyMessage>( OnMyMessageArrived );
}
private void OnMyMessageArrived( MyMessage p_myMessage )
{
// Set the 1st VM's property from p_myMessage
MyProperty = p_myMessage.Item3
}
And the MyMessage class should look like this:
public class MyMessage : MvxMessage
{
public MyMessage( object sender, string p_item3 ): base( sender )
{
Item3 = p_item3;
}
public string Item3 { private set; get; }
}
And the 2nd VM, when ready to send the value back, it should Publish the MyMessage with the value to be passed back to 1st VM(and any subscribers) as follows:
_messenger.Publish( new MyMessage( this, item3 ) );
It's a good practice to have the Subscribers to unsubscribe from any messages they subscribe to, so back in the 1st VM, perhaps just before you close it you should do:
_messenger.Unsubscribe<MyMessage>( myToken );
For a more complete example checkout Stuart's N=9 video and associated sample. Here's the link to both:
http://slodge.blogspot.com/2013/05/n9-getting-message-n1-days-of-mvvmcross.html
R,
Pap
I'm not familiar with mvvmcross, so I'll answer your question using general M/VM/V/C architecture patterns:
Views and ViewModels shouldn't be aware of each other, that's the Controller's job. Your FirstView should alert its controller that the user clicked New and it is the controller's responsibility to then create and manage your SecondView.
The process would be like this (abstract, psuedocode-ish):
void ControllerAction() {
using(View listView = new MyListView() ) {
ListViewViewModel listViewVM = CreateListViewVM();
listViewVM.NewClicked = () => this.NewClicked(listViewVM); // wiring-up ViewModel events via anonymous methods
listView.SetViewModel( listViewVM );
listView.ShowModal();
}
}
void NewClicked(ListViewViewModel parentViewViewModel) {
using(View newEntryView = new NewEntryView()) {
if( newEntryView.ShowDialog() == DialogResult.OK ) {
parentViewViewModel.Refresh();
parentViewViewModel.SelectedItem = newEntryView.NewItemText; // passing the new item's name
}
}
}
I'm using EF 4.1 Code First. I have an entity defined with a property like this:
public class Publication
{
// other stuff
public virtual MailoutTemplate Template { get; set; }
}
I've configured this foreign key using fluent style like so:
modelBuilder.Entity<Publication>()
.HasOptional(p => p.Template)
.WithMany()
.Map(p => p.MapKey("MailoutTemplateID"));
I have an MVC form handler with some code in it that looks like this:
public void Handle(PublicationEditViewModel publicationEditViewModel)
{
Publication publication = Mapper.Map<PublicationEditViewModel, Publication>(publicationEditViewModel);
publication.Template = _mailoutTemplateRepository.Get(publicationEditViewModel.Template.Id);
if (publication.Id == 0)
{
_publicationRepository.Add(publication);
}
else
{
_publicationRepository.Update(publication);
}
_unitOfWork.Commit();
}
In this case, we're updating an existing Publication entity, so we're going through the else path. When the _unitOfWork.Commit() fires, an UPDATE is sent to the database that I can see in SQL Profiler and Intellitrace, but it does NOT include the MailoutTemplateID in the update.
What's the trick to get it to actually update the Template?
Repository Code:
public virtual void Update(TEntity entity)
{
_dataContext.Entry(entity).State = EntityState.Modified;
}
public virtual TEntity Get(int id)
{
return _dbSet.Find(id);
}
UnitOfWork Code:
public void Commit()
{
_dbContext.SaveChanges();
}
depends on your repository code. :) If you were setting publication.Template while Publication was being tracked by the context, I would expect it to work. When you are disconnected and then attach (with the scenario that you have a navigation property but no explicit FK property) I'm guessing the context just doesn't have enough info to work out the details when SaveChanges is called. I'd do some experiments. 1) do an integration test where you query the pub and keep it attached to the context, then add the template, then save. 2) stick a MailOutTemplateId property on the Publicaction class and see if it works. Not suggesting #2 as a solution, just as a way of groking the behavior. I"m tempted to do this experiment, but got some other work I need to do. ;)
I found a way to make it work. The reason why I didn't initially want to have to do a Get() (aside from the extra DB hit) was that then I couldn't do this bit of AutoMapper magic to get the values:
Publication publication = Mapper.Map<PublicationEditViewModel, Publication>(publicationEditViewModel);
However, I found another way to do the same thing that doesn't use a return value, so I updated my method like so and this works:
public void Handle(PublicationEditViewModel publicationEditViewModel)
{
Publication publication = _publicationRepository.Get(publicationEditViewModel.Id);
_mappingEngine.Map(publicationEditViewModel, publication);
// publication = Mapper.Map<PublicationEditViewModel, Publication>(publicationEditViewModel);
publication.Template = _mailoutTemplateRepository.Get(publicationEditViewModel.Template.Id);
if (publication.Id == 0)
{
_publicationRepository.Add(publication);
}
else
{
_publicationRepository.Update(publication);
}
_unitOfWork.Commit();
}
I'm injecting an IMappingEngine now into the class, and have wired it up via StructureMap like so:
For<IMappingEngine>().Use(() => Mapper.Engine);
For more on this, check out Jimmy's AutoMapper and IOC post.