-
Notifications
You must be signed in to change notification settings - Fork 2
web.rest.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
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();
}
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;
}
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; }
}
}
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.
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.
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);
}
// ...
}
}
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
The following is the results when calling: AutoMapUpdateRelations
The following shows examples of specifying resource links using string URLs.
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"));
}
}
}
}
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}"));
}
}
}
}
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.
- 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);
}
- 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();
}
- 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();
}
- 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 |
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:
If the 'embed' query string is not specified, then all embedded resources will be returned:
Lastly, the client can specify a comma separated value indicating names of the embedded resourced to be returned:
-
Templates
-
Resources
-
Bootstrapping
-
Modules Details
-
Settings
-
Validation
-
Monitoring
- Setup
- Commands
- Queries
- Domain Events
- Message Logs
- Message Publishers
- Message Enrichers
- Message Filters
-
Azure Service Bus
-
RabbitMQ
-
Redis
-
MongoDB