How to create new instance of a view/viewmodel when navigation occurs with Prism - silverlight-4.0

I am trying to control when a new view is created and when an existing view is shown.
This is a very similar scenario as outlined in the "Navigating to Existing Views" section in the Prism documentation, but I can't get it to work fully:
http://msdn.microsoft.com/en-us/library/gg430861(v=pandp.40).aspx
I am finding I can create the view/view model to begin with ok, but I am then unable to create a new instance of it. I.e. I want more than one instance to exist at once.
Here's an example of the view model:
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class DataEntryPageViewModel : INavigationAware, IRegionMemberLifetime
{
private Guid id;
[ImportingConstructor]
public DataEntryPageViewModel()
{
id = Guid.NewGuid();
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
// In actual fact there would be more logic here to determine
// whether this should be shown to the user
return false;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
public bool KeepAlive
{
// For the purposes of this example we don't want the view or the viewModel
// to be disposed of.
get { return true; }
}
}
I am navigating to this as follows:
m_RegionManager.RequestNavigate(
"MainRegion",
new Uri("/DataEntryPageView", UriKind.Relative));
So the first time I call the above the view is shown.
The next time I call RequestNavigate the IsNavigationTarget is hit and it returns false. What I then want it to do is to create a new instance but that doesn't happen. I know it's not happening because the constructor does not get hit and the UI does not update to show the new instance of the view.
Any ideas how I can make it create a new instance?
Many thanks,
Paul
Edit
I have noticed that the second time I call RequestNavigate (to request another instance of the same view) the callback reports an error "View already exists in region." It therefore seems that I can have multiple instances of different views in a region, but not multiple instances of the same view. My understand of this isn't great though so I could be wrong.

Why are you not creating the view when you want a new one to be created? It looks to me like you are using MEF.
Use the container to resolve a new instance of your view
Add the new instance of the view to the MainRegion
Then call Navigate and handle the appropriate logic in IsNavigationTarget

You should use the [Export] attribute in your view with a contract name: [Export("DataEntryPageView")].

I have now been able to get this to work, it was because I didn't have
[PartCreationPolicy(CreationPolicy.NonShared)]
on the class declaration of the view. I had it on the ViewModel.
So this is now resulting in the behaviour I expected.
Thanks though to Zabavsky and Alan for your suggestions.

Related

Switching MVC view on Post back using strongly typed views/view models

User requests page for Step1, fills out and submits form that contains selected person, so far so good. After validation of ModelState the next viewmodel is constructed properly using the selected person. I then attempt a redirect to action using the newVM but find on entry to Step2 that MVC wipes out the viewmodel attempted to be passed in. I suspect this is due to how MVC attempts to new up and instance based on query string results. I'll put a breakpoint in and check that, but am wondering how does one change a view from a post back with a new view model passed in?
public ActionResult Step1()
{
var vm = new VMStep1();
return View(vm);
}
[HttpPost]
public ActionResult Step1(VMStep1 vm)
{
if (ModelState.IsValid)
{
var newVM = new VMStep2(vm.SelectedPerson);
return RedirectToAction("Step2", newVM);
}
return View(vm);
}
public ActionResult Step2(VMStep2 vm)
{
return View(vm);
}
I can fix this by containing VMStep2 and a partial to Step2 in Step1 view, but that requires hide and seek logic when really I just want user to see Step2.
I don't see why you should want to call RedirectToAction! What it does it the following:
it tells your browser to redirect and like it or not your browser doesn't understand how to handle your object -- what it does understand is JSON. So if you really insist on using return RedirectToAction("Step2", newVM); you should consider a way to serialize your VMStep2 object to JSON and when the browser requests the Redirect, it will be properly passed and created in your action method public ActionResult Step2(VMStep2 vm)
HOWEVER I'd use a much simpler way ---
instead of
return RedirectToAction("Step2", newVM);
I would use
return View("Step2", newVM);
Thanks to everyone for the great input!
Here's what I did...
I created three views MainView, Step1View, Step2View (Step 1 and 2 were partial strong typed views)
I created a MainViewModel that contained VMStep1 and VMStep2
When controller served Step1 the MainViewModel only initialized VMStep1 and set state logic to tell MainView Step1 was to be shown.
When user posted back the MainView containing the MainViewModel, the MainViewModel knew what to do by the answers provided in VMStep1.
VMStep2 was initialized on the post back, and state was set to tell MainView to show Step2. VMStep1 was no longer relevant and was set to null.
User was now able to answer using VMStep2 and all was well.
The key to this working is that some flag tells the view which partial to show, the partial takes a model supporting it's strong type which is initialized at the right time. End result is fast rendering and good state machine progression.

The view or its master was not found or no view engine supports the searched locations

Error like:The view 'LoginRegister' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/MyAccount/LoginRegister.aspx
~/Views/MyAccount/LoginRegister.ascx
~/Views/Shared/LoginRegister.aspx
~/Views/Shared/LoginRegister.ascx
~/Views/MyAccount/LoginRegister.cshtml
~/Views/MyAccount/LoginRegister.vbhtml
~/Views/Shared/LoginRegister.cshtml
~/Views/Shared/LoginRegister.vbhtml
Actually my page view page is ~/Views/home/LoginRegister.cshtml so what i do
and my RouteConfig is
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "MyAccount", action = "LoginRegister", id = UrlParameter.Optional }
);
}
}
Be careful if your model type is String because the second parameter of View(string, string) is masterName, not model. You may need to call the overload with object(model) as the second parameter:
Not correct :
protected ActionResult ShowMessageResult(string msg)
{
return View("Message",msg);
}
Correct :
protected ActionResult ShowMessageResult(string msg)
{
return View("Message",(object)msg);
}
OR (provided by bradlis7):
protected ActionResult ShowMessageResult(string msg)
{
return View("Message",model:msg);
}
Problem:
Your View cannot be found in default locations.
Explanation:
Views should be in the same folder named as the Controller or in the Shared folder.
Solution:
Either move your View to the MyAccount folder or create a HomeController.
Alternatives:
If you don't want to move your View or create a new Controller you can check at this link.
In Microsoft ASP.net MVC, the routing engine, which is used to parse incoming and outgoing URL Combinations, is designed with the idea of Convention over Configuration. What this means is that if you follow the Convention (rules) that the routing engine uses, you don't have to change the Configuration.
The routing engine for ASP.net MVC does not serve web pages (.cshtml). It provides a way for a URL to be handled by a Class in your code, which can render text/html to the output stream, or parse and serve the .cshtml files in a consistent manner using Convention.
The Convention which is used for routing is to match a Controller to a Class with a name similar to ControllerNameController i.e. controller="MyAccount" means find class named MyAccountController. Next comes the action, which is mapped to a function within the Controller Class, which usually returns an ActionResult. i.e. action="LoginRegister" will look for a function public ActionResult LoginRegister(){} in the controller's class. This function may return a View() which would be by Convention named LoginRegister.cshtml and would be stored in the /Views/MyAccount/ folder.
To summarize, you would have the following code:
/Controllers/MyAccountController.cs:
public class MyAccountController : Controller
{
public ActionResult LoginRegister()
{
return View();
}
}
/Views/MyAccount/LoginRegister.cshtml: Your view file.
In your LoginRegister action when returning the view, do below, i know this can be done in mvc 5, im not sure if in mvc 4 also.
public ActionResult Index()
{
return View("~/Views/home/LoginRegister.cshtml");
}
Check the build action of your view (.cshtml file) It should be set to content. In some cases, I have seen that the build action was set to None (by mistake) and this particular view was not deploy on the target machine even though you see that view present in visual studio project file under valid folder
This could be a permissions issue.
I had the same issue recently. As a test, I created a simple hello.html page. When I tried loading it, I got an error message regarding permissions. Once I fixed the permissions issue in the root web folder, both the html page and the MVC rendering issues were resolved.
Check whether the View (.ASPX File) that you have created is having the same name as mentioned in the Controller. For e.g:
public ActionResult GetView()
{
return View("MyView");
}
In this case, the aspx file should be having the name MyView.aspx instead of GetView.aspx
I got this error because I renamed my View (and POST action).
Finally I found that I forgot to rename BOTH GET and POST actions to new name.
Solution : Rename both GET and POST actions to match the View name.
If the problem happens intermittently in production, it could be due to an action method getting interrupted. For example, during a POST operation involving a large file upload, the user closes the browser window before the upload completes. In this case, the action method may throw a null reference exception resulting from a null model or view object. A solution would be to wrap the method body in a try/catch and return null. Like this:
[HttpPost]
public ActionResult Post(...)
{
try
{
...
}
catch (NullReferenceException ex) // could happen if POST is interrupted
{
// perhaps log a warning here
return null;
}
return View(model);
}
I had this same issue.
I had copied a view "Movie" and renamed it "Customer" accordingly.
I also did the same with the models and the controllers.
The resolution was this...I rename the Customer View to Customer1 and
just created a new view and called it Customer....I then just copied
the Customer1 code into Customer.
This worked.
I would love to know the real cause of the problem.
UPDATE
Just for grins....I went back and replicated all the renaming scenario again...and did not get any errors.
I came across this error due to the improper closing of the statement,
#using (Html.BeginForm("DeleteSelected", "Employee", FormMethod.Post))
{
} //This curly bracket needed to be closed at the end.
In Index.cshtml view file.I didn't close the statement at the end of the program. instead, I ended up closing improperly and ran into this error.
I was sure there isn't a need of checking Controller ActionMethod code because I have returned the Controller method properly to the View. So It has to be the view that's not responding and met with similar Error.
If you've checked all the things from the above answers (which are common mistakes) and you're sure that your view is at the location in the exceptions, then you may need to restart Visual Studio.
:(
In my case, I needed to use RedirectToAction to solve the problem.
[HttpGet]
[ControleDeAcessoAuthorize("Report/ExportToPDF")]
public ActionResult ExportToPDF(int id, string month, string output)
{
try
{
// Validate
if (output != "PDF")
{
throw new Exception("Invalid output.");
}
else
{
...// code to generate report in PDF format
}
}
catch (Exception ex)
{
return RedirectToAction("Error");
}
}
[ControleDeAcessoAuthorize("Report/Error")]
public ActionResult Error()
{
return View();
}
I ran into this a while ago and it drove me crazy because it turned out to be simple. So within my View I was using a grid control that obtained data for the grid via an http request. Once the middle tier completed my request and returned the dataset, I received the same error. Turns out my return statement was 'return View(dataset);' instead of 'return Json(dataset);
I couldn't find any solution to this problem, until I found out the files didn't exist!
This took me a long time to figure out, because the Solution Explorer shows the files!
But when I click on Index.cshtml I get this error:
So that was the reason for this error to show. I hope this answer helps somebody.

How do I get Xtext's model from a different plugin?

I've written an Xtext-based plugin for some language. I'm now interested in creating a new independent view (as a separate plugin, though it requires my first plugin), which will interact with the currently-active DSL document - and specifically, interact with the model Xtext parsed (I think it's called the Ecore model?). How do I approach this?
I saw I can get an instance of XtextEditor if I do something like this when initializing my view:
getSite().getPage().addPartListener(new MyListener());
And then, in MyListener, override partActivated and partInputChanged to get an IWorkbenchPartReference, which is a reference to the XtextEditor. But what do I do from here? Is this even the right approach to this problem? Should I instead use some notification functionality from the Xtext side?
Found it out! First, you need an actual document:
IXtextDocument doc = editor.getDocument();
Then, if you want to access the model:
doc.modify(new IUnitOfWork.Void<XtextResource>() { // Can also use just IUnitOfWork
#Override public void process(XtextResource state) throws Exception {
...
}
});
And if you want to get live updates whenever it changes:
doc.addModelListener(new IXtextModelListener() {
#Override public void modelChanged(XtextResource resource) {
for (EObject model : resource.getContent()) {
...
}
}
});

How to click back button programatically in IWizardPage in Eclipse

I'm writing an Eclipse plugin, I want to create a wizard for my new project type. I created pages by classes extends org.eclipse.jface.wizard.WizardPage. My requirement is, based on some condition in one page, I need to go back to previous page without pressing back button on page(programmatically).
Is it possible?
Thanks a million in advance!
I don't think this is a good idea. The user will be confused by doing this. I would disable the finish and the next button and give an error, telling the user that he has to go back to the first page.
If you want to reuse some UI from the first page, define the UI as a new class and reuse it.
I implemented something similar using the WizardDialog:showPage() method:
MyWizard.java
public void createPageControls(Composite pageContainer) {
// TODO Auto-generated method stub
super.createPageControls(pageContainer);
wizardDialog = (WizardDialog) getContainer();
}
public void skipProcessPage() {
wizardDialog.showPage(workPage == arisDbPage ? focusPage : arisDbPage);
}
public void setWorkPage(IWizardPage workPage) {
this.workPage = workPage;
}
here the processPage does the lengthy db lookup!
HTH thomas

How to add a new entity to a domain context and immediately see it in data bound controls before SubmitChanges?

I've got a Silverlight 4 RIA Services (SP1) app using Entity Frameworks 4 CTP5. I can databind a grid or listbox to the IEnumerable loaded by the domain context and it shows data from the server. Great.
Now I want to create a new instance of MyEntity and add it to the client-side data so that the user can see the newly added entity. MyEntity is a true entity descendant, not a POCO.
The only Add method I can find is domainContext.EntityContainer.GetEntitySet<MyEntity>().Add(newobj)
This does add the new entity to the domain context, and the domainContext.HasChanges does become true, but the new entity doesn't show up in the databound controls.
How do I get the new entity to show up in the databound controls prior to SubmitChanges?
(Probably related to this SO question from years ago that never got an answer)
Here's the server side declarations of the domain service, per requests:
[EnableClientAccess()]
public class MyDomainService : LinqToEntitiesDomainService<MyObjectContext>
{
protected override MyObjectContext CreateObjectContext()
{
return new MyObjectContext();
}
public IQueryable<MyEntity> GetMyEntities()
{
return this.ObjectContext.MyEntities;
}
public void InsertMyEntity(MyEntity MyEntity)
{
// ...
}
public void UpdateMyEntity(MyEntity currentMyEntity)
{
// ...
}
public void DeleteMyEntity(MyEntity MyEntity)
{
// ...
}
}
I've figured this out with a combination of my own trial and error and hints provided by some of the other responses to this question.
The key point I was missing was that it's not enough for the ViewModel to keep track of the DomainContext and hand out query results to the View for databinding. The ViewModel also has to capture and retain the query results if you want entity adds and deletes performed by the ViewModel to appear in the UI before DomainContext.SubmitChanges(). The ViewModel has to apply those adds to the collection view of the query results.
The ViewModel collection property for View databinding. In this case I'm using the Telerik QueryableDomainServiceCollectionView, but other collection views can be used:
public IEnumerable<MyEntity> MyEntities
{
get
{
if (this.view == null)
{
DomainContextNeeded();
}
return this.view;
}
}
private void DomainContextNeeded()
{
this.context = new MyDomainContext();
var q = context.GetMyEntitiesQuery();
this.view = new Telerik.Windows.Data.QueryableDomainServiceCollectionView<MyEntity>(context, q);
this.view.Load();
}
The ViewModel function that adds a new entity for the UI to display:
public void AddNewMyEntity(object selectedNode)
{
var ent = new MyEntity() { DisplayName = "New Entity" };
if (selectedNode == null)
{
this.view.AddNew(ent);
}
else if (selectedNode is MyEntity)
{
((MyEntity)selectedNode).Children.Add(ent);
}
}
Other responses mentioned ObservableCollection. The query results and the collection view may not return instances of ObservableCollection. They could be just IEnumerables. What is critical is that they implement INotifyCollectionChanged and IEditableCollectionView.
Thanks to those who contributed responses. I've +1'd each response that was helpful, but since none directly solved my problem I couldn't justify marking any as the definitive answer.
Your domainContext will have a property domainContext.MyEntities. Does it not show up in there when you add it?
Bind to that collection or watch that collection for changes.
domainContext.MyEntities.PropertyChanged += MyEventHandler;
I assume you bind your control to the IEnumerable which is provided by LoadOperation<TEntity>.Entities. In that case your binding source is not the DomainContext.GetEntitySet<MyEntity>().
DomainContext.GetEntitySet<MyEntity>() holds all your currently tracked instances of MyEntity, including the one you add with .Add().
LoadOperation<TEntity>.Entities only contains the instances of MyEntity that were actually loaded by your last LoadOperation/Query.
You have two options: Either add the new entity to the ItemsSource-collection for your control (I recommend that) or rebuild the collection with the contents of DomainContext.GetEntitySet<MyEntity>(). That may contain other elements that you have not cleared out before, though.