Integration¶
How to plug the ADP.Menus package into a Shift-based ASP.NET Core API and Blazor WebAssembly app.
Packages¶
| Package | Purpose |
|---|---|
ShiftSoftware.ADP.Menus.Shared |
DTOs, validators, action tree, country/language contracts |
ShiftSoftware.ADP.Menus.Data |
EF Core entities, AutoMapper profiles, export services |
ShiftSoftware.ADP.Menus.API |
ASP.NET Core controllers + AddMenuApiServices registration |
ShiftSoftware.ADP.Menus.Web |
Blazor WebAssembly pages + AddMenuBlazorServices registration |
All packages target .NET 10.
Architecture¶
flowchart LR
Web[Blazor WASM<br/>ADP.Menus.Web pages] -->|HTTP| API[ASP.NET Core<br/>ADP.Menus.API controllers]
API --> Data[ADP.Menus.Data<br/>EF Core + Exporters]
Data --> DB[(Your DbContext)]
Web -.-> Shared[ADP.Menus.Shared<br/>DTOs / Validators]
API -.-> Shared
Data -.-> Shared
API --> CP[IMenuCountryProvider]
API --> PP[IMenuPartPriceService]
The consumer app supplies three things: a DbContext, an IMenuCountryProvider, and an IMenuPartPriceService (stock lookup). Everything else is in the box.
Installation¶
dotnet add package ShiftSoftware.ADP.Menus.API # in your API project
dotnet add package ShiftSoftware.ADP.Menus.Web # in your Blazor WASM project
ADP.Menus.Data and ADP.Menus.Shared come in transitively.
Registration — API side¶
1. Implement IMenuCountryProvider¶
public class CountryProvider : IMenuCountryProvider
{
public ValueTask<IReadOnlyList<CountryInfo>> GetSupportedCountriesAsync() =>
ValueTask.FromResult<IReadOnlyList<CountryInfo>>(new[]
{
new CountryInfo { Id = 3, Name = "Uzbekistan" },
new CountryInfo { Id = 4, Name = "Turkmenistan" },
});
}
The package adapts its UI and exports to the size of this list — see Country modes below.
2. Wire it up in Program.cs¶
builder.Services.AddSingleton<IMenuCountryProvider, CountryProvider>();
builder.Services.AddScoped<IMenuPartPriceService, MyStockService>();
var mvc = builder.Services.AddControllers(/* ... */).AddShiftEntityWeb(/* ... */);
builder.Services.AddMenuApiServices<DB>(mvc, options =>
{
options.RoutePrefix = "api"; // default "api"
options.EnableMenuActionTreeAuthorization = false; // turn on to gate by TypeAuth
options.ApplyMenuPrefixPostfixToStandalones = false; // when true, the variant's MenuPrefix/MenuPostfix
// are also applied to standalone menu codes during
// export. Periodic codes always include them.
options.Languages = new()
{
new MenuLanguageOption("en-US", "English"),
new MenuLanguageOption("ar-IQ", "Arabic"),
};
});
One call does it all. AddMenuApiServices<TDbContext> internally registers:
- Controllers, AutoMapper profiles, validators, and export services
MenuActionTreewith TypeAuth (services.Configure<TypeAuthAspNetCoreOptions>)- Data and AutoMapper assemblies with ShiftEntity (
services.Configure<ShiftEntityOptions>) - EF Core entity configuration via
IModelBuildingContributor— nomodelBuilder.ConfigureMenuEntities()needed in your DbContext
Your DbContext stays clean:
public class DB : ShiftDbContext
{
public DbSet<TodoItem> TodoItems { get; set; } = default!;
public DB(DbContextOptions<DB> options) : base(options) { }
}
Registration — Blazor WASM side¶
// Program.cs of your Blazor WASM app
builder.Services.AddSingleton<IMenuCountryProvider, CountryProvider>();
builder.Services.AddMenuBlazorServices(options =>
{
options.EnableMenuActionTreeAuthorization = false;
options.IdentityApiUrl = "https://identity.example.com"; // base URL of the ShiftIdentity API,
// used by Menu list/form pages to
// look up brands
options.Layout = typeof(ShiftMainLayout); // optional: use a custom layout for
// Menu pages (defaults to built-in MenuLayout)
options.Languages = new()
{
new MenuLanguageOption("en-US", "English"),
new MenuLanguageOption("ar-IQ", "Arabic"),
};
});
One call does it all. AddMenuBlazorServices internally registers:
- Menu web services and auth helper
MenuActionTreewith TypeAuth (services.Configure<TypeAuthBlazorOptions>)- ADP.Menus.Web routing assembly with ShiftBlazor (
services.Configure<AppStartupOptions>) — no manualAdditionalAssembliesentry needed in yourApp.razor.cs
Menu pages auto-register at the /Menu prefix. Add navigation entries pointing to e.g. /Menu/MenuList, /Menu/VehicleModelList, /Menu/MenuExport.
Layout option¶
By default, Menu pages use the built-in MenuLayout which includes a sidebar drawer with Menu navigation links. To use the consumer app's own layout instead:
options.Layout = typeof(MyAppLayout);
When Layout is null (the default), the built-in MenuLayout is used.
Pages and endpoints¶
Blazor pages (prefix /Menu)¶
| Route | Purpose |
|---|---|
/Menu/MenuList |
List menus, launch exports |
/Menu/MenuForm/{Key?} |
Create/edit a menu and its variants |
/Menu/MenuExport |
Export menus to Excel |
/Menu/MenuDetailReport |
Menu detail change report |
/Menu/VehicleModelList · /VehicleModelForm/{Key?} |
Vehicle models + per-country labour rates |
/Menu/ReplacementItemList · /ReplacementItemForm/{Key?} |
Service items (oil filter, battery…) |
/Menu/ServiceIntervalList · /ServiceIntervalGroupList |
10K/20K/… milestones and labour groups |
/Menu/BrandMappingList · /LabourRateMappingList |
Mappings to RTS codes |
/Menu/StandaloneReplacementItemGroupList |
Standalone item groups |
API controllers (prefix from MenuApiOptions.RoutePrefix)¶
MenuController, MenuVariantController, MenuVersionController, VehicleModelController, ReplacementItemController, ServiceIntervalController, ServiceIntervalGroupController, BrandMappingController, LabourRateMappingController, StandaloneReplacementItemGroupController — all standard ShiftEntity CRUD plus:
| Endpoint | Purpose |
|---|---|
GET {prefix}/Menu/ExportMenusToExcel |
Excel export of menus (parts + labour) |
GET {prefix}/Menu/ExportRTSCodesToExcel |
RTS code export |
GET {prefix}/Menu/MenuDetailReportExcel |
Detail change report |
GET {prefix}/Menu/StockByPartNumber/{partNumber} |
Single part stock lookup |
POST {prefix}/Menu/UpdatePartsPrice |
Bulk price update |
GET {prefix}/MenuVariant/ByMenu/{menuID} |
Variants of a menu |
GET {prefix}/VehicleModel/GetById/{key} |
Vehicle model with labour structure |
Domain model¶
classDiagram
Menu "1" --> "*" MenuVariant
MenuVariant "1" --> "*" MenuItem
MenuVariant "1" --> "*" MenuVariantLabourRate : per country
MenuVariant --> VehicleModel
MenuItem "1" --> "*" MenuItemPart
MenuItemPart "1" --> "*" MenuItemPartCountryPrice
MenuItem --> ReplacementItem
VehicleModel "1" --> "*" VehicleModelLabourRate : per country
ServiceIntervalGroup "1" --> "*" ServiceInterval
MenuVariant --> ServiceIntervalGroup : MenuLabourDetails
MenuVariant.LabourRate is the primary rate. MenuVariantLabourRate rows are country-specific overrides used only when more than one country is configured.
Country modes (0 / 1 / N)¶
The Menu package adapts to whatever IMenuCountryProvider returns:
| Countries | Country selector | Per-country labour table | Transfer rate input | Stored CountryID |
Export labour rate | Export transfer rate |
|---|---|---|---|---|---|---|
| 0 (empty/null) | hidden | hidden | hidden, forced 1 |
0 |
primary (MenuVariant.LabourRate) |
server forces 1 |
| 1 | hidden | hidden | hidden, forced 1 |
the single id | primary | server forces 1 |
| N (≥2) | shown | shown | shown | user-selected | per-country (with primary fallback) | user value |
The transferRate = 1 and countryId overrides are enforced server-side in MenuController — a stale UI or hand-crafted API call cannot bypass them.
flowchart TD
A[Request hits export endpoint] --> B[NormalizeCountryAndTransferRate]
B --> C{countries.Count}
C -->|0| D[countryId=0<br/>transferRate=1<br/>use primary labour rate]
C -->|1| E[countryId=single<br/>transferRate=1<br/>use primary labour rate]
C -->|2+| F[keep user values<br/>per-country labour with fallback]
D --> G[MenuExportService]
E --> G
F --> G
Authorization (optional)¶
Set EnableMenuActionTreeAuthorization = true in both API and Web options. MenuActionTree is registered automatically by AddMenuApiServices / AddMenuBlazorServices — you do not need to add it to your AddTypeAuth block. Available nodes:
- Entity:
Menus,MenuVariants,MenuVersions,VehicleModels,ReplacementItems,ServiceIntervals,ServiceIntervalGroups,BrandMappings,LabourRateMappings,StandaloneReplacementItemGroups(eachRead/Write/Delete) - Operations:
CreateMenuVersion,UpdatePartsPrice - Reports:
ExportMenusToExcel,ExportRTSCodesToExcel,ExportMenuDetailReport
All Menu list pages, form pages, and navigation links are wired with TypeAuthAction so the UI automatically hides elements the user doesn't have permission for.
When disabled (default), all checks pass — you can adopt authorization later without code changes.
Sample app¶
A working end-to-end sample lives under ADP.Menus/samples/ADP.Menus.Sample.API and ADP.Menus.Sample.Web in the ADP repo. It hosts ShiftIdentity internally (login with SuperUser / OneTwo) and seeds demo data on startup. Change the return value of CountryProvider.GetSupportedCountriesAsync (empty / one / many) to see all three country modes in action.