Академический Документы
Профессиональный Документы
Культура Документы
Further reading
Microsoft Dynamics AX 2012 for Developers [AX 2012] SDK Download
a. Go to Project ContosoShippingCarrier Properties Signing b. 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.Serialization d. 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.IShippingCarrierService i. 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 Validation b. Rate Calculation c. 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.
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" />
Figure 3. Changes to CommerceRuntime.config to load the entire assembly. 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 adapters 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.
/// <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; } } }
/// <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.
/// <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; }
} }
/// <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(); } } }
/// <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"; } }