4.11 Adding SPA authentication
In the previous chapters, we have seen how Xomega helps you to model and build a web application using modern Blazor and classic ASP.NET web forms, which generate the HTML for the page on the server side. We also saw how to easily reuse most of the code from the web application in a WPF desktop client - both as a client-server and multi-tier applications.
In this chapter we are going to show you how to create and secure a different type of web client, Single Page Application (SPA), that will be served as static HTML, JavaScript and other resource files, and all the client behavior will be implemented in JavaScript, which will be executed by the user's browser. To develop that JavaScript, we will actually use TypeScript that provides type safety and compilation checks. We will also use our XomegaJS library - a TypeScript counterpart of the Xomega Framework, - as well as other popular client-side libraries and frameworks, such as Knockout, JQuery, Durandal, etc.
The Xomega solution template that we used already included a SPA project, which is called AdventureWorks.Client.Spa. It was preconfigured to work with the REST API from the Services.Rest project in our solution, and had all necessary client-side artifacts, such as HTML views and TypeScript objects, generated from our model when we were building the model. It also helped that we kept updating the ui:html-control configurations of the model types, which affects generation of the HTML views.
Unfortunately, since the client code is in TypeScript, it doesn’t include any customizations that we’ve been adding to the shared client-side C# data objects and view models, so we’d have to replicate them in TypeScript. Luckily though, XomegaJS, just like Xomega Framework, provides enough support to minimize the amount of custom code that you have to write.
By default the SPA project was configured to auto-login with the REST services using empty user/password and without any login dialog. Since we’ve already secured our REST services, we need to add authentication to our SPA project as well. To begin with, let's expand our generated Login View, and open the LoginViewCustomized.ts nested under it. Here we can customize our view in the attached method using JQuery, as follows.

As you see, we changed the text of the Save button to be Login, set the width of the view, and configured the authentication data object to not track modifications, in order to suppress the confirmation dialog when navigating away from it, which we could also have done in the customized subclass of the AuthenticationObject, like we did in C#.
Now let's open the Login class in the login.ts file that was provided with the project by default, and make it extend from our LoginViewCustomized class. After that, we need to remove the getView method that creates an empty view and the activate() method with anonymous auto-login logic, and add an override for the onSave method instead, where we will use supplied email and password to log in, as shown below.

To run the SPA client, we will open the solution properties, and set the SPA client and REST services projects as the startup projects. The former does not need to be started in debug mode, since we will use the browser's debugger for our client-side TypeScript/JavaScript code.

If we run the project now, the browser will show our Login View, and if we enter invalid credentials then we’ll see our authentication error message from the REST services, as shown below.

Let’s log in using the email address “amy1@adventure-works.com” for our external customer, and our test “password”, which should open up the home screen with the top level menu to other screens. If you navigate to the Sales Order List and run the search without any criteria, you will notice that this SPA client looks very much like the Blazor web application that we have built initially, with the same master-details layout, showing only the sales orders for our customer.

However, you will notice that it does not have any custom client-side logic that we added to the C# clients, such as hiding the customer store and name criteria for external customer users, showing sales order number as part of the view title, or displaying contextual data for the billing and shipping address of the selected customer on a sales order.
All this logic was implemented in C# using Xomega Framework, and was successfully reused between Blazor, WebForms and WPF clients. But since the SPA client is written in TypeScript, we will need to re-implement this logic using XomegaJS framework instead. Luckily XomegaJS makes it very easy, and very similar to what we did using Xomega Framework, as we will see below.
If you remember, in other web clients we made sure that views under the Sales top menu are only accessible to internal users and external customers, but not to other types of users, such as vendors. In the SPA client we'll do it by configuring rules for Durandal routes in the shell.ts file that is nested under the shell.html as follows.

Here we just check if the route contains the word 'Sales' or equals to the route of the customer list view. If you log in as a vendor user now (e.g. jon2@adventure-works.com), you will see that the Sales menu is hidden, because none of the menu items in it are allowed for the current user.

If you try to manually enter the URL for those views, or just pull them up from a saved favorite, you will see that the views will not be shown unless your user has a proper role.
Next, let’s add the same custom changes to the customized SalesOrderCriteria object in TypeScript that we did in C#. To remind you, this included displaying status in the auto-complete as “ID - Text”, cascading selection of the sales territory based on the selected global region and the salespersons based on the selected territory, hiding customer store and customer name criteria for external customers, and validating that the From order date is earlier than the To order date when the order date operator is set to Between.
Let’s open the SalesOrderCriteriaCustomized.ts file under the DataObjects/Sales folder in the SPA project, and add the onInitialized and validate functions for these features. The following snippet shows this custom code in TypeScript with each case highlighted separately.

Notice how we can still use generated constants for the “person type” enumeration and attributes of the SalesTerritory and SalesPerson enumerations. You can see that the TypeScript code pretty much mirrors the C# code, which should significantly flatten the learning curve.
Let’s run the application as an external customer user to view these changes in action. The status values will have the specified ID - Text format, as shown below.

As you can see from the following screenshot, the customer store and name criteria are hidden, the list of sales territories is filtered based on the selected global region, and our validation of the From and To order dates is displayed under the criteria panel.

In order to implement a dynamic view title for the sales order view to include the sales order number for existing orders like we did in the C# view model, let’s open up the SalesOrderViewCustomized.ts file nested inside the SalesOrderView.html under the Views/Sales folder of the SPA project, and add override the getViewTitle() method as follows.

If you run the application now, and select an existing sales order from the list, you’ll see that the view title will include the sales order number.

The generated Sales Order details screen in our SPA client has a proper structure based on our Xomega model, but it fails to display customer address details for the selected or current customer, since that was implemented using custom client-side logic in C#. Let’s see how to do the same in TypeScript for the SPA client. This logic is a little more complicated than our previous customizations, but leveraging XomegaJS framework, as well as all the different pieces that were pre-generated for us, makes it still fairly easy and straightforward.
Similar to the way we did it in C#, we will open the SalesOrderCustomerObjectCustomized.ts file, and declare an addressLoader member with a type of the cache loader that was generated for us from the ReadList operation on the BusinessEntityAddress service. We will create it in the onInitialized() method, and assign it as a local cache loader to the AddressId properties of the billing and shipping address child objects. Then we will subscribe to the changes in the StoreId and PersonId properties using a new onCustomerChanged event listener, where we will set the input parameters for the address loader with the business entity ID populated from either the person ID or the store ID. When calling the setParameters method, we’ll pass a handler to execute when the cache is reloaded, where we will clear any invalid values of the billing and shipping address IDs, and will notify the listeners of the change in their possible values. The following code snippet illustrates this logic.

This logic here is the same as we did in C#, and can be significantly simplified in more common scenarios, as we’ll see later. Notice also that we turn off modification tracking on the child LookupObject, since its values are only used to conveniently look up customers, and should not prompt for unsaved changes.
Next, you will need to add custom code in the AddressObjectCustomized.ts file, which would update the AddressObject's properties from the attributes of the selected address value whenever the latter is changed. Below is the code that shows how to do it.

Let's run the application now, and open up the customer tab on a new or existing sales order. If we select a customer that has more than one address type, we should see those types populated in the billing and shipping address panels. Once you select a specific address, it's details will be shown in the rest of the panel.

To see how much easier this functionality can be implemented in a more common case, where the list of values in a property depends only on a value of one other property, let’s implement reloading of the list of the person’s credit cards when you select a new customer on a sales order. All we have to do is to open the SalesOrderObjectCustomized.ts file, which has the common parent object for both the customer and the credit card child objects, and add the following code to the onInitialized method.

We just set the local cache loader for our CreditCardId property to a new instance of the PersonCreditCardReadListCacheLoader class that was generated for us from the model, and then call setCacheLoaderParameters to make it use the PersonId property as the source of its BusinessEntityId input parameter, for which we also use a generated constant. This is exactly how we did it in C# as well.
Again, to display the details of the selected credit card when the value of the CreditCardId property is changed, we will add the following logic to the CreditCardPaymentObjectCustomized.ts file.

Now let's open the sales order details screen, make sure we have a customer selected, and then select the Payment tab. We should see the list of saved credit cards for the selected customer, which pretty much always consists of just one credit card in the demo database. Selecting the credit card from the list will display the credit card number and expiration, as shown in the following screen.

As you see, while we cannot completely reuse our C# client-side code in TypeScript SPA applications, XomegaJS framework allows us to add any customizations just as easily as the Xomega Framework in C# using the same or similar constructs to minimize the learning curve.
Next: Next steps >