Core Concepts
In ASP.NET, you separate concerns with Controllers, Services, and Repositories.
The question
In ASP.NET, you separate concerns with Controllers, Services, and Repositories. In ServiceNow, there's something called 'Script Includes' that people say is like a reusable class library. But then there's also 'GlideAjax' which apparently bridges client and server. Explain the ServiceNow layered architecture to someone who thinks in MVC.
The Intent Behind This Question
This tests whether you can map ServiceNow's server-client architecture to the layered patterns you've built in .NET. The key insight: ServiceNow's UI is server-rendered (think classic ASP.NET Web Forms / Razor Pages), not a detached SPA. The client-server boundary is explicit and enforced.
The Architecture Map
// ASP.NET MVC Architecture (what you know) // // View (.cshtml) → Client Scripts + UI Policies // Controller → Business Rules (server triggers) // Service Layer → Script Includes (reusable classes) // Repository / DbContext → GlideRecord (data access) // AJAX calls (fetch/axios) → GlideAjax (client-to-server bridge) // ViewComponents → UI Scripts, Widgets (Service Portal) // [Authorize] attribute → ACLs (access control lists) // Startup.cs middleware → System Properties + Before Query BRs
Script Includes — Your Service Layer
// This is like a C# class in your Services/ folder
var IncidentHelper = Class.create();
IncidentHelper.prototype = Object.extendsObject(
AbstractAjaxProcessor, { // Extend this to expose via GlideAjax
// Server-only method (like a private service method)
calculateSLA: function(incidentGR) {
var priority = incidentGR.getValue('priority');
var slaHours = {1: 4, 2: 8, 3: 24, 4: 72};
return slaHours[priority] || 72;
},
// Client-callable method (like a [HttpGet] API endpoint)
getIncidentSLA: function() {
var incId = this.getParameter('sysparm_incident_id');
var gr = new GlideRecord('incident');
if (gr.get(incId)) {
return this.calculateSLA(gr);
}
return -1;
},
type: 'IncidentHelper'
});GlideAjax — Your AJAX Calls
// Client Script calling GlideAjax
// This is like: fetch('/api/incident/getSLA?id=123')
var ga = new GlideAjax('IncidentHelper'); // The Script Include name
ga.addParam('sysparm_name', 'getIncidentSLA'); // Method to call
ga.addParam('sysparm_incident_id', g_form.getUniqueValue());
ga.getXMLAnswer(function(answer) {
// 'answer' is the return value from the server method
g_form.setValue('u_sla_hours', answer);
});The Critical Boundary
- Client-side (runs in browser): Client Scripts, UI Policies, UI Scripts, GlideAjax calls. You have
g_form(form manipulation API — like jQuery for ServiceNow forms) andg_user(current user context). - Server-side (runs on instance): Business Rules, Script Includes, Scheduled Jobs, Flow Designer. You have
GlideRecord,GlideSystem (gs), and full database access. - You cannot call GlideRecord from a Client Script. This is the #1 mistake .NET devs make — assuming they can query the DB from the browser like they would with Entity Framework in a Blazor Server app. The bridge is always GlideAjax.
💡 .NET Developer Pro Tip
Think of GlideAjax as your fetch('/api/...') calls. The Script Include with AbstractAjaxProcessor is your API Controller. The method you call via sysparm_name is the action method. The parameters you pass are your query string. Everything you know about async client-server communication applies — just with different syntax.