Middle Tier Security - .NET Remoting Service

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 .NET Remoting. 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.

MiddleTier_Diagram

Note
  • The Middle Tier does not support the Entity Framework data model, it is for XPO only.
  • The Middle Tier does not make a lot of sense for an ASP.NET application from a logical, maintenance and performance point of view. A web application is a client-server setup by default. However, it is technically possible to use Middle Tier with ASP.NET applications, which is demonstrated below.
  • The Middle Tier and the database engine can be physically located on the same server. Technically, the application server can also be installed at the end-user workstation together with the application itself, but such a configuration makes no sense from a security standpoint.
Tip

A complete sample project is available in the DevExpress Code Examples database at http://www.devexpress.com/example=E4035.

Create and Configure the Server

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 v18.2 XAF Solution Wizard.

  • Right-click the solution in the Solution Explorer.
  • In the invoked context menu, choose Add | New Project....
  • Choose the DevExpress v18.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) 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.

Modifications in the Program.cs (Program.vb) file are:

  • 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. To add modules in to your Module projects, add platform-dependent modules to the ServerApplication.Modules collection. Adding a 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 Hashtable object, which is later passed to the TcpChannel object's constructor.

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();

        // ...
        IDictionary t = new Hashtable();
        t.Add("port", 1425);
        t.Add("secure", true);
        t.Add("impersonate", true);

        TcpChannel channel = new TcpChannel(t, null, null);
        // ...
    }
    // ...
}
Tip

Remember to add 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

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>

Configure the Client

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.Runtime.Remoting.dll and DevExpress.ExpressApp.Security.v18.2.dll assemblies, and modify the Program.cs (Program.vb) file.

using System.Collections;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using DevExpress.ExpressApp.Security.ClientServer;
using DevExpress.ExpressApp.Security.ClientServer.Remoting;
// ...
static void Main() {
    // ...
    MySolutionWindowsFormsApplication winApplication = 
        new MySolutionWindowsFormsApplication();

    string connectionString = "tcp://localhost:1425/DataServer";
    try {
        Hashtable t = new Hashtable();
        t.Add("secure", true);
        t.Add("tokenImpersonationLevel", "impersonation");
        TcpChannel channel = new TcpChannel(t, null, null);
        ChannelServices.RegisterChannel(channel, true);
        IDataServer clientDataServer = (IDataServer)Activator.GetObject(
            typeof(RemoteSecuredDataServer), connectionString);
        ServerSecurityClient securityClient = 
            new ServerSecurityClient(clientDataServer, new ClientInfoFactory());
        securityClient.IsSupportChangePassword = true;
        winApplication.ApplicationName = "MySolution";
        winApplication.Security = securityClient;
        winApplication.CreateCustomObjectSpaceProvider += 
            delegate(object sender, CreateCustomObjectSpaceProviderEventArgs e) {
                e.ObjectSpaceProvider = 
                new DataServerObjectSpaceProvider(clientDataServer, securityClient);
            };
        winApplication.Setup();
        winApplication.Start();
    }
    catch(Exception e) {
        winApplication.HandleException(e);
    }
}

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 on the server side, set this property to true. If 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 does not grant 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 accomplished in the default application project). Here, the connection is hardcoded for the sake of simplicity.

An ASP.NET application is configured in a similar way. Edit the Global.asax.cs (Global.asax.vb) file.

using System.Collections;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using DevExpress.ExpressApp.Security.ClientServer;
using DevExpress.ExpressApp.Security.ClientServer.Remoting;
// ...
protected void Application_Start(Object sender, EventArgs e) {
    ASPxWebControl.CallbackError += new EventHandler(Application_Error);
    string connectionString = "tcp://localhost:1425/DataServer";
    Hashtable t = new Hashtable();
    t.Add("secure", true);
    t.Add("tokenImpersonationLevel", "impersonation");
    TcpChannel channel = new TcpChannel(t, null, null);
    ChannelServices.RegisterChannel(channel, true);
    this.Application["DataServer"] = Activator.GetObject(
        typeof(RemoteSecuredDataServer), connectionString);
}
protected void Session_Start(Object sender, EventArgs e) {
    WebApplication.SetInstance(Session, new MySolutionAspNetApplication());
    IDataServer clientDataServer = (IDataServer)this.Application["DataServer"];
    ServerSecurityClient securityClient = new ServerSecurityClient(
        clientDataServer, new ClientInfoFactory());
    WebApplication.Instance.ApplicationName = "MySolution";
    WebApplication.Instance.Security = securityClient;
    WebApplication.Instance.CreateCustomObjectSpaceProvider += 
        delegate(object _sender, CreateCustomObjectSpaceProviderEventArgs args) {
            args.ObjectSpaceProvider = new DataServerObjectSpaceProvider(
                clientDataServer, securityClient);
    };
    WebApplication.Instance.Setup();
    WebApplication.Instance.Start();
}

When the application server is in use, the compatibility check is performed on 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.");
        }
    }
}

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;
    }
}

A reference to the DevExpress.ExpressApp.Security.v18.2 assembly is required to compile the code above.

Please note that 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.

Troubleshooting

If "The application cannot connect to the specified database" error occurs on 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.v18.2). Local version: 18.2.3.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.

Enable Server Logs

The Application Server does not write logs by default. However, you can easily enable the logging functionality. The How to: Enable Logging in the Application Server topic describes how to:

  • write logs to the system event log;
  • write logs to the text file;
  • send e-mail alerts;
  • write logs to the server console.

Alternatively, you can use the WCF service instead of Remoting. Proceed to the Middle Tier Security - WCF Service topic to learn how to use WCF.

See Also