SavedView component
Overview
SavedView Component is a component that allows configuring the state of the user interface. For example, a view is a table view containing a list of visible and invisible columns, column widths, column ordering, format, etc.
Install
The component consists of the following NuGet packages:
| Package | Description |
|---|---|
| ASOL.SavedViewComponent | Core package containing application logic |
| ASOL.SavedViewComponent.Abstraction | Package containing interfaces and abstractions |
| ASOL.SavedViewComponent.Domain | Package containing domain models and business logic |
| ASOL.SavedViewComponent.AspNetCore | ASP.NET Core integration package providing endpoints and controllers |
| ASOL.SavedViewComponent.Persistence.MongoDB | MongoDB persistence provider |
| ASOL.SavedViewComponent.Persistence.EFCore | Entity Framework Core persistence provider for PostgreSQL |
| ASOL.SavedViewComponent.Connector | HTTP Client connector package |
| ASOL.SavedViewComponent.Contracts | Package containing data transfer objects and service contracts |
Usage of third-party libraries
The component uses the following third-party packages:
| Package | Version | License | Description |
|---|---|---|---|
| Microsoft.EntityFrameworkCore | 8.0.0 | MIT | Entity Framework Core ORM |
| MongoDB.Driver | 2.23.1 | Apache | MongoDB driver for .NET |
Changelog
Component registration
CoCo implementation
Package: ASOL.SavedViewComponent
To properly register the component, the following configuration values must be set:
- AuthorizationServiceOptions:ApplicationCode
- SsoAuth:ClientId
MongoDb
Package: ASOL.SavedViewComponent.Persistence.MongoDB
using ASOL.SavedViewComponent.Extensions;
using ASOL.SavedViewComponent.Persistence.MongoDB.Extensions;
builder.Services.AddSavedViewComponent(builder.Configuration, configurator =>
{
configurator
.UseSavedViewComplexComponent()
.UseMongoDb();
});
EF Core
Package: ASOL.SavedViewComponent.Persistence.EFCore
using ASOL.SavedViewComponent.Extensions;
using ASOL.SavedViewComponent.Persistence.EFCore.Extensions;
builder.Services.AddSavedViewComponent(builder.Configuration, configurator =>
{
configurator
.UseSavedViewComplexComponent()
.UseEfCore<DatabaseContext>();
});
Apply saved view component configuration to your database context:
using ASOL.Core.Persistence.EFCore;
using ASOL.SavedViewComponent.Persistence.EFCore.Extensions;
using Microsoft.EntityFrameworkCore;
public class DatabaseContext(
DbContextOptions<DatabaseContext> options,
IMasterDbContextConfigurationProvider configurationProvider,
IServiceProvider serviceProvider)
: MasterDbContext(
options,
configurationProvider,
serviceProvider)
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ...
modelBuilder.ApplySavedViewComponentConfiguration();
// ...
base.OnModelCreating(modelBuilder);
}
}
Then Add Migration and Update Database.
Endpoints
By adding package ASOL.SavedViewComponent.AspNetCore the endpoints below will be automatically registered.
| HTTP | Path | Description | Route params |
|---|---|---|---|
| GET | /api/v1/SavedView/target/{targetId} | Get aggregated saved views | targetId |
| POST | /api/v1/SavedView/target/{targetId}/personal | Create a personal saved view | targetId |
| PATCH | /api/v1/SavedView/target/{targetId}/personal/{id}/label | Update label of a personal saved view | targetId, id |
| PATCH | /api/v1/SavedView/target/{targetId}/personal/{id}/value | Update value of a personal saved view | targetId, id |
| PUT | /api/v1/SavedView/target/{targetId}/personal/{id} | Update label and value of a personal saved view | targetId, id |
| GET | /api/v1/SavedView/target/{targetId}/personal/{id} | Get a personal saved view by id | targetId, id |
| DELETE | /api/v1/SavedView/target/{targetId}/personal/{id} | Delete a personal saved view | targetId, id |
| POST | /api/v1/SavedView/target/{targetId}/personal/{id}/share | Share a personal saved view (creates shared view) | targetId, id |
| PUT | /api/v1/SavedView/target/{targetId}/personal/layout | Create or replace the personal saved views layout | targetId |
| PATCH | /api/v1/SavedView/target/{targetId}/personal/layout/defaultview | Update default saved view in the personal layout | targetId |
| PATCH | /api/v1/SavedView/target/{targetId}/personal/layout/sorting | Update sorting of saved views in the personal layout | targetId |
| GET | /api/v1/SavedView/target/{targetId}/personal/layout | Get the personal saved views layout | targetId |
| DELETE | /api/v1/SavedView/target/{targetId}/personal/layout | Delete the personal saved views layout | targetId |
| POST | /api/v1/SavedView/target/{targetId}/shared | Create a shared saved view | targetId |
| PUT | /api/v1/SavedView/target/{targetId}/shared/{id} | Create or replace a shared saved view | targetId, id |
| PATCH | /api/v1/SavedView/target/{targetId}/shared/{id}/label | Update label of a shared saved view | targetId, id |
| PATCH | /api/v1/SavedView/target/{targetId}/shared/{id}/value | Update value of a shared saved view | targetId, id |
| GET | /api/v1/SavedView/target/{targetId}/shared/{id} | Get a shared saved view by id | targetId, id |
| DELETE | /api/v1/SavedView/target/{targetId}/shared/{id} | Delete a shared saved view | targetId, id |
Authorization
If the package ASOL.SavedViewComponent.AspNetCore is added to the project authorization configuration is mandatory using the UseSavedViewComplexComponentAuthorization method and implementing the SavedViewAuthorizationImporter class.
using ASOL.SavedViewComponent.Extensions;
using ASOL.SavedViewComponent.AspNetCore.Extensions;
builder.Services.AddSavedViewComponent(builder.Configuration, configurator =>
{
configurator
.UseSavedViewComplexComponent()
.UseSavedViewComplexComponentAuthorization<SavedViewAuthorizationImporter>()
// ...
});
Example of authorization importer:
using ASOL.SavedViewComponent.Authorization;
public class SavedViewAuthorizationImporter : ISavedViewAuthorizationImporter
{
public void ConfigureAuthorizationImport(ISavedViewAuthorizationImporterConfigurator configurator)
{
configurator
.AllowAccessToSavedViews("ASOL-SavedViewComponentTest-AP-.User")
.AllowSharingSavedViews("ASOL-SavedViewComponentTest-AP-.User");
// configurator.AllowSharingSavedViewsForSpecificTargetIds("ASOL-SavedViewComponentTest-AP-.User",
// "1696f874-9b60-43e0-a038-95a80524d864");
}
}
AllowAccessToSavedViewsrole that can access saved viewsAllowSharingSavedViewsrole or user that can share saved viewsAllowSharingSavedViewsForSpecificTargetIdsrole that can share saved views for specific target ids
Note: A regular user sees only those shared saved views that are for the given tenant context. A user with sharing rights sees all shared saved views in all tenants to which they have access to.
Connector
Package: ASOL.SavedViewComponent.Connector
using ASOL.SavedViewComponent.Connector.Extensions;
builder.Services.AddSavedViewComponentConnector(builder.Configuration);
Import/Seed Application (Global) Saved Views
For importing/seeding application (global) saved views, you need to implement the IGlobalSavedViewsImporter interface.
Use methods UpsertGlobalSavedView, SetDefaultGlobalSavedView and DeleteGlobalSavedView to import global saved views for your application.
using ASOL.SavedViewComponent.Importers;
using ASOL.SavedViewComponent.Extensions;
public class GlobalSavedViewsImporter : IGlobalSavedViewsImporter
{
public async Task ImportGlobalSavedViewsAsync(IGlobalSavedViewImporterConfigurator configurator, CancellationToken ct)
{
configurator
.UpsertGlobalSavedView(
"1696f874-9b60-43e0-a038-95a80524d864", // targetId
"9fa42dd4-0c77-4377-8819-1b3a094a72c9", // id
"imported global 1", // label
new
{
Column = "Column1",
Sort = 1,
Filter = "Filter1",
ExtendFilter = new []{"Filter1", "Filter2"},
ExtendFilter2 = new List<dynamic>{new {Filter1 = "Filter1", Filter2 = "Filter2"}}
}.ToExpando()
)
.UpsertGlobalSavedView(
"1696f874-9b60-43e0-a038-95a80524d864", // targetId
"417f5d5a-4de4-4123-b3d2-582b0334f324", // id
"imported global 2", // label
new { Column = "Column1", Sort = 2, Filter = new { Town = "Praha" }, Country = "Czech Republic" }.ToExpando()
)
.SetDefaultGlobalSavedView("1696f874-9b60-43e0-a038-95a80524d864", "417f5d5a-4de4-4123-b3d2-582b0334f324")
.DeleteGlobalSavedView("1696f874-9b60-43e0-a038-95a80524d864", "3a888ab4-b929-45a8-8218-b3225b4b613d");
await configurator.Import(ct);
}
}
Then you need to register your importer:
MongoDb
using ASOL.SavedViewComponent.Extensions;
using ASOL.SavedViewComponent.Persistence.MongoDB.Extensions;
builder.Services.AddSavedViewComponent(builder.Configuration, configurator =>
{
configurator
.UseSavedViewComplexComponent()
.UseGlobalSavedViewsImporter<GlobalSavedViewsImporter>()
.UseMongoDb();
});
EF Core
using ASOL.SavedViewComponent.Extensions;
using ASOL.SavedViewComponent.Persistence.EFCore.Extensions;
builder.Services.AddSavedViewComponent(builder.Configuration, configurator =>
{
configurator
.UseSavedViewComplexComponent()
.UseGlobalSavedViewsImporter<GlobalSavedViewsImporter>()
.UseEfCore<DatabaseContext>();
});
Apply saved view component configuration to your database context:
using ASOL.Core.Persistence.EFCore;
using ASOL.SavedViewComponent.Persistence.EFCore.Extensions;
using Microsoft.EntityFrameworkCore;
public class DatabaseContext(
DbContextOptions<DatabaseContext> options,
IMasterDbContextConfigurationProvider configurationProvider,
IServiceProvider serviceProvider)
: MasterDbContext(
options,
configurationProvider,
serviceProvider)
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ...
modelBuilder.ApplySavedViewComponentConfiguration();
// ...
base.OnModelCreating(modelBuilder);
}
}
Then Add Migration and Update Database.
CoCoS implementation
Packages:
- ASOL.SavedViewComponent
- ASOL.SavedViewComponent.AspNetCore
MongoDb
Package: ASOL.SavedViewComponent.Persistence.MongoDB
using ASOL.SavedViewComponent.Extensions;
using ASOL.SavedViewComponent.AspNetCore.Extensions;
using ASOL.SavedViewComponent.Persistence.MongoDB.Extensions;
builder.Services.AddSavedViewComponent(builder.Configuration, configurator =>
{
configurator
.UseSavedViewComplexComponentService()
.UseMongoDb();
});
EF Core
Package: ASOL.SavedViewComponent.Persistence.EFCore
using ASOL.SavedViewComponent.Extensions;
using ASOL.SavedViewComponent.AspNetCore.Extensions;
using ASOL.SavedViewComponent.Persistence.EFCore.Extensions;
builder.Services.AddSavedViewComponent(builder.Configuration, configurator =>
{
configurator
.UseSavedViewComplexComponentService()
.UseEfCore<DatabaseContext>();
});
Authorization
Authorization configuration is mandatory using the UseSavedViewComplexComponentAuthorization method and implementing the SavedViewAuthorizationImporter class.
using ASOL.SavedViewComponent.Extensions;
using ASOL.SavedViewComponent.AspNetCore.Extensions;
builder.Services.AddSavedViewComponent(builder.Configuration, configurator =>
{
configurator
.UseSavedViewComplexComponentService()
.UseSavedViewComplexComponentServiceAuthorization<SavedViewAuthorizationImporter>()
// ...
});
Example of authorization importer:
using ASOL.SavedViewComponent.Authorization;
public class SavedViewAuthorizationImporter : ISavedViewAuthorizationImporter
{
public void ConfigureAuthorizationImport(ISavedViewAuthorizationImporterConfigurator configurator)
{
configurator
.AllowAccessToSavedViews("ASOL-SavedViewComponentTest-AP-.User")
.AllowSharingSavedViews("ASOL-SavedViewComponentTest-AP-.User");
// configurator.AllowSharingSavedViewsForSpecificTargetIds("ASOL-SavedViewComponentTest-AP-.User",
// "1696f874-9b60-43e0-a038-95a80524d864");
}
}
AllowAccessToSavedViewsrole that can access saved viewsAllowSharingSavedViewsrole or user that can share saved viewsAllowSharingSavedViewsForSpecificTargetIdsrole that can share saved views for specific target ids
Note: A regular user sees only those shared saved views that are for the given tenant context. A user with sharing rights sees all shared saved views in all tenants to which they have access to.
Example of authorization importer:
using ASOL.SavedViewComponent.Authorization;
public class SavedViewAuthorizationImporter : ISavedViewAuthorizationImporter
{
public void ConfigureAuthorizationImport(ISavedViewAuthorizationImporterConfigurator configurator)
{
configurator
.AllowAccessToSavedViews("ASOL-SavedViewComponentTest-AP-.User")
.AllowSharingSavedViews("ASOL-SavedViewComponentTest-AP-.User");
// configurator.AllowSharingSavedViewsForSpecificTargetIds("ASOL-SavedViewComponentTest-AP-.User",
// "1696f874-9b60-43e0-a038-95a80524d864");
}
}
Endpoints
The endpoints listed here will be automatically registered.
| HTTP | Path | Description | Route params |
|---|---|---|---|
| GET | /api/v1/SavedView/application/{applicationCode}/target/{targetId} | Get aggregated saved views | targetId |
| POST | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal | Create a personal saved view | targetId |
| PATCH | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/{id}/label | Update label of a personal saved view | targetId, id |
| PATCH | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/{id}/value | Update value of a personal saved view | targetId, id |
| PUT | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/{id} | Create or replace a personal saved view | targetId, id |
| GET | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/{id} | Get a personal saved view by id | targetId, id |
| DELETE | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/{id} | Delete a personal saved view | targetId, id |
| POST | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/{id}/share | Share a personal saved view (creates shared view) | targetId, id |
| PUT | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/layout | Create or replace the personal saved views layout | targetId |
| PATCH | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/layout/defaultview | Update default saved view in the personal layout | targetId |
| PATCH | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/layout/sorting | Update sorting of saved views in the personal layout | targetId |
| GET | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/layout | Get the personal saved views layout | targetId |
| DELETE | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/personal/layout | Delete the personal saved views layout | targetId |
| POST | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/shared | Create a shared saved view | targetId |
| PUT | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/shared/{id} | Create or replace a shared saved view | targetId, id |
| PATCH | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/shared/{id}/label | Update label of a shared saved view | targetId, id |
| PATCH | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/shared/{id}/value | Update value of a shared saved view | targetId, id |
| GET | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/shared/{id} | Get a shared saved view by id | targetId, id |
| DELETE | /api/v1/SavedView/application/{applicationCode}/target/{targetId}/shared/{id} | Delete a shared saved view | targetId, id |
Connector
Package: ASOL.SavedViewComponent.Connector
To properly register the component, the following configuration values must be set:
- AuthorizationServiceOptions:ApplicationCode
using ASOL.SavedViewComponent.Connector.Extensions;
builder.Services.AddSavedViewComponentConnector(builder.Configuration, configurator =>
{
configurator
.UseSavedViewComplexComponentConnector();
});
Import/Seed Application (Global) Saved Views
Package: ASOL.SavedViewComponent.Connector
For importing/seeding application (global) saved views, you need to implement the IGlobalSavedViewsImporter interface.
Use methods UpsertGlobalSavedView, SetDefaultGlobalSavedView and DeleteGlobalSavedView to import global saved views for your application.
using ASOL.SavedViewComponent.Importers;
using ASOL.SavedViewComponent.Extensions;
public class GlobalSavedViewsImporter : IGlobalSavedViewsImporter
{
public async Task ImportGlobalSavedViewsAsync(IGlobalSavedViewImporterConfigurator configurator, CancellationToken ct)
{
configurator
.UpsertGlobalSavedView(
"1696f874-9b60-43e0-a038-95a80524d864", // targeId
"9fa42dd4-0c77-4377-8819-1b3a094a72c9", // id
"imported global 1", // label
new
{
Column = "Column1",
Sort = 1,
Filter = "Filter1",
ExtendFilter = new[] { "Filter1", "Filter2" },
ExtendFilter2 = new List<dynamic> { new { Filter1 = "Filter1", Filter2 = "Filter2" } }
}.ToExpando()
)
.UpsertGlobalSavedView(
"1696f874-9b60-43e0-a038-95a80524d864", // targetId
"417f5d5a-4de4-4123-b3d2-582b0334f324", // id
"imported global 2", // label
new { Column = "Column1", Sort = 2, Filter = new { Town = "Praha" }, Country = "Czech Republic" }.ToExpando()
)
.SetDefaultGlobalSavedView("1696f874-9b60-43e0-a038-95a80524d864", "417f5d5a-4de4-4123-b3d2-582b0334f324")
.DeleteGlobalSavedView("1696f874-9b60-43e0-a038-95a80524d864", "3a888ab4-b929-45a8-8218-b3225b4b613d");
await configurator.Import(ct);
}
}
The next step is implementation of a hosted service. Example:
using System.Diagnostics;
using ASOL.Core.Identity;
using ASOL.Core.Identity.Contexts;
using ASOL.Core.Identity.Options;
using ASOL.SavedViewComponent.Importers;
using Microsoft.Extensions.Options;
public class GlobalSavedViewConnectorService(IRuntimeContextScope runtimeContextScope, IOptions<SsoAuthOptions> ssoAuthOptions) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
ArgumentNullException.ThrowIfNull(ssoAuthOptions);
var claimsPrincipal = ClaimsPrincipalBuilder
.CreateClientPrincipal(ssoAuthOptions.Value.ClientId!)
.AddAuthenticateType()
.AddLocale(LocalizationContext.DefaultLanguageCode)
.Build();
runtimeContextScope.LocalImpersonate(claimsPrincipal, ActivityTraceId.CreateRandom().ToString());
var globalSavedViewImporter = runtimeContextScope.ScopeProvider.GetRequiredService<IGlobalSavedViewsImporter>();
var globalSavedViewImporterConfigurator = runtimeContextScope.ScopeProvider.GetRequiredService<IGlobalSavedViewImporterConfigurator>();
await globalSavedViewImporter.ImportGlobalSavedViewsAsync(globalSavedViewImporterConfigurator, stoppingToken);
}
}
Finally, you need to register your importer and hosted service:
using ASOL.SavedViewComponent.Connector.Extensions;
builder.Services.AddSavedViewComponentConnector(builder.Configuration, configurator =>
{
configurator
.UseSavedViewComplexComponentConnector()
.UseGlobalSavedViewsImporter<GlobalSavedViewsImporter, GlobalSavedViewConnectorService>();
});
Complete list of endpoints
[BASE] is a route prefix depending on the type of the component registration
| HTTP | Path | Controller | Action | Description | Visibility |
|---|---|---|---|---|---|
| GET | [BASE]/ | SavedViewController | Get | Get aggregated saved views | Public |
| POST | [BASE]/personal | SavedViewController | CreatePersonalSavedView | Create a personal saved view | Public |
| PATCH | [BASE]/personal/{id}/label | SavedViewController | UpdatePersonalSavedViewLabel | Update label of a personal saved view | Public |
| PATCH | [BASE]/personal/{id}/value | SavedViewController | UpdatePersonalSavedViewValue | Update value of a personal saved view | Public |
| PUT | [BASE]/personal/{id} | SavedViewController | UpdatePersonalSavedView | Create or replace a personal saved view | Public |
| GET | [BASE]/personal/{id} | SavedViewController | GetPersonalSavedViewById | Get a personal saved view by id | Public |
| DELETE | [BASE]/personal/{id} | SavedViewController | DeletePersonalSavedView | Delete a personal saved view | Public |
| PUT | [BASE]/personal/layout | SavedViewController | CreatePersonalSavedViewsLayout | Create or replace the personal saved views layout | Public |
| PATCH | [BASE]/personal/layout/defaultview | SavedViewController | UpdatePersonalSavedViewsLayoutDefaultView | Update default saved view in the personal layout | Public |
| PATCH | [BASE]/personal/layout/sorting | SavedViewController | UpdatePersonalSavedViewsLayoutSorting | Update sorting of saved views in the personal layout | Public |
| GET | [BASE]/personal/layout | SavedViewController | GetPersonalSavedViewsLayout | Get the personal saved views layout | Public |
| DELETE | [BASE]/personal/layout | SavedViewController | DeletePersonalSavedViewsLayout | Delete the personal saved views layout | Public |
| POST | [BASE]/global | SavedViewController | CreateGlobalSavedView | Create a global saved view | Hidden |
| PATCH | [BASE]/global/{id}/default | SavedViewController | SetGlobalSavedViewAsDefault | Set a global saved view as default | Hidden |
| GET | [BASE]/global/{id} | SavedViewController | GetGlobalSavedViewById | Get a global saved view by id | Hidden |
| PATCH | [BASE]/global/{id}/label | SavedViewController | UpdateGlobalSavedViewLabel | Update label of a global saved view | Hidden |
| PATCH | [BASE]/global/{id}/value | SavedViewController | UpdateGlobalSavedViewValue | Update value of a global saved view | Hidden |
| DELETE | [BASE]/global/{id} | SavedViewController | DeleteGlobalSavedView | Delete a global saved view | Hidden |
Deleted saved views have a retention period of 31 days. After this period, they are permanently removed from the database.
Useful examples of requests
Here are some useful examples of http requests.
POST http://localhost:5123/api/v1/SavedView/application/ASOL-SavedViewComponentTest-AP-/target/1696f874-9b60-43e0-a038-95a80524d864/personal
Content-Type: application/json
{
"label": "label-1",
"value": {
"column1": "test",
"sort": "column1",
"allowValues": [
"a",
"b"
],
"filters": [
{
"f1": 1,
"f3": 5
}
],
"value": {
"column1": "test",
"sort": "column1"
}
}
}
PUT http://localhost:5123/api/v1/SavedView/application/ASOL-SavedViewComponentTest-AP-/target/1696f874-9b60-43e0-a038-95a80524d864/personal/layout
Content-Type: application/json; charset=utf-8
{
"defaultSavedViewId": "9011b5df-e917-4d9b-96d7-124975074def",
"sortedSavedViewIds": [
"9011b5df-e917-4d9b-96d7-124975074def",
"417f5d5a-4de4-4123-b3d2-582b0334f324"
]
}