Skip to content

web.rest.server.overview

Brian Greco edited this page Jan 27, 2021 · 14 revisions

REST/HAL Server - Overview

The following section is specific to server-side code used to configure and return resources with links. Reference the following NuGet packages from a ASP.NET Core Web API application:

  • NetFusion.Rest.Server
dotnet add ./src/Demo.WebApi/Demo.WebApi.csproj package NetFusion.Rest.Server

Service APIs are most often coded to return response models. These models define the contract exposed by the API. NetFusion provides a non intrusive method for converting simple POCO models into HAL resources. This is accomplished by wrapping models within resource classes containing the additional HAL information. The following discusses more about the HAL specification.

Plug-in Bootstrap Configuration

The NetFusion.Rest.Server plug-in is added to the composite-container by invoking the AddRest() method on the ICompositeContainerBuilder interface:

 public void ConfigureServices(IServiceCollection services)
 {
     services.CompositeContainer(_configuration, new SerilogExtendedLogger())
         .AddRest()
         .Compose();

     services.AddControllers(); 
 }

ASP.NET Core Pipeline Configuration

All resource REST/HAL link settings are stored external to the MVC controller within mappings classes. A controller action method simply returns a resource and a filter applies any HAL information configured by mapping classes.

Resource Definitions

Resources extend normal API response POCO models with the following:

  • Relations: These are named links associated with the model. Links are used to invoke actions on the model represented by the resource and used to load additional related resources.
  • Embedded: Parent resources can have embedded child resources.

IMAGE Embedded resources and models allow for related data to be easy returned without having to create large models containing typed properties and collections. Also, based on the client making the call, the WebApi method can vary the size of the data returned based on the type of client. For example, if a mobile application is making the request, returning less initial data and allowing the client to navigate to related resources can be adventurous.


Resources POCO classes are used to model the external models returned by an API. The NetFusion implementation of REST/HAL does not require modifying response models. The HAL related information is simply added by wrapping normal models in resources.

Following is an example of a model and how it is wrapped by a resource.

var model = new ListingModel
{
    ListingId = 100,
    DateListed = DateTime.UtcNow,
    City = "Cheshire",
    State = "CT",
    ZipCode = "06410",
    ListPrice = 450000,
    YearBuild = 1996
};

var resource = model.AsResource();

Resource HAL Link Meta-data

The URIs to be associated with a model are specified external from models and controllers. During the bootstrap process, the NetFusion.Rest.Server plug-in module finds all mapping classes and caches the information used at runtime to generate resource links. The following is an example class consisting of mappings for the above model and associated model. The next examples topic will describe the different types of links that can be defined.


IMAGEBy associating links with a resource, the code accessing the REST API becomes much easier to maintain. Also, this avoids having to hard-code the service's API URLs on clients and having them become incorrect if the server changes the URL.


public class ResourceMappings : HalResourceMap
{
    protected override void OnBuildResourceMap()
    {
    		Map<ListingModel>()    
        		.LinkMeta<ListingController>(meta =>
            		{
                		meta.Url(RelationTypes.Self, (c, r) => c.GetListing(r.ListingId));
                    meta.Url("listing:update", (c, r) => c.UpdateListing(r.ListingId, default));
                    meta.Url("listing:delete", (c, r) => c.DeleteListing(r.ListingId));
                })
                
             .LinkMeta<PriceHistoryController>(meta => {
             		meta.Url(RelationTypes.History.Archives, (c, r) => c.GetPriceHistoryEvents(r.ListingId));
              });
    }
}

All HAL resource mappings derive from the HalResourceMap class and can be located anywhere within a plug-in (usually the assembly containing the ASP.NET Web application).


IMAGEThe specified links contained within derived HalResourceMap classes are read and cached when the NetFusion.Rest.Server plug-in bootstraps. When the client makes a request with an Accept header value of application/json+hal, the HalJsonOutputFormatter checks if any link meta-data exists for the model of the resource being returned. If present, the link meta-data and the state of the model are used to generate the link URLs which are associated with the resource.


Embedded Resources

When a model is wrapped within a resource, additional resources and/or models can be embedded and returned. Embedded items can be either resources (model wrapped in resource) or just a model if there are no associated links or embedded items. The embedded item can also be a collection of resources or models.

When a resource is returned, the HalJsonOutputFormatter recursively checks all embedded resources and applies and associated links. The resource name is a string used to identify the resource to the client.

Embedding Resource into Parent Resource

var studentRes = new Student
{
    FirstName = "John",
    LastName = "Smith"
}.AsResource();

var classRes = new Class
{
    Name = "Microservice Architecture",
    Instructor = "James Adams",
    NumberStudents = 22
}.AsResource();

studentRes.EmbedResource(classRes, "favorite-class");

Embedding Model into Parent Resource

var studentRes = new Student
{
    FirstName = "John",
    LastName = "Smith"
}.AsResource();

var classModel = new Class
{
    Name = "Fixing 15 Year Old Monolithic Applications",
    Instructor = "Mr. Hammer",
    NumberStudents = 1
};

studentRes.EmbedModel(classModel, "required-class");

Note that the embedded item is a model not wrapped within a resource. Therefore, when the parent resource is returned, this embedded model (since it is not a resource) will not be processed by the HalJsonOutputFormatter.

Embedding Resource Collection into Parent Resource

var studentRes = new Student
{
    FirstName = "John",
    LastName = "Smith"
}.AsResource();

var classModels = new[]
{
    new Class
    {
        Name = "Fixing 15 Year Old Monolithic Applications",
        Instructor = "Mr. Hammer",
        NumberStudents = 1
    },
    new Class
    {
        Name = "Software Design and Architecture",
        Instructor = "Mr. Dry",
        NumberStudents = 10
    }
}.AsResources();

studentRes.EmbedResources(classModels, "required-classes");

The above example invokes the AsResources on the array of Class models. Since the embedded item is a collection of resources, the HalJsonOutputFormatter will recursively process each resource in the collection and assign any defined links.

Embedding Model Collection into Parent Resource

This example is the same as the prior but a list of Class models, not wrapped in resources, embedded into the parent resource. Since these models are not wrapped in resources, they will not be processed by the HalJsonOutputFormatter.

var studentRes = new Student
{
    FirstName = "John",
    LastName = "Smith"
}.AsResource();

var classModels = new[]
{
    new Class
    {
        Name = "Fixing 15 Year Old Monolithic Applications",
        Instructor = "Mr. Hammer",
        NumberStudents = 1
    },
    new Class
    {
        Name = "Software Design and Architecture",
        Instructor = "Mr. Dry",
        NumberStudents = 10
    }
};

studentRes.EmbedModels(classModels, "required-classes");

Embedded Item Selection

When requesting a resource, the client can specify which embedded resources they require. This is an optional feature that can be used to reduce the amount of data returned to the client without having several specific API methods.


IMAGEThis is important for clients running on mobile devices where the amount of data returned needs to be taken into consideration.


The client conveys this information in a query string parameter. An application's service component or WebApi controller can inject the IHalEmbeddedResourceContext service to determine if an embedded resource has been requested. The client specifies the embedded resources to be returned by adding the embed query string parameter. If the client does not specify any embedded resources, it is assumed they want all resources provided by the API call. The following is an example controller checking if the client requested the CommentModel embedded type.

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using Demo.WebApi.Models;
using NetFusion.Rest.Server.Hal;

namespace Demo.WebApi.Controllers
{
    [Route("api/[controller]")]
    public class ListingEmbeddedController : Controller
    {
        private readonly IHalEmbeddedResourceContext _context;
        
        public ListingEmbeddedController(IHalEmbeddedResourceContext context)
        {
            _context = context;
        }
        
        [HttpGet("{id}")]
        public IActionResult GetListing(int id)
        {
            // ...
          
            var listingResource = listingModel.AsResource();
            if (_context.IsRequested("price-history"))
            {
                if (id == 1000)
                {
                    var pricingResources = GetPricingHistory().Select(m => m.AsResource()).ToArray();
                    listingResource.EmbedResources(pricingResources, "price-history");
                }
                else
                {
                    var pricingResources = GetPricingHistory().ToArray();
                    listingResource.EmbedModels(pricingResources, "price-history");
                }
            }
            
            return Ok(listingResource);
        }

        // ...
    }
}

And the following shows an example query string requested by the client:

 http://localhost:5000/api/listingembedded/1000?embed=price-history

Multiple embedded resources can be specified by providing additional values separated by a comma as follows:

 http://localhost:5000/api/listingembedded/1000?embed=price-history,price-averages

Additional Resource Methods

When returning collections of resources or models, it is best to always return them as embedded items into a parent resource. This allows future changes to be easily made by embedding additional needed resources/models into the parent. If there is no logical parent resource, the HalResource.New method can be used.

The following returns an empty parent resource into which a collection of resources are embedded:

var ratings = new[]
{
    new Rating { Value = 90, DateSubmitted = DateTime.UtcNow },
    new Rating { Value = 78, DateSubmitted = DateTime.UtcNow }
}.AsResources();

var resource = HalResource.New(r => r.EmbedResources(ratings));

// ... return resource to client as normal...

There is also a version of the HalResource.New method accepting a model to be wrapped by the resource. Below is an example:

var customerModel = new Customer
{
    FirstName = "Mark",
    LastName = "Twain"
};

var ratings = new[]
{
    new Rating { Value = 90, DateSubmitted = DateTime.UtcNow },
    new Rating { Value = 78, DateSubmitted = DateTime.UtcNow }
}.AsResources();

var resource = HalResource.New(customerModel,r => r.EmbedResources(ratings));

Summary

This topic provided information on how to create resources from response models and how to return additional resources and models by embedding into a parent resource. The only remaining topic is defining the links (URLs) that should be associated with returned resources.

Clone this wiki locally