An XAF test environment usually uses a non-production database. Developers often populate such databases with initial security roles. They use a runtime administrative UI to do this since the visual approach is often faster than writing code, especially for complex permissions with criteria. At some point, developers may need to transfer role data to production databases on customer sites.
This example relies on standard XAF mechanisms that help you seed initial data in databases: ModuleUpdater API and DBUpdater tool.
The transfer mechanism suggested in this solution works as follows: you embed controllers from this example into your test application, and these embedded controllers can analyze database content and generate ModuleUpdater
code for required roles. You can then copy and paste this code into your production project's ModuleUpdater
descendant. Use the standard DBUpdater
tool to seed data in the database.
The example intentionally skips user creation code. User names are often unknown at early stages which simplifies creating and linking users to predefined roles later in a production environment.
If this solution is not suitable, you can use one of the following alternatives:
- Save the data records from the development database in an XML file and then load this XML file in an application that uses the production database.
- Transfer data using built-in RDBMS capabilities.
For more information, see the following Support Center ticket: Security - Best Practices for Export/Import Role Permissions at runtime (without releasing a new application version to clients). Please note that this solution is applicable only if you use XPO.
Note
You can find a solution for .NET Framework, Web Forms, and VB.NET in branch 20.1.3.
Implementation Details
- In the Solution Explorer, include RoleGenerator.csproj in your XAF solution.
- In the YourSolutionName.Module project, add a reference to the RoleGenerator project.
- Add the following files to your XAF solution projects:
- YourSolutionName.Module: RoleGeneratorController.cs
- YourSolutionName.Win: RoleGeneratorControllerWin.cs
- YourSolutionName.Blazor.Server: RoleGeneratorControllerBlazor.cs
- Modify the CS/EFCore/GenerateRoleEF/GenerateRoleEF.Win/App.config and YourSolutionName.Blazor.Server/appsettings.json files to add the
EnableRoleGeneratorAction
key.XML<appSettings> ... <add key="EnableRoleGeneratorAction" value="True" /> </appSettings>
JSON"EnableRoleGeneratorAction": "True",
- Run the YourSolutionName.Win or YourSolutionName.Blazor.Server project, select the roles in the
Role
List View, and click theGenerate Role
Action (in the WinForms project, you can find this Action in the Tools menu).
ASP.NET Core Blazor
Windows Forms
- Save the generated file. It contains code that creates initial roles based on the data stored in your test database. To use this file in your XAF solution, consider one of the following techniques:
- Modify the existing YourSolutionName.Module/DatabaseUpdate/Updater.xx file based on the
CreateUsersRole
method code copied from the generated Updater.xx file. - Include the generated Updater.xx file into the YourSolutionName.Module/DatabaseUpdate folder and modify the YourSolutionName/Module.cs file to use this new
RoleUpdater
class as follows:
C#// C# using System; using DevExpress.ExpressApp; using System.Collections.Generic; using DevExpress.ExpressApp.Updating; namespace YourSolutionName.Module { public sealed partial class YourSolutionNameModule : ModuleBase { public override IEnumerable<ModuleUpdater> GetModuleUpdaters(IObjectSpace objectSpace, Version versionFromDB) { ModuleUpdater updater = new DatabaseUpdate.Updater(objectSpace, versionFromDB); ModuleUpdater roleUpdater = new RoleUpdater(objectSpace, versionFromDB); return new ModuleUpdater[] { updater, roleUpdater }; //...
- Modify the existing YourSolutionName.Module/DatabaseUpdate/Updater.xx file based on the
Note
In the ASP.NET Core Blazor application, the file is saved to the Documents folder by default. You can change this behavior by overriding theRoleGeneratorControllerBlazor.SaveFile
method.
Customization for Custom Security Roles
You can use a custom security role class. For example, ExtendedSecurityRole
implementations are available in the following examples: Implement a Custom Security System User Based on an Existing Business Class, Implement Custom Security Objects (Users, Roles, Operation Permissions). If a security role has custom properties, you need to include these properties in the generated code. To do this, handle the RoleGenerator.CustomizeCodeLines
event in the RoleGeneratorController
class added in Step 2:
C#// C#
using System.Collections.Generic;
using System.Linq;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using DevExpress.Persistent.Base;
using RoleGeneratorSpace;
namespace XafSolution.Module.Controllers {
public abstract class RoleGeneratorController : ViewController<ListView> {
//...
protected void RoleGeneratorAction_Execute(object sender, SimpleActionExecuteEventArgs e) {
RoleGenerator roleGenerator = new RoleGenerator(roleType);
roleGenerator.CustomizeCodeLines += RoleGenerator_CustomizeCodeLines;
IEnumerable<IPermissionPolicyRole> roleList = e.SelectedObjects.OfType<IPermissionPolicyRole>();
string updaterCode = roleGenerator.GetUpdaterCode(roleList);
SaveFile(updaterCode);
}
private void RoleGenerator_CustomizeCodeLines(object sender, CustomizeCodeLinesEventArg e) {
ExtendedSecurityRole exRole = e.Role as ExtendedSecurityRole;
if(exRole != null) {
e.CustomCodeLines.Add(string.Format("role.CanExport = {0};", exRole.CanExport.ToString().ToLowerInvariant()));
}
}
}
}
[!WARNING]
We created this example for demonstration purposes and it is not intended to address all possible usage scenarios.
You can extend this example or change its behavior as needed. This can be a complex task that requires good knowledge of XAF: UI Customization Categories by Skill Level, and you might also need to research the internal architecture of DevExpress components. Refer to the following help topic for more information: Debug DevExpress .NET Source Code with PDB Symbols.
We are unable to help with such tasks. Custom programming is outside our Support Service scope: Technical Support Scope.
Files to Review
- RoleGeneratorController.cs
- RoleGeneratorControllerWin.cs
- RoleGeneratorControllerBlazor.cs
- RoleGenerator
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
Code<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Remove="UpdaterRuntimeTemplate.tt" />
</ItemGroup>
<ItemGroup>
<Content Include="UpdaterRuntimeTemplate.tt">
<Generator>TextTemplatingFilePreprocessor</Generator>
<LastGenOutput>UpdaterRuntimeTemplate.cs</LastGenOutput>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="DevExpress.ExpressApp" Version="24.2.1-alpha-24207" />
<PackageReference Include="DevExpress.ExpressApp.CodeAnalysis" Version="24.2.1-alpha-24207" />
<PackageReference Include="DevExpress.ExpressApp.ConditionalAppearance" Version="24.2.1-alpha-24207" />
<PackageReference Include="DevExpress.ExpressApp.Security" Version="24.2.1-alpha-24207" />
<PackageReference Include="DevExpress.ExpressApp.Validation" Version="24.2.1-alpha-24207" />
<PackageReference Include="DevExpress.Persistent.Base" Version="24.2.1-alpha-24207" />
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<Compile Update="UpdaterRuntimeTemplate.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>UpdaterRuntimeTemplate.tt</DependentUpon>
</Compile>
</ItemGroup>
</Project>
C#using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Linq;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using DevExpress.ExpressApp.Security;
using DevExpress.Persistent.Base;
using DevExpress.Persistent.BaseImpl.EF.PermissionPolicy;
namespace XafSolution.Module.Controllers {
public abstract class RoleGeneratorController : ViewController<ListView> {
private Type roleType;
private IContainer components = null;
protected SimpleAction roleGeneratorAction;
public RoleGeneratorController() {
InitializeComponent();
TargetObjectType = typeof(IPermissionPolicyRole);
}
private void InitializeComponent() {
components = new Container();
roleGeneratorAction = new SimpleAction(components);
roleGeneratorAction.Caption = "Generate Role";
roleGeneratorAction.Category = "Tools";
roleGeneratorAction.ConfirmationMessage = null;
roleGeneratorAction.Id = "RoleGeneratorAction";
roleGeneratorAction.ImageName = "Action_Export";
roleGeneratorAction.ToolTip = null;
roleGeneratorAction.Execute += new SimpleActionExecuteEventHandler(RoleGeneratorAction_Execute);
roleGeneratorAction.SelectionDependencyType = SelectionDependencyType.RequireMultipleObjects;
Actions.Add(roleGeneratorAction);
}
protected override void OnActivated() {
base.OnActivated();
SecurityStrategy security = Application.GetSecurityStrategy();
roleType = ((SecurityStrategyComplex)(Application.Security)).RoleType;
roleGeneratorAction.Active["ActionExecuted"] = IsEnableRoleGeneratorAction() && security.CanRead(roleType);
}
protected void RoleGeneratorAction_Execute(object sender, SimpleActionExecuteEventArgs e) {
RoleGeneratorSpace.RoleGenerator roleGenerator = new RoleGeneratorSpace.RoleGenerator(roleType);
IEnumerable<IPermissionPolicyRole> roleList = e.SelectedObjects.OfType<IPermissionPolicyRole>();
var reloadedRoles=new List<IPermissionPolicyRole>();
var os = Application.CreateObjectSpace(typeof(PermissionPolicyRole));
foreach (var role in roleList) {
var reloadedRole=os.GetObject(role);
reloadedRoles.Add(reloadedRole);
}
string updaterCode = roleGenerator.GetUpdaterCode(reloadedRoles);
SaveFile(updaterCode);
}
protected abstract void SaveFile(string updaterCode);
protected abstract bool IsEnableRoleGeneratorAction();
}
}
C#using System;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using XafSolution.Module.Controllers;
namespace XafSolution.Module.Win.Controllers {
public class RoleGeneratorControllerWin : RoleGeneratorController {
protected override void SaveFile(string updaterCode) {
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "cs|*.cs";
if(saveFileDialog.ShowDialog() == DialogResult.OK) {
using(StreamWriter file = new StreamWriter(saveFileDialog.FileName, false)) {
file.Write(updaterCode);
}
}
}
protected override bool IsEnableRoleGeneratorAction() {
string enableRoleGeneratorActionString = ConfigurationManager.AppSettings["EnableRoleGeneratorAction"];
return enableRoleGeneratorActionString == null ? false : bool.Parse(enableRoleGeneratorActionString);
}
}
}
C#using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Blazor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
using XafSolution.Module.Controllers;
namespace XafSolution.Module.Blazor.Controllers {
public class RoleGeneratorControllerBlazor : RoleGeneratorController {
protected override void SaveFile(string updaterCode) {
//Place your save logic here
try {
var folder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var path = Path.Combine(folder, "RoleUpdater.cs");
File.WriteAllText(path, updaterCode);
}
catch {
throw new UserFriendlyException("An error occurred while saving the file. Please check the directory you are saving to.");
}
}
protected override bool IsEnableRoleGeneratorAction() {
IConfiguration configuration = ((BlazorApplication)Application).ServiceProvider.GetRequiredService<IConfiguration>();
string enableRoleGeneratorActionString = configuration.GetSection("EnableRoleGeneratorAction").Value;
return enableRoleGeneratorActionString == null ? false : bool.Parse(enableRoleGeneratorActionString);
}
}
}
Code<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System">
<section name="DevExpress.LookAndFeel.Design.AppSettings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<DevExpress.LookAndFeel.Design.AppSettings>
<setting name="DPIAwarenessMode" serializeAs="String">
<value>System</value>
</setting>
</DevExpress.LookAndFeel.Design.AppSettings>
</applicationSettings>
<appSettings>
<add key="EnableRoleGeneratorAction" value="True" />
<add key="Modules" value="" />
<add key="NewVersionServer" value="" />
<add key="EnableDiagnosticActions" value="False" />
<!--
Use the one of predefined values: None, ApplicationFolder, CurrentUserApplicationDataFolder. The default value is ApplicationFolder.
<add key="TraceLogLocation" value="CurrentUserApplicationDataFolder"/>
<add key="UserModelDiffsLocation" value="CurrentUserApplicationDataFolder"/>
<add key="Languages" value="de;es;ja"/>
-->
<!-- Use one of the following predefined values: 0-Off, 1-Errors, 2-Warnings, 3-Info, 4-Verbose. The default value is 3. -->
<add key="eXpressAppFrameworkTraceLevel" value="3" />
</appSettings>
<connectionStrings>
<add name="EasyTestConnectionString" connectionString="Integrated Security=SSPI;MultipleActiveResultSets=true;Data Source=(localdb)\mssqllocaldb;Initial Catalog=GenerateRoleEFEasyTest" providerName="System.Data.SqlClient" />
<add name="ConnectionString" connectionString="Integrated Security=SSPI;MultipleActiveResultSets=True;Data Source=(localdb)\mssqllocaldb;Initial Catalog=GenerateRoleEF" providerName="System.Data.SqlClient" />
<!--
Use the following connection string to connect to a Jet (Microsoft Access) database:
<add name="ConnectionString" connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Password=;User ID=Admin;Data Source=GenerateRoleEF.mdb;Mode=Share Deny None;"/>
-->
</connectionStrings>
</configuration>
JSON{
"ConnectionStrings": {
"ConnectionString": "Integrated Security=SSPI;Pooling=false;MultipleActiveResultSets=true;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=GenerateRoleEF",
"EasyTestConnectionString": "Integrated Security=SSPI;Pooling=false;MultipleActiveResultSets=true;Data Source=(localdb)\\mssqllocaldb;Initial Catalog=GenerateRoleEFEasyTest"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"DevExpress.ExpressApp": "Information"
}
},
"EnableRoleGeneratorAction": "True",
"AllowedHosts": "*",
"DevExpress": {
"ExpressApp": {
"Languages": "en-US;",
"ShowLanguageSwitcher": false,
"ThemeSwitcher": {
"DefaultItemName": "Office White", // This line is changed according to the following BC: https://supportcenter.devexpress.com/internal/ticket/details/t1090666,
"ShowSizeModeSwitcher": true,
"Groups": [
{
"Caption": "DevExpress Themes",
"Items": [
{
"Caption": "Blazing Berry",
"Url": "_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css",
"Color": "#5c2d91"
},
{
"Caption": "Blazing Dark",
"Url": "_content/DevExpress.Blazor.Themes/blazing-dark.bs5.min.css",
"Color": "#46444a"
},
{
"Caption": "Office White",
"Url": "_content/DevExpress.Blazor.Themes/office-white.bs5.min.css",
"Color": "#fe7109"
},
{
"Caption": "Purple",
"Url": "_content/DevExpress.Blazor.Themes/purple.bs5.min.css",
"Color": "#7989ff"
}
]
}
]
}
}
}
}
C#using System.ComponentModel;
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Base;
using DevExpress.ExpressApp.Model;
using DevExpress.ExpressApp.Actions;
using DevExpress.ExpressApp.Editors;
using DevExpress.ExpressApp.Updating;
using DevExpress.ExpressApp.Model.Core;
using DevExpress.ExpressApp.Model.DomainLogics;
using DevExpress.ExpressApp.Model.NodeGenerators;
namespace GenerateRoleEF.Module;
// For more typical usage scenarios, be sure to check out https://docs.devexpress.com/eXpressAppFramework/DevExpress.ExpressApp.ModuleBase.
public sealed class GenerateRoleEFModule : ModuleBase {
public GenerateRoleEFModule() {
//
// GenerateRoleEFModule
//
AdditionalExportedTypes.Add(typeof(GenerateRoleEF.Module.BusinessObjects.ApplicationUser));
AdditionalExportedTypes.Add(typeof(DevExpress.Persistent.BaseImpl.EF.PermissionPolicy.PermissionPolicyRole));
AdditionalExportedTypes.Add(typeof(DevExpress.Persistent.BaseImpl.EF.ModelDifference));
AdditionalExportedTypes.Add(typeof(DevExpress.Persistent.BaseImpl.EF.ModelDifferenceAspect));
RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.SystemModule.SystemModule));
RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.Security.SecurityModule));
RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.Objects.BusinessClassLibraryCustomizationModule));
RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.ConditionalAppearance.ConditionalAppearanceModule));
RequiredModuleTypes.Add(typeof(DevExpress.ExpressApp.Validation.ValidationModule));
DevExpress.ExpressApp.Security.SecurityModule.UsedExportedTypes = DevExpress.Persistent.Base.UsedExportedTypes.Custom;
}
public override IEnumerable<ModuleUpdater> GetModuleUpdaters(IObjectSpace objectSpace, Version versionFromDB) {
ModuleUpdater updater = new DatabaseUpdate.Updater(objectSpace, versionFromDB);
return new ModuleUpdater[] { updater };
}
public override void Setup(XafApplication application) {
base.Setup(application);
// Manage various aspects of the application UI and behavior at the module level.
}
}