Skip to content

Latest commit

 

History

History

Blazor.ServerSide

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

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.

If you wish to create a Blazor project with our Blazor Components from scratch, follow the Create a New Blazor Application article.


Step 1. Configure the Blazor Application

  1. 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" />
  2. Install Entity Framework Core, as described in the Installing Entity Framework Core article.

  3. For detailed information about the ASP.NET Core application configuration, see official Microsoft documentation.

  • Configure the Blazor Application in the 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. Authentication and Permission Configuration

  • Register the business objects that you will access from your code in the Types Info system.

    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. In EFCore, it is DbContextFactory. Add a security extension to it to 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 the MS SQL database. We do not recommend that you remove “MultipleActiveResultSets=True;“ from the connection string or set the MultipleActiveResultSets parameter to false.

  • Register security system and authentication in the 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 the 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. Pages

Login.cshtml is a login 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 user with the AuthenticationStandard authentication and return a ClaimsPrincipal object with all the 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 class implements the Logout logic

public IActionResult OnGet() {
    this.HttpContext.SignOutAsync();
    return Redirect("/Login");
}

Index.razor is the main page. It configures the Blazor Data Grid and allows a user to log out.

The OnInitialized method creates an ObjectSpace instance and gets Employee and Department objects.

protected override void OnInitialized() {
    objectSpace = objectSpaceFactory.CreateObjectSpace<Employee>();
    employees = objectSpace.GetObjectsQuery<Employee>();
    departments = objectSpace.GetObjectsQuery<Department>();
    base.OnInitialized();
}

The HandleValidSubmit method saves changes if data is valid.

async Task HandleValidSubmit() {
    objectSpace.CommitChanges();
    await grid.Refresh();
    employee = null;
    await grid.CancelRowEdit();
}

The OnRowRemoving method removes an object.

Task OnRowRemoving(object item) {
    objectSpace.Delete(item);
    objectSpace.CommitChanges();
    return grid.Refresh();
}

To show/hide the New, Edit, and Delete actions, use the appropriate CanCreate, CanEdit, and CanDelete methods of the Security System.

<DxDataGridCommandColumn Width="100px">
    <HeaderCellTemplate>
        @if(Security.CanCreate<Employee>()) {
            <button class="btn btn-link" @onclick="@(() => StartRowEdit(null))">New</button>
        }
    </HeaderCellTemplate>
    <CellTemplate>
        @if(Security.CanWrite(context)) {
            <a @onclick="@(() => StartRowEdit(context))" href="javascript:;">Edit </a>
        }
        @if(Security.CanDelete(context)) {
            <a @onclick="@(() => OnRowRemoving(context))" href="javascript:;">Delete</a>
        }
    </CellTemplate>
</DxDataGridCommandColumn>

The page is decorated with the Authorize attribute to prohibit unauthorized access.

@attribute [Authorize]

To show the ******* text instead of a default value in data grid cells and editors, use SecuredContainer

<DxDataGridColumn Field="@nameof(Employee.FirstName)">
    <DisplayTemplate>
        <SecuredContainer Context="readOnly" CurrentObject="@context" PropertyName="@nameof(Employee.FirstName)">
            @(((Employee)context).FirstName)
        </SecuredContainer>
    </DisplayTemplate>
</DxDataGridColumn>
//...
<DxFormLayoutItem Caption="First Name">
    <Template>
        <SecuredContainer Context="readOnly" CurrentObject=@employee PropertyName=@nameof(Employee.FirstName) IsEditor=true>
            <DxTextBox @bind-Text=employee.FirstName ReadOnly=@readOnly />
        </SecuredContainer>
    </Template>
</DxFormLayoutItem>

To show the ******* text instead of the default 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.

private bool HasAccess => objectSpace.IsNewObject(CurrentObject) ?
    SecurityProvider.Security.CanWrite(CurrentObject.GetType(), PropertyName) :
    SecurityProvider.Security.CanRead(CurrentObject, PropertyName);

Step 4: Run and Test the App

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

  • Note that secured data is displayed as '*******'.

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