This example demonstrates how to access data protected by the Security System from a non-XAF Blazor application. You will also see how to execute Create, Write, and Delete data operations and take security permissions into account.
-
Visual Studio 2022 v17.0+ with the following workloads:
- ASP.NET and web development
- .NET Core cross-platform development
-
Download and run the 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
You may have a pre-release version of our components installed. For example, you may have downloaded a hotfix from our website. In such cases, you also installed a pre-release version of NuGet packages. These packages will not be restored automatically. Update them manually as described in the following article: Updating Packages. Use the Include prerelease option.
If you wish to create a new project with DevExpress Blazor Components, follow instructions in Create a New Blazor Application.
-
Add EFCore DevExpress NuGet packages to your project:
<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 following article: Installing Entity Framework Core.
-
For detailed information about ASP.NET Core application configuration, see official Microsoft documentation.
-
Configure your Blazor Application in Program.cs:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); builder.Services.AddServerSideBlazor(); builder.Services.AddDevExpressBlazor(); builder.Services.AddSession(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseSession(); app.UseHttpsRedirection(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); }); app.Run();
-
Use the Types Info system to register the business objects that you will access from code.
builder.Services.AddSingleton<ITypesInfo>((serviceProvider) => { TypesInfo typesInfo = new TypesInfo(); typesInfo.GetOrAddEntityStore(ti => new XpoTypeInfoSource(ti)); typesInfo.RegisterEntity(typeof(Employee)); typesInfo.RegisterEntity(typeof(PermissionPolicyUser)); typesInfo.RegisterEntity(typeof(PermissionPolicyRole)); return typesInfo; })
-
Register ObjectSpaceProviders that will be used in your application. To do this, implement the
IObjectSpaceProviderFactory
interface.builder.Services.AddScoped<IObjectSpaceProviderFactory, ObjectSpaceProviderFactory>() // ... public class ObjectSpaceProviderFactory : IObjectSpaceProviderFactory { readonly ISecurityStrategyBase security; readonly ITypesInfo typesInfo; readonly IDbContextFactory<ApplicationDbContext> dbFactory; public ObjectSpaceProviderFactory(ISecurityStrategyBase security, ITypesInfo typesInfo, IDbContextFactory<ApplicationDbContext> dbFactory) { this.security = security; this.typesInfo = typesInfo; this.dbFactory = dbFactory; } IEnumerable<IObjectSpaceProvider> IObjectSpaceProviderFactory.CreateObjectSpaceProviders() { yield return new SecuredEFCoreObjectSpaceProvider<ApplicationDbContext>((ISelectDataSecurityProvider)security, dbFactory, typesInfo); } }
-
Set up database connection settings in your Data Store Provider object (
DbContextFactory
in EFCore). Apply a security extension to the object - allow your application to filter data based on user permissions.builder.Services.AddDbContextFactory<ApplicationDbContext>((serviceProvider, options) => { string connectionString = builder.Configuration.GetConnectionString("ConnectionString"); options.UseSqlServer(connectionString); options.UseLazyLoadingProxies(); options.UseChangeTrackingProxies(); options.UseSecurity(serviceProvider); }, ServiceLifetime.Scoped);
The
IConfiguration
object is used to access the application configuration appsettings.json file. In appsettings.json, add the connection string."ConnectionStrings": { "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 MS SQL databases. We do not recommend that you remove “MultipleActiveResultSets=True;“ from the connection string or change the parameter value to False.
-
Register security system and authentication in Program.cs. AuthenticationStandard authentication and ASP.NET Core Identity authentication is registered automatically in AspNetCore Security setup.
builder.Services.AddXafAspNetCoreSecurity(builder.Configuration, options => { options.RoleType = typeof(PermissionPolicyRole); options.UserType = typeof(PermissionPolicyUser); }).AddAuthenticationStandard();
-
Call the
UseDemoData
method at the end of Program.cs to update the database:public static WebApplication UseDemoData(this WebApplication app) { using var scope = app.Services.CreateScope(); var updatingObjectSpaceFactory = scope.ServiceProvider.GetRequiredService<IUpdatingObjectSpaceFactory>(); using var objectSpace = updatingObjectSpaceFactory .CreateUpdatingObjectSpace(typeof(BusinessObjectsLibrary.BusinessObjects.Employee), true)); new Updater(objectSpace).UpdateDatabase(); return app; }
For more details about how to create demo data from code, see the Updater.cs class.
EditableEmployee is an edit model class for the Employee
business object.
public class EditableEmployee {
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public Department Department { get; set; }
}
You can use extension methods to easily convert an Employee
object to an EditableEmployee
edit model and vice versa.
public static class EmployeeExtensions {
public static EditableEmployee ToModel(this Employee employee) {
return new EditableEmployee {
FirstName = employee.FirstName,
LastName = employee.LastName,
Email = employee.Email,
Department = employee.Department
};
}
public static void FromModel(this EditableEmployee editableEmployee, Employee employee) {
employee.FirstName = editableEmployee.FirstName;
employee.LastName = editableEmployee.LastName;
employee.Email = editableEmployee.Email;
employee.Department = editableEmployee.Department;
}
}
Login.cshtml is a page that allows you to log into the application.
Login.cshtml.cs class uses IStandardAuthenticationService
from XAF Security System to implement the Login logic. It authenticates a user with the AuthenticationStandard authentication and returns a ClaimsPrincipal object with necessary XAF Security data. That principal is then authenticated to the ASP.NET Core Identity authentication.
readonly IStandardAuthenticationService authenticationStandard;
// ...
public IActionResult OnPost() {
Response.Cookies.Append("userName", Input.UserName ?? string.Empty);
if(ModelState.IsValid) {
ClaimsPrincipal principal = authenticationStandard.Authenticate(new AuthenticationStandardLogonParameters(Input.UserName, Input.Password));
if(principal != null) {
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
return Redirect("/");
}
ModelState.AddModelError("Error", "User name or password is incorrect");
}
return Page();
}
Logout.cshtml.cs implements the Logout logic.
public IActionResult OnGet() {
this.HttpContext.SignOutAsync();
return Redirect("/Login");
}
Index.razor is the main page. It configures Blazor Grid and allows a user to log out.
The OnInitialized
method creates security
and objectSpace
instances and gets Employee
and Department
objects.
protected override void OnInitialized() {
security = (SecurityStrategy)securityProvider.GetSecurity();
objectSpace = objectSpaceFactory.CreateObjectSpace<Employee>();
employees = objectSpace.GetObjectsQuery<Employee>();
departments = objectSpace.GetObjectsQuery<Department>();
base.OnInitialized();
}
The Grid_CustomizeEditModel
method creates the EditableEmployee
edit model.
void Grid_CustomizeEditModel(GridCustomizeEditModelEventArgs e) {
e.EditModel = e.IsNew ? new EditableEmployee() : ((Employee)e.DataItem).ToModel();
editableEmployee = (Employee)e.DataItem;
}
The Grid_EditModelSaving
method transfers edit model changes to the business object.
void Grid_EditModelSaving(GridEditModelSavingEventArgs e) {
Employee employee = e.IsNew ? objectSpace.CreateObject<Employee>() : (Employee)e.DataItem;
((EditableEmployee)e.EditModel).FromModel(employee);
UpdateData();
}
The Grid_DataItemDeleting
method removes an object.
void Grid_DataItemDeleting(GridDataItemDeletingEventArgs e) {
objectSpace.Delete(e.DataItem);
UpdateData();
}
The UpdateData
method commits changes and refreshes grid data.
void UpdateData() {
objectSpace.CommitChanges();
employees = objectSpace.GetObjectsQuery<Employee>();
editableEmployee = null;
}
To show/hide New
, Edit
, and Delete
actions, use the corresponding CanCreate
, CanEdit
, and CanDelete
methods of the Security System.
<DxGridCommandColumn Width="160px" NewButtonVisible=@(security.CanCreate<Employee>())>
<CellDisplayTemplate>
@if(security.CanWrite(context.DataItem)) {
<DxButton Text="Edit" Click="@(() => context.Grid.StartEditRowAsync(context.VisibleIndex))" RenderStyle="ButtonRenderStyle.Link" />
}
@if(security.CanDelete(context.DataItem)) {
<DxButton Text="Delete" Click="@(() => context.Grid.ShowRowDeleteConfirmation(context.VisibleIndex))" RenderStyle="ButtonRenderStyle.Link" />
}
</CellDisplayTemplate>
</DxGridCommandColumn>
The page is decorated with the Authorize
attribute to prohibit unauthorized access.
@attribute [Authorize]
To show asterisks instead of actual values in grid cells and editors, use SecuredDisplayCellTemplate and SecuredEditCellTemplate.
<DxGridDataColumn FieldName="@nameof(Employee.FirstName)">
<CellDisplayTemplate>
<SecuredDisplayCellTemplate CurrentObject="@context.DataItem" PropertyName="@nameof(Employee.FirstName)">
@(((Employee)context.DataItem).FirstName)
</SecuredDisplayCellTemplate>
</CellDisplayTemplate>
<CellEditTemplate>
<SecuredEditCellTemplate Context=readOnly CurrentObject=editableEmployee PropertyName=@nameof(Employee.FirstName)>
<DxTextBox @bind-Text=((EditableEmployee)context.EditModel).FirstName ReadOnly=@readOnly />
</SecuredEditCellTemplate>
</CellEditTemplate>
</DxGridDataColumn>
To determine whether asterisks must appear instead of actual text, check the Read permission by using the CanRead
method of the Security System.
Use the CanWrite
method of the Security System to check if a user is allowed to edit a property and an editor should be created for this property.
protected bool CanWrite => CurrentObject is null ? Security.CanWrite(typeof(T), PropertyName) : Security.CanWrite(CurrentObject, PropertyName);
protected bool CanRead => CurrentObject is null ? Security.CanRead(typeof(T), PropertyName) : Security.CanRead(CurrentObject, PropertyName);