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

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!

RESTful Endpoints vs Page Methods/Web Methods

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