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
| Aspect | Page Methods/Web Methods | RESTful Endpoints |
|---|---|---|
| Method Structure | Static methods only | Instance methods with Page lifecycle |
| Parameter Handling | Fixed signatures, explicit parameters | Dynamic FormData, flexible parameters |
| Routing | Method name in URL path | Action-based routing to single endpoint |
| Response Format | Wrapped in .d property | Direct response control |
| Error Handling | Exception-based | Custom error codes (0|Error message) |
| Extensibility | Add new WebMethod for each operation | Add new case to switch statement |
| HTTP Methods | Always POST | Can handle GET/POST appropriately |
| Session Access | Limited (static context) | Full access to Session, Request, Response |
| File Uploads | Complex, requires separate handling | Natural 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
