We’ll continue our exploration of the Anatomy of a LightSwitch Application by examining the presentation tier-also known as the client. A LightSwitch 1.0 client is a Silverlight 4.0 application. The client can run either as a Windows desktop application or hosted in a browser. The Windows desktop mode uses Silverlight’s out-of-browser capability.
The client is responsible primarily for human interaction, data visualization and data entry. While these may sound simple, there really is a lot to it. The client should be easy to deploy and upgrade. It should have good looking, intuitive and functional visuals, including keyboard-accessibility. It must track data changes and updates and perform business logic such as data validation. It must handle threading and asynchrony, be responsive, and interact efficiently with the logic and data tiers. Even with all of the great .NET technologies available, there is a lot to putting it all together! The LightSwitch design philosophy aims to simplify creating great clients by providing a solid architectural foundation and letting the developer focus on the screens, data and logic that are specific to their business problem.
As Julius Caesar might say, the LightSwitch client est omnis divisa en partes tres. Or rather, it is divided into three parts, 1) the shell, 2) screens, and 3) the data workspace.
Even if you don’t remember your Gallic War commentaries, let’s divide and conquer to get a general understanding of the LightSwitch client. We’ll cover the following areas.
- The LightSwitch Shell
- LightSwitch Business Objects and Business Rules (Screens and Entities)
- The LightSwitch Screen
- The LightSwitch Data Workspace
The LightSwitch shell is made up of the hosting process, the shell UI, and the theming service.
For a LightSwitch desktop application, we configure the application to allow out-of-browser install and we control the application startup sequence to ensure that the application is indeed installed and running in the out-of-browser host (sllauncher.exe). We configure desktop application to require elevated permissions. This gives LightSwitch desktop applications access Windows and Office automation and enables the built-in “Export to Excel” capability from within screens.
A LightSwitch browser application can be hosted within any browser and on any OS that Silverlight 4.0 supports. The application runs in the Silverlight sandbox. Access to OLE automation is not available (no “Export to Excel”) and access to the Windows file system is restricted. LightSwitch handles the common problem where navigating away from the page or closing the browser would terminate the application when there are unsaved changes in a screen.
LightSwitch manages the application startup, upgrade, and shutdown sequences.
The application shell provides the root-level user interfaces for logging in, launching and activating screens. The shell UI is pluggable. LightSwitch comes with a default shell, but one can write a custom shell and plug it in via a LightSwitch Extension package. This design enforces consistent behavior in the application while allowing overall the look and feel to be highly customized.
The application shell follows the MVVM pattern. The UI controls (views) are pluggable. The LIghtSwitch runtime provides several shell-related view models that the shell view controls bind to. These include:
- Current user
- Application logo
- Screen activation and navigation
- Active screens
- Screen commands
- Screen validation errors
If this all sounds complicated, don’t worry. The developer just uses the LightSwitch IDE to pick a pre-built shell-and it just works! You can use the Visual Studio Gallery to find and download shells that are published by the community and by vendors.
LightSwitch supports a basic level of theming to control colors, fonts, and to support the OS high-contrast themes. The intrinsic Silverlight controls support simple styling. LightSwitch goes one further by defining a set of styles as a theme and ensuring that the controls we use for the shell and in screens will select styles from the current theme. Themes are extensible and can be created and added via a LightSwitch Extension package.
LightSwitch business objects are the foundational components for defining the business logic of your application. There are two basic kinds of business objects: screens and entities. Screens represent a unit of work on the client (a client use-case) and entities represent an identifiable and durable piece of business data. When you define a business object in LightSwitch, you can also attach business rules to it. LightSwitch business rules help you to encapsulate the business logic where it belongs. No more putting all of your inventory update logic behind a button click event!
Business rules are associated with a business object or a member (a property or command) of the object. Business rules come in several categories:
Computed Property – a read-only property on an entity or screen that is dynamically evaluated
Validation Rule – a rule on an entity or screen property that returns a set of validation results based on the state of the object
Read-Only Rule – a rule on an entity or screen property that determines the present UI read-only state of the property
Can-Execute Rule – a rule on a command that determines whether it is presently available for execution
LightSwitch automatically evaluates and keeps track of these business rules. Evaluation is lazy and just-in-time: If no one is observing the state of a rule, the rule will not be evaluated. The rule is guaranteed to be up-to-date at the point you observe its value.
The result of a business rule is observable in the API from the Details property on the member to which the rule is applied. The Details property also includes other view-model state such as display name and description. Here are some example Details properties for a FirstName property and a PostOrder command.
Business rules are typically associated with a block of code-similar to an event handler. LightSwitch tracks the dependencies of the rule during runtime evaluation. In this way we know when the value of a rule is “dirty” and must be re-evaluated. So long as the code refers to data elements defined on itself or another business object, we can track it properly. However, if the code refers to something that is not defined on a business object, such as
DateTime.Now, LightSwitch can’t track that. But you can force re-evaluation of a rule programmatically.
Some business rules are declarative and specified at design-time. There are several built-in declarative validation rules. Declarative validation rules are also extensible. You can create new ones and publish them via a LightSwitch Extension package.
A LightSwitch screen represents and independent unit of work on the client. Each screen has isolated state from other screens-there is no shared data between screens. Multiple screens can be opened simultaneously, but the shell keeps track of a single active screen.
LightSwitch screens are made up of three layers of objects that follow the MVVM pattern. The screen object is the model, encapsulating the data and business logic. The screen layout defines a tree of view-models that define the logical layout of the screen. And the visual tree is the resulting presentation view. The visual tree is made up of actual Silverlight controls that are data-bound to the view-models in the screen layout.
The screen object is a LightSwitch business object that contains the data and business logic for a given screen-but no UI. It defines a sort of data-type for the screen’s internal state. At design-time, developer writes code against the screen object’s class to provide custom business logic for the screen.
When defining a screen, the developer can add properties (scalar or entity values), parameters (scalar values), screen collections (query results), and commands. For each of these members, LightSwitch tracks detail properties such as display name, enable and read-only states, the data-loaded state, and validation results. The detail properties provide additional model state for the screen layout.
Each instance of a running screen owns its own isolated state. No two screens can share the same copy of an entity, but each can have its own copy. This is all managed by the screen’s data workspace which is described in more detail below.
The screen layout is a tree of content items. A content item is a view model that represents a single element on the screen. It forms a data-bindable bridge between the screen object (model) and the visual tree (view). Each content item may represent some data item, list, or command that is reachable from the screen object. It may also represent a container for child content items. Each content item specifies a control type and properties that govern the display behavior. The control type is just a name that will be mapped at runtime to an appropriate visual control.
When a screen is created, the LightSwitch runtime builds the screen layout tree dynamically by interpreting the screen layout model that was persisted via the screen designer at design-time. Content items are accessible programmatically from screen code for advanced scenarios. In most cases, you will not need to access it to define the business logic for a screen, but it may be useful to control the presentation.
Screen Visual Tree
The visual tree is the actual tree of Silverlight controls that present the screen. The LightSwitch developer typically does not interact with the visual tree. LightSwitch builds the visual tree dynamically by walking the screen layout tree at runtime. The layout strategy specified by each content item node is used to activate the right Silverlight control. The control is then given the content item as its
DataContext. The control binds to various properties of the content item in order to display such things as the current value, display name, or to determine child elements to display.
This dynamic layout strategy provides two important benefits. The first is professional 2D flow, spacing, control alignment, and automatic placement of labels. This is a huge time saver-no hours spent tweaking the layout margins, borders, alignment, etc. The second benefit is the ability to modify running screens when running from the LightSwitch tool. When the screen is in runtime design mode, the runtime editor simply modifies the screen layout and content items dynamically. The screen runtime can render the new visual tree dynamically and provide immediate live feedback to the developer.
Where’s my XAML?
If you area already familiar with using the Silverlight presentation framework, you might be wondering where XAML is. The short answer is-there isn’t any. LightSwitch developers aren’t required to know XAML to build screens. LightSwitch saves the logical layout of the screen and dynamically creates the visual tree based on Silverlight controls that are registered for each data and layout type.
When you need more control of the UI, you can drop down to the XAML level by plugging in just about any Silverlight control as the presenter for any content item. You can even replace the entire screen. This feature allows you to wire-up an off-the-shelf control or to write your own. If you write your own, you can still inject LightSwitch data presenters and get the built-in presentation support for LightSwitch data elements.
The data in a screen is managed by a data workspace. The data workspace tracks local copies of entities. An entity type represents a specific kind of data record. The entity encapsulates the business data, business logic, and durable storage for the data. The data workspace is responsible for fetching entities from a data service in the logic tier via entity queries, for tracking changes, and for sending updates back to data service for processing. The data workspace also tracks relationships between entities.
Data Service Client
When you add a data source to a LightSwitch application, we create a data service for accessing its data. The data services lives in the logic tier. The client accesses it via a data service client. A data workspace aggregates data from multiple sources by providing one data service client instance per data service.
For example, if you add a Northwind SQL data source and a MySharePoint data source, you’ll see one data service client for each in the data workspace.
The data service client manages the entities and tracks changes (change set) for the corresponding data service. LightSwitch manages the granularity of updates in the following way: One screen has one screen Save command which invokes one data service with one change set which runs in one update transaction.
The data service client exposes entity query operations and update operations. In the following examples “Northwind” is a data service client.
var customer = this.DataWorkspace.Northwind.Customers_Single(customerId);
Entities are the center of gravity for data and business logic in a LightSwitch application. These represent persistent business data such as a
Book or a
TimeCardEntry. In LightSwitch you can define new entity types and we will create the corresponding storage in SQL tables. Or you can import the schema from an existing data source and LightSwitch will define the corresponding entity types.
Entities encapsulate business logic including validation rules, data update rules, and have attributes that drive defaults for visualization and data entry. Entities are also shared between the client and the logic tier, so that you write business logic such as validation once and LightSwitch can execute it on both tiers.
In the client runtime, LightSwitch uses a pair of objects to represent an entity instance. There is a hidden, low-level data-transfer-object (DTO) that integrates with the WCF RIA Services client. A there is a public business object that encapsulates the business logic and provides view-model support for data binding. The section above regarding business objects applies. The entity has business rules for data validation, computed properties, and computed read-only status. The entity object has an API for getting to the simple data values on the entity and a more advanced API for accessing the validation status and various other attributes such as the default display name and description of each member.
LightSwitch models and manages relationships between entities. It supports cardinalities of many-to-one, many-to-zero-or-one, and one-to-zero-or-one. It does not support one-to-one and does not directly support many-to-many. You can, however, manage many-to-many relationships by using your own join entity.
LightSwitch creates navigation properties on entities to manage the entity or entities on the other side of the relationship. An entity reference property points to a single entity and an entity collection property points to many. Navigation properties encapsulate the details of foreign keys, so you don’t typically deal with those directly. LightSwitch handles loading the related entities on-demand when the navigation property is requested.
We are all familiar with relationships within a single data source. LightSwitch also supports inter-data-source relationships. These relationships cannot guarantee referential integrity, but they are very convenient for managing associated external data that shares a common value such as an ID or an email address.
LightSwitch models a table in the storage tier as an entity set in the logic tier. Each entity set is exposed by a data service client as a queriable entity sets and as query operations.
Query operations support query composition. This means that when the client invokes a query operation, it can send over additional query predicates to further refine the filter and ordering of the query. LightSwitch uses query composition to compose queries from screens and from LINQ queries in client-side business logic. Under the hood the LightSwitch client uses WCF RIA Services to create the query request and serialize it to a
DomainService on the logic tier.
The LightSwitch client attempts to parallelize query execution as much as possible to improve client response times. For example, when a single screen attempts to load multiple lists, the queries are executed asynchronously and in parallel. Likewise, when two different screens run, their queries (and updates) are executed in parallel. However, LightSwitch business logic code loads data from queries and navigation properties synchronously to keep the API simple to use and easy to understand.
We will look into what happens in the logic tier during a query operation in a later post.
The data service client exposes a single
SaveChanges operation. This operation packages up the change set for that data service and sends it to the logic tier for processing. LightSwitch implements a
DomainService (WCF RIA Services) in the logic tier to process the update.
LightSwitch ensures that the data is valid on the client before processing the SaveChanges operation. If the client data is valid, it is sent to the data service where it is validated again. If modified entities on the client have validation errors, the screen’s Save command is automatically disabled to prevent the end-user from attempting to save bad data.
Just as with entity queries, there is a lot going on in the logic tier to process the update. We will get into those details in a later post when we cover the logic tier.
We came, we saw, and we conquered the client. Well, actually, there is a lot more to be said about all of this. We’ve looked briefly at the shell, screens, and the data workspace. In our next posts we’ll look at the logic tier and the storage tier.