+ All Categories
Home > Documents > HowTo IntegratingRealTimeShippingServiceWithCRT

HowTo IntegratingRealTimeShippingServiceWithCRT

Date post: 08-Feb-2016
Category:
Upload: islam-sultan
View: 62 times
Download: 1 times
Share this document with a friend
Description:
HowTo IntegratingRealTimeShippingServiceWithCRT
Popular Tags:
16
How to: Integrate a shipping carrier adapter (real time shipping service) with Commerce Runtime Further reading Microsoft Dynamics AX 2012 for Developers [AX 2012] SDK Download What is a shipping carrier adapter? Shipping related functionality inside Commerce Runtime (CRT) is made up of two services: 1) Shipping Service 2) Shipping Carrier Adapter Service The expectation is that for any shipment related information, the requestor should call into shipping service. If shipping service needs additional real time information from the actual shipping carrier to adequately handle the request, only then the shipping service should call into the carrier adapter service. The carrier adapter service then gets the latest information from the carrier’s web service. This is best understood with the following examples: 1. Instead of a flat shipping rate agreement, the retailer needs to get customized rates for each individual package from the shipping carrier. 2. A seller wants to display the latest tracking information (available from the shipping carrier) of the customer’s order shipments.
Transcript

How to: Integrate a shipping carrier adapter (real time shipping service) with Commerce Runtime

Further reading Microsoft Dynamics AX 2012 for Developers [AX 2012] SDK Download

What is a shipping carrier adapter?Shipping related functionality inside Commerce Runtime (CRT) is made up of two services:

1) Shipping Service2) Shipping Carrier Adapter Service

The expectation is that for any shipment related information, the requestor should call into shipping service. If shipping service needs additional real time information from the actual shipping carrier to adequately handle the request, only then the shipping service should call into the carrier adapter service. The carrier adapter service then gets the latest information from the carrier’s web service.

This is best understood with the following examples:

1. Instead of a flat shipping rate agreement, the retailer needs to get customized rates for each individual package from the shipping carrier.

2. A seller wants to display the latest tracking information (available from the shipping carrier) of the customer’s order shipments.

3. Before a seller accepts an order, they want to know if the destination shipping address entered by the customer is supported by the specific shipping carrier selected.

The shipping carrier adapter provides an entry point for any entity inside Commerce Runtime to communicate with an external shipping carrier. Both Shipping Service and Shipping Carrier Adapter Service are designed such that they can be replaced/extended independently of each other as long as they implement IShippingService and IShippingCarrierService respectively.

Creating a new shipping carrier adapter (sample code provided)1. Create a new Class Library C# project in Visual Studio. (We will call it ContosoShippingCarrier for

this example.)

2. Add a key to sign the Assembly.

Figure 1. Recommended layout of code.

a. Go to Project ContosoShippingCarrier Properties Signingb. Check ‘Sign the assembly’.c. Select a key file from the drop down.

3. Add following references to the project:a. Microsoft.Dynamics.Commerce.Runtime (Required for access to data model, data

manager and services interface.)b. Microsoft.Dynamics.Commerce.Runtime.Services.Messages (Required for access to

Request/Response types)c. System.Runtime.Serializationd. System.ComponentModel.DataAnnotations

4. Make ContosoShippingCarrierAdapter class implement :a. Microsoft.Dynamics.Commerce.Runtime.Services.Service

i. ExecuteRequest(ServiceRequest) should contain the logic for determining the type of a request and handling it accordingly.

ii. If the carrier adapter requires a set of specific configuration values to contact a web service, then these values should be provided as part of a ParameterSet inside the ServiceRequest object.

b. Microsoft.Dynamics.Commerce.Runtime.Services.IShippingCarrierServicei. The ‘Name’ property serves as a unique identifier for the adapter binary. For a

given delivery mode, CRT determines which adapter to instantiate based on this string value. This value must match one of the rows inside RetailShipCarrierInterface table.

5. Ensure that ContosoShippingCarrierAdapter class is decorated with [Export(typeof(IService))] attribute.

6. Out of the box, an extensible framework for supporting at least three of the following functionalities is provided. It is recommended to encapsulate the logic of each of these in separate classes (Fig. 1).

a. Address Validationb. Rate Calculationc. Tracking Information

7. If the carrier expects the country codes and state codes in a format different from that used by CRT, then add a country region mapper.

8. The names of specific configuration properties that the carrier adapter requires can be encapsulated inside a separate class called AdapterConfigurationFields.

Integrating the new shipping adapter assembly with CRT1. Add reference of the new shipping carrier adapter to commerce runtime configuration file so

that it is picked up during CRT initialization.a. The commerce runtime configuration is specified inside the commerceRuntime.config

xml file. The location of the file can vary based on the deployment. For a Sharepoint OOB storefront deployment, this file can be located as follows:

i. Open Internet Information Services Managerii. Right click on the ‘Public’ web application under the ‘Sites’ folder and click on

‘Explore’.

Figure 2. Locating CRT configuration file in a sharepoint OOB storefront deployment.

iii. For SharePoint deployments, the new shipping adapter assembly must be placed in the global assembly cache.

iv. Restart the web application every time changes are made to the config file.

b. Edit CommerRuntime.Config. (Note: CRT loads services/assemblies in the same sequence that they are specified in the config file. Fig 3.)

i. To load the entire assembly, add a line like the following:<add source="assembly" value="ContosoShippingCarrier, Version=1.0.0.0, Culture=neutral, PublicKeyToken=6598494e9dab8361, processorArchitecture=MSIL" />

ii. To load only a specific data type from the assembly, add a line like the following:<add source="type" value="Fully_Qualified_Name_Of_Type, AssemblyName"/>

2. Map a shipping mode in AX to this new shipping adapter. (For details, see the how-to on mapping a shipping mode to an external carrier.)

3. Specify the new adapter’s configuration in AX. (For details, see the how-to on mapping a shipping mode to an external carrier.)

4. Run A-1120_OC/N-1120_OC to push delivery mode mappings to CRT database.

Sample code for an external shipping adapter in CRTSample code is provided for the following classes:

1. ContosoShippingAdapter2. AddressValidator3. RateCalculator

Figure 3. Changes to CommerceRuntime.config to load the entire assembly.

4. Tracker5. AdapterConfigurationFields

Sample Code: ContosoShippingAdapter.csnamespace ContosoShippingCarrier{ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.Composition; using Microsoft.Dynamics.Commerce.Runtime; using Microsoft.Dynamics.Commerce.Runtime.DataModel; using Microsoft.Dynamics.Commerce.Runtime.Services; using Microsoft.Dynamics.Commerce.Runtime.Services.Messages;

/// <summary> /// Encapsulates code for contoso shipping carrier adapter. /// </summary> [Export(typeof(IService))] public sealed class ContosoShippingCarrierAdapter : Service, IShippingCarrierService { /// <summary> /// Enumerates list of services supported by carrier. /// </summary> private enum WebServiceType { /// <summary> /// List of webservices that this adapter supports. /// </summary> AddressValidation = 1, Tracking = 2, Rating = 3 };

/// <summary> /// Uniquely identifies an adapter binary. /// </summary> public string Name { get { return "Contoso"; } }

/// <summary> /// Executes the specified request. /// </summary> /// <typeparam name="TResponse">The type of the response.</typeparam> /// <param name="request">The request.</param> /// <returns>The response.</returns> protected override TResponse ExecuteRequest<TResponse>(ServiceRequest request) { if (request == null) { throw new ArgumentNullException("request"); }

object response; Type requestType = request.GetType();

if (requestType == typeof(GetShippingRateFromCarrierServiceRequest)) { response = GetShippingRate((GetShippingRateFromCarrierServiceRequest)request); } else if (requestType == typeof(GetTrackingInformationFromCarrierServiceRequest)) { response = GetTrackingDetails((GetTrackingInformationFromCarrierServiceRequest)request); } else if (requestType == typeof(ValidateShippingAddressCarrierServiceRequest)) { response = ValidateShippingAddress((ValidateShippingAddressCarrierServiceRequest)request); } else { throw new NotSupportedException(string.Format("Request '{0}' is not supported.", request)); }

return (TResponse)response; }

/// <summary> /// Called for service specific initialization. /// </summary> /// <param name="runtime">The runtime.</param> public void Initialize(CommerceRuntime runtime) { // Any service initiliation code goes here. // Example: Instantiation of data mapper, etc. }

/// <summary> /// Called for services specific deinitialization. /// </summary> public void Uninitialize() { // Do nothing. }

/// <summary> /// Validates the shipping address. /// </summary> /// <param name="request">The request.</param> /// <returns>Address validation response.</returns> private static ValidateShippingAddressCarrierServiceResponse ValidateShippingAddress(ValidateShippingAddressCarrierServiceRequest request) { if (request.AddressToValidate == null) { throw new ArgumentNullException("request.AddressToValidate");

} if (request.AdapterConfig == null) { throw new ArgumentNullException("request.AdapterConfig"); }

IEnumerable<Address> suggestedAddresses = new Collection<Address>(); bool isValid = false;

try { isValid = AddressValidator.ValidateShippingAddress(request.AddressToValidate, request.SuggestAddress, request.AdapterConfig, out suggestedAddresses); } catch (Exception ex) { ContosoErrorHandler(ex, WebServiceType.AddressValidation); }

return new ValidateShippingAddressCarrierServiceResponse(isValid, suggestedAddresses); }

/// <summary> /// Gets the shipping rate. /// </summary> /// <param name="request">The request.</param> /// <returns>Shipping Rate response from carrier.</returns> private static GetShippingRateFromCarrierServiceResponse GetShippingRate(GetShippingRateFromCarrierServiceRequest request) { if (request.ShippingRateInfo == null) { throw new ArgumentNullException("request.ShippingRateInfo"); }

if (request.AdapterConfig == null) { throw new ArgumentNullException("request.AdapterConfig"); }

decimal rates = 0;

try { rates = RateCalculator.GetShippingRate(request.ShippingRateInfo, request.AdapterConfig); } catch (Exception ex) { ContosoErrorHandler(ex, WebServiceType.Rating); }

return new GetShippingRateFromCarrierServiceResponse(rates); }

/// <summary> /// Gets the tracking details. /// </summary> /// <param name="request">The request.</param> /// <returns>Tracking details response from carrier.</returns> private static GetTrackingInformationFromCarrierServiceResponse GetTrackingDetails(GetTrackingInformationFromCarrierServiceRequest request) { if (request.AdapterConfig == null) { throw new ArgumentNullException("request.AdapterConfig"); }

if (request.TrackingNumbers == null) { throw new ArgumentNullException("request.TrackingNumbers"); }

IEnumerable<TrackingInfo> trackingDetails = new Collection<TrackingInfo>(); try { trackingDetails = Tracker.GetTrackingDetails(request.AdapterConfig, request.TrackingNumbers); } catch (Exception ex) { ContosoErrorHandler(ex, WebServiceType.Tracking); }

return new GetTrackingInformationFromCarrierServiceResponse(trackingDetails); }

/// <summary> /// Common handler for all exceptions for all Contoso shipping adapter services. /// </summary> /// <param name="exception">The exception.</param> /// <param name="serviceType">Type of the service that triggered the exception.</param> /// <exception cref="CommunicationException">To notify called of any exception with customized message</exception> private static void ContosoErrorHandler(Exception exception, WebServiceType serviceType) { Type exceptionType = exception.GetType(); Exception translatedException = null;

if (exceptionType == typeof(System.Web.Services.Protocols.SoapException)) { var ex = exception as System.Web.Services.Protocols.SoapException;

translatedException = new CommunicationException(CommunicationErrors.ExternalProviderError,

"Soap exception in Contoso adapter." + exception.Message, exception);

} else { translatedException = new CommunicationException(CommunicationErrors.ProviderCommunicationFailure, "Unable to communicate with Contoso." + exception.Message, exception);

} throw translatedException; } }}

Sample Code: AddressValidator.csnamespace ContosoShippingCarrier{ using System; using System.Collections.Generic; using Microsoft.Dynamics.Commerce.Runtime; using Microsoft.Dynamics.Commerce.Runtime.DataModel;

/// <summary> /// Static class to call into Contoso Address Validation WebService. /// </summary> internal static class AddressValidator { /// <summary> /// Validates the shipping address. /// </summary> /// <param name="enteredAddress">The entered address.</param> /// <param name="suggestAddress">A value indicating whether the service should suggest addresses.</param> /// <param name="adapterConfig">The adapter config.</param> /// <param name="suggestedAddresses">The suggested addresses.</param> /// <returns> /// Returns true if address was valid. /// </returns> public static bool ValidateShippingAddress(Address enteredAddress, bool suggestAddress, ParameterSet adapterConfig, out IEnumerable<Address> suggestedAddresses) { if (enteredAddress == null) { throw new ArgumentNullException("enteredAddress"); }

if (adapterConfig == null) { throw new ArgumentNullException("adapterConfig"); }

suggestedAddresses = null;

Boolean isValid = true;

// This is where a call to carrier web service would be made to validate the address and get suggestions. // Currently the code always returns true.

return isValid; } }}

Sample Code: RateCalculator.csnamespace ContosoShippingCarrier{ using System; using Microsoft.Dynamics.Commerce.Runtime; using Microsoft.Dynamics.Commerce.Runtime.DataModel;

/// <summary> /// Static class to call into ContosoShipping Rate WebService. /// </summary> internal static class RateCalculator { /// <summary> /// Gets the shipping rate. /// </summary> /// <param name="shippingRateInfo">The shipping rate info.</param> /// <param name="adapterConfig">The adapter config.</param> /// <returns> /// Shipping Rate /// </returns> public static decimal GetShippingRate(ShippingRateInfo shippingRateInfo, ParameterSet adapterConfig) { if (adapterConfig == null) { throw new ArgumentNullException("adapterConfig"); }

if (shippingRateInfo == null) { throw new ArgumentNullException("shippingRateInfo"); }

if (shippingRateInfo.ToAddress == null) { throw new ArgumentNullException("shippingRateInfo.ToAddress"); }

if (shippingRateInfo.FromAddress == null) { throw new ArgumentNullException("shippingRateInfo.FromAddress"); }

if (shippingRateInfo.GrossWeight <= 0)

{ throw new ArgumentOutOfRangeException("shippingRateInfo.GrossWeight","Gross weight cannot be zero or negative."); }

decimal totalCharges = 0M; // Call external service to get rate by using info in ShippingRateInfo (FromAddress, GrossWeight, ToAddress).

// Our sample code returns a preset shipping charge if (shippingRateInfo.GrossWeight < 10M) { decimal ChargePerUnitOfWeight = 2; totalCharges = shippingRateInfo.GrossWeight * ChargePerUnitOfWeight; } else { totalCharges = 20M; } return totalCharges; }

} }

Sample Code: Tracker.csnamespace ContosoShippingCarrier{ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using Microsoft.Dynamics.Commerce.Runtime; using Microsoft.Dynamics.Commerce.Runtime.DataModel;

/// <summary> /// Static class to call into ContosoShipping Track WebService. /// </summary> internal static class Tracker { /// <summary> /// Message contents of response from webservice when a shipment's information is not in their database. /// </summary> private const string ShipmentNotReceivedYet = "No information for the following shipments has been received by our system yet";

/// <summary> /// Gets the tracking details using tracking numbers specified inside <see cref="TrackingInfo"/>. /// Makes multiple calls to the webservice. One call per tracking number. /// </summary> /// <param name="adapterConfig">The adapter configuration.</param>

/// <param name="trackingNumbers">The tracking numbers.</param> /// <returns>Tracking details for all the tracking numbers.</returns> public static ReadOnlyCollection<TrackingInfo> GetTrackingDetails(ParameterSet adapterConfig, IEnumerable<string> trackingNumbers) { if (adapterConfig == null) { throw new ArgumentNullException("adapterConfig"); }

if (trackingNumbers == null) { throw new ArgumentNullException("trackingNumbers"); }

Collection<TrackingInfo> trackingDetails = new Collection<TrackingInfo>();

// Call external web service to get tracking info.

foreach (string trackingNumber in trackingNumbers) {

TrackingInfo trackingInfo = new TrackingInfo { TrackingNumber = trackingNumber }; // Use web service response to set trackingInfo porperties such as ShippedOnDate, DeliveredOnDate, DestinationAddress, EstimatedDeliveryDate, OriginAddress, // PackageWeight, PackagingType, ServiceType, ShipmentProgress, ShippedOnDate, ShippingCharge, Status, TrackingNumber, TrackingUrl.

trackingDetails.Add(trackingInfo); }

return trackingDetails.AsReadOnly(); } }}

Sample Code: AdapterConfigurationFields.csnamespace ContosoShippingCarrier{ /// <summary> /// Container of name of all the fields that are read from the adapter configuration for ContosoShipping. /// </summary> internal static class AdapterConfigurationFields { /// <summary> /// Name of field that contains the web service url in adapter configuration. /// </summary> public const string TrackWebServiceUrl = "TrackWebServiceUrl";

/// <summary>

/// Name of field that contains the web service url in adapter configuration. /// </summary> public const string RateWebServiceUrl = "RateWebServiceUrl";

/// <summary> /// Adapter configuration field. /// </summary> public const string UserCredentialKey = "UserCredentialKey";

/// <summary> /// Adapter configuration field. /// </summary> public const string UserCredentialPassword = "UserCredentialPassword";

/// <summary> /// Adapter configuration field. /// </summary> public const string AccountNumber = "AccountNumber";

/// <summary> /// Adapter configuration field. /// </summary> public const string MeterNumber = "MeterNumber";

/// <summary> /// Adapter configuration field. /// </summary> public const string DropOffType = "DropOffType";

/// <summary> /// Adapter configuration field. /// </summary> public const string RateRequestType = "RateRequestType";

/// <summary> /// Adapter configuration field. /// </summary> public const string ServiceType = "ServiceType";

/// <summary> /// Adapter configuration field. /// </summary> public const string PackagingType = "PackagingType";

/// <summary> /// Adapter configuration field. /// </summary> public const string WeightUnits = "WeightUnits";

/// <summary> /// Adapter configuration field. /// </summary> public const string LinearUnits = "LinearUnits";

/// <summary> /// Adapter configuration field. /// </summary>

public const string CurrencyCode = "CurrencyCode"; }}


Recommended