Skip to main content

use-mvc-javascript-apis

Use ABP's browser JavaScript APIs in an MVC / Razor Pages app: generated JS service proxies + the DataTables adapter, plus the client-side abp.* surface (auth, localization, settings/features, messages, busy/block, events). USE FOR: calling an app service from client JS (acme.bookStore.books.book.getList(...)) via the dynamic proxy (/Abp/ServiceProxyScript) or static proxies (abp generate-proxy -t js), feeding a proxy into abp.libs.datatables.createAjax with normalizeConfiguration / rowAction / dataFormat, and the page abp.* APIs — abp.auth.isGranted, abp.localization.localize, abp.setting/abp.features, abp.message/abp.notify, abp.ui.block/setBusy, abp.event, abp.ajax. DO NOT USE FOR: C# typed HTTP client proxies (use consume-remote-services), running the proxy-generation CLI itself / its flags (use abp-cli-commands), exposing app services as HTTP APIs on the server (use expose-http-apis), general MVC page/modal/tag-helper/bundling work (use mvc-razor-ui).

Jump to install

Source facts

Repository
abpframework/abp-skills
Last source activity
July 20, 2026 at 03:24
Detected SKILL.md language
English
Stars
18
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
use-mvc-javascript-apis
description
Use ABP's browser JavaScript APIs in an MVC / Razor Pages app: generated JS service proxies + the DataTables adapter, plus the client-side abp.* surface (auth, localization, settings/features, messages, busy/block, events). USE FOR: calling an app service from client JS (acme.bookStore.books.book.getList(...)) via the dynamic proxy (/Abp/ServiceProxyScript) or static proxies (abp generate-proxy -t js), feeding a proxy into abp.libs.datatables.createAjax with normalizeConfiguration / rowAction / dataFormat, and the page abp.* APIs — abp.auth.isGranted, abp.localization.localize, abp.setting/abp.features, abp.message/abp.notify, abp.ui.block/setBusy, abp.event, abp.ajax. DO NOT USE FOR: C# typed HTTP client proxies (use consume-remote-services), running the proxy-generation CLI itself / its flags (use abp-cli-commands), exposing app services as HTTP APIs on the server (use expose-http-apis), general MVC page/modal/tag-helper/bundling work (use mvc-razor-ui).
license
MIT
# Use ABP JavaScript APIs in MVC / Razor Pages Call your server-side application services from browser JavaScript through ABP's **JavaScript API Client Proxies** instead of hand-writing `$.ajax`. A proxy function **corresponds to** the C# method — ABP drops the `Async` suffix and camelCases the name, DTOs are serialized to JSON, and the call returns a jQuery Deferred/Promise (with a trailing `ajaxParams` option), so `acme.bookStore.authors.author.getList({ maxResultCount: 10 })` maps to `IAuthorAppService.GetListAsync(...)`. This skill also covers wiring a proxy into ABP's DataTables adapter, and the rest of the client-side `abp.*` API surface (auth, localization, settings/features, messages, busy/block, events) that page code uses alongside the proxies. ## When to Use - You need to call an app service (`getList`, `get`, `create`, `update`, `delete`, …) from page JS. - You want a server-bound data grid using `abp-table` + DataTables. - You are deciding between the runtime (dynamic) and build-time (static) proxy. ## Two proxy systems Both give the identical `acme.bookStore.authors.author.get(...)` calling surface; they differ in *when* the proxy JS is produced. - **Dynamic (default):** generated at runtime. The layout automatically pulls the script from the `/Abp/ServiceProxyScript` endpoint. Easiest dev experience — nothing to regenerate when the API changes. Visit `/Abp/ServiceProxyScript` in the browser to see the emitted functions. - **Static:** generated at development time with `abp generate-proxy -t js -u https://localhost:53929/` (server must be running). Slight runtime perf gain because the API definition isn't fetched at runtime, but you must **re-generate whenever the API changes**. Output lands under `ClientProxies` (e.g. `app-proxy.js`); import it with `<abp-script src="/client-proxies/app-proxy.js"/>`. ### Static requires disabling the dynamic proxy for that module Otherwise both proxies register and you get duplicate/racing definitions. In the module's `ConfigureServices`: ```csharp Configure<DynamicJavaScriptProxyOptions>(options => { options.DisableModule("app"); }); ``` `"app"` is the main application; for a module, pass that module's name. `abp generate-proxy` also assumes `app` as the module name unless you pass `-m` / `--module`. ## Calling a proxy ```js acme.bookStore.authors.author.getList({ maxResultCount: 10 }) .then(function (result) { console.log(result.items); }); acme.bookStore.authors.author.delete('7245a066-5457-4941-8aa7-3004778775f0') .then(function () { abp.notify.info('Successfully deleted!'); }); ``` - Proxy functions run on `abp.ajax` under the hood, so you get **automatic error handling** for free. - **Return value is a jQuery Deferred object** — chain `.then` (result), `.catch` (error), `.always` (finally). It is not a raw value. - **The LAST argument is always `ajaxParams`, not a business parameter.** It's an object that overrides the underlying AJAX options — easy to mistake for a real method arg: ```js acme.bookStore.authors.author .delete('7245a066-5457-4941-8aa7-3004778775f0', { timeout: 10000, headers: { 'X-Demo-Header': 'value' } }) .then(function () { abp.notify.info('Successfully deleted!'); }); ``` `ajaxParams` accepts [jQuery.ajax](https://api.jquery.com/jQuery.ajax/) options — but don't override the HTTP method, URL, content type, or data type the proxy generates (e.g. don't turn a `DELETE` into a `POST` or force an `xml` response); use it for `timeout`, extra headers, `beforeSend`, and similar. ## DataTables integration The startup templates ship [DataTables.Net](https://datatables.net/) pre-installed and bundled. Add a table and call `.DataTable(...)` on it, wrapping the config in ABP's normalizer and feeding the proxy through ABP's AJAX adapter: ```html <abp-table striped-rows="true" id="BooksTable"></abp-table> ``` ```js var dataTable = $('#BooksTable').DataTable( abp.libs.datatables.normalizeConfiguration({ serverSide: true, paging: true, order: [[1, "asc"]], searching: false, ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList), columnDefs: [ { title: l('Actions'), rowAction: { items: [ { text: l('Edit'), action: function (data) { /* ... */ } } ] } }, { title: l('Name'), data: "name" }, { title: l('CreationTime'), data: "creationTime", dataFormat: 'datetime' }, { title: l('Price'), data: "price" } ] }) ); ``` ### `createAjax` — the adapter (do not skip it) DataTables and ABP use **different request/response shapes** for paging and sorting. `abp.libs.datatables.createAjax(proxyFn)` translates between them and works with the proxy system. **Passing the proxy function straight into DataTables' `ajax` is NOT equivalent** — the paging/sorting params and the `{ items, totalCount }` response won't line up. Customize request params and/or response with the optional 2nd/3rd args: ```js var inputAction = function (requestData, dataTableSettings) { return { id: $('#Id').val(), name: $('#Name').val() }; }; var responseCallback = function (result) { return { recordsTotal: result.totalCount, recordsFiltered: result.totalCount, data: result.items }; }; ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList, inputAction, responseCallback) ``` If you only need to add fixed request params, pass a plain object as the 2nd arg: ```js ajax: abp.libs.datatables.createAjax(acme.bookStore.books.book.getList, { id: $('#Id').val() }) ``` ### ABP extensions to the config (not native DataTables) - **`normalizeConfiguration`** — sets `scrollX` (default `true` here), fills in column `targets` indexes, and sets `language` to localize the table. Defaults come from `abp.libs.datatables.defaultConfigurations` (`scrollX`, `dom`, `language`), which you can override. - **`rowAction`** — a column-def option that renders a per-row actions dropdown. Each `items[]` entry supports: `text`, `action` (receives `data` with `data.record` = row object, `data.table` = DataTables instance), `confirmMessage` (function returning a confirmation string), `visible` (bool or function → bool; commonly `abp.auth.isGranted('BookStore.Books.Delete')`), `enabled` (function → bool), `iconClass`, `displayNameHtml`. If no item is visible, the actions column isn't rendered. - **`dataFormat`** — column option for built-in rendering without a custom `render`: `boolean` (check/times icon), `date`, `datetime`. Register new formats via `abp.libs.datatables.defaultRenderers['name'] = function(value){ ... }`. Reload after a mutating action: ```js action: function (data) { acme.bookStore.books.book.delete(data.record.id) .then(function () { abp.notify.info("Successfully deleted!"); data.table.ajax.reload(); }); } ``` ## Browser JS APIs (beyond proxies) Alongside the service proxies, ABP injects a client-side `abp.*` object into every page. These values **mirror server-side configuration** — permissions, localization, settings, features, and the current user are resolved on the server and serialized into the page (via the application configuration), so on the client you read them **synchronously, no request needed**. **Auth (permissions/policies)** — against the current user's granted policies: ```js if (abp.auth.isGranted('BookStore.Books.Delete')) { /* ... */ } abp.auth.isAnyGranted('A', 'B'); // any granted abp.auth.areAllGranted('A', 'B'); // all granted ``` **Current user** — a plain object; check `isAuthenticated` first (anonymous → fields `null`, `roles` `[]`): ```js if (abp.currentUser.isAuthenticated) { console.log(abp.currentUser.userName); } // id, tenantId, userName, name, surName, email, roles, ... ``` **Localization** — reuses the server-side resources; an unlocalized key returns the key itself: ```js var res = abp.localization.getResource('BookStore'); res('HelloWorld'); // localize res('WelcomeMessage', 'John'); // with {0} args abp.localization.localize('HelloWorld', 'BookStore'); // shortcut (key, resource) ``` **Settings & features** — only those whose server definition allows client visibility are present: ```js abp.setting.get('...'); abp.setting.getInt('...'); abp.setting.getBoolean('...'); abp.features.isEnabled('...'); abp.features.get('...'); abp.globalFeatures.isEnabled('...'); ``` **AJAX (low-level)** — prefer proxies; `abp.ajax` wraps `$.ajax`, returns a promise, auto-handles+localizes errors, and auto-adds the anti-forgery (CSRF) token. Pass `abpHandleError: false` to opt out of the automatic error UI; a 401 auto-redirects to login. **Messages (blocking) & notifications (toasts)** — `confirm`/`prompt` return **promises**: ```js abp.message.confirm('Delete the "admin" role?') .then(function (confirmed) { if (confirmed) { /* ... */ } }); abp.message.success(msg, title); // + info / warn / error abp.notify.info(msg, title); // auto-dismissing toast (+ success / warn / error) ``` **UI busy / block**, **events**, **DOM hooks**: ```js abp.ui.block('#MySection'); abp.ui.unblock('#MySection'); abp.ui.setBusy('#MySection'); abp.event.on('basketUpdated', fn); abp.event.trigger('basketUpdated', basket); abp.event.off('basketUpdated', fn); abp.dom.onNodeAdded(function (args) { /* init elements added after page load, e.g. via AJAX */ }); ``` `abp.event` is **browser-only** pub/sub (unrelated to the server local/distributed event bus). `abp.dom` (backed by `MutationObserver`) fires for nodes added later — use it to initialize AJAX-inserted HTML. `abp.ResourceLoader.loadScript(url)` / `loadStyle(url)` fetch a file **once**. ## Validation - **Dynamic proxy present:** open `/Abp/ServiceProxyScript` in the browser — your service's proxy functions appear there, and the object path (e.g. `acme.bookStore.books.book`) resolves in the JS console. - **Static proxy present:** the `ClientProxies` / `app-proxy.js` file exists, is imported via `<abp-script>`, and — after `DisableModule` — the console shows exactly one definition of the proxy object (no dynamic one racing it). - **Table works:** the grid pages/sorts server-side (network requests carry the translated paging/sort params) and the response `items` render; row actions run their `action` and `data.table.ajax.reload()` refreshes the grid. ## Common Pitfalls - **Treating `ajaxParams` as a method parameter** — the trailing object overrides AJAX options; passing a business value there silently does nothing useful. - **Awaiting the return as a plain value** — it's a jQuery Deferred; use `.then` / `.catch` / `.always`. - **Enabling static proxies without `DisableModule`** — leaves the dynamic proxy live too, so both register. Disable the module in `DynamicJavaScriptProxyOptions`. - **Forgetting to re-run `abp generate-proxy` after changing the API** — static proxies are frozen at generation time; dynamic ones update automatically. - **Passing the proxy straight into DataTables `ajax`** — skips `createAjax`, so ABP's paging/sorting/response shape (`items`/`totalCount`) never gets adapted. Always wrap with `abp.libs.datatables.createAjax`. - **Assuming `rowAction` / `dataFormat` / `normalizeConfiguration` are DataTables features** — they are ABP additions; they exist only when you go through ABP's adapter.
View on GitHub