| name | mvc-razor-ui |
| description | Build and debug ABP MVC / Razor Pages UI — pages on AbpPageModel, abp-* tag helpers, the modal system, the bundling system, and per-page CSS/JS. USE FOR: writing pages on AbpPageModel, using abp-* tag helpers (abp-button, abp-modal, abp-dynamic-form, abp-input/select, …), the modal system (abp-modal + abp.ModalManager JS), bundling with AbpBundlingOptions / abp-script(-bundle) / abp-style(-bundle), co-locating page-specific CSS/JS. DO NOT USE FOR: Blazor UI (use blazor-ui), Angular UI (use angular-ui), the IMenuContributor menu pattern and translating localization json (use menus-and-localization), backend permission definitions (use permissions-and-authorization).
|
| license | MIT |
ABP MVC / Razor Pages UI
Guidance for building UI on ABP's ASP.NET Core MVC / Razor Pages stack. Framework code lives under Volo.Abp.AspNetCore.Mvc* and the Volo.Abp.AspNetCore.Mvc.UI.* packages (tag helpers, bundling, theming).
When to Use
- Writing Razor Page models on
AbpPageModel.
- Rendering UI with
abp-* tag helpers instead of raw Bootstrap markup.
- Building modals (a Razor Page wrapped in
abp-modal, opened via abp.ModalManager).
- Configuring bundles with
AbpBundlingOptions and emitting them with the bundle tag helpers.
- Co-locating and registering page-specific CSS/JS with
abp-script / abp-style.
When Not to Use
- Blazor UI — use the blazor-ui skill.
- Angular UI — use the angular-ui skill.
- Contributing menu items or translating localization json — use the menus-and-localization skill (the menu section below only points there).
- Defining backend permissions — use the permissions-and-authorization skill.
Page base class
Derive Razor Page models from AbpPageModel (namespace Volo.Abp.AspNetCore.Mvc.UI.RazorPages). It extends ASP.NET Core's PageModel and gives you framework services (resolved lazily through LazyServiceProvider) instead of re-injecting them:
L — an IStringLocalizer; set LocalizationResourceType to bind it to your module's resource.
CurrentUser, CurrentTenant, AuthorizationService, SettingProvider
ObjectMapper (set ObjectMapperContext to pick a profile), GuidGenerator, Clock, Logger
Alerts / AlertManager, UnitOfWorkManager / CurrentUnitOfWork
CheckPolicyAsync(policyName), ValidateModel(), RedirectSafelyAsync(returnUrl)
public class CreateModalModel : AbpPageModel
{
public CreateModalModel()
{
LocalizationResourceType = typeof(MyProjectResource);
}
public async Task OnGetAsync() { }
}
Where pages live
Razor Pages live under Pages/ in the web project, grouped by feature (e.g. Pages/Books/Index.cshtml + Index.cshtml.cs, CreateModal.cshtml + .cs). Module UIs ship their own Pages/ inside their *.Web packages. Each page pairs a .cshtml view with a .cshtml.cs model deriving from AbpPageModel.
Tag helpers
ABP ships abp-* tag helpers (namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers) that render Bootstrap markup and honor the active theme. Verified examples:
<abp-button button-type="Primary" text="@L["Save"]" icon="check" />
<abp-dynamic-form abp-model="@Model.Book" />
<abp-button> — attrs include button-type (AbpButtonType), size, text, icon, icon-type, busy-text, disabled.
<abp-dynamic-form> — renders a full form from a bound model; skip properties with [DynamicFormIgnore].
- Layout/form helpers also exist:
<abp-card>, <abp-row>/<abp-column>, <abp-input>, <abp-select>, <abp-modal> (below). Match the tag helpers already used in the project rather than reaching for raw Bootstrap HTML.
Modal system
A modal is a normal Razor Page (its own .cshtml + AbpPageModel) whose body is wrapped in the abp-modal tag helpers. The submit happens by posting a form, so wrap the entire <abp-modal> in a single <form> (matching modules like TenantManagement's CreateModal.cshtml) and put the fields in the body with <abp-input> / <abp-select> — the footer's Save button then submits that form:
<form method="post" asp-page="/Books/CreateModal">
<abp-modal size="Large">
<abp-modal-header title="@L["NewBook"]" />
<abp-modal-body>
<abp-input asp-for="Book.Name" />
<abp-input asp-for="Book.Price" />
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel | AbpModalButtons.Save)" />
</abp-modal>
</form>
To build the whole form from a model instead of listing inputs, use <abp-dynamic-form> — it renders as the <form> itself, so make it the outermost element wrapping the modal and place the fields with <abp-form-content /> (see the Docs module's Create.cshtml). Never nest an <abp-dynamic-form> inside another <form>. (It only emits its own submit button when submit-button="true"; the default does not, so it won't duplicate a footer Save.)
<abp-modal> attrs: size (AbpModalSize), centered, scrollable, static. On the calling page, open it from JS with the abp.ModalManager global (defined in .../Theme.Shared/.../bootstrap/modal-manager.js):
var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal');
createModal.open();
createModal.onResult(function () { dataTable.ajax.reload(); });
new abp.ModalManager(viewUrl) (or an options object) returns a manager with open, close, reopen, onOpen, onResult. It posts the modal form and raises the result callback on success.
Bundling
ABP has its own bundling system configured via AbpBundlingOptions (namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling) in your module's ConfigureServices:
Configure<AbpBundlingOptions>(options =>
{
options.ScriptBundles
.Configure(StandardBundles.Scripts.Global, bundle =>
{
bundle.AddFiles("/global-scripts.js");
});
});
AbpBundlingOptions exposes ScriptBundles / StyleBundles, plus Mode (BundlingMode), DeferScriptsByDefault, PreloadStylesByDefault. Reusable bundle content is added via an IBundleContributor / BundleContributor. Bundles are emitted with the bundle tag helpers <abp-script-bundle name="..."> and <abp-style-bundle name="...">.
Page-specific CSS/JS
Convention: co-locate a page's assets next to the .cshtml (e.g. Index.cshtml + Index.cshtml.js + Index.cshtml.css) and register them per page inside @section scripts / @section styles using the single-file tag helpers <abp-script> / <abp-style> (verified [HtmlTargetElement("abp-script")]):
@section scripts {
<abp-script src="/Pages/Books/Index.cshtml.js" />
}
@section styles {
<abp-style src="/Pages/Books/Index.cshtml.css" />
}
These minify/version the file and slot it into the right bundle. Prefer them over raw <script>/<link> so ABP's minification and cache-busting apply.
Menu contribution
MVC/Razor navigation uses the same IMenuContributor mechanism as the rest of ABP (context.Menu.AddItem(new ApplicationMenuItem(...)), gated on StandardMenus.Main), registered through AbpNavigationOptions. See the menus-and-localization skill for the full pattern and the rule about translating any new localization keys into every culture's json.
Validation
- The web project builds and the page renders; a model deriving from
AbpPageModel compiles and its lazily-resolved services (L, CurrentUser, Alerts, …) are available without manual injection.
abp-* tag helpers emit themed Bootstrap markup; a modal opened via abp.ModalManager loads the page's HTML and fires onResult after a successful post.
abp-script / abp-style and the bundle tag helpers produce versioned (cache-busted) output rather than raw asset tags — the output is minified only when minification is enabled (BundleAndMinify, or Auto outside Development).
Common Pitfalls
- Inventing tag helper names — verify a tag helper exists first (
Volo.Abp.AspNetCore.Mvc.UI.Bootstrap/TagHelpers/**). A tag helper maps to its element via an explicit [HtmlTargetElement("abp-…")] or, absent that, via Razor's class-name naming convention (e.g. AbpModalTagHelper has no [HtmlTargetElement] yet still binds to <abp-modal>).
- Nesting
<abp-dynamic-form> inside another <form> — it renders as the <form> itself; make it the outermost element wrapping the modal and place fields with <abp-form-content />.
- Wrapping only part of the modal in the form — wrap the entire
<abp-modal> in a single <form> so the footer Save button submits it.
- Using raw
<script> / <link> / raw Bootstrap HTML — prefer abp-script / abp-style and abp-* tag helpers so cache-busting and theming apply (and minification when it's enabled, i.e. BundleAndMinify or Auto outside Development); match the tag helpers already used in the project.