Working With JSON in Razor Pages

UI generation and processing within web applications is moving increasingly to the client. Data processing and storage is still undertaken on the server, with JSON being the preferred format for exchanging data between the server and the client. There are a number of ways in which you can generate JSON when working with Razor Pages. This article explores some of them.

First I need some data to expose as JSON, so I will create a small service class that generates some data based on the following entity:

public class Car
{
    public int Id { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }
    public int Year { get; set; }
    public int Doors { get; set; }
    public string Colour { get; set; }
    public decimal Price { get; set; }
}

The simple service will have one method that conforms to an interface, generating and returning a list of cars:

public interface ICarService
{
    List<Car> ReadAll();
}

public class CarService : ICarService
{
    public List<Car> ReadAll()
    {
        List<Car> cars = new List<Car>{
            new Car{Id = 1, Make="Audi",Model="R8",Year=2014,Doors=2,Colour="Red",Price=79995},
            new Car{Id = 2, Make="Aston Martin",Model="Rapide",Year=2010,Doors=2,Colour="Black",Price=54995},
            new Car{Id = 3, Make="Porsche",Model=" 911 991",Year=2016,Doors=2,Colour="White",Price=155000},
            new Car{Id = 4, Make="Mercedes-Benz",Model="GLE 63S",Year=2017,Doors=5,Colour="Blue",Price=83995},
            new Car{Id = 5, Make="BMW",Model="X6 M",Year=2016,Doors=5,Colour="Silver",Price=62995},
        };
        return cars;
    }
}

This service is registered with the ASP.NET Core Dependency Injection system so that it can be made available throughout the application:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    services.AddTransient<ICarService, CarService>();
}

JsonResult in a Razor Page

Because Razor Pages is built on top of the ASP.NET Core MVC framework, primitives from MVC are available to PageModel files. One of those primitives is the JsonResult action result which serialises data to JSON and returns is as a response with the content type set to application/json. Therefore you can configure a Razor Page to deliver JSON. The following example shows the PageModel file content for a simple Razor Page (GetCars.cshtml) that is designed solely to return JSON:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorJSON.Services;

namespace RazorJSON.Pages
{
    public class GetCarsModel : PageModel
    {
        private readonly ICarService _carService;

        public GetCarsModel(ICarService carService)
        {
            _carService = carService;
        }

        public JsonResult OnGet()
        {
            return new JsonResult(_carService.ReadAll());
        }
    }
}

The service is injected into the PageModel constructor and the data is returned as a JsonResult. To make the JsonResult type available to the PageModel file, you need to add using Microsoft.AspNetCore.Mvc; to the top of the file. You add a place in a different page for the results to appear:

<ul id="car-list"></ul>

And a snippet of jQuery to make the call to the page:

@section scripts{
<script type="text/javascript">
    $(function () {
        $.get('/GetCars/').done(function (cars) {
            $.each(cars, function (i, car) {
                var item = `<li>
                            <strong>${car.make} ${car.model}</strong>
                            (£${car.price})</li>`;
                $('#car-list').append(item);
            });
        });
    });
</script>
}

When you run the page, the list of cars appears:

list of cars

This works fine but creating a Razor Page just to serve JSON doesn't feel right. Razor Pages are intended to generate UI, not act as a data service. You could instead use an existing page and add a named handler to deliver JSON. Let's alter the method in the example page to adopt this approach by renaming OnGet to OnGetCarList:

public JsonResult OnGetCarList()
{
    return new JsonResult(_carService.ReadAll());
}

Then we change the actual Razor Page so that the special handler parameter becomes a route parameter rather than a querystring parameter:

@page "{handler?}"

Now we can call this method from client-side code:

$.get('/GetCars/carlist').done(function (cars) {
    $.each(cars, function (i, car) {
        var item = `<li>
                    <strong>${car.make} ${car.model}</strong>
                    (£${car.price})</li>`;
        $('#car-list').append(item);
    });
});

This is a bit better in that we are not creating a bunch of Razor Pages purely to serve data, but this kind of piggy-backing of a resource that is intended to generate UI still feels a bit like the old PageMethods approach that Web Forms offered.

JsonResult from an MVC Controller

Since MVC primitives are available to a Razor Pages application, you can use an MVC Controller to deliver JSON. You can try this out by adding a folder named Controllers to the application and then right click on the folder and choose Add... Controller (if using Visual Studio). Then choose the Empty Controller option from the resulting dialogue and name it CarsController (plural). Replace the content with the following:

using Microsoft.AspNetCore.Mvc;
using RazorAPI.Services;

namespace RazorAPI.Controllers
{
    public class CarsController : Controller
    {
        private readonly ICarService _carService;

        public CarsController(ICarService carService)
        {
            _carService = carService;
        }

        public JsonResult Index()
        {
            return Json(_carService.ReadAll());
        }
    }
}

Once again, you inject the CarService into the controller via its constructor. The car data is obtained in the Index method and returned as JSON to the callee. The same client side code is use but the URL is changed:

@section scripts{
<script type="text/javascript">
    $(function () {
        $.get('/Cars/').done(function (cars) {
            $.each(cars, function (i, car) {
                var item = `<li>
                            <strong>${car.make} ${car.model}</strong>
                            (£${car.price})</li>`;
                $('#car-list').append(item);
            });
        });
    });
</script>
}

This certainly feels like an improvement, but there is still another (possibly better) option.

Web API

The final option is to use a Web API controller. Web API is designed specifically to provide data services over HTTP. It is also easy to work with in ASP.NET Core as its part of the framework, unlike previous versions of ASP.NET. Finally, in the context of this discussion, the default data format that Web API works with is JSON.

Web API provides RESTful services, that is resources available over HTTP that respond in specific ways depending on the HTTP verb that is used for the request. The HTTP verbs typically map to standard CRUD operations as follows:

HTTP Verb CRUD Operation
POST Create
GET Read
PUT Update
DELETE Delete

To test this out, right click on the Controllers folder and choose Add... Controller. This time, choose API Controller - Empty and name the controller CarController (singular). Amend the existing code as follows:

using Microsoft.AspNetCore.Mvc;
using RazorAPI.Models;
using RazorAPI.Services;
using System.Collections.Generic;

namespace RazorAPI.Controllers
{
    [Produces("application/json")]
    [Route("api/Car")]
    public class CarController : Controller
    {
        private readonly ICarService _carService;

        public CarController(ICarService carService)
        {
            _carService = carService;
        }

        // GET: api/Car
        [HttpGet]
        public IEnumerable<Car> Get()
        {
            return _carService.ReadAll();
        }
    }
}

The controller is decorated with a couple of attributes. The Produces attribute specifies the content type that the controller works with, and the Route attribute specifies the URL at which the service is available. This controller only contains one method which responds to all GET requests, resulting in the collection of Cars being returned as JSON. You can see examples of other operations at Working With JSON on learnrazorpages.com. And this is the key point about Web API resources- they are designed ot work with a variety of CRUD operations and so are much more suitable if you want to do more than just produce read-only data to be consumed by client code.

Once again, the only alteration required to the existing client code to make it work with the Web API approach is a change in the URL that's called:

@section scripts{
<script type="text/javascript">
    $(function () {
        $.get('/api/car/').done(function (cars) {
            $.each(cars, function (i, car) {
                var item = `<li>
                            <strong>${car.make} ${car.model}</strong>
                            (£${car.price})</li>`;
                $('#car-list').append(item);
            });
        });
    });
</script>
}

Summary

This article has looked at a number of ways to work with JSON in a Razor Pages application. The first option was a Razor Page handler method producing a JsonResult. While this works, it's not recommended - unless you are providing a small number of read-only operations. If that's the case, you should look to re-use existing pages with named handlers rather than cluttering your Pages folder with files that will invariably add routes to your application but are not intended to be navigated to.

An MVC Controller providing JsonResults offers an improvement over a Razor Page, in that you can work with them without having to add superfluous view files to the application. Web API really shines if you want to offer a full range of CRUD services in a RESTful manner - especially if you want to expose those services to third parties.