first commit
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
|
||||
namespace USPS.Configuration
|
||||
{
|
||||
public class UspsSettings
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string EndpointUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Landmark.OLA.Models.Addresses;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using USPS.Configuration;
|
||||
|
||||
namespace USPS.ApiCalls
|
||||
{
|
||||
public class AddressValidationRequestBuilder : IRequestBuildable
|
||||
{
|
||||
private readonly UspsSettings _settings;
|
||||
private readonly Address _address;
|
||||
|
||||
public AddressValidationRequestBuilder(UspsSettings settings, Address address)
|
||||
{
|
||||
_settings = settings;
|
||||
_address = address;
|
||||
}
|
||||
|
||||
public virtual string BuildAddressXML()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var sw = new StringWriter(sb);
|
||||
var xml = new XmlTextWriter(sw);
|
||||
|
||||
xml.WriteStartElement("AddressValidateRequest");
|
||||
xml.WriteAttributeString("USERID", _settings.Username);
|
||||
xml.WriteStartElement("Address");
|
||||
// USPS considers unit numbers (ex: APT 204) to be Address1. Our app's models would consider these to be Address2. DO NOT CHANGE
|
||||
xml.WriteElementString("Address1", _address.Address2);
|
||||
xml.WriteElementString("Address2", _address.Address1);
|
||||
xml.WriteElementString("City", _address.City);
|
||||
xml.WriteElementString("State", _address.State.ToString());
|
||||
xml.WriteElementString("Zip5", _address.PostalCode);
|
||||
xml.WriteElementString("Zip4", "");
|
||||
xml.WriteEndElement();
|
||||
xml.WriteEndElement();
|
||||
xml.Close();
|
||||
|
||||
var query = HttpUtility.ParseQueryString(string.Empty);
|
||||
query["API"] = "Verify";
|
||||
query["XML"] = sb.ToString();
|
||||
|
||||
return query.ToString();
|
||||
}
|
||||
|
||||
public string BuildGET()
|
||||
{
|
||||
var xml = BuildAddressXML();
|
||||
|
||||
var message = _settings.EndpointUrl + xml;
|
||||
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
namespace USPS.ApiCalls
|
||||
{
|
||||
public interface IRequestBuildable
|
||||
{
|
||||
string BuildAddressXML();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Xml.Linq;
|
||||
using Landmark.OLA.Models.Addresses;
|
||||
using Landmark.OLA.Models.Addresses.Enums;
|
||||
|
||||
namespace USPS.Parsers
|
||||
{
|
||||
public class AddressValidationResponseParser : IResponseParser<Address>
|
||||
{
|
||||
|
||||
public bool CheckForError(XElement xdoc)
|
||||
{
|
||||
var elementAddress = xdoc.Element("Address");
|
||||
if (elementAddress == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var error = elementAddress.Element("Error");
|
||||
return (error != null);
|
||||
}
|
||||
|
||||
public Address Parse(XElement xdoc)
|
||||
{
|
||||
if( xdoc == null )
|
||||
{
|
||||
throw new ArgumentNullException("Address xml is null");
|
||||
}
|
||||
|
||||
var elementAddress = xdoc.Element("Address");
|
||||
if (elementAddress == null || elementAddress.Element("Error") != null)
|
||||
{
|
||||
throw new FormatException("Attempt to parse address that is in error.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var compareAddress = new Address
|
||||
{
|
||||
// USPS considers unit numbers (ex: APT 204) to be Address1. Our app's models would consider these to be Address2. DO NOT CHANGE
|
||||
Address2 = (string)elementAddress.Element("Address1"),
|
||||
Address1 = (string)elementAddress.Element("Address2"),
|
||||
City = (string)elementAddress.Element("City"),
|
||||
State = MakeStateOptionEnum((string)elementAddress.Element("State")),
|
||||
PostalCode = (string)elementAddress.Element("Zip5"),
|
||||
PostalCodeExt = (string)elementAddress.Element("Zip4"),
|
||||
Country = CountryOption.UnitedStates
|
||||
};
|
||||
|
||||
return compareAddress;
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new FormatException("USPS XML is in an unexpected layout.");
|
||||
}
|
||||
}
|
||||
|
||||
public static StateOption MakeStateOptionEnum(string state)
|
||||
{
|
||||
if (Enum.TryParse(state, out StateOption parsedState))
|
||||
{
|
||||
return parsedState;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FormatException("USPS XML contains an unexpected 'State'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace USPS.Parsers
|
||||
{
|
||||
public interface IResponseParser<T>
|
||||
{
|
||||
T Parse(XElement xElement);
|
||||
bool CheckForError(XElement xElement);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace USPS.Communication
|
||||
{
|
||||
public interface IUspsService
|
||||
{
|
||||
Task<XElement> SendMessage(string request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using USPS.Configuration;
|
||||
|
||||
namespace USPS.Communication
|
||||
{
|
||||
public class UspsService : IUspsService
|
||||
{
|
||||
private readonly UspsSettings _settings;
|
||||
|
||||
public UspsService(
|
||||
IOptions<UspsSettings> settings)
|
||||
{
|
||||
|
||||
_settings = settings.Value;
|
||||
}
|
||||
|
||||
public async Task<XElement> SendMessage(string request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var hc = new HttpClient();
|
||||
var result = await hc.GetAsync(request);
|
||||
|
||||
if (result.IsSuccessStatusCode)
|
||||
{
|
||||
var resultString = await result.Content.ReadAsStringAsync();
|
||||
var xdoc = XDocument.Parse(resultString);
|
||||
XElement xel = XElement.Parse(xdoc.ToString());
|
||||
return xel;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Bad status code from USPS");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Debug("{exception}", e.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Serilog;
|
||||
using USPS.Parsers;
|
||||
using USPS.ApiCalls;
|
||||
using USPS.Configuration;
|
||||
using Landmark.OLA.Models;
|
||||
using Landmark.OLA.Models.Vendor;
|
||||
using Landmark.OLA.Models.Addresses;
|
||||
|
||||
namespace USPS.Communication
|
||||
{
|
||||
public class UspsVendorClient : IUspsVendorClient
|
||||
{
|
||||
private readonly IUspsService _uspsService;
|
||||
private readonly IResponseParser<Address> _addressParser;
|
||||
|
||||
private readonly UspsSettings _settings;
|
||||
|
||||
public UspsVendorClient(IUspsService uspsService,
|
||||
IResponseParser<Address> addressParser,
|
||||
IOptions<UspsSettings> settings)
|
||||
{
|
||||
_uspsService = uspsService;
|
||||
_addressParser = addressParser;
|
||||
_settings = settings.Value;
|
||||
}
|
||||
|
||||
|
||||
public async Task<Address> CompareAddress(Address address)
|
||||
{
|
||||
|
||||
ModelValidator.Validate(address);
|
||||
|
||||
AddressValidationRequestBuilder api = new(_settings, address);
|
||||
|
||||
string createXml = api.BuildGET();
|
||||
|
||||
XElement response = await _uspsService.SendMessage(createXml);
|
||||
|
||||
try
|
||||
{
|
||||
if (_addressParser.CheckForError(response) )
|
||||
{
|
||||
throw new ArgumentException("USPS rejected address");
|
||||
}
|
||||
|
||||
return _addressParser.Parse(response);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// Do not log; this is thrown when a bad address is submitted and it just spams the logs.
|
||||
throw;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Log.Fatal("Error parsing response.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
|
||||
<PackageReference Include="CoreWCF.Primitives" Version="1.8.0" />
|
||||
<PackageReference Include="CoreWCF.ConfigurationManager" Version="1.8.0" />
|
||||
<PackageReference Include="CoreWCF.Http" Version="1.8.0" />
|
||||
<PackageReference Include="CoreWCF.WebHttp" Version="1.8.0" />
|
||||
<PackageReference Include="CoreWCF.NetTcp" Version="1.8.0" />
|
||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Landmark.OLA.Models\Landmark.OLA.Models.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user