Booking Extensions
XML Extensions
If the seller works with XML format, we can specify the integration to work with XML with the following:
internal static class SearchExtensions
{
public static void AddSearchServices(this IServiceCollection services,
IConfiguration configuration)
{
services.AddXmlSerializer<SearchRequest, SearchResponse>(ConfigureXmlOptions);
services.AddSearchOperation<SearchOperation, SearchRequest, SearchResponse, AccessModel>(TgxPlatform.Name,
configuration);
}
private static void ConfigureXmlOptions(XmlSerializerOptions options) { }
}
caution
Be aware of the changes from JSON to XML:
- Now the service that is being used is "AddXmlSerializer" instead of "AddJsonSerializer"
- The method that will have options for the serialization is now called "ConfigureXmlOptions" and has a "XmlSerializerOptions".
Multi JSON serializer
If the seller sends different json response structures in their response, implement a MultiJSONSerializer:
- Add the "AddMultiJsonSerializer" service in the extensions of the operation, for instance, in Search it would look like this:
AddSerializerDependencySearch<SearchRequest, ResponseWrapper<SearchResponse>>(services);
services.AddSearchOperation<SearchOperation, SearchRequest, ResponseWrapper<SearchResponse>>, AccessModel>(TgxPlatform.Name, configuration);
public static void AddSerializerDependencySearch<TRequest, TResponse>(IServiceCollection services)
{
MultiResponseJsonType[] multiResponseTypes = new MultiResponseJsonType[]
{
new(typeof(TResponse)),
new(typeof(List<TResponse>)),
new(typeof(ErrorResponse))
};
IDictionary<Type, JsonSerializerOptions> serializerOptions = ConfigureJsonOptions(multiResponseTypes);
ResponseJsonSerializer<TRequest, TResponse> serializer = new(multiResponseTypes, serializerOptions);
services.AddMultiJsonSerializer<TRequest, ResponseWrapper<TResponse>, MultiResponseJsonSerializer<TRequest, ResponseWrapper<TResponse>>>(serializer, serializerOptions);
}
//Add your JsonSerializerOptions as needed
private static IDictionary<Type, JsonSerializerOptions> ConfigureJsonOptions(IEnumerable<MultiResponseJsonType> multiResponseTypes) =>
multiResponseTypes.ToDictionary(
type => type.Type,
type => new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
- The class used in this service should also be created, like this:
public class ResponseJsonSerializer<TRequest, TResponse> : MultiResponseJsonSerializer<TRequest, ResponseWrapper<TResponse>>
{
public ResponseJsonSerializer(
IEnumerable<MultiResponseJsonType> multiResponseTypes,
IDictionary<Type, JsonSerializerOptions> serializerOptions)
: base(multiResponseTypes, serializerOptions) { }
protected override ResponseWrapper<TResponse> BuildResponse(object response) => response switch
{
ErrorResponse errorResponse => new ResponseWrapper<TResponse> { ErrorResponse = errorResponse },
_ => new ResponseWrapper<TResponse> { Response = (TResponse)response }
};
}
Multi XML serializer
If the seller sends different XML response structures in their response, implement a MultiXmlSerializer:
- Add the "AddMultiXmlSerializer" service in the extensions of the operation, for instance, in Search it would look like this:
AddMultiSerializer<SearchRequest, ResponseWrapper<SearchResponse>>(services);
services.AddSearchOperation<SearchOperation, SearchRequest, ResponseWrapper<SearchResponse>, AccessModel>(TgxPlatform.Name, configuration);
public static void AddMultiSerializer<TRequest, TResponse>(this IServiceCollection services, string name)
{
var nameSpace = "http://www.opentravel.org/OTA/2003/05";
var multiResponseTypes = new[]
{
new MultiResponseType(typeof(ErrorResponse), "ErrorResponse", nameSpace),
new MultiResponseType(typeof(TResponse), name, nameSpace),
};
var serializer = new ResponseXmlSerializer<TRequest, TResponse>(services.BuildServiceProvider(), multiResponseTypes);
services.AddMultiXmlSerializer<TRequest, ResponseWrapper<TResponse>, ResponseXmlSerializer<TRequest, TResponse>>(serializer);
}
- The class used in this service should also be created, like this:
public class ResponseXmlSerializer<TRequest, TResponse> : MultiResponseXmlSerializer<TRequest, ResponseWrapper<TResponse>>
{
public ResponseXmlSerializer(
IServiceProvider serviceProvider,
IEnumerable<MultiResponseType> multiResponseTypes,
XmlSerializerOptions xmlSerializerOptions = default)
: base(serviceProvider, multiResponseTypes, xmlSerializerOptions) { }
protected override ResponseWrapper<TResponse> BuildResponse(object response) => response switch
{
ErrorResponse errorRs => new ResponseWrapper<TResponse> { ErrorResponse = errorRs },
_ => new ResponseWrapper<TResponse> { Response = (TResponse)response }
};
}
Polling
In some scenarios, polling is required to handle pagination. You can use the AddSearchPollingOperationFor extension to enable this behavior.
- Specify Polling in the Search node in appsettings.local.json
"Search": {
"ConnectorsHttpClient": "Default",
"Polling": {
"Margin": "00:00:00.000",
"Delay": "00:00:00.000"
}
}
- Implement the Polling in the SearchExtensions.cs
internal static class SearchExtensions
{
public static void AddSearchServices(this IServiceCollection services,
IConfiguration configuration)
{
services.AddJsonSerializer<SearchRequest, SearchResponse>(JsonOptions.Configure);
services.AddSearchOperation<SearchOperation, SearchRequest, SearchResponse, AccessModel>(TgxPlatform.Name,
configuration).AddSearchPollingOperationFor<SearchRequest, SearchResponse, AccessModel>();
}
}
This enables pagination support; you can then handle pages in your ParseResponses method as shown below:
public SearchConnectorResponse ParseResponses(
SearchConnectorRequest connectorsRequest,
SearchParameters<AccessModel> connectorParameters,
IEnumerable<SupplierResponseWrapper<SearchResponse>> supplierResponses,
CancellationToken cancellationToken)
{
var firstResponse = supplierResponses.First();
// Parses connector-specific options; not the focus here
ParseOptions(firstResponse);
var accommodations = connectorParameters.OptionsGenerator.Combine();
// Sets up the pagination context for the next page, if applicable
SetPaginationContext(firstResponse.header.Pagination);
// Marks whether this is the last page of results
return new SearchConnectorResponse(new SearchRs(accommodations))
{
IsFinished = IsPaginationFinished(firstResponse.header.Pagination)
};
}
private static bool IsPaginationFinished(Pagination pagination)
{
// Pagination is finished when the current page is the last one
return pagination.currentPage >= pagination.totalPages;
}
private void SetPaginationContext(Pagination pagination)
{
// If more pages exist, store the next page number in context for future requests
if (!IsPaginationFinished(pagination))
{
_contextCache.TryAdd(TgxPlatform.Pagination_Page, pagination.currentPage + 1);
}
}