This example demonstrates how to access data protected by the Security System from a non-XAF Windows Forms App (.NET Core) using EF Core for data access. You will also learn how to execute Create, Read, Update, and Delete data operations taking into account security permissions.
-
Download and run our Unified Component Installer or add NuGet feed URL to Visual Studio NuGet feeds.
We recommend that you select all products when you run the DevExpress installer. It will register local NuGet package sources and item / project templates required for these tutorials. You can uninstall unnecessary components later.
NOTE
If you have a pre-release version of our components, for example, provided with the hotfix, you also have a pre-release version of NuGet packages. These packages will not be restored automatically and you need to update them manually as described in the Updating Packages article using the Include prerelease option.
-
Create a new .NET 6 WinForms application or use an existing application.
-
Add DevExpress NuGet packages to your project:
<PackageReference Include="DevExpress.Win.Grid" Version="22.2.3" /> <PackageReference Include="DevExpress.ExpressApp.EFCore" Version="22.2.3" /> <PackageReference Include="DevExpress.Persistent.BaseImpl.EFCore" Version="22.2.3" />
-
Install Entity Framework Core, as described in the Installing Entity Framework Core article.
-
Open the application configuration file (App.config). Add the following line to this file.
<add name="ConnectionString" connectionString="Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=EFCoreTestDB;Integrated Security=True;MultipleActiveResultSets=True"/>
NOTE
The Security System requires Multiple Active Result Sets in EF Core-based applications connected to the MS SQL database. Do not remove
MultipleActiveResultSets=True;
from the connection string or set theMultipleActiveResultSets
parameter tofalse
. -
Create an instance of
TypesInfo
required for the correct operation of the Security System.TypesInfo typesInfo = new TypesInfo();
-
Initialize the Security System.
namespace WindowsFormsApplication { static class Program { [STAThread] static void Main() { // ... AuthenticationStandard authentication = new AuthenticationStandard(); SecurityStrategyComplex security = new SecurityStrategyComplex(typeof(PermissionPolicyUser), typeof(PermissionPolicyRole), authentication, typesInfo); // ... } } }
-
Create a SecuredEFCoreObjectSpaceProvider object. It allows you to create a EFCoreObjectSpace to ensure a secured data access.
string connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString; var objectSpaceProvider = new SecuredEFCoreObjectSpaceProvider<ApplicationDbContext>(security, typesInfo, connectionString, (builder, connectionString) => builder.UseSqlServer(connectionString).UseChangeTrackingProxies());
-
Add a
CreateDemoData
method and call it at the beginning of theMain
method in Program.cs:private static void CreateDemoData(string connectionString, TypesInfo typesInfo) { using (var objectSpaceProvider = new EFCoreObjectSpaceProvider<ApplicationDbContext>(typesInfo, connectionString, (builder, connectionString) => builder.UseSqlServer(connectionString).UseChangeTrackingProxies())) using (var objectSpace = objectSpaceProvider.CreateUpdatingObjectSpace(true)) { new Updater(objectSpace).UpdateDatabase(); } }
For more details about how to create demo data from code, see the Updater.cs class.
LoginForm contains two TextEdit
controls for a user name and a password, and the Log In button that attempts to log the user into the security system and returns DialogResult.OK if a logon was successful.
private readonly SecurityStrategyComplex security;
private readonly IObjectSpaceProvider objectSpaceProvider;
public LoginForm(SecurityStrategyComplex security, IObjectSpaceProvider objectSpaceProvider, string userName) {
InitializeComponent();
this.security = security;
this.objectSpaceProvider = objectSpaceProvider;
userNameEdit.Text = userName;
}
private void Login_Click(object sender, EventArgs e) {
IObjectSpace logonObjectSpace = ((INonsecuredObjectSpaceProvider)objectSpaceProvider).CreateNonsecuredObjectSpace();
string userName = userNameEdit.Text;
string password = passwordEdit.Text;
security.Authentication.SetLogonParameters(new AuthenticationStandardLogonParameters(userName, password));
try {
security.Logon(logonObjectSpace);
DialogResult = DialogResult.OK;
Close();
}
catch(Exception ex) {
XtraMessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
-
In YourWinFormsApplication/Program.cs, create MainForm using a custom constructor with
SecurityStrategyComplex
/SecuredEFCoreObjectSpaceProvider
from Step 1.MainForm
is the MDI parent form for EmployeeListForm and EmployeeDetailForm.Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); MainForm mainForm = new MainForm(security, objectSpaceProvider); Application.Run(mainForm);
-
Display the LoginForm in the Form.Load event handler. If the dialog returns DialogResult.OK,
EmployeeListForm
is created.private void MainForm_Load(object sender, EventArgs e) { ShowLoginForm(); } private void ShowLoginForm(string userName = "User") { using(LoginForm loginForm = new LoginForm(Security, ObjectSpaceProvider, userName)) { DialogResult dialogResult = loginForm.ShowDialog(); if(dialogResult == DialogResult.OK) { CreateListForm(); Show(); } else { Close(); } } }
-
The
CreateListForm
method creates and showsEmployeeListForm
using a custom constructor withSecurityStrategyComplex
/SecuredEFCoreObjectSpaceProvider
from Step 1.private void CreateListForm() { EmployeeListForm employeeForm = new EmployeeListForm(security, objectSpaceProvider) { MdiParent = this, WindowState = FormWindowState.Maximized }; employeeForm.Show(); }
-
Handle the
ItemClick
event of the Log Out ribbon button to close all forms and sign out.private void LogoutButtonItem_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { foreach(Form form in MdiChildren) { form.Close(); } string userName = Security.UserName; Security.Logoff(); Hide(); ShowLoginForm(userName); }
Step 4. Authorization. Implement the List Form to Access and Manipulate Data/UI Based on User/Role Rights
-
EmployeeListForm contains a DevExpress Grid View that displays a list of all Employees. Handle the Form.Load event and do the following:
- Create a
SecuredEFCoreObjectSpace
instance to access protected data and use its data manipulation APIs (for instance,IObjectSpace.GetObjects
) OR if you prefer, the familiar DbContext object accessible through the EFCoreObjectSpace.DbContext property. - Set the grid's
DataSource
property to BindingList<Employee>. You can see the code of theGetBindingList<TEntity>
method in the ObjectSpaceHelper.cs file. - Call the
CanCreate
method to check the Create operation availability and thus determine whether the New button can be enabled.
private void EmployeeListForm_Load(object sender, EventArgs e) { securedObjectSpace = objectSpaceProvider.CreateObjectSpace(); // The EFCore way: // var dbContext = ((EFCoreObjectSpace)securedObjectSpace).DbContext; // // The XAF way: employeeGrid.DataSource = securedObjectSpace.GetBindingList<Employee>(); newBarButtonItem.Enabled = security.CanCreate<Employee>(); protectedContentTextEdit = new RepositoryItemProtectedContentTextEdit(); }
- Create a
-
Handle the GridView.CustomRowCellEdit event and check Read operation availability. If not available, use the
RepositoryItemProtectedContentTextEdit
object which displays the '*******' placeholder.private void GridView_CustomRowCellEdit(object sender, CustomRowCellEditEventArgs e) { string fieldName = e.Column.FieldName; object targetObject = employeeGridView.GetRow(e.RowHandle); if (!security.CanRead(targetObject, fieldName)) { e.RepositoryItem = protectedContentTextEdit; } }
Note that
SecuredEFCoreObjectSpace
returns default values (for instance,null
) for protected object properties - it is secure even without any custom UI. Use theSecurityStrategy.CanRead
method to determine when to mask default values with the '*******' placeholder in the UI. -
Handle the FocusedRowObjectChanged event and use the
SecurityStrategy.CanDelete
method to check the Delete operation availability and thus determine if the Delete button can be enabled.private void EmployeeGridView_FocusedRowObjectChanged(object sender, FocusedRowObjectChangedEventArgs e) { deleteBarButtonItem.Enabled = security.CanDelete(e.Row); }
-
Delete the current object in the deleteBarButtonItem.ItemClick event handler.
private void DeleteBarButtonItem_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { object cellObject = employeeGridView.GetRow(employeeGridView.FocusedRowHandle); securedObjectSpace.Delete(cellObject); securedObjectSpace.CommitChanges(); }
-
Create and show
EmployeeDetailForm
using a custom constructor with theEmployee
object andSecurityStrategyComplex
/SecuredEFCoreObjectSpaceProvider
from Step 1.private void CreateDetailForm(Employee employee = null) { EmployeeDetailForm detailForm = new EmployeeDetailForm(employee, security, objectSpaceProvider) { MdiParent = MdiParent, WindowState = FormWindowState.Maximized }; detailForm.Show(); detailForm.FormClosing += DetailForm_FormClosing; }
The application should create
EmployeeDetailForm
in three cases:-
When a user clicks the New button:
private void NewBarButtonItem_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { CreateDetailForm(); }
-
When a user clicks the Edit button that passes the current row handle as a parameter to the
CreateDetailForm
method:private void EditEmployee() { Employee employee = employeeGridView.GetRow(employeeGridView.FocusedRowHandle) as Employee; CreateDetailForm(employee); } private void EditBarButtonItem_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { EditEmployee(); }
-
When a user double-clicks a grid row:
private void EmployeeGridView_RowClick(object sender, RowClickEventArgs e) { if(e.Clicks == 2) { EditEmployee(); } }
-
Step 5. Authorization. Implement the Detail Form to Access and Manipulate Data/UI Based on User/Role Rights
-
EmployeeDetailForm contains detailed information on the Employee object. Perform the following operation in the Form.Load event handler:
- Create a
SecuredEFCoreObjectSpace
instance to get the current Employee object or create a new one. - Use the
SecurityStrategy.CanDelete
method to check the Delete operation availability and thus determine if the Delete button can be enabled (note that this button is always disabled if you create new object).
private void EmployeeDetailForm_Load(object sender, EventArgs e) { securedObjectSpace = objectSpaceProvider.CreateObjectSpace(); if(employee == null) { employee = securedObjectSpace.CreateObject<Employee>(); } else { employee = securedObjectSpace.GetObject(employee); deleteBarButtonItem.Enabled = security.CanDelete(employee); } AddControls(); }
- Create a
-
The
AddControls
method creates controls for all members declared in thevisibleMembers
dictionary.private Dictionary<string, string> visibleMembers; // ... public EmployeeDetailForm(Employee employee) { // ... visibleMembers = new Dictionary<string, string>(); visibleMembers.Add(nameof(Employee.FirstName), "First Name:"); visibleMembers.Add(nameof(Employee.LastName), "Last Name:"); visibleMembers.Add(nameof(Employee.Department), "Department:"); } // ... private void AddControls() { foreach(KeyValuePair<string, string> pair in visibleMembers) { string memberName = pair.Key; string caption = pair.Value; AddControl(dataLayoutControl1.AddItem(), employee, memberName, caption); } }
-
The
AddControl
method creates a control for a specific member. Use theSecurityStrategy.CanRead
method to check Read operation availability. If not available, create and disable theProtectedContentEdit
control which displays the '*******' placeholder. Otherwise:- Call the
GetControl
method to create an appropriate control depending on the member type. We use the LookUpEdit control for the associated Department property. - Add a binding to the Control.DataBindings collection.
- Use the
SecurityStrategy.CanWrite
method to check Write operation availability and thus determine whether the control should be enabled.
private void AddControl(LayoutControlItem layout, object targetObject, string memberName, string caption) { layout.Text = caption; Type type = targetObject.GetType(); BaseEdit control; if(security.CanRead(targetObject, memberName)) { control = GetControl(type, memberName); if(control != null) { control.DataBindings.Add( new Binding(nameof(BaseEdit.EditValue), targetObject, memberName, true, DataSourceUpdateMode.OnPropertyChanged)); control.Enabled = security.CanWrite(targetObject, memberName); } } else { control = new ProtectedContentEdit(); } dataLayoutControl1.Controls.Add(control); layout.Control = control; } private BaseEdit GetControl(Type type, string memberName) { BaseEdit control = null; ITypeInfo typeInfo = securedObjectSpace.TypesInfo.FindTypeInfo(type); IMemberInfo memberInfo = typeInfo.FindMember(memberName); if(memberInfo != null) { if(memberInfo.IsAssociation) { control = new LookUpEdit(); ((LookUpEdit)control).Properties.DataSource = securedObjectSpace.GetBindingList<Department>(); ((LookUpEdit)control).Properties.DisplayMember = nameof(Department.Title); LookUpColumnInfo column = new LookUpColumnInfo(nameof(Department.Title)); ((LookUpEdit)control).Properties.Columns.Add(column); } else { control = new TextEdit(); } } return control; }
- Call the
-
Use
SecuredEFCoreObjectSpace
to save all changes to the database in the saveBarButtonItem.ItemClick event handler and delete the current object in the deleteBarButtonItem.ItemClick event handler.private void SaveBarButtonItem_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { securedObjectSpace.CommitChanges(); Close(); } private void DeleteBarButtonItem_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { securedObjectSpace.Delete(employee); securedObjectSpace.CommitChanges(); Close(); }
.NET Core WinForms designer is in preview, so it is only possible to work with UI controls in code or use a workaround with linked files designed in .NET Framework projects (learn more). See also: .NET Core Support | WinForms Documentation.
- Log in under User with an empty password.
- Notice that secured data is displayed as "*******"
- Press the Log Out button and log in under Admin to see all records.