Skip to content

web.rest.server.overview

grecosoft edited this page May 30, 2018 · 14 revisions

REST/HAL Server - Overview

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

  • NetFusion.Web.Mvc
  • NetFusion.Rest.Server
  • NetFusion.Rest.Resources
  • NetFusion.Rest.Common

Plug-in Bootstrap Configuration

The bootstrapping of the NetFusion.Rest.Server plug-in is minimal and provides the RestApiConfig container plug-in configuration used to specify the following:

  • ControllerSuffix - Used to specify controller naming convention. Defaults to "Controller".

The RestApiConfig only needs to be specified if the default conventions need to be changed.

For returning template based resource URLs, the routing meta-data for all controllers needs to be made available for querying. To enable the reading of controller route meta-data, specify the *WebMvcConfig configuration and set the enableRouteMetadata property to True.

Like all other plug-in configurations these default values can be overridden during the bootstrap process:

private IBuiltContainer CreateAppContainer(IServiceCollection services, IConfiguration configuration, ILoggerFactory loggerFactory)
{
    // Creates an instance of a type resolver that will look for plug-ins within 
    // the assemblies matching the passed patterns.
    var typeResolver = new TypeResolver(
        "Demo.WebApi",
        "Demo.*");

    return services.CreateAppBuilder(
            configuration,
            loggerFactory,
            typeResolver)
        .Bootstrap(c => {
            c.WithConfig((WebMvcConfig cfg) => {
                cfg.EnableRouteMetadata = true;
                cfg.UseServices(services);
            });
        })
      .Build();
}

ASP.NET Core Pipeline Configuration

All resource REST/HAL link-relations concerns 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. During the ASP.NET Core startup, the filter can be added to the response pipe-line by placing a call to the UseHalFormatter method on the services collection.

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options => {
        options.UseHalFormatter();
    });

    var builtContainer = CreateAppContainer(services, _configuration, _loggerFactory);

    builtContainer.Start();
    return builtContainer.ServiceProvider;
}

Resource Definitions

Resources POCO classes are used to model the external models returned by an API. These types are usually placed within an assembly containing the public API for a service. HAL based resources derive from the base HalResource class. The following is an example:

using NetFusion.Rest.Resources.Hal;
using System;

namespace Listing.Api.Resources
{
    public class ListingResource : HalResource
    {
        public int ListingId { get; set; }
        public DateTime DateListed { get; set; }
        public int NumberBeds { get; set; }
        public int NumberFullBaths { get; set; }
        public int NumberHalfBaths { get; set; }
        public int SquareFeet { get; set; }
        public decimal AcresLot { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string State { get; set; }
        public string ZipCode { get; set; }
        public decimal ListPrice { get; set; }
        public decimal PricePerSqFt { get; set; }
        public int YearBuild { get; set; }
    }
}

Resource HAL Link Meta-data

The URIs to be associated with a resource are specified external from resources and controllers. During the bootstrap process, the NetFusion.Rest.Server plugin module finds all mapping files and caches the information to be used at runtime to generate resource HAL link-relations. The following is an example class consisting of mappings for the above resource and associated resources:

using Demo.Api.Resources;
using NetFusion.Rest.Common;
using NetFusion.Rest.Server.Hal;
using Demo.WebApi.Controllers;
using System.Net.Http;

namespace Demo.WebApi.HalMappings
{
#pragma warning disable CS4014
    namespace ApiHost.Relations
    {
        public class ResourceMappings : HalResourceMap
        {
            public override void OnBuildResourceMap()
            {
                Map<ListingResource>()
                    .LinkMeta<ListingController>(meta =>
                    {
                        meta.Url(RelationTypes.Self, (c, r) => c.GetListing(r.ListingId));
                        meta.Url("listing:update", (c, r) => c.UpdateListing(r.ListingId, null));
                        meta.Url("listing:delete", (c, r) => c.DeleteListing(r.ListingId));
                    })

                    .LinkMeta<PriceHistoryController>(meta => {
                        meta.Url(RelationTypes.History.Archives, (c, r) => c.GetPriceHistoryEvents(r.ListingId));
                    });

                Map<ListingResource>()
                    .LinkMeta(meta => meta.Href(RelationTypes.Alternate, HttpMethod.Get, 
                        r => $"http://www.homes.com/for/sale/{r.ListingId}"));
            }
        }
    }
}

All HAL resource mappings derive from the HalResourceMap class and can be located anywhere within a plugin (usually the assembly containing the ASP.NET Web application). The following will provide details and examples for each of the provided base mapping methods.

Expression Based Resource Link Mappings

Allows resource's URLs to be specified using a fluent syntax typed to a controller and resource. When this mapping method is used, all URL generation is delegated to the ASP.NET Core infrastructure. This is the method that should be used when specifying URLs that are handled by controllers defined in the same application as the mapping.

Method: Url(relName: string, action: Expression<Action<Controller, Resource>>

using Demo.Api.Resources;
using NetFusion.Rest.Common;
using NetFusion.Rest.Server.Hal;
using Demo.WebApi.Controllers;

namespace Demo.WebApi.HalMappings
{
    #pragma warning disable CS4014
    namespace ApiHost.Relations
    {
        public class ResourceMappings : HalResourceMap
        {
            public override void OnBuildResourceMap()
            {
                Map<ListingResource>()
                    .LinkMeta<ListingController>(meta =>
                    {
                        meta.Url(RelationTypes.Self, (c, r) => c.GetListing(r.ListingId));
                        meta.Url("listing:update", (c, r) => c.UpdateListing(r.ListingId, default(ListingResource)));
                        meta.Url("listing:delete", (c, r) => c.DeleteListing(r.ListingId));
                    })

                    .LinkMeta<PriceHistoryController>(meta => {
                        meta.Url(RelationTypes.History.Archives, (c, r) => c.GetPriceHistoryEvents(r.ListingId));
                    });

            }
        }
    }
}

The following is one of the controller actions referenced in the above mapping:

using Demo.Api.Resources;
using Microsoft.AspNetCore.Mvc;
using NetFusion.Rest.Resources.Hal;
using NetFusion.Web.Mvc.Metadata;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace Demo.WebApi.Controllers
{
    [Route("api/listing/price-history"), GroupMeta("Listing")]
    public class PriceHistoryController : Controller
    {

        // ...

        [HttpGet("{listingId}/events")]
        public Task<HalResource> GetPriceHistoryEvents(int listingId)
        {
            var items = GetPricingHistory().Where(h => h.ListingId == listingId);
            var historyResource = new HalResource();
            historyResource.Embed(items, "price-history");

            return Task.FromResult(historyResource);
        }

        // ...
    }
}

IMAGE


Method: AutoMapSelfRelation and AutoMapUpdateRelations

The AutoMapSelfRelation method will automatically determine the HAL "self" link based on conventions and define it for the resource. Similarly, the AutoMapUpdateRelations method will automatically determine the HAL create, update and delete relation links based on conventions and define them for the resource.

See the table below for the convention details.

using Demo.Api.Resources;
using NetFusion.Rest.Server.Hal;
using Demo.WebApi.Controllers;

namespace Demo.WebApi.HalMappings
{
#pragma warning disable CS4014
    namespace ApiHost.Relations
    {
        public class ResourceMappings2: HalResourceMap
        {
            public override void OnBuildResourceMap()
            {
                Map<ListingResource>()
                    .LinkMeta<ListingController>(meta => {
                        meta.AutoMapSelfRelation();
                        meta.AutoMapUpdateRelations();
                });
            }
        }
    }
}

The following is the results when calling: AutoMapSelfRelation IMAGE

The following is the results when calling: AutoMapUpdateRelations IMAGE

String Based Resource Link Mappings

The following shows examples of specifying resource links using string URLs.

Method: Href(relName: string, method: HttpMethod, href: string)

Allows the mapping of a hard-coded URL or template based URL to an external service API.

using Demo.Api.Resources;
using NetFusion.Rest.Server.Hal;
using System.Net.Http;

namespace Demo.WebApi.HalMappings
{
#pragma warning disable CS4014
    namespace ApiHost.Relations
    {
        public class ResourceMappings2: HalResourceMap
        {
            public override void OnBuildResourceMap()
            {
                Map<ListingResource>()
                    .LinkMeta(meta => meta.Href("conn", HttpMethod.Get, "https://www.realtor.com/propertyrecord-search/Connecticut"))
                    .LinkMeta(meta => meta.Href("conn-cheshire", HttpMethod.Get, "https://www.realtor.com/realestateandhomes-search/Cheshire_CT"));
            }
        }
    }
}

IMAGE

Method: Href(relName: string, method: HttpMethod, resourceUrl: Expression<Resource, string>

Allows the mapping to specify an URL based on the state of the returned resource using C# string interpolation.

using Demo.Api.Resources;
using NetFusion.Rest.Common;
using NetFusion.Rest.Server.Hal;
using System.Net.Http;

namespace Demo.WebApi.HalMappings
{
#pragma warning disable CS4014
    namespace ApiHost.Relations
    {
        public class ResourceMappings2: HalResourceMap
        {
            public override void OnBuildResourceMap()
            {
                Map<ListingResource>()
                    .LinkMeta(meta => meta.Href(RelationTypes.Alternate, HttpMethod.Get, r => $"http://www.homes.com/for/sale/{r.ListingId}"));
            }
        }
    }
}

IMAGE

Auto Link Generation Conventions

The following describes the conventions used when calling the AutoMapSelfRelation or AutoMapUpdateRelation mapping methods. These methods configure links for a resource based on a set of common conventions. This eliminates the need for the developer to map common resource actions such as get, create, update, and delete.

Get Resource

  • The controller must have an action method returning the resource type or a task of that resource type.
  • The controller's action must be a HTTP GET associated method.
  • The controller's action method takes an identity argument of the same type as the resource property determined to store the resource's identity. The resource and action method argument need not be named the same. See below for how a resource property or action argument, representing an identity value, is determined.
[HttpGet("{id}")]
public async Task<ListingResource> GetListing(int id)
{
    var listing = await _listingRepo.GetListing(id);
    return _objectMapper.Map<ListingResource>(listing);
}

Create Resource

  • The controller must have an action method accepting the resource type as an argument.
  • The controller's action must be an HTTP POST associated method.
[HttpPost]
public Task<ListingResource> CreateListing(ListingResource listing)
{
    throw new NotImplementedException();
}

Update Resource

  • The controller must have an action method accepting the resource type as an argument.
  • The controller's action must be a HTTP PUT or POST associated method.
  • The controller's action method takes an identity argument of the same type as the resource property determined to store the resource's identity. The resource and action method argument need not be named the same. See below for how a resource property or action argument, representing an identity value, is determined.
[HttpPut("{id}")] 
public Task<ListingResource> UpdateListing(int id, ListingResource listing)
{
    throw new NotImplementedException();
}

Delete Resource

  • The controller's action must be a DELETE associated method.
  • The controller's action method takes an identity argument of the same type as the resource property determined to store the resource's identity. The resource and action method argument need not be named the same. See below for how a resource property or action argument, representing an identity value, is determined.
[HttpDelete("{id}"), ResourceType(typeof(ListingResource))]
public Task<bool> DeleteListing(int id)
{
    throw new NotImplementedException();
}

The same conventions are used when determining the resource property and controller action method argument representing an identity value. The following are valid property and action argument identity names:

Resource Class Name Property/Argument Name
Customer Id, CustomerId
CustomerResource Id, CustomerId, CustomerResourceId

Embedded Resources

In addition to the resource being returned, embedded resources can be associated. An embedded resource is just a named resource or resource collection that is a child of a parent resource. The resource name is just a string used to identify the resource to the client. The following shows a resource being embedded into a parent resource:

 [HttpGet("{id}"), ActionMeta("property-listing")]
  public async Task<ListingResource> GetListing(int id)
  {
      var listing = await _listingRepo.GetListing(id);
      var listingResource = _objectMapper.Map<ListingResource>(listing);

      var comment = new CommentResource {
          Message = "Sample embedded resource.",
          UserName = "Sam Smith"
      };

      listingResource.Embed(comment);
      return listingResource;
  }

The name used to identify the embedded resource can specified when calling the Embed method on the parent resource or by decorating the embedded resource with the NamedResource attribute as follows:

[NamedResource("user-comment")]
public class CommentResource : HalResource
{
    public string Message { get; set; }
    public string UserName { get; set; }
}

If the name is passed when calling the Embed method, it will override the value specified on the resource via the attribute.

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. 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. The following is an example:

[HttpGet("{id}"), ActionMeta("property-listing")]
public async Task<ListingResource> GetListing(int id)
{
    var listing = await _listingRepo.GetListing(id);
    var listingResource = _objectMapper.Map<ListingResource>(listing);

    if (_resourceContext.IsResourceRequested<CommentResource>())
    {
        var comment = new CommentResource
        {
            Message = "Sample embedded resource.",
            UserName = "Sam Smith"
        };

        listingResource.Embed(comment);

    }

    return listingResource;
}

The following request will result in no embedded resources being returned:

IMAGE

If the 'embed' query string is not specified, then all embedded resources will be returned: IMAGE

Lastly, the client can specify a comma separated value indicating names of the embedded resourced to be returned: IMAGE

Clone this wiki locally