Within an Aurelia project I have a view model that I want to maintain state between router navigation. I thought that adding #singleton() to my view model class would accomplish this.
In fact, I have created a simple Aurelia project where this works. I am able to navigate away from and back to the same page and state is maintained. My constructor is only called the first time I navigate to that page.
import { singleton } from 'aurelia-framework';
#singleton()
export class Welcome {
heading = 'Welcome to the Aurelia Navigation App!';
constructor() {
console.log('constructor');
}
activate() {
console.log('activate');
}
attached() {
console.log('attached');
}
}
However, in my larger application this is not working. I add the decorator and my view model's constructor is still called the second time I navigate to that page. (I have even copied this view model into my larger application and it is not treated as a singleton.)
Obviously something must be different between these two projects. However, I don't see any difference. Is there a setting I may have set that would override the behavior of #singleton()?
It turns out the solution was to jspm update. I had thought that deleting my jspm_packages and running jspm install would be equivalent. But it is not.
Now that my Aurelia modules are up to date the singleton() decorator works fine.
You likely have multiple instances of the singleton custom element - singleton by definition cannot have multiple instances of itself. What you want is a view model that is created once, and reused.
Here's a solution that I used successfully. The idea is to have a global view model instance that is constructed once, and there is a controller view that uses that to compose the view/vm.
HTML
<template>
<require from="yourView.html"></require>
<p> parentClass.html </p>
<compose view="yourView.html" view-model.bind="singletonViewModel"></compose>
</template>
JS
import { YourView } from 'yourView';
export class parentClass {
// constructor for parentClass will run multiple times.
constructor() {
/** this is the key, you must instantiate a custom view model once
on some globally singleton object like window or global in Node */
if ( typeof window.yourView === 'undefined' ) {
window.yourView = new YourView();
}
}
bind() {
this.singletonViewModel = window.yourView;
}
}
Everything you do within this view model will persist now between navigations and constructor will only be called once. attached() and detached() for the singleton view model will still activate as you would expect.
Also note that you can use this to cache multiple instances of a view models that you can switch between. My application has a global service to keep track of multiple VMs that constructs/returns the VM as requested. Advantage this brings is I can do heavy processing once at the constructor stage then never worry about it again.
Related
I want to use the Navigation feature in UWP. Unfortunately, the argument to the Navigate method is a type, not an instance of a page. It looks like the activation of this type is done behind the scenes. I question the design decision, but my immediate problem is that all my MVVM forms are instantiated with the view model. Typically I create pages using the Dependency Injection container.
How do you create pages in UWP when they're used with the Navigate method when those pages have DI constructors?
How do you create pages in UWP when they're used with the Navigate method when those pages have DI constructors?
Instead trying to navigate to the page based on its type, you could set the Content of the Frame to an instance that you create yourself:
rootFrame.Content = new YourPage(yourDependency);
The other option is to make sure that all your pages have a default parameterless constructor and inject the dependencies somewhere else, for example in the OnNavigatedTo method as suggested by #Richard Zhang - MSFT.
In UWP, the navigation parameters of Frame.Navigate are Type rather than instances. This is really a design.
In fact, navigating in UWP doesn't require instances, as well as DI, and in general, if you need to combine Page and ViewModel, you can do this:
1. Initialize ViewModel inside the page constructor
Frame
MyFrame.Navigate(typeof(MyPage));
MyPage
private MyViewModel vm;
public MyPage()
{
this.InitializeComponent();
vm = new MyViewModel();
}
2. Initialize ViewModel by passing parameters when navigating
Frame
var vm = new MyViewModel();
MyFrame.Navigate(typeof(MyPage), vm);
MyPage
private MyViewModel vm;
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if(e.Parameter!=null && e.Parameter is MyViewModel _vm)
{
vm = _vm;
// do other things
}
}
If you want to reuse pages, you can enable page caching, it will save the current page state (including ViewModel), and use the cache when you next navigate to the page, so you can avoid repeatedly creating ViewModel.
public MyPage()
{
this.InitializeComponent();
NavigationCacheMode = NavigationCacheMode.Enabled;
}
Best regards.
I am developing an MVC app to serve multiple domains - each is a branch of a larger company.
A LocalBranch class stores details such as phone, address, email, location coordinates etc.
I want to create a single instance of this class per http request and have it available throughout the application - from within controllers, views, some helper classes and other code.
Is there a recommended way of doing this?
Right now I have it as a property on a BaseController and use ViewBagto pass it to views. But I would prefer it strongly typed in Views if possible.
I don't want to put it in an application variable, because we need to serve different values to different domains.
I would rather avoid a session variable if possible because we might scale up to use multiple servers in the future, and I've heard this doesn't play well with sessions.
Please feel free to update tags / title if you think there is a clearer way of expressing what I'm after. Thank you.
The best way to maintain your state in a web application per request is simply use the HttpContext class.
You need to store your state(LocalBranch) as an Item in the HttpContext:
HttpContext.Current.Items.Add("LocalBranch", GetLocalBranch());
You can fetch the Item all across your application like this:
LocalBranch branch = HttpContext.Current.Items["LocalBranch"] as LocalBranch;
The Items property is simply a key value Dictionary. The value is an object. You will have to check for nulls and this is really similar to the Session object you know. The main difference is the scope. The HttpContext is a dot net object that has a lifetime of an http request.
Now using the HttpContext the way I've shown you is the simplest way to do it.
You can go two steps forward and use a framework called Unity and add a lifetime to your objects.
Unity does much more and the lifetime management is just one gem.
You can create a custom HttpContext lifetime that generates objects per request. Something like this.
And them all you need to do is:
1.Register you LocalBranch class with the HttpContext lifetime.
2.Add a static Current property which will use the Unity container and resolve the correct instance of LocalBranch.
3.Use it something like this: LocalBranch.Current
BTW, you can use Unity's dependency injection for injecting objects into controllers and other modules. That's a better practice then just using the static Current property.
You kind of have two questions here. The first is "How do I create a single instance of this class per HttpRequest?" The second is "How do I make this available to strongly typed views?"
The first has pretty much been answered by #amir-popovich to use dependency injection. However, FWIW I would probably use Ninject instead of Unity (just preference, really) and I would probably implement it differently. I would not use HttpContext, and simply build a service (which is instanciated using Ninject's OnePerHttpRequest Module, passing the domain as an argument to get the proper values).
Then, in order to add these LocalBranch values to your strongly typed View Model, you can first create a base view model which holds this type:
public class BaseViewModel
{
public LocalBranch Branch {get;set;}
}
Then, make all of your current view models inherit this base type
public MyViewModel : BaseViewModel
{
public string SomeValue {get;set;}
}
Then in your controller, it is easy enough to add these values from the service you created from the first step
public ActionResult SomeAction()
{
var vm = new MyViewModel();
vm.Branch = LocalBranchService.GetLocalBranchValues(); //Local Branch Service has been injected with Ninject
//do other stuff
return View(vm);
}
However, that gets pretty tedious to add that to each controller action, so you can instead create a Result Filter to add it for you:
public class LocalBranchResultFilter : FilterAttribute, IResultFilter
{
public void OnResultExecuting(ResultExecutingContext filterContext)
{
//This method gets invoked before the ActionResult is executed.
filterContext.Controller.ViewData.Model.Branch = LocalBranchService.GetLocalBranchValues(); //Local Branch Service has been injected with Ninject
}
}
Now, you can just decorate your Controller and/or Actions with the filter (you could even set it in the Global Filters if you want).
You can embed the child actions into your layout or a view. You can even cache its output so you don't keep re-querying the database.
controller
[ChildActionOnly]
[OutputCache(Duration=500, VaryByParam="*")]
public ActionResult Info()
{
var localBranch = db.GetLocalBranch();
return PartialView("_Info", localBranch);
}
_Info view
This bit will get inserted into your other views
#model LocalBranch
<span>#Model.address</span>
<span>#Model.phone</span>
Use in _Layout or other view
<p>lorem ipsum...</p>
#Html.Action("Info")
I have the following super class grails service:
abstract class SuperClassService {
public def execute(def payload) {
def tracker = new TrackerDomain().save()
doWork()
tracker.status = 'done'
tracker.save()
}
protected abstract doWork(def payload);
}
and seveeral child class grails services that follow this pattern:
class SubClassService extends SuperClassService {
protected doWork(def payload){
new SomeDomain().save()
}
}
In my controllers I kick off a call to the 'execute' method of the various child classes.
What I want is for the SubClass services to follow the traditional Service pattern where any problems get rolled back, but I want the domains created int he parent class code to both NOT be rolled back and be committed immediately (so that they can be viewed on a tracking page while the subclass service code is still executing. I would PREFER not to set everything as non-transactional and only set the functions in the subclass as transactional but if that's the only option here I would like to know that too.
Have you tried annotating your subclasses service method with a #Transactional(propagation = Propagation.REQUIRES_NEW)? I think that it should do the trick, regardless of whether the outside service code is transactional or not.
I'm building a site where I often have to go through the same sequence of steps as part of different operations. In simple terms, part of my data model is hierarchical, and I walk the hierarchy to locate a particular element, after which I do one of several different things (e.g., add a detail record to it, edit it).
Currently I do this by retrieving an object via my data layer which represents a node in the hierarchy, and which contains details about its child nodes. I use this same descriptor class in several different areas of my site.
What I'd like to be able to do is to package the various action methods that comprise walking the tree and selecting a node into a "subroutine" that I could "call" from the various controller classes which need to retrieve a node. But I can't think of a good way to do that within the ASPNET MVC structure. Suggestions?
p.s. one approach was presented here, but I'm interested to see if there are others.
Without seeing any code, what you're describing as "package the various action methods that comprise walking the tree" is simply creating a method. In other words, you shouldn't be looking to invoke the set of action methods that comprise walking the tree but calling the methods that are used by those action methods.
What does means is refactoring the action methods so that the logic is handled in service classes and creating a method that consists of the actions you're interested in.
So you're controllers would look something like this:
public class ControllerA {
public ActionResult DoSomething() {
serviceA.DoSomething();
}
}
public class ControllerB {
public ActionResult DoSomethingElse() {
serviceB.DoSomethingElse();
}
}
You'll define those services with the logic needed and can now create a SharedService class that calls the methods you need.
public class SharedService {
public void DoBoth() {
serviceA.DoSomething();
serviceB.DoSomethingElse();
}
}
I have a scenario where I would like to register a single instance of a component in the container, but unfortunately it cannot be created at application startup.
This component could only be instantiated passing some objects which are only available a bit later in application lifecycle (they are not other IoC registered services, however) [see note below].
Is registering a component in a IoC container after the initial configuration (run in app startup) a bad practice?
How to accomplish it without directly referencing the container? Should I abstract a registration service?
There is a better approach to support the scenario?
NOTE about the actual scenario
The component I would like to put in the container is initialized with a particular instance of an UI control (it is basically an adapter), hence I have to manually create the component instance and register it in the container.
I would have done this at application startup, but unfortunately I don't have the UI control instance available yet (nor can I create it by myself).
Even at later time, I cannot reach the UI control instance from the surface of other components without knowing their concrete class.
For this reason I thought I could put the responsibility for the adapter registration into the class which owns the UI control.
My initial scenario:
public interface IDockManager { ... }
public class AcmeDockManagerAdapter : IDockManager {
public AcmeDockManager(DockControl control) { ... }
...
}
public class ShellViewModel { ... }
public class ShellView : Window {
internal DockControl theDockControl;
}
public class AnotherViewModel {
AnotherViewModel(IDockManager dockManager) { ... }
}
The solution I'm unconfortable with:
public class ShellView : Window {
internal DockControl theDockControl;
public ShellView () {
InitializeComponents();
var dockManager = new AcmeDockManagerAdapter(theDockControl);
//registration in the container
}
}
You could register a "lazy wrapper" instead. Such a wrapper implements the same interface and can be instantiated immediately, but will internally postpone the creation of the actual component that does the work. Take a look at ploeh's example of a LazyOrderShipper or LazyOrderShipper2.
edit: If I understand correctly, you're just trying to connect your views to your viewmodels, MVVM-style. I prefer to let the container handle viewmodel construction, but to do the view construction and viewmodel wiring myself. My start-up code woul look like this:
var mainViewModel = container.Get<MainViewModel>();
var mainView = new MainView(mainViewModel);
Application.Run(mainView);
And inside the MainView constructor I'd take care of child controls which require their own viewmodel:
public MainView(MainViewModel viewModel)
{
// link "subviews" to "subviewmodels"
this.SomeChildControl.ViewModel = viewModel.SomeChildViewModel;
// normal MVVM property wiring
viewModel.TitleChanged += delegate { this.Text = viewModel.Title; };
...
}
If you strictly follow the MVVM approach, then you should not have to register any view with the container. Anything that "needs to talk to the view" really needs to talk to underlying viewmodel instead. (Things get more interesting when you want to allow for pluggable views in tabbed interface or docked window GUI, but that's another story.)
The solution the way I understand the question, is relatively simple - provide theDockControl from outside. I know that's messing with autogenerated WinForms/WPF/whatever-you're-using crap, but I'm afraid there's no pretty solutions here.