Sys is not defined error message

If you Google the error you’ll find a lot of results about updating your web.config to enable ASP.NET AJAX (which is a necessary step), but what if you’ve already done that? Not only will your search be an exercise in frustration, but it will lead you down the wrong path completely. Maybe I can help.


What if Sys really is undefined?

After web.config problems, the most common reason for this error is JavaScript that references the Sys namespace too early. Take this code, for example:

<head>
<script type="text/javascript">
  Sys.WebForms.PageRequestManager.getInstance().add_endRequest(End);
 
  function End(sender, args) { }
</script>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ScriptManager ID="sm1" runat="server" />
  <asp:UpdatePanel ID="up1" runat="server">
    <ContentTemplate>
      <!-- Interesting content goes here -->
    </ContentTemplate>
  </asp:UpdatePanel>
  </form>
</body>

Loading this page will result in a Sys undefined error, identical to the one in the screenshot above. The reason for it is simple: The Sys namespace is undefined when the script block executes.

The ScriptManager control injects JavaScript includes to initialize the ASP.NET AJAX Framework, the ScriptManager, and then the UpdatePanel, but it injects the bulk of that JavaScript in the exact location that the ScriptManager control is positioned at in the page. So, the script block in the page head tries to access the PageRequestManager long before the AJAX Framework has been fully loaded. Hence the error.

The same thing happens if you try to include the same JavaScript code from an external file in the head, or even in the ScriptManager’s Scripts collection.

The Quick Fix

The most obvious fix for this is to move the script block below the ScriptManager control:

<asp:ScriptManager ID="sm1" runat="server" />
<script type="text/javascript">
  Sys.WebForms.PageRequestManager.getInstance().add_endRequest(End);
 
  function End(sender, args) { }
</script>  
<asp:UpdatePanel ID="up1" runat="server">
  <ContentTemplate>
    <!-- Interesting content goes here -->
  </ContentTemplate>
</asp:UpdatePanel>

That’s the same JavaScript code, but now it works because it’s referencing the PageRequestManager after the AJAX Framework has been initialized. While it does work, hopefully this solution doesn’t sit well with you. It’s not a very good one.

A Better Way

The correct way to remedy this problem is to add the script to the ScriptManager’s Scripts collection and then call our wireups in a Sys.Application.init event handler:

<asp:ScriptManager ID="sm1" runat="server">
  <Scripts>
    <asp:ScriptReference Path="Init.js" />
  </Scripts>
</asp:ScriptManager>
<asp:UpdatePanel ID="up1" runat="server">
  <ContentTemplate>
    <!-- Interesting content goes here -->
  </ContentTemplate>
</asp:UpdatePanel>

Init.js:

Sys.Application.add_init(AppInit);
 
function AppInit(sender) {
  Sys.WebForms.PageRequestManager.getInstance().add_endRequest(End);
}
 
function End(sender, args) { }

This is much better. Not only is it a bit more sturdy, but it’s just good practice to separate presentation from functionality as much as possible.

Remember, all of your JavaScript functions are globally scoped. So, the AppInit function could still call functions defined at the page level or in other script includes. This is especially handy if you want to use Control.ClientID to more robustly reference controls in your client script, since you can’t do that inside a JavaScript include file.