Skip to content

Files

Latest commit

4d46456 · Apr 3, 2024

History

History
This branch is 33 commits ahead of, 46 commits behind 24.2.1+.

Blazor.ServerSide

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
Mar 13, 2024
Sep 28, 2022
Mar 13, 2024
Mar 26, 2024
Jul 21, 2021
Aug 11, 2021
Aug 10, 2021
May 5, 2022
Jan 23, 2024
Jul 21, 2021
Sep 28, 2022
Apr 3, 2024
Mar 13, 2024
Jul 21, 2021
Aug 10, 2021

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.

Prerequisites

  • Visual Studio 2022 v17.0+ with the following workloads:

    • ASP.NET and web development
    • .NET Core cross-platform development
  • .NET SDK 6.0+

  • 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.


Step 1. Configure the 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();

Step 2. Initialize Data Store and XAF Security System, Configure Authentication and Permissions

  • 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.

Step 3. Create an edit model

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

Step 4. Pages

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.

CellEditTemplateBase:

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

Step 4: Run and Test the App

  • Log in a 'User' with an empty password.

  • Note that asterisks replace secured data.

  • Press the Logout button and log in as 'Admin' to see all records and their actual values.