Important
This topic demonstrates the code that can be generated automatically by the Solution Wizard. Proceed, if you want to implement the demonstrated functionality in the existing XAF solution. If you are creating a new XAF solution, use the wizard instead.
When the security engine is running in a client application, the database is exposed to a client workstation. An end-user can see the connection string in the application's configuration file and can use it to directly access the database tables, bypassing the security engine. This topic demonstrates how to place the Middle Tier application server between your application and the database server. The server will use Windows Communication Foundation (WCF). Restricted data will be filtered out by the server, and clients may have no direct access to the database server and may not even know its actual network location. The diagram below illustrates such a configuration.

While debugging, it is convenient to use the Application Server that is implemented as a simple console application. Later, you will be able to convert it to a Windows Service, which can be deployed in the production environment (see Run the Application Server as a Windows Service). To add the Console Application Server project to your XAF solution, use the Application Server Project template of the DevExpress v19.2 XAF Solution Wizard.
- Right-click the solution in the Solution Explorer.
- In the invoked context menu, choose Add | New Project....
- Choose the DevExpress v19.2 XAF Solution Wizard template.
- Specify the project name (e.g., MySolution.AppServer) and click OK.
- Select the Application Server Project in the Solution Wizard and click Next.
- Specify what type of authentication you use (Standard or Active Directory), choose the WCF Service database security type and click Finish.
The created application server will connect directly to your application's database, and handle initial database creation and updates. Therefore, the application server must be aware of the client application name, security system settings, modules used in the client application and the database connection string. To configure the newly added server, modify the following two files: Program.cs (Program.vb) and App.config.
Below is the list of required modifications to the Program.cs (Program.vb) file.
- The ServerApplication.ApplicationName property value. It should be the same as your client application name (i.e., XafApplication.ApplicationName);
- The ServerApplication.Modules collection. It should contain modules that are directly referenced by your client application. To see which client application modules are required, refer to the InitializeComponent method code in your WinApplication/WebApplication descendant. Add the required module projects references (e.g., MySolution.Module.Win and MySolution.Module.Web). Right-click the newly created Application Server project and choose Add reference.... In the invoked dialog, switch to the Projects tab, select module projects and click OK. Add platform-dependent modules to the ServerApplication.Modules collection. Adding of the platform-agnostic module is not required because platform-dependent modules include it.
- The network port to be listened to by the server. This setting is provided by the ServiceHost.AddServiceEndpoint method in the address parameter.
The snippet below illustrates these modifications in the Program.cs (Program.vb) file.
static void Main(string[] args) {
try {
// ...
ServerApplication serverApplication = new ServerApplication();
serverApplication.ApplicationName = "MySolution";
serverApplication.Modules.BeginInit();
serverApplication.Modules.Add(new DevExpress.ExpressApp.Security.SecurityModule());
serverApplication.Modules.Add(new MySolution.Module.Win.MySolutionWindowsFormsModule());
serverApplication.Modules.Add(new MySolution.Module.Win.MySolutionAspNetModule());
serverApplication.Modules.EndInit();
// ...
Func dataServerSecurityProvider = () => {
SecurityStrategyComplex security = new SecurityStrategyComplex(
typeof(PermissionPolicyUser), typeof(PermissionPolicyRole), new AuthenticationStandard());
security.SupportNavigationPermissionsForTypes = false;
return security;
};
serviceHost = new WcfXafServiceHost(connectionString, dataServerSecurityProvider);
serviceHost.AddServiceEndpoint(typeof(IWcfXafDataServer),
WcfDataServerHelper.CreateNetTcpBinding(), "net.tcp://localhost:1451/DataServer");
serviceHost.Open();
// ...
}
catch(Exception e) {
Console.WriteLine("Exception occurs: " + e.Message);
Console.WriteLine("Press Enter to close.");
Console.ReadLine();
}
}
Shared Sub Main(ByVal args() As String)
Try
' ...
Dim serverApplication As New ServerApplication()
serverApplication.ApplicationName = "MySolution"
serverApplication.Modules.BeginInit()
serverApplication.Modules.Add(New DevExpress.ExpressApp.Security.SecurityModule())
serverApplication.Modules.Add(New MySolution.Module.Win.MySolutionWindowsFormsModule())
serverApplication.Modules.Add(New MySolution.Module.Win.MySolutionAspNetModule())
serverApplication.Modules.EndInit()
' ...
Dim dataServerSecurityProvider As Func = Function()
Dim security As New SecurityStrategyComplex( _
GetType(PermissionPolicyUser), GetType(PermissionPolicyRole), New AuthenticationStandard())
security.SupportNavigationPermissionsForTypes = False
Return security
End Function
serviceHost = New WcfXafServiceHost(connectionString, dataServerSecurityProvider)
serviceHost.AddServiceEndpoint(GetType(IWcfXafDataServer), _
WcfDataServerHelper.CreateNetTcpBinding(), "net.tcp://localhost:1451/DataServer")
serviceHost.Open()
' ...
Catch e As Exception
Console.WriteLine("Exception occurs: " & e.Message)
Console.WriteLine("Press Enter to close.")
Console.ReadLine()
End Try
End Sub
Tip
Remember to add the required references to your module projects (e.g., MySolution.Module.Win and MySolution.Module.Web). Right-click the newly created Application Server project and choose Add reference.... In the invoked dialog, switch to the Projects tab, select module projects and click OK.
Note
- If you use custom permission requests, custom logon parameters, or other types to be serialized (for example, non-persistent objects), use the static WcfDataServerHelper.AddKnownType method to register them before a data server is initialized. Registered these types both at the server and client sides. Do not use this method to add business classes.
- If you want to use a custom Binding object, do not use the WcfDataServerHelper.CreateDefaultBinding method. Create the required Binding object yourself and pass it to the ServiceHost.AddServiceEndpoint method.
When AuthenticationActiveDirectory is used, all the methods of the application server should be invoked in the caller's context (a Windows account under which the client application is running). Refer to the Delegation and Impersonation with WCF and Security in Remoting articles in MSDN for more details on how this can be done, depending on the transport technology used. For instance, in the case of WCF, you can modify the ServiceAuthorizationBehavior.ImpersonateCallerForAllOperations property in the code of your service.
- The Security System environment is not changed in the scope of the InstanceContext object. A new Security System instance (including updated permissions from a database, new cleared caches, etc.) is created with each new InstanceContext object.
By default, the WCF server uses the InstanceContextMode.PerSession mode. The Security System creates a new session for each login and disposes of it with each logoff. If you want to change the InstanceContextMode, use another WcfXafServiceHost constructor that takes the InstanceContextMode parameter.
Finally, provide the connection string that will be used to access your database server in the App.config file.
<?xml version="1.0"?>
<configuration>
<connectionStrings>
<add name="ConnectionString" connectionString=
"Integrated Security=SSPI;Pooling=false;
Data Source=.\SQLEXPRESS;Initial Catalog=MySolution" />
</connectionStrings>
</configuration>
In contrast to client-side security, we will perform all configurations in code, without the use of the application designer. To configure the Windows Forms application, add a reference to System.ServiceModel.dll and DevExpress.ExpressApp.Security.v19.2.dll assemblies, and modify the Program.cs (Program.vb) file.
using System.ServiceModel;
using DevExpress.ExpressApp.Security.ClientServer.Wcf;
// ...
string connectionString = "net.tcp://localhost:1451/DataServer";
winApplication.DatabaseUpdateMode = DatabaseUpdateMode.Never;
WcfSecuredClient wcfSecuredClient = new WcfSecuredClient(WcfDataServerHelper.CreateNetTcpBinding(), new EndpointAddress(connectionString));
MiddleTierClientSecurity security = new MiddleTierClientSecurity(wcfSecuredClient);
security.IsSupportChangePassword = true;
winApplication.Security = security;
winApplication.CreateCustomObjectSpaceProvider +=
delegate(object sender, CreateCustomObjectSpaceProviderEventArgs e) {
e.ObjectSpaceProvider = new MiddleTierServerObjectSpaceProvider(wcfSecuredClient);
};
winApplication.Setup();
winApplication.Start();
wcfSecuredClient.Dispose();
Imports System.ServiceModel
Imports DevExpress.ExpressApp.Security.ClientServer.Wcf
' ...
Dim connectionString as String = "net.tcp://localhost:1451/DataServer"
winApplication.DatabaseUpdateMode = DatabaseUpdateMode.Never
Dim wcfSecuredClient As New WcfSecuredClient(WcfDataServerHelper.CreateNetTcpBinding(), New EndpointAddress(connectionString))
Dim security As New MiddleTierClientSecurity(wcfSecuredClient)
security.IsSupportChangePassword = True
winApplication.Security = security
AddHandler winApplication.CreateCustomObjectSpaceProvider, Sub(sender As Object, e As CreateCustomObjectSpaceProviderEventArgs) e.ObjectSpaceProvider = New MiddleTierServerObjectSpaceProvider(wcfSecuredClient)
winApplication.Setup()
winApplication.Start()
wcfSecuredClient.Dispose()
The ServerSecurityClient.IsSupportChangePassword property indicates whether or not user passwords can be changed using the ChangePasswordByUser and ResetPasswords Actions. If the AuthenticationStandard authentication is used at the server side, set this property to true. If the AuthenticationActiveDirectory is in use, there is no need to initialize the IsSupportChangePassword property as its default value is false. Note that this setting only influences the visibility of the ChangePasswordByUser and ResetPasswords Actions, and do not grant a write permission to the user's StoredPassword property. Create the corresponding member-level permission to allow non-administrative users to change their passwords.
Note
While debugging, the server host name is "localhost" in the connection string. Change the port number according to the server-side setting. You can also read the connection string from the configuration file using the ConfigurationManager object (as it is done in the default application project). Here, the connection is hardcoded for the sake of simplicity.
When the application server is in use, the compatibility check is performed at the server side. You should unconditionally throw an exception when the XafApplication.DatabaseVersionMismatch event occurs. Edit WinApplication.cs (WinApplication.vb) and WebApplication.cs (WebApplication.vb) files as follows.
public partial class MySolutionWindowsFormsApplication : WinApplication {
//...
private void MySolutionWindowsFormsApplication_DatabaseVersionMismatch(
object sender, DevExpress.ExpressApp.DatabaseVersionMismatchEventArgs e) {
throw new InvalidOperationException(
"The application cannot connect to the specified database " +
"because the latter does not exist or its version is older " +
"than that of the application.");
}
}
}
Partial Public Class MySolutionWindowsFormsApplication
Inherits WinApplication
' ...
Private Sub MySolutionWindowsFormsApplication_DatabaseVersionMismatch(ByVal sender As Object, _
ByVal e As DevExpress.ExpressApp.DatabaseVersionMismatchEventArgs) _
Handles MyBase.DatabaseVersionMismatch
Throw New InvalidOperationException( _
"The application cannot connect to the specified database because " & _
"the latter does not exist or its version is older than " & _
"that of the application.")
End Sub
End Class
Register types used by security within a platform-agnostic module. Edit the Module.cs (Module.vb) file in the following manner.
using DevExpress.Persistent.BaseImpl.PermissionPolicy;
// ...
public sealed partial class MySolutionModule : ModuleBase {
// ...
protected override IEnumerable<Type> GetDeclaredExportedTypes() {
List<Type> result = new List<Type>(base.GetDeclaredExportedTypes());
result.AddRange(new Type[] { typeof(PermissionPolicyUser), typeof(PermissionPolicyRole) });
return result;
}
}
Imports DevExpress.Persistent.BaseImpl.PermissionPolicy
' ...
Public NotInheritable Partial Class MySolutionModule
Inherits ModuleBase
' ...
Protected Overrides Function GetDeclaredExportedTypes() As IEnumerable(Of Type)
Dim result As New List(Of Type)(MyBase.GetDeclaredExportedTypes())
result.AddRange(New Type() { GetType(PermissionPolicyUser), GetType(PermissionPolicyRole) })
Return result
End Function
End Class
A reference to the DevExpress.ExpressApp.Security.v19.2 assembly is required to compile the code above.
Important
The application server does not pass the currently used role type to the client. This is the designed behavior. That is why the Role navigation item is not available by default. To add it, use the approach described in the Add an Item to the Navigation Control topic. (The required List View identifier is "PermissionPolicyRole_ListView" by default.)
Run both the Server and the Client.
Set the Application Server project as a startup in the Solution Explorer and run the server. You will see the console window with the following output:
Starting...
Setup...
CheckCompatibility...
Starting server...
Server is started. Press Enter to stop.
Note
If a Windows Security Alert dialog is also displayed, click Allow access within this dialog.
To run the client application, right-click the application project within the Solution Explorer and choose Debug | Start new instance. The image below illustrates the server and client.

Display Server-Side Exception Details in the Client Application
For debugging purposes, you may wish to enable the passing of server error information to the client. This will allow you to debug errors like "The server was unable to process the request due to an internal error". Modify the ServiceHost object initialization in the following manner.
WcfXafServiceHost serviceHost = new WcfXafServiceHost(connectionString, dataServerSecurityProvider);
ServiceDebugBehavior debug = serviceHost.Description.Behaviors.Find<ServiceDebugBehavior>();
if(debug == null) {
serviceHost.Description.Behaviors.Add(
new ServiceDebugBehavior() { IncludeExceptionDetailInFaults = true });
}
else {
if(!debug.IncludeExceptionDetailInFaults) {
debug.IncludeExceptionDetailInFaults = true;
}
}
Dim serviceHost As New WcfXafServiceHost(connectionString, dataServerSecurityProvider)
Dim debug As ServiceDebugBehavior = serviceHost.Description.Behaviors.Find(Of ServiceDebugBehavior)()
If debug Is Nothing Then
serviceHost.Description.Behaviors.Add(New ServiceDebugBehavior() With {.IncludeExceptionDetailInFaults = True})
Else
If Not debug.IncludeExceptionDetailInFaults Then
debug.IncludeExceptionDetailInFaults = True
End If
End If
Use this code only when debugging. Remove it before deploying your service to the production environment. For details, refer to the ServiceBehaviorAttribute.IncludeExceptionDetailInFaults Property topic in MSDN.
Troubleshooting
If "The application cannot connect to the specified database" error occurs at the client, please ensure that both client and server have the same modules set. Refer to the Visual Studio Output window content or see the client application log (eXpressAppFramework.log). For instance, the following message indicates that the SystemAspNetModule module is not added to the server's Modules collection. (The missing module version is displayed as "0.0.0.0".)
module 'SystemAspNetModule' (DevExpress.ExpressApp.Web.v19.2). Local version: 19.2.4.0, Version on DB: 0.0.0.0
To fix the issue, add this module to the ServerApplication.Modules collection as shown in the Create and Configure the Server section of this topic.
See Also