RESTful API Endpoints vs Page Methods/Web Methods – (Vanilla Web Forms)

Jul 23, 2025
Updated Jun 26, 2026
adriancs

This is part of the series of introducing Vanilla ASP.NET Web Forms Architecture. Read more about this at the main menu:

Main Article: [Introducing Vanilla ASP.NET Web Forms Architecture]

What is the Differences Between RESTful Endpoints and Page Methods/Web Methods

The key difference is that Page Methods/Web Methods lock you into rigid, static method signatures with fixed parameters, while RESTful Endpoints give you the flexibility of a true API with dynamic parameter handling and single-endpoint routing.

Page Methods feel like you’re calling specific functions remotely, but RESTful Endpoints feel like you’re communicating with a proper web service that can intelligently handle different operations based on the action you specify.

The RESTful approach in Vanilla Web Forms gives you:

  • Flexibility: Add new parameters without breaking existing code
  • Consistency: One endpoint, multiple operations
  • Control: Full access to Request/Response/Session
  • Scalability: Easy to extend with new actions
  • Modern: Follows REST principles within Web Forms

This is what makes the “Vanilla Web Forms” architecture innovative – you’re getting REST-like benefits while staying within the familiar Web Forms framework!

Traditional Web Forms: Page Methods/Web Methods

Backend Implementation

public partial class Users : System.Web.UI.Page
{
    [WebMethod]
    public static string GetUser(int id)
    {
        // Limited to static methods only
        var user = Database.GetUser(id);
        return JsonConvert.SerializeObject(user);
    }

    [WebMethod]
    public static string SaveUser(int id, string name, string email)
    {
        // Each parameter must be explicitly declared
        // No flexibility in parameter structure
        var user = new User { Id = id, Name = name, Email = email };
        Database.SaveUser(user);
        return "Success";
    }

    [WebMethod]
    public static string DeleteUser(int id)
    {
        Database.DeleteUser(id);
        return "Deleted";
    }
}

Frontend Usage

// jQuery/AJAX with fixed method signatures
$.ajax({
    type: "POST",
    url: "Users.aspx/GetUser",
    data: JSON.stringify({ id: 123 }),
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function(response) {
        // Response wrapped in .d property
        var user = JSON.parse(response.d);
        console.log(user);
    }
});

// Separate method calls for each operation
$.ajax({
    type: "POST", 
    url: "Users.aspx/SaveUser",
    data: JSON.stringify({ id: 123, name: "John", email: "john@email.com" }),
    contentType: "application/json; charset=utf-8"
});

$.ajax({
    type: "POST",
    url: "Users.aspx/DeleteUser", 
    data: JSON.stringify({ id: 123 }),
    contentType: "application/json; charset=utf-8"
});

Vanilla Web Forms: RESTful Endpoints

Backend Implementation

public partial class UserAPI : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // Single endpoint handles multiple operations
        string action = Request["action"]?.ToLower() ?? "";
        
        switch (action)
        {
            case "get":
                GetUser();
                break;
            case "save":
                SaveUser();
                break;
            case "delete":
                DeleteUser();
                break;
            case "list":
                GetAllUsers();
                break;
            default:
                Response.Write("0|Invalid action");
                break;
        }
    }

    void GetUser()
    {
        int id = int.Parse(Request["id"] ?? "0");
        var user = Database.GetUser(id);
        
        Response.Clear();
        Response.ContentType = "application/json";
        Response.Write(JsonSerializer.Serialize(user));
    }

    void SaveUser()
    {
        // Flexible parameter handling
        int id = int.Parse(Request["id"] ?? "0");
        string name = Request["name"] ?? "";
        string email = Request["email"] ?? "";
        string phone = Request["phone"] ?? "";
        
        var user = new User { Id = id, Name = name, Email = email, Phone = phone };
        var result = Database.SaveUser(user);
        
        Response.Write(result ? "1" : "0|Save failed");
    }

    void DeleteUser()
    {
        int id = int.Parse(Request["id"] ?? "0");
        bool success = Database.DeleteUser(id);
        Response.Write(success ? "1" : "0|Delete failed");
    }

    void GetAllUsers()
    {
        var users = Database.GetAllUsers();
        Response.Clear();
        Response.ContentType = "application/json";
        Response.Write(JsonSerializer.Serialize(users));
    }
}

Frontend Usage

// Single API endpoint with action-based routing
async function fetchAPI(action, data = {}) {
    const formData = new FormData();
    formData.append('action', action);
    
    // Dynamic parameter handling
    Object.keys(data).forEach(key => {
        formData.append(key, data[key]);
    });

    const response = await fetch('/UserAPI.aspx', {
        method: 'POST',
        body: formData,
        credentials: 'include'
    });

    return await response.text();
}

// Clean, consistent usage pattern
const user = await fetchAPI('get', { id: 123 });
const saveResult = await fetchAPI('save', { 
    id: 123, 
    name: 'John', 
    email: 'john@email.com',
    phone: '555-1234'  // Easy to add new fields
});
const deleteResult = await fetchAPI('delete', { id: 123 });
const allUsers = await fetchAPI('list');

Key Contrasts

AspectPage Methods/Web MethodsRESTful Endpoints
Method StructureStatic methods onlyInstance methods with Page lifecycle
Parameter HandlingFixed signatures, explicit parametersDynamic FormData, flexible parameters
RoutingMethod name in URL pathAction-based routing to single endpoint
Response FormatWrapped in .d propertyDirect response control
Error HandlingException-basedCustom error codes (0|Error message)
ExtensibilityAdd new WebMethod for each operationAdd new case to switch statement
HTTP MethodsAlways POSTCan handle GET/POST appropriately
Session AccessLimited (static context)Full access to Session, Request, Response
File UploadsComplex, requires separate handlingNatural FormData support

Real-World Example: User Management

Traditional WebMethod Approach

[WebMethod]
public static string UpdateUserProfile(int userId, string name, string email, 
    string phone, string address, DateTime birthDate, bool isActive)
{
    // Adding new field requires method signature change
    // All calling code must be updated
    // No way to make fields optional easily
}

RESTful Endpoint Approach

void UpdateUserProfile()
{
    int userId = int.Parse(Request["userId"] ?? "0");
    var updates = new Dictionary<string, object>();
    
    // Only update provided fields - natural optional parameters
    if (!string.IsNullOrEmpty(Request["name"])) 
        updates["name"] = Request["name"];
    if (!string.IsNullOrEmpty(Request["email"])) 
        updates["email"] = Request["email"];
    if (!string.IsNullOrEmpty(Request["phone"])) 
        updates["phone"] = Request["phone"];
    // Easy to add new fields without breaking existing calls
    
    Database.UpdateUserPartial(userId, updates);
    Response.Write("1");
}

Summary

Page Methods/Web Methods are rigid, static method calls that feel like traditional web services but with Web Forms constraints.

RESTful Endpoints in Vanilla Web Forms provide true API flexibility – one endpoint can handle multiple operations, parameters are dynamic, and you have full control over the request/response cycle. This approach is more scalable, maintainable, and aligns with modern web development practices.

Feature image credit: Photo by Barn Images on Unsplash