I wanted to try and follow up on this idea of the XAML Continuum and the basic example that I did with Silverlight and WPF versions of the Pong game by doing something that looked a bit more like a business application.
I figured that I’d take my trusty Northwind database and build some kind of functionality that let you maintain the Products table. Specifically;
- Display a grid with data from the Products table allowing CRUD against that table. Well, perhaps not CRUD because I don’t think the Products table is meant to support deletes (there’s a discontinued flag) and so maybe I’ll just do CRU rather than CRUD.
- Allow for searching against products.
- Allow for paging of the product data.
- Display appropriate lookups for the related entities – Supplier and Category.
- For a particular product, display a graph showing sales of that product over time.
- Have user roles that allow for users to be able to [View], [Update] and [See Sales Data].
- Pass all data over a secure transport to avoid prying eyes getting hold my user names, passwords and my sensitive Northwind data 🙂
I figured that’d do for the minute.
Ideally, I’d like to be able to make this work in both Silverlight and WPF and I figured I’d try and do this;
- Using the core framework
- Using the .NET RIA Services Framework
- Using the Prism framework
So I’ll start off just using the core framework and I’m going to need some web services and some data access code so I’ll go with WCF and LINQ to SQL for those things.
I also need a membership/role store and I can’t see any reason not to go with the existing database that ASP.NET Membership gives me.
This opens up the question of how to authenticate to the application and how to ensure privacy of data. I want to be able to;
- User user names and passwords
- Use declarative [PrincipalPermission] style demands on the server-side to control access to my operations.
The range of options is limited in that Silverlight doesn’t have support for WCF’s WS-Security and nor does it have support for sending HTTP authorization headers which makes it tricky to pass the security credentials that we need “out of band” of the actual payload for the service operations themselves.
I spent quite a bit of time on this. This article is very useful and I ultimately decided to do this with plain-old-ASP.NET authentication based on HTTP cookies. That is;
- Silverlight application puts up user name/password form.
- Silverlight application calls ASP.NET membership service over SSL to validate credentials and return HTTP ASP.NET Cookie.
- Silverlight application calls other services over SSL to perform CRUD and so (because it sits on the browser’s network stack) will pass on the ASP.NET Cookie.
- Server-side code picks up cookie and validates membership of the user prior to running operations.
and in the WPF version of the application I’ll have to make sure that I manually do (3) myself to pass the ASP.NET authentication cookie around.
The downside of this is that there will come a time when the cookie times-out and my application would have to do something about that.
To get going with this;
- I configured ASP.NET in order that membership and roles were enabled with a SQL store.
<system.web>
<membership userIsOnlineTimeWindow="30"/>
<roleManager enabled="true"/>
<authentication mode="Forms"/>
- I enabled the authenticationService to make that membership service over SSL.
<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled="true"
requireSSL="true"/>
</webServices>
</scripting>
</system.web.extensions>
- I plugged in an SVC file that makes that authenticationService available over WCF.
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceAuthorization principalPermissionMode="UseAspNetRoles">
<authorizationPolicies>
<add policyType="AuthPolicy, AuthBits"/>
</authorizationPolicies>
</serviceAuthorization>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="bindingConfig">
<security mode="Transport"/>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="ServiceBehavior"
name="System.Web.ApplicationServices.AuthenticationService">
<endpoint address=""
binding="basicHttpBinding"
bindingNamespace="http://asp.net/ApplicationServices/v200"
contract="System.Web.ApplicationServices.AuthenticationService"
bindingConfiguration="bindingConfig"/>
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
</system.serviceModel>
Those steps are very well documented up here and the only difference that I had here was that I configured my services to operate over SSL rather than HTTP and I also hit a bit of a problem in that I did not seem to be able to get my Forms based identity to be passed correctly into my WCF code.
I banged my head against a brick wall here until I remembered this post of mine where I’d used a IAuthorizationPolicy implementation in order to ensure that the HTTP based identify is flowed into WCF’s view of identity – I think I got this originally from Dominick and it took me a long time to remember that it was necessary here but I plugged that in ( as you can see in the config above ) and that sorted that issue for me.
I defined some users and roles for my application ( using the ASP.NET configuration site );
- users ( can see the product data but not edit it or view sales data ) and I added a user ursula
- viewers ( as users but can also view historical sales data ) and I added a user veronica
- editors ( can edit the product data but not view historical sales data ) and I added a user eric
- I also added a user called nobody who wasn’t in any of these roles but is still in the membership database and a user mike who was in all the roles.
With that in place, I’ve got a membership service available over HTTPS to Silverlight.
If I was working purely in WPF then I might be application to use the “Client Application Services” version of the Membership class to make those services easily programmable on the client side ( see video here ) but in Silverlight (AFAIK) I’ve got to roll something myself. So, I created a class library for Security and added a service reference to the ASP.NET service ( I did encounter some gremlins where Add Service Reference seems to somehow add pieces of config that are incompatible with Silverlight here but I equally found it wasn’t too hard to just edit them out afterwards );
public static class Security { public static event EventHandler LoginSucceeded; public static event EventHandler LoginFailed; public static void LoginUser(string userName, string password) { AuthenticationServiceClient proxy = new AuthenticationServiceClient(); proxy.LoginCompleted += (s, e) => { if ((e.Error == null) && (!e.Cancelled) && e.Result) { IsLoggedIn = true; SecurityRoleManager.LoadAsync(); if (LoginSucceeded != null) { LoginSucceeded(null, null); } } else if (LoginFailed != null) { LoginFailed(null, null); } }; proxy.LoginAsync(userName, password, null, false); } internal static bool IsLoggedIn { get; set; } }
but I also wanted to be able to do some data-binding based on login status – that is, I might want to enable/disable UI based on login status and so I wrote this class to try and help out with that;
public class DynamicLoginStatus : PropertyChangeNotifier { public DynamicLoginStatus() { IsLoggedIn = Security.IsLoggedIn; Security.LoginSucceeded += OnLoginSucceeded; } void OnLoginSucceeded(object sender, EventArgs e) { IsLoggedIn = true; } public bool IsLoggedIn { get { return (loggedIn); } set { loggedIn = value; FirePropertyChanged("IsLoggedIn"); } } bool loggedIn; }
where PropertyChangeNotifier is just a base class that implements INotifyPropertyChanged for me. That means that I can then declare one of these DynamicLoginStatus in a piece of XAML ( I declare mine in App.xaml as a resource );
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SilverlightApplication6.App"
xmlns:sec="clr-namespace:Security;assembly=Security"
xmlns:ut="clr-namespace:Utilities;assembly=Utilities">
<Application.Resources>
<sec:DynamicLoginStatus
x:Key="logonStatus" />
and then I can bind some UI element’s visibility to it as in;
<local:LoginControl
Visibility="{Binding Source={StaticResource logonStatus},Path=IsLoggedIn, Converter={StaticResource visibilityInverter}}"
where that Converter is just converting a Boolean to a Visibility for me.
I also wanted to make the ASP.NET role service available as well as in;
<system.web.extensions>
<scripting>
<webServices>
<authenticationService enabled="true"
requireSSL="true"/>
<roleService enabled="true"/>
</webServices>
</scripting>
</system.web.extensions>
and then the service;
<service name="System.Web.ApplicationServices.RoleService"
behaviorConfiguration="ServiceBehavior">
<endpoint contract="System.Web.ApplicationServices.RoleService"
binding="basicHttpBinding"
bindingConfiguration="bindingConfig"
bindingNamespace="http://asp.net/ApplicationServices/v200"/>
</service>
and then a bit of code to expose it ( you might notice that this is invoked after login up above and we don’t actually consider ourselves logged in until the roles have also been loaded );
internal static class SecurityRoleManager { internal static event EventHandler RolesLoaded; internal static void LoadAsync() { RoleServiceClient proxy = new RoleServiceClient(); proxy.GetRolesForCurrentUserCompleted += (s, e) => { if ((e.Error == null) && (!e.Cancelled)) { Roles = e.Result.ToList(); if (RolesLoaded != null) { RolesLoaded(null, null); } } }; proxy.GetRolesForCurrentUserAsync(); } internal static bool IsInRole(string role) { return ((Roles != null) && (Roles.Contains(role))); } internal static List<string> Roles { get; set; } }
once again – I wanted to have something that made it easy to be able to enable/disable features based on role membership so for my simple app I added another class;
public class DynamicRoleStatus : PropertyChangeNotifier { public DynamicRoleStatus() { SecurityRoleManager.RolesLoaded += OnRolesLoaded; } void OnRolesLoaded(object sender, EventArgs e) { FirePropertyChanged("IsUser"); FirePropertyChanged("IsEditor"); FirePropertyChanged("IsViewer"); } public bool IsUser { get { return (SecurityRoleManager.IsInRole("users")); } } public bool IsEditor { get { return (SecurityRoleManager.IsInRole("editors")); } } public bool IsViewer { get { return (SecurityRoleManager.IsInRole("viewers")); } } public bool IsInRole(string role) { return (SecurityRoleManager.IsInRole(role)); } }
and I can then declare one of those in my App.xaml as;
<sec:DynamicRoleStatus
x:Key="roleStatus" />
and then use it in various places to enable/disable functionality based on role. Note – I think this is a bit “brittle” to be honest and I’d probably move towards some kind of commanding architecture but it’s just a sample rather than something I’m about to sell or maintain over time 🙂 Anyway an example of using this to make a DataGrid enabled and/or read-only based on role membership.
<df:DataGrid
IsEnabled="{Binding Source={StaticResource roleStatus},Path=IsUser}"
IsReadOnly="{Binding Source={StaticResource roleStatus},Path=IsEditor,Converter={StaticResource booleanInverter}}"
On top of these services, I built a simple UserControl that manages login with just a XAML based definition of a couple of TextBlocks and TextBox/Password and that control has its visibility bound to DynamicLoginStatus.IsLoggedIn and the rest of my UI is bound to the inverse of that property. The control fires events for LoggedIn and LoginFailed so that the user of the control doesn’t have to know anything about the underlying Security class.
With my login control in place, I figure that I want 4/5 controls for my app;
- LoginControl – visible initially and until user logs in, fires LoggedIn and LoginFailed events.
- SearchControl – simple control with a TextBox and a Button. Fires a SearchTermChanged event when the user clicks the button. Event carries the new search term.
- ChartControl – simple control that wraps up a chart from the Silverlight Toolkit and displays chart data for a particular product selection.
- GridControl – displays all products that match a search term ( originates with the SearchControl ). Allows;
- Editing of the data ( to editors role )
- Viewing of the historical data for a particular product ( to the viewers role ) by firing an event requesting viewing of a particular product.
- Paging of data displaying “Page N of M” and allowing for Next/Prev navigation by firing an event requesting next/previous page.
- Inserting of a new product by firing an event requesting insert.
- Saving of changes made to products by firing an event requesting save of data.
I want the ChartControl and the GridControl to be data-bound and I’ve gone with this “event-based” mechanism so that they don’t have to write explicit code against any data. They just fire events and code elsewhere deals with the result of those events, updates the data and data-binding will update any UI that needs updating. This could probably be formalised into some kind of model/view/commanding infrastructure but I’ll not do that explicitly here but it feels like it follows “similar” lines to me.
I can see a need for a bunch of web service operations along the lines of;
- Login/Roles ( already done by ASP.NET 🙂 )
- GetProducts ( pageNumber, pageSize, searchTerm ). Roles for this would be “users”.
- GetReferenceData (). Roles for this would be “users”.
- UpdateModifiedProducts ( products ) . Roles for this would be “users” and “editors”.
- InsertNewProducts( products ). Roles for this would be “users” and “editors”.
- GetChartData( product). Roles for this would be “users” and “viewers”.
GetReferenceData – a lot of applications have lookups. I’ve decided here that for my purposes the foreign key values to the Supplier and Category tables are just static lookups so I want to load this data early and I want to load it once and I want to present it nicely to the user ( e.g. ComboBox in my grid which displays the available options ).
UpdateModifiedProducts – this would need to deal with concurrency violations as reported from LINQ to SQL and that would probably mean;
- being able to pass back the original data along with any modified data ( unless I want to go for last-write-wins which I don’t )
- being able to determine which records have/haven’t been modified by the server
In the next post I’ll set about building some services to underpin the application…I’ll publish the source when it’s a bit further evolved.