| 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:
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
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:
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 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 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:
<abp-table striped-rows="true" id="BooksTable"></abp-table>
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:
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:
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:
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:
if (abp.auth.isGranted('BookStore.Books.Delete')) { }
abp.auth.isAnyGranted('A', 'B');
abp.auth.areAllGranted('A', 'B');
Current user — a plain object; check isAuthenticated first (anonymous → fields null, roles []):
if (abp.currentUser.isAuthenticated) { console.log(abp.currentUser.userName); }
Localization — reuses the server-side resources; an unlocalized key returns the key itself:
var res = abp.localization.getResource('BookStore');
res('HelloWorld');
res('WelcomeMessage', 'John');
abp.localization.localize('HelloWorld', 'BookStore');
Settings & features — only those whose server definition allows client visibility are present:
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:
abp.message.confirm('Delete the "admin" role?')
.then(function (confirmed) { if (confirmed) { } });
abp.message.success(msg, title);
abp.notify.info(msg, title);
UI busy / block, events, DOM hooks:
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) { });
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.