Implementing and Testing ASP.NET Web API Controllers
Modifying the Web API Route Configuraton
By default, ASP.NET Web API uses a routing convention that maps HTTP methods (GET, POST, PUT, DELETE) to controller actions without requiring an explicit action name in the URL. This convention-based routing can become problematic when you need multiple operations on the same resource using the same HTTP method.
To resolve this, you can modify the default route template to include an {action} parameter. This allows you to call specific controller methods directly via the URL.
Navigate to the App_Start/WebApiConfig.cs file and update the route registration as follows:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
// Include {action} to enable direct method invocation
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
The {id} parameter corresponds to the idantifier parameter in your API methods. This change prevents conflicts between different controller actions that might have identical parameters.
Creating Controllers and Models
Defining Data Models
First, create model classes within the Models folder to represent your data structures.
// Models/VideoGame.cs
public class VideoGame
{
public string Title { get; set; }
public string Developer { get; set; }
public string Publisher { get; set; }
public string Genre { get; set; }
public decimal Cost { get; set; }
}
// Models/InventoryItem.cs
public class InventoryItem
{
public int ItemId { get; set; }
public string ItemName { get; set; }
public string Category { get; set; }
public decimal UnitPrice { get; set; }
}
Implementing API Controllers
Add new Web API controllers by right-clicking the Controllers folder and selecting Add → Controller. Choose the Web API 2 Controller - Empty tmeplate.
// Controllers/GameLibraryController.cs
public class GameLibraryController : ApiController
{
VideoGame[] gameCollection = new VideoGame[]
{
new VideoGame { Title="CyberQuest", Developer="Nexus Studios", Publisher="A", Genre="RPG", Cost=49.99M},
new VideoGame { Title="ShadowFall", Developer="Umbra Games", Publisher="B", Genre="Horror", Cost=39.99M},
new VideoGame { Title="Starlight", Developer="Cosmic Dev", Publisher="C", Genre="Adventure", Cost=29.99M},
new VideoGame { Title="IronFront", Developer="Tactical Soft", Publisher="D", Genre="Strategy", Cost=59.99M}
};
public IEnumerable<VideoGame> GetAllGames()
{
return gameCollection;
}
public IHttpActionResult FindGame(string title)
{
var selectedGame = gameCollection.FirstOrDefault(g => g.Title == title);
if (selectedGame == null)
{
return NotFound();
}
return Ok(selectedGame);
}
}
// Controllers/StockController.cs
public class StockController : ApiController
{
InventoryItem[] stockItems = new InventoryItem[]
{
new InventoryItem { ItemId = 101, ItemName = "Tomato Soup", Category = "Groceries", UnitPrice = 1.50M },
new InventoryItem { ItemId = 102, ItemName = "Building Blocks", Category = "Toys", UnitPrice = 15.99M },
new InventoryItem { ItemId = 103, ItemName = "Hammer", Category = "Hardware", UnitPrice = 12.75M }
};
public IEnumerable<InventoryItem> GetCompleteInventory()
{
return stockItems;
}
public IHttpActionResult LocateItem(int id)
{
var item = stockItems.FirstOrDefault(i => i.ItemId == id);
if (item == null)
{
return NotFound();
}
return Ok(item);
}
}
// Controllers/UtilityController.cs
public class UtilityController : ApiController
{
[HttpGet]
public string CombineParameters(string textValue, int numericValue)
{
string output = "";
output = textValue + numericValue.ToString();
// Additional processing logic
return output;
}
}
Testing the API Endpoints
Run the application and test the endpoints by navigating to URLs like:
http://localhost:port/api/GameLibrary/GetAllGameshttp://localhost:port/api/Stock/LocateItem/102http://localhost:port/api/Utility/CombineParameters?textValue=test&numericValue=5
Configuring JSON Response Format
To ensure the API returns JSON instead of XML, update the WebApiConfig.cs file:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Remove XML formatter to default to JSON
var xmlFormatter = config.Formatters.XmlFormatter.SupportedMediaTypes
.FirstOrDefault(media => media.MediaType == "application/xml");
config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(xmlFormatter);
}
}
Standardizing API Response Structure
Create a helper class to provide consistent response formatting across all endpoints.
// Helpers/ApiResponseFormatter.cs
public class ApiResponseFormatter
{
private readonly string responseTemplate = "{{\"statusCode\":{0},\"statusMessage\":\"{1}\",\"data\":{2}}}";
public HttpResponseMessage CreateResponse(ApiStatusCode code, string message, string dataContent)
{
string numericPattern = @"^(\-|\+)?\d+(\.\d+)?$";
string formattedJson;
if (Regex.IsMatch(dataContent, numericPattern) ||
dataContent.ToLower() == "true" ||
dataContent.ToLower() == "false" ||
dataContent == "[]" ||
dataContent.Contains('{'))
{
formattedJson = string.Format(responseTemplate, (int)code, message, dataContent);
}
else
{
formattedJson = string.Format(responseTemplate, (int)code, message, "\"" + dataContent + "\"");
}
return new HttpResponseMessage
{
Content = new StringContent(formattedJson, Encoding.UTF8, "application/json")
};
}
}
public enum ApiStatusCode
{
OperationFailed = 0,
OperationSuccessful = 10000
}
// Controllers/ValidationController.cs
public class ValidationController : ApiController
{
private ApiResponseFormatter formatter = new ApiResponseFormatter();
[HttpGet]
public HttpResponseMessage VerifyUsername(string userName)
{
int userCount = CheckUserExistence(userName);
if (userCount > 0)
{
return formatter.CreateResponse(ApiStatusCode.OperationFailed,
"Username already registered",
"1 " + userName);
}
else
{
return formatter.CreateResponse(ApiStatusCode.OperationSuccessful,
"Username available",
"0 " + userName);
}
}
private int CheckUserExistence(string username)
{
// Simulated database check
return username == "existinguser" ? 1 : 0;
}
}
Consuming the Web API from JavaScript
Create an HTML page that uses jQuery to interact with you're Web API endpoints.
<!DOCTYPE html>
<html>
<head>
<title>Product Inventory</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div>
<h2>Complete Inventory</h2>
<ul id="itemList"></ul>
</div>
<div>
<h2>Search by Item ID</h2>
<input type="text" id="itemIdInput" size="5" />
<button onclick="searchItem()">Search</button>
<p id="itemDetails"></p>
</div>
<script>
const apiBaseUrl = 'api/Stock';
$(function() {
$.getJSON(apiBaseUrl + '/GetCompleteInventory')
.done(function(data) {
$.each(data, function(index, item) {
$('#itemList').append('<li>' + formatItemDisplay(item) + '</li>');
});
});
});
function formatItemDisplay(item) {
return item.ItemName + ': $' + item.UnitPrice;
}
function searchItem() {
const itemId = $('#itemIdInput').val();
$.getJSON(apiBaseUrl + '/LocateItem/' + itemId)
.done(function(data) {
$('#itemDetails').text(formatItemDisplay(data));
})
.fail(function(jqXHR, textStatus, error) {
$('#itemDetails').text('Error: ' + error);
});
}
</script>
</body>
</html>
Deployment to IIS
- Publish the Application: In Visual Studio, right-click the project and select Publish. Choose the IIS target and configure the publish settings.
- IIS Configuration: Open Internet Information Services (IIS) Manager, right-click Sites, and select Add Website. Specify the site name, physical path (where you published the files), and binding information (typically
localhost).