This is the second article about new Data Moving Plug-in SPA View Engine, so the previous article is a pre-requisite for reading it.
In any MVVM (Model View ViewModel) SPA (Single Page Applications) dependencies among the View-ViewModel pairs, and dependencies of each View-ViewModel pair on the global state of the application may undermine the modularity and stability of the whole application:
The Data Moving Plug-in View Engine faces all above problems by concentrating the contextual dependencies in context rules that are run immediately before rendering a View-ViewModel pair. Context rules are run on the ViewModel being rendered till a matching rule is found. The matching rule collects global information the View-ViewModel pair might depend on both to build a context object and to fill some ViewModel properties. The information copied by the context rules in the ViewModel may include interface objects that may be used by the ViewModel methods to manipulate the global application state. This way each template ViewModel pair may cooperate with the remainder of the application while keeping its modularity. In case of virtual pages the interface passed to a ViewModel usually depends not only on the module, and local template name, but also on the role assigned to the virtual page by the virtualReference object that required the page to the page store. The virtual page role is available in its role property.
This way all burden of interpreting the external environment falls on the contextual rules and doesn’t undermine the modularity of the View-ViewModel pairs: View-ViewModel pairs may be used in different contexts by simply changing or adding some context rules, without modifying their code.
The purpose of the context object is twofold:
Below a context rule that handles a page that is available only for logged-in users:
A new context rule is added by passing a function to the mvcct.ko.dynamicTemplates.addContextRule method. This function receives the ViewModel and a context builder object as parameters. The function is expected to return true when its pre-concitions are satisfied and false otherwise. Inside the context rule we may build the context object by using the fluent interface of the context builder object.
In our case the preconditions of the rules are satisfied only if the module name is “People”. In more complex applications having several modules that require login, a simple solution is to give them a common name prefix: something like “secure_people”, so that context rules may just check this prefix.
In our case the rule verifies if the user is logged in with a call to the authorizationManager. If the user is not logged, we insert a “redirect to the login virtual page” request into the context object by calling the redirect method of the context builder.
If the user is logged, instead, we add the username and the authentication ticket to the url. In this case we don’t write anything in the ViewModel, since the partial views that implement the templates and AMD will take care of adapting the content deployed to the logged user.
The addition of the authorization ticket to the URL must be done if and only if the AMD and template contain sensitive information that must be protected from unauthorized accesses. The insertion of the authorization ticket in the URL DE FACTO prevents the caching of the templates and AMD in the browser cache, since the authorization ticket usually has a short life. Moreover, caching MUST be explicitly prohibited with an adequate OutputCache attribute to protect the sensitive information from malicious users that might manipulate the Browser Cache.
For the above reasons, it is advised to avoid the insertion of the authorization ticket in the URL, and to it is advised to require any sensitive information with a subsequent ajax call to the server. In this case the username passed in the URL may be used just to adapt the graphics of the virtual page to the logged user by removing links to pages that the user is not authorized to visit, and by customizing the virtual page with some user preference the user might have provided someway.
The add context builder method stacks the strings passed as parameters in the URL one after the other, before the module name. For instance if after the add(currUser) we perform another add for the selected culture we get URLs like: …./en-US/john32/People/ for the templates and …./en-US/john32/People.js for the AMD. In case we require also the addition of the authorization ticket, the ticket is appended just to AMD module URL,: …./en-US/john32/People.js?ticket=<authorization ticket>. The request for the templates doesn’t need to include explicitly the authorization ticket in the URL, since this request uses cookies and consequently it is authenticated with the authorization ticket contained in the Asp.net authorization cookie.
The context builder object contains also other two add methods, namely:
Below a set of context rules adequate for handling user authorization:
The first rule changes the view used to render the Login and Register virtual pages in case the user is already logged, by adding a view prefix with vadd. The second rule is the rule we already analyzed for redirecting anonymous users to the login page when they need to access pages that require login.
Finally, the last rules is fired for all virtual pages that doesn’t satisfy the previous rules. The mvvct.ko.dynamicTemplates.isBaseViewModel verifies that data is a virtual page ViewModel. This rules, requires the re-rendering of a virtual whenever the logged user changes by means of the cadd method. This re-rendering is necessary because of page headers that might depend on the logged use (for instance the login/logout link).
It is worth to point out that re-rendering doesn’t imply necessarily any loss of data that might be contained in the ViewModel. Data are reset just when a virtual page is rendered the first time, or when it has been re-rendered because of a change of the template/javscript selected. Thus, re-rendering caused by a cadd doesn’t cause any loss of data but just a refresh of the contextual information.
The javascript code associated to any template is informed about the need to re-initialize the whole ViewModel through the _initialized boolean property that is added to each ViewModel. When the javascript code initializes a ViewModel it sets this property to true. The property is automatically re-set to false whenever the framework realizes that the ViewModel needs to be re-initialized. Below an example of usage of the _initialized property:
In the example above, if re-rendering is required because of a cadd-only context change the template is re-rendered, and the afterRender function is re-executed on the newly created html, but all ViewModel building is saved because it has been enclosed within an (!vm_initialized) if.
The context object may be accessed also in the javascript code associated to a template by calling the function: mvcct.ko.dynamicTemplates.getContext(vm). In the example below the context object is used to decide which javascript module to call for initializing the ViewModel:
In fact in case the user is logged we not only need completely different templates, but also a completely different initialization code, since the underlying data are completely different.
In order to add multi-lingual support to the context rules of our previous example it is enough to add a .add(<language string>) call to all our context rules. The <language string> may be extracted by the cookie we use to record the user language settings.
In order to add support for user authorization we need also to initialize properly the authorizationManager, and to implement the action methods, to login, logout, register, and to verify the name of the currently logged user. The Register action method is not conceptually different from any standard register method. Below, all other methods:
The first two methods are self-explanatory, while the login action method differs from a standard Mvc Login method just for the use of the SPASecurity.SetAuthCookie method instead of the FormsAuthentication.SetAuthCookie method.
The login model is sent to the server by a viewModelUpdatesManager, that takes care also of dispatching possible server errors(such as the login failure):
In the onUpdateComplete callback, in case of success, we reset the undo-redo stack of the Data Moving Plug-in Form, reset the password property of the ViewModel, then we declare the new user (contained in the additionalData property of the response) to the authorizationManager. If the ViewModel rendered the login page because of a redirect context directive, the ViewModel will be automatically re-initialized to display the original content required as soon as the new user is declared to the authorizationManager. Otherwise, we pass false in the user declaration, which requires a silent user change. As a consequence no re-rendering of the current virtual page is attempted. Then we go manually to the home page by calling the goTo method of the virtual page (see the previous post of the series for more details on the goTo method).
The call to the Logout and CurrentUser action methods are handled automatically by the by the logout(onError) and updateUserFromserver(onServerResponse, onError) methods of the authorizationManager, that are called from within the click hanlders of the logout and refresh links that are in the header of each virtual page:
The authorization managers is initialized with the call:
The first argument is the link of the CurrentUser action method, the second one the link of the Logout method, the third argument, if provided, is an onLogout callback while the fourth argument is a single observable or an array of observables that must be refreshed when the user changes. In our case we passed as fourth argument our unique container of all virtual pages. In a more complex application that hosts several physical pages in the same physical page we might have passed an array containing several observables used as virtual page containers.
In all action methods that deploy templates/AMD only to logged users we must call the method to SPASecurity.ValidatedUser to validated the user received as parameter:
The SPASecurity.ValidatedUser method attempts validation with both the asp.net authorization cookie and with the ticket passed as argument.
That’s all for now! A video showing the Data Moving Plug-in SPA View Engine is available here.
Francesco