Not every controller action needs to return a full HTML page. When your front-end JavaScript makes AJAX calls, or when you're building lightweight API endpoints within an MVC project, you need to return structured JSON data. JsonResult is your tool for exactly this — clean, serialized JSON responses from within MVC controllers.
JsonResult is an ActionResult type that serializes an object to JSON format and sends it as the HTTP response body with the content type application/json. It's part of the Microsoft.AspNetCore.Mvc namespace and is returned using the Json() helper method.
ActionResult
├── ViewResult → Returns HTML page
├── JsonResult → Returns JSON data ← You are here
├── PartialViewResult → Returns HTML fragment
├── RedirectResult → Redirects to URL
├── ContentResult → Returns raw string
├── FileResult → Returns file download
└── StatusCodeResult → Returns HTTP status code
fetch() or jQuery.ajax()), it expects JSON, not HTMLpublic class ProductsController : Controller
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
=> _productService = productService;
// Returns JSON for AJAX calls
public async Task<IActionResult> GetProductJson(int id)
{
var product = await _productService.GetByIdAsync(id);
if (product == null)
return NotFound(new { success = false, message = "Product not found" });
// The Json() method serializes the object and sets Content-Type: application/json
return Json(new
{
success = true,
data = new
{
product.Id,
product.Name,
product.Price,
product.Category,
product.InStock
}
});
}
}
// Controller — Search endpoint returning JSON
[HttpGet]
public async Task<IActionResult> Search(string query)
{
if (string.IsNullOrWhiteSpace(query) || query.Length < 2)
return Json(new { results = Array.Empty<object>() });
var products = await _productService.SearchAsync(query, limit: 10);
return Json(new
{
results = products.Select(p => new
{
p.Id,
p.Name,
p.Price,
imageUrl = p.ImageUrl ?? "/img/placeholder.png",
url = Url.Action("Details", "Products", new { id = p.Id })
}),
totalFound = products.Count
});
}
// Front-end — JavaScript calling the endpoint
const searchInput = document.getElementById('searchBox');
searchInput.addEventListener('input', async function() {
if (this.value.length < 2) return;
const response = await fetch('/Products/Search?query=' + encodeURIComponent(this.value));
const data = await response.json();
// data.results is the JSON array from our controller
const html = data.results.map(item =>
'<a href="' + item.url + '" class="search-result">' +
'<img src="' + item.imageUrl + '" /> ' + item.name +
' — $' + item.price + '</a>'
).join('');
document.getElementById('searchResults').innerHTML = html;
});
// When you need camelCase, custom date formats, or null handling
public IActionResult GetOrderDetails(int id)
{
var order = _orderService.GetById(id);
// Custom serialization options per-response
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false // Compact in production
};
return new JsonResult(order, options);
}
// Global configuration in Program.cs (applies to ALL JSON responses)
builder.Services.AddControllersWithViews()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
| Method | Returns | Content-Type | Best For |
|---|---|---|---|
Json() | JSON object | application/json | MVC controllers returning data for AJAX |
Ok() | 200 + optional body | application/json | API controllers (ControllerBase) |
Content() | Raw string | text/plain | Returning plain text, XML, or custom formats |
{ success, data, message }Program.cs for consistencycamelCase naming to match JavaScript conventionsWhenWritingNull to keep payloads clean[ValidateAntiForgeryToken])ApiControllerQ: "When would you use JsonResult in an MVC controller instead of creating a separate Web API controller?"
Architect Answer: "I use JsonResult when the AJAX endpoint is tightly coupled to a specific Razor view — like a live search box, a cascade dropdown, or a 'like' button. These are UI-driven interactions, not domain APIs. If the endpoint serves multiple consumers (mobile app, third-party integration, separate SPA), I create a proper [ApiController] with versioning, content negotiation, and proper REST semantics. The distinction is: JsonResult is for view support, ApiController is for domain services."