If you build on nopCommerce, here’s a small thing that can cause big head-scratching when requests hit your server:
WebHelper.IsAjaxRequest relies on the X-Requested-With: XMLHttpRequest header.
By default, many modern fetch calls don’t send it.
That means code paths that depend on “is this an AJAX request?” can behave inconsistently. Legacy jQuery .ajax() adds the header for you, but fetch() and many newer client libraries don’t.
This results in a server that can’t reliably tell if the request is an asynchronous call intended for JSON/partial responses or if it’s a full-page request.
Below you’ll find details on why this matters, how to fix it, and a few options that balance compatibility, security, and maintainability.
Wrong response shape: If your server thinks the request is “not AJAX,” it might return a full HTML view when your client expects JSON. This cues cryptic errors.
Error handling: Many apps return partial views or JSON for AJAX requests and redirects for non-AJAX. Misclassification breaks user flows.
Caching & middleware: CDNs, proxies, and server middleware sometimes treat AJAX differently. Without the header, you can get odd cache behaviors.
Security checks: Some anti-CSRF or anti-automation rules are looser/tighter for AJAX calls. Mis-detection can trip false positives.
X-Requested-With: XMLHttpRequest is a convention, not a browser requirement. Old-school XHR and jQuery .ajax() set it automatically, while fetch() does not. So in nopCommerce, WebHelper.IsAjaxRequest becomes a best-effort guess tied to a header the client might never send.
Server truth: By the time a request hits WebHelper.IsAjaxRequest, there’s no universal way to detect “AJAX” if the header isn’t present.
If you already control call sites:
await fetch('/some/url', {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/json' },
body: JSON.stringify({ fullName: 'Arnold Williamson' })
});
Safer than monkey-patching globals, and easy to test and swap out.
export async function ajax(input, init = {}) {
const headers = new Headers(init.headers || {});
if (!headers.has('X-Requested-With')) {
headers.set('X-Requested-With', 'XMLHttpRequest');
}
return fetch(input, { ...init, headers });
}
Usage:
const res = await ajax('/api/customer', { method: 'POST' });
If you can’t touch every call site but must standardize now:
// keep reference
const _fetch = window.fetch;
window.fetch = function (input, init = {}) {
const headers = new Headers(init?.headers || {});
if (!headers.has('X-Requested-With')) {
headers.set('X-Requested-With', 'XMLHttpRequest');
}
return _fetch(input, { ...init, headers });
};
Caveats: global overrides can complicate debugging, testing, and 3rd-party scripts. Prioritize a wrapper where possible.
If your team prefers jQuery for legacy reasons (or because a client insists) you can still write modern, readable code:
const result = await $.ajax({
url: '/some/url',
method: 'POST',
data: { fullName: 'Arnold Williamson' }
});
jQuery will include X-Requested-With for you.
Even with client fixes, consider server resilience:
If you’d like a quick audit of your nopCommerce front end, Aperture Labs can scan your call sites, add a lightweight wrapper, and harden server endpoints so your UI behaves predictably (without a big refactor). Reach out here to get in touch!