WCF Services Developer Guide 2021.1
WCF Services Developer Guide 2021.1
WCF Services Developer Guide 2021.1
| Contents | ii
Contents
Introduction...............................................................................................................4
Audience................................................................................................................................................................4
Prerequisites..........................................................................................................................................................4
Conventions...........................................................................................................................................................4
Overview....................................................................................................................4
When to Use HTTP Transport...............................................................................................................................5
When to Use the TCP Transport...........................................................................................................................5
HTTP Transport........................................................................................................5
WCF Services Example #1 - Calling Epicor Web Services Using BasicHttpBinding or WSHttpBinding..........5
Create the Visual Studio Project................................................................................................................6
Add the Web Service References .............................................................................................................6
Add Helper Classes...................................................................................................................................7
Add Functionality to the Program Class.................................................................................................10
Code the Main Method............................................................................................................................13
WCF Services Example #2 - Using SOAP Call Headers ...................................................................................19
Create the Visual Studio Project .............................................................................................................19
Add the Web Service References ...........................................................................................................20
Add Helper Classes.................................................................................................................................21
Add Functionality to the Program Class.................................................................................................25
Code The Main Method .........................................................................................................................28
WCF Services Example #3 - Azure AD token authentication ...........................................................................30
Create the Visual Studio Project .............................................................................................................30
Add NuGet Package................................................................................................................................31
Add the Web Service Reference..............................................................................................................31
Add Helper Classes.................................................................................................................................31
Add Functionality to the Program Class.................................................................................................34
Code the Main Method............................................................................................................................35
Add Message Header...............................................................................................................................40
Add AzureADAuth Class........................................................................................................................41
Add Token Header to the Server Request...............................................................................................48
Setting Up SSL.........................................................................................................48
Create Site Binding.............................................................................................................................................48
Connect the Application Server .........................................................................................................................52
| Contents | iii
Index................................................................................................................................................55
| Introduction | 4
Introduction
Kinetic 2021 and earlier versions connect to services through Windows Communication Foundation (WCF). If you run
Kinetic 2021 or earlier and you need to call these services, review this guide.
Important: Calling services through WCF is obsolete. Starting with version 2022.1, Kinetic no longer uses WCF for
service calls. The system now uses Web APIs.
If you run Kinetic 2022.1 or later, you need to make changes to complete the transition to Web APIs. Integrations that
use REST or client-side contract .dlls to communicate with the Kinetic server should work without issues. Integrations
that directly use WCF as a service reference will require rework. You must use REST or contract assemblies, as Kinetic
only supports these integrations.
This guide is written for the .NET Framework platform (Kinetic 2022.1 and later use the .NET 6 platform). It describes
how to call Kinetic services though WCF, how to configure the Web services for different scenarios, and other information
developers may find useful when working with Kinetic services.
Audience
This guide is intended for developers responsible for integrating with Epicor ERP using web services. By leveraging
web services, developers can make Epicor data available for use in third party applications, custom storefront applications,
and other purposes. These web services also can move Epicor data into Java or other programming environments.
Prerequisites
This document assumes you have a working knowledge of C#, Visual Studio 2012, .NET 4.0, and Internet Information
Services (IIS). You should also understand the basic concepts behind web services and WCF.
This guide assumes you have already installed your Epicor application server, so application server installation instructions
are not included in this document. This document also assumes you have a configured Epicor client application on your
local development machine. You will need access to the client assemblies to complete some coding projects.
Conventions
This guide uses the following conventions.
• When this document refers to Internet Information Services (IIS), it refers to the server where you installed the Epicor
application server.
• Code samples appear in the follow font style:
Overview
The Epicor WCF services support two transport mechanisms: net.tcp and http/ https.
Each of these mechanism requires you follow a different approach when you use them. You also need to properly
configure the application server’s Web.Config file.
| HTTP Transport | 5
HTTP Transport
In WCF, you specify how to transfer data across a network between endpoints through a binding, and each binding is
made up of a sequence of binding elements.
For HTTP Transport, this document explores BasicHttpBinding, WSHttpBinding and HttpsTextEncodingAzureChannel.
• BasicHttpBinding - Used when you communicate with ASMX-based web services, web clients, and other services
that conform to the WS-I Basic Profile 1.1 (Web Services Interoperability Organization, 2004).
• WSHttpBinding - Supports the SOAP 1.2 and WS-Addressing specifications. By default BasicHttpBinding sends
data in plain text, while WSHttpBinding sends it in an encrypted and secured manner.
• HttpsTextEncodingAzureChannel - Custom binding to work with Azure Active Directory tokens; uses WCF
header to send Azure AD tokens to the server.
Tip: You may download the below examples from Epicor ERP Application Help using link at the bottom of this section.
Requirements
To complete this example, set up the following items:
• IIS needs to be configured for SSL. If you have not done this, review the Setting Up SSL section later in this guide.
• Your application server must also be configured to enable the BasicHttpBinding and WSHttpBinding endpoints. For
information on setting up your application server to use these two forms of HTTP transport, review the Enable
HTTP Endpoints section later in this guide.
5. Click OK.
The reference is added to your project.
6. Repeat steps 1-5 again. This time add a reference to the AbcCode service. The address for this service is like
http://localhost/ERP100500/Erp/BO/AbcCode.svc, and you use a namespace value of Epicor.AbcCodeSvc.
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
| HTTP Transport | 8
7. Visual Studio creates an empty class for you named CustomMessageInspector. However before you do any work
on it, add two other classes to this file.
class CustomMessageInspector { }
Directly beneath the closing curly brace for the CustomMessageInspector class, add these classes:
class SessionInfoHeader
{
class HookServiceBehavior
{
8. You next work on the SessionInfoHeader. This class represents the session information SOAP header item.
a) Your SessionInfoHeader must inherit from the System.ServiceModel.Channels.MessageHeader class. You
have already added a using statement for the System.ServiceModel.Channels namespace, so next indicate this
class inherits from MessageHeader:
b) MessageHeader is an abstract class, so it contains two properties (Name and Namespace) and a method
(OnWriteHeaderContents). You override these properties to implement them. The OnWriteHeaderContents
method is called when the header contents are serialized. You write the contents of the header through this code
block:
{
get { return "urn:epic:headers:SessionInfo"; }
}
c) To complete this class, add two properties that store the user’s Epicor session Id and Epicor user Id:
• AddBindingParameters -- Passes custom data at runtime to enable bindings to support custom behavior.
• ApplyClientBehavior -- Modifies, examines, or inserts extensions to an endpoint in a client application.
• ApplyDispatchBehavior -- Modifies, examines, or inserts extensions to endpoint-wide execution in a service
application.
• Validate -- Confirms a ServiceEndpoint meets specific requirements. Use this method to ensure an endpoint
has a certain configuration setting enabled, supports a particular feature, and uses other requirements.
You must implement all four methods, but you only add code to the ApplyClientBehavior method. Do the following:
a) Specify that the HookServiceBehavior implements the IEndpointBehavior interface.
b) After the opening curly brace, add two private variables that hold the user’s Session ID and Epicor User ID.
c) Now create a constructor method for the class that takes for parameters a Guid and a string. This constructor then
uses those values to set the class level variables you created.
Tip: You may notice that Visual Studio displays errors that refer to code in the ApplyClientBehavior method.
You can ignore these messages, as they will stop once you implement the CustomMessageInspector class.
d) Provide implementations for the four methods defined by the interface.
10. Now implement the CustomMessageInspector class. This class adds the SessionInfo header to the outgoing request
headers.
a) Specify the CustomMessageInspector class implements the IClientMessageInspector interface. This interface
defines a message inspector object that can be added to the MessageInspectors collection to view or modify
messages.
b) Add two private class level variables that hold the Epicor session Id and user Id.
c) Add a constructor method for the class that uses for parameters a Guid and a string. Assign their values to the
two class level variables you added.
d) Enter the two methods required for the IClientMessageInspector interface. In this example, only add code to
the BeforeSendRequest method, as this method is where you place the SessionInfo header information.
At this point your helper classes are complete, but the project does not actually function. However you can run a test
build of the project to make sure you don’t have build errors. If you do, review these steps again to correct these errors.
class Program
{
private enum EndpointBindingType
{
SOAPHttp,
BasicHttp
}
2. You next code the first two helper methods. These methods programmatically create the endpoint bindings.
Tip: You can also configure the endpoint bindings through the application configuration file (app.config). For this
example, you configure these endpoint bindings in code.
| HTTP Transport | 11
a) First add the using statements. Visual Studio has already added the standard using statements, so you add the
following using statements to this list:
using System.Net;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;
using AbcCodeServiceClient.Epicor.AbcCodeSvc;
using AbcCodeServiceClient.Epicor.SessionModSvc;
b) Now enter the GetWsHttpBinding method. This method can be added immediately after the Main method.
return binding;
}
c) Name the second method GetBasicHttpBinding; this method creates a new, properly configured instance of the
BasicHttpBinding class.
Notice this method is similar to the first method. Unfortunately these two bindings inherit from different classes
and the ReaderQuotas property is defined on each base class instead of common base class from which they
both inherit. Add the following method directly below the GetWsHttpBinding method.
return binding;
Notice this code first creates a new instance of the binding. It then sets the maximum values for the message size
and reader quotas. The reader quotas element defines the constraints on the complexity of the SOAP messages
| HTTP Transport | 12
processed by endpoints configured through this binding. With the Epicor application, you can retrieve large record
sets.
The code next specifies the security mode the binding will use:
• For the BasicHttpBinding, set it to TransportwithMessageCredential. This mode indicates you use https
and that the service is configured with a certificate. SOAP message security provides the client authentication.
• For the WSHttpBinding, set the security mode to Message as the WSHttpBinding encrypts its messages by
default.
For both bindings, set the client credential type to UserName to indicate you want to include a UserName token
in the header.
3. Now add the last helper method – GetClient. This method creates an instance of the client classes. Add the following
method directly after the GetBasicHttpBinding method.
switch (bindingType)
{
case EndpointBindingType.BasicHttp:
binding = GetBasicHttpBinding();
break;
case EndpointBindingType.SOAPHttp:
binding = GetWsHttpBinding();
break;
}
return client;
}
This code first creates an EndpointAddress instance that represents the URL of the service you are calling. The
code then creates a binding based upon the EndpointBindingType passed in (you defined the EndpointBindingType
enumeration in step 1). Once the code has the EndpointAddress and binding, it creates an instance of the client class.
Lastly this code assigns the user name and password to the ClientCredentials instance on the client class.
You can also add a couple items to this code that help correct errors. If you receive operation timeout errors when calling
a service, adjust the timeout values. A binding has four timeout values:
• OpenTimeout
• CloseTimeout
| HTTP Transport | 13
• ReceiveTimeout
• SendTimeout
By default, each has a value of one minute. If you receive errors like “The open operation did not complete within
the allotted timeout…”, the service has exceeded the time period defined on a timeout. To correct this error, adjust the
timeout values on the binding. Place this code immediately after the ‘switch(bindingType)’ block:
In the above example, notice you set the timeout values to twelve minutes.
You may need to make another code change to take into account the DNS identity difference between the service URL
and the identity specified in the certificate used by the web server. This code handles error messages similar to the
following:
“Identity check failed for outgoing message. The expected DNS identity of the remote endpoint was 'l303415' but the
remote endpoint provided DNS claim 'L303415.americas.epicor.net'. If this is a legitimate remote endpoint, you can fix
the problem by explicitly specifying DNS identity 'L303415.americas.epicor.net' as the Identity property of
EndpointAddress when creating channel proxy.”
You can resolve this error in two ways. One way is to change the service URL so it uses the fully qualified machine
name. If you use the Epicor SaaS environment, your DNS Identity is EpicorSaaS.com.
You can also specify a DnsEndpointIdentity object to the EndpointAddress you created. To do this, replace the line:
ServicePointManager.ServerCertificateValidationCallback += (sender,
certificate, chain, errors) => { return true; };
This code tells the ServicePointManager (a class that provides connection management for HTTP connections) to
validate the server certificate. This prevents the method from trying to validate the certificate with the known
authorities.
2. Create a variable that is an instance of the EndpointBindingType you created earlier. This instance determines
which endpoint binding to use at runtime. You can toggle the value assigned to this variable to test the two bindings:
Tip: If you run the Epicor SaaS environment, you typically select the EndpointBindingType.BasicHttp option
instead of the EndpointBindingType.SOAPHttp defined in this example.
| HTTP Transport | 14
3. Now create two variables that hold the Epicor User ID and Password. Set the value of these variables to match a
valid User ID in your Epicor application.
Tip: This example uses a plain string to hold the password. In production code, you store the password though a
SecureString instance.
4. You next use code to determine what scheme will connect (HTTP vs. HTTPS). You also enter an UriBuilder code
line that creates the URL for the services.
In the constructor for the UriBuilder, be sure to enter the name of the machine that hosts your services instead of
“localhost”.
5. Add instances of the two service client classes created by Visual Studio; these classes were created when you added
the service references in Step 2. Before creating each instance, set the Path property of the UriBuilder to the path of
the service.
In this example code, the Epicor 10 appserver name is “ERP100500”; replace this value with your appserver name.
builder.Path = "ERP100500/Ice/Lib/SessionMod.svc";
SessionModSvcContractClient sessionModClient =
GetClient<SessionModSvcContractClient, SessionModSvcContract>(
builder.Uri.ToString(),
epicorUserID,
epiorUserPassword,
bindingType);
builder.Path = "ERP100500/Erp/BO/AbcCode.svc";
ABCCodeSvcContractClient abcCodeClient = GetClient<ABCCodeSvcContractClient,
ABCCodeSvcContract>(
builder.Uri.ToString(),
epicorUserID,
epiorUserPassword,
bindingType);
6. Use the services. First call the SessionModSvc Login method to establish a session in the application server
(AppServer). This returns a Guid that identifies the session.
7. Create a new instance of the SessionModSvc. Do this because when you call any method on the service client class,
you cannot modify its Endpointbehaviors.
builder.Path = "ERP100500/Ice/Lib/SessionMod.svc";
sessionModClient = GetClient<SessionModSvcContractClient,
SessionModSvcContract>(
builder.Uri.ToString(),
epicorUserID,
| HTTP Transport | 15
epiorUserPassword,
bindingType);
8. Next create an instance of the HookServiceBehavior class for each client instance, passing it to the Guid you receive.
This adds the SessionInfo header to each subsequent call.
sessionModClient.Endpoint.EndpointBehaviors.Add(new
HookServiceBehavior(sessionId, epicorUserID));
abcCodeClient.Endpoint.EndpointBehaviors.Add(new HookServiceBehavior(sessionId,
epicorUserID));
9. When you have an instance of the sessionModeClient with the HookServiceBehavior added to it, you can call any
SetXYZ method on the sessionModClient to change company, site, and so on. These values then persist between
method calls. For example, if you want change the company, enter this code:
10. Make calls to the AbcCodeSvc service. First create an instance of an empty ABCCodeTableset to pass into the
service. Then call the GetNewABCCode method to create a new ABCCode record.
This call returns a new row in the tableset with default values, but it does not save the row to the database. When
you call a “GetNew” methods on the Epicor services, the row should return with the RowMod field set to “A”. The
code uses this to select a new row from the ABCCode table in the tableset.
if (newRow != null)
{
11. You can now set the required fields and call the Update method on the service to save the record.
In this example, the code sets the ABCCode field for the new row to a “G” value. However for your code, use a
value that does not exist. This code goes between the curly braces after the if (newRow != null) statement you added
in the previous step.
newRow.ABCCode = "G";
newRow.CountFreq = 30;
newRow.StockValPcnt = 100;
abcCodeClient.Update(ref ts);
12. When you have added extended user defined (UD) columns to a table, these columns are exposed to web services
though a collection property on the individual rows. This property is called UserDefinedColumns; the property is
a name/value collection.
| HTTP Transport | 16
For example, you add two extended UD columns to the AbcCode table. One is a string column named ‘UDCol1_c’
and the other is an integer column named ‘UDCol2_c’. To access these columns through web services, enter the
following code:
13. Since you saved the record, you next fetch it from the server. This example demonstrates your record was saved by
the previous code. Replace the “G” in the call to GetByID with the code you entered in the previous step.
ts = null;
ts = abcCodeClient.GetByID("G");
if (ts != null && ts.ABCCode.Any())
{
}
14. Next update some values on the record and save it back to the server again. This code goes inside the curly braces
that appear after the if(ts != null && ts.ABCCode.Any()) code added in the previous step.
This code example demonstrates two important concepts:
• First, server business logic often requires both the ‘before’ and ‘updated’ versions of the row be sent. This requires
you make a copy of the row and then add it to the table before making changes.
• Second, when you update an existing record, set the RowMod field in the row to a “U” value. If you do not do
this, your changes are not saved. After saving the change, you null out the tableset and call GetByID again to
demonstrate the record was updated.
ts.ABCCode.Add(backupRow);
ts.ABCCode[0].CountFreq = 45;
ts.ABCCode[0].RowMod = "U";
abcCodeClient.Update(ref ts);
ts = null;
ts = abcCodeClient.GetByID("G");
if (ts != null && ts.ABCCode.Any())
{
Console.WriteLine("CountFreq = {0}", ts.ABCCode[0].CountFreq);
}
15. To finish the AbcCodeSvc service, delete the record. Place the following code immediately after the call to
Console.WriteLine and inside the curly braces that surround it.
To delete a record, set the RowMod field to a “D” value and call the Update method. Again you try to call the
GetByID method to retrieve the record. This time no record should exist, so a “record not found” error should get
thrown by the application server.
| HTTP Transport | 17
The following code demonstrates wrapping the service call in a try/catch block. You should always code defensively
and have code that can handle exceptions that may occur when calling the application server. Other possible values
for the ExceptionKindValue are “ServerException”, “BLException”, “SqlException”, “DuplicateRecord”,
“ConversionPending”, “VersionMismatch” and “UnknownException”.
ts.ABCCode[0].RowMod = "D";
abcCodeClient.Update(ref ts);
try
{
ts = abcCodeClient.GetByID("G");
}
catch (FaultException<Epicor.AbcCodeSvc.EpicorFaultDetail> ex)
{
if (ex.Detail.ExceptionKindValue.Equals("RecordNotFound",
StringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine("Record deleted.");
}
else
{
Console.WriteLine(ex.Message);
}
}
16. Lastly, call the LogOut method on the SessionModSvc. This releases the server session and makes sure you are not
needlessly consuming license seats.
This code goes after the closing curly brace that is tied to the “if(newRow != null)” block you added in step 10:
if (sessionId != Guid.Empty)
{
sessionModClient.Logout();
}
Console.ReadLine();
This completes the code for the Main method. You can now compile it in Visual Studio by selecting the Build > Build
Solution option. Correct compilation errors. Run the code by selecting the Debug > Start Debugging menu option.
Remember you can change the value of the bindingType variable to test the BasicHttpBinding and WSHttpBinding
endpoint bindings.
The full code for the Main method displays below for your reference:
builder.Path = "ERP100500/Ice/Lib/SessionMod.svc";
SessionModSvcContractClient sessionModClient =
GetClient<SessionModSvcContractClient, SessionModSvcContract>(
builder.Uri.ToString(),
epicorUserID,
epiorUserPassword,
bindingType);
builder.Path = "ERP100500/Erp/BO/AbcCode.svc";
ABCCodeSvcContractClient abcCodeClient =
GetClient<ABCCodeSvcContractClient, ABCCodeSvcContract>(
builder.Uri.ToString(),
epicorUserID,
epiorUserPassword,
bindingType);
builder.Path = "ERP100500/Ice/Lib/SessionMod.svc";
sessionModClient = GetClient<SessionModSvcContractClient,
SessionModSvcContract>(builder.Uri.ToString(),epicorUserID,epiorUserPassword,bindingType);
sessionModClient.Endpoint.EndpointBehaviors.Add(new
HookServiceBehavior(sessionId, epicorUserID));
abcCodeClient.Endpoint.EndpointBehaviors.Add(new
HookServiceBehavior(sessionId, epicorUserID));
if (newRow != null)
{
newRow.ABCCode = "G";
newRow.CountFreq = 30;
newRow.StockValPcnt = 100;
abcCodeClient.Update(ref ts);
ts = null;
ts = abcCodeClient.GetByID("G");
if (ts != null && ts.ABCCode.Any())
{
ABCCodeRow backupRow = new ABCCodeRow();
var fields =
backupRow.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
ts.ABCCode[0].CountFreq = 45;
ts.ABCCode[0].RowMod = "U";
abcCodeClient.Update(ref ts);
ts = null;
ts = abcCodeClient.GetByID("G");
if (ts != null && ts.ABCCode.Any())
{
Console.WriteLine("CountFreq = {0}", ts.ABCCode[0].CountFreq);
ts.ABCCode[0].RowMod = "D";
abcCodeClient.Update(ref ts);
try
{
ts = abcCodeClient.GetByID("G");
}
catch (FaultException<Epicor.AbcCodeSvc.EpicorFaultDetail> ex)
{
if (ex.Detail.ExceptionKindValue.Equals("RecordNotFound",
StringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine("Record deleted.");
}
else
{
Console.WriteLine(ex.Message);
}
}
}
}
}
if (sessionId != Guid.Empty)
{
sessionModClient.Logout();
}
Console.ReadLine();
}
using System.Collections.Generic;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
Notice that Visual Studio creates an empty class for you named CustomMessageInspector:
class CustomMessageInspector
{
}
6. Before you do any work on this class, you must add another class to this file. Directly beneath the closing curly brace
for theCustomMessageInspector class, enter:
class HookServiceBehavior
{
This class implements the System.ServiceModel.Description.IEndpointBehavior interface. You can then implement
methods to extend run-time behavior for an endpoint in the client application. The interface exposes four methods:
• AddBindingParameters -- Passes custom data at runtime to enable bindings to support custom behavior.
• ApplyClientBehavior -- Modifies, examines, or inserts extensions to an endpoint in a client application.
• ApplyDispatchBehavior -- Modifies, examines, or inserts extensions to endpoint-wide execution in a service
application.
• Validate -- Confirms a ServiceEndpoint meets specific requirements. Use this method to ensure an endpoint has
a certain configuration setting enabled, supports a particular feature, and uses other requirements.
7. Now update the HookServiceBehavior class. You must implement all four methods, but you only add code to the
ApplyClientBehavior method. Do the following:
a) Specify that the HookServiceBehavior implements the IEndpointBehavior interface:
b) Now after the opening curly brace, add a private variable to hold the custom headers.:
c) Create a constructor method for the class that takes parameters for a Guid and a string. This constructor then uses
those values to set the class level variables you created.
Tip: You may notice that Visual Studio displays errors that refer to code in the ApplyClientBehavior method.
You can ignore these messages, as they will stop once you implement the CustomMessageInspector class.
d) Now implement the AddBindingParameters, ApplyClientBehavior, ApplyDispatchBehavior, and Validate
methods. These methods are defined by the interface.
{
clientRuntime.ClientMessageInspectors.Add(new
CustomMessageInspector(_headers));
}
8. Next implement the CustomMessageInspector class. This class adds the SessionInfo header to the outgoing request
headers. To implement this class:
a) Specify that the CustomMessageInspector class implements the IClientMessageInspector interface. This
interface defines a message inspector object that can be added to the MessageInspectors collection to view or
modify messages.
b) Now add a private class level variable that holds the custom headers:
c) Add a constructor method for the class that uses a Guid and a string parameter. Assign their values to the two
class level variables you added previously:
_headers = headers;
| HTTP Transport | 23
d) Enter the two methods required for the IClientMessageInspector interface. In this example, only add code to
the BeforeSendRequest method, as this method is where you place the SessionInfo header information:
return request;
9. Now implement your first custom header -- the LicenseHeader. This class represents the license claim for the web
service calls. The most common license types and their GUID values:
• "DefaultUser" = "00000003-B615-4300-957B-34956697F040"
• "WebService" = "00000003-9439-4B30-A6F4-6D2FD4B9FD0F"
• "WebServiceShared" = "00000003-231B-4E2B-9B47-13F87EA9A0A3"
Note: You can obtain the GUIDs of other specific licenses from your .lic license file.
a) Return to the Solution Explorer.
b) First add a folder to your project that holds these classes. To do this, right-click the AbcCodeServiceClient2
project; from the context menu, select Add > New Folder.
c) Name the folder MessageHeaders.
10. Now add a new class file to the MessageHeaders folder. Right-click the MessageHeaders folder; from the context
menu, select Add > Class.
The Add New Item - AbcCodeServiceClient2 dialog box displays:
a) From the icon list, select the Class option.
b) Enter a Name of LicenseHeader.cs.
c) Click Add.
This creates the LicenseHeader.cs file.
11. Return to the Solution Explorer.
12. Click on the LicenseHeader.cs file.
This opens the file in an Editor tab. The LicenseHeader class must inherit from the
System.ServiceModel.Channels.MessageHeader class.
a) First add a using statement for the System.ServiceModel.Channels namespace:
using System.ServiceModel.Channels;
The MessageHeader is an abstract class which contains the Name and Namespace properties and an
OnWriteHeaderContents method. You override these properties to implement them. The code calls the
OnWriteHeaderContents method when it serializes the header contents.
c) Enter the following code to override the two properties and the OnWriteHeaderContents method so they return
information about the custom header:
writer.WriteElementString("SessionID",
@"http://schemas.datacontract.org/2004/07/Epicor.Hosting",
SessionId.ToString());
}
d) To complete the class add two public properties used to store the user’s Session ID and the Claimed License
GUID, enter the following code:
13. Now implement the second header class. To do this, you add a new class file to the MessageHeaders folder. Return
to the Solution Explorer.
The Add New Item - AbcServiceClient2 window displays.
a) From the icon list, select the Class option.
b) Enter a Name of CallSettings.cs.
c) Click Add.
14. Return to the Solution Explorer.
15. Click on the CallSettings.cs file.
This opens the file in an Editor tab. The CallSettings class is similar to the LicenseHeader class, as it inherits from
the MessageHeader. It must override the Name and Namespace properties and the OnWriteHeaderContents
method.
16. To do this, you add public properties to store the user’s Company ID, Plant ID, Language, Format Culture and
Time Zone Offset. Enter this code:
using System;
using System.ServiceModel.Channels;
namespace AbcCodeServiceClient2.MessageHeaders
{
class CallSettings : MessageHeader
{
public string Company { get; set; } = string.Empty;
| HTTP Transport | 25
/// <summary>
/// Gets the name of the message header.
/// </summary>
/// <returns>The name of the message header.</returns>
public override string Name
{
get { return "CallSettings"; }
}
/// <summary>
/// Gets the namespace of the message header.
/// </summary>
/// <returns>The namespace of the message header.</returns>
public override string Namespace
{
get { return "urn:epic:headers:CallSettings"; }
}
}
}
Your helper classes are complete, but the project does not function yet. However you can run a test build of the project
to make sure you don’t have build errors. If you have errors, review these steps again to correct them.
1. You first add the Using statements. Visual Studio already generated the standard using statements, so you add the
following using statement to this list:
using AbcCodeServiceClient2.Epicor.AbcCodeSvc;
2. Because this project demonstrates how you call into the Epicor services using either BasicHttpBinding or
WSHttpBinding, you next define an enumeration that specifies which binding to use. For example:
class Program
{
private enum EndpointBindingType
{
SOAPHttp,
BasicHttp
}
3. You now code the first two helper methods; these methods create the endpoint bindings. Enter the GetWsHttpBinding
method. You can add this method immediately after the Main method.
return binding;
}
4. Name the second method GetBasicHttpBinding; this method creates a new, properly configured instance of the
BasicHttpBinding class.
Notice this method is similar to the first method. Unfortunately these two bindings inherit from different classes,
and the ReaderQuotas property is defined on each base class instead of common base class from which they both
inherit. Add the following method directly below the GetWsHttpBinding method.
return binding;
Review this code. Notice in both methods, the code first creates a new instance of the binding. It then sets the
maximum values for the message size and reader quotas. The reader quotas element defines the constraints on the
complexity of the SOAP messages; these messages are processed by endpoints configured through this binding.
With the Epicor application, you can retrieve large record sets.
The code next specifies the security mode the binding will use:
• For the WSHttpBinding, set the security mode to Message; the WSHttpBinding encrypts its messages by default.
• For the BasicHttpBinding, set it to TransportwithMessageCredential. This mode indicates you use https and
that the service is configured with a certificate. SOAP message security provides the client authentication.
5. For both bindings, set the client credential type to UserName. This indicates you want to include a UserName token
in the header.
6. Now add the last helper method – GetClient. This method creates an instance of the client classes. Add the following
method directly after the GetBasicHttpBinding method
switch (bindingType)
{
case EndpointBindingType.BasicHttp:
binding = GetBasicHttpBinding();
break;
case EndpointBindingType.SOAPHttp:
binding = GetWsHttpBinding();
break;
}
return client;
}
This code first creates an EndpointAddress instance that represents the URL of the service you are calling. The
code then creates a binding based upon the EndpointBindingType passed in; you previously defined the
EndpointBindingType enumeration. Once the code has the EndpointAddress and binding, it creates an instance of
the client class.
| HTTP Transport | 28
Lastly, this code assigns the user name and password to the ClientCredentials instance on the client class.
7. You can also add a couple items to this code that help correct errors. If you receive operation timeout errors when
calling a service, adjust the timeout values.
A binding has four timeout values:
• OpenTimeout
• CloseTimeout
• ReceiveTimeout
• SendTimeout
By default, each has a value of one minute. If you receive errors like “The open operation did not complete within
the allotted timeout…”, the service has exceeded the time period defined on a timeout. To correct this error, adjust
the timeout values on the binding. Place this code immediately after the switch(bindingType) block:
In the above example, notice you set the timeout values to twelve minutes.
8. You may need to make another code change to take into account the DNS identity difference between the Service
URL and the identity specified in the certificate used by the web server. This code handles error messages similar
to the following:
"Identity check failed for outgoing message. The expected DNS identity of the remote endpoint was 'l303415' but
the remote endpoint provided DNS claim 'L303415.americas.epicor.net'. If this is a legitimate remote endpoint, you
can fix the problem by explicitly specifying DNS identity 'L303415.americas.epicor.net' as the Identity property of
EndpointAddress when creating channel proxy".
You can resolve this error in two ways. One way is to change the Service URL so it uses the fully qualified machine
name. You can also specify a DnsEndpointIdentity object to the EndpointAddress you created. To do this, replace
the line:
ServicePointManager.ServerCertificateValidationCallback += (sender,
certificate, chain, errors) => { return true; };
| HTTP Transport | 29
The ServicePointManager is a class that provides management for HTTP connections. This code tells the
ServicePointManager to validate the server certificate. This prevents the method from trying to validate the certificate
with the known authorities.
2. Enter a variable that is an instance of the EndpointBindingType you created earlier. This instance determines which
endpoint binding to use at runtime. Toggle the value assigned to this variable to test the two bindings:
3. Now create two variables that hold the Epicor User ID and Password. Set the value of these variables to match a
valid User ID in your Epicor application.
Tip: This example uses a plain string to hold the password. In production code, you store the password though a
SecureString instance.
4. Next enter code that determines what HTTP or HTTPS scheme will connect the services.
5. You also enter an UriBuilder code line that creates the URL for the services. In the constructor for the UriBuilder,
be sure to enter the name of the machine that hosts your services instead of “localhost”.
6. Add an instance of the service client classes created by Visual Studio; you created this class when you added the
service reference. Before creating the instance, set the Path property of the UriBuilder to the path of the service.
In this example code, the Epicor 10 appserver name is ERP100500; replace this value with your appserver name.
builder.Path = "ERP100500/Erp/BO/AbcCode.svc";
ABCCodeSvcContractClient abcCodeClient = GetClient<ABCCodeSvcContractClient,
ABCCodeSvcContract>(
builder.Uri.ToString(),
epicorUserID,
epiorUserPassword,
bindingType);
8. Create the two custom headers and attach them to your abcCodeClient instance.
a) The first is the LicenseHeader. You specify that this call will use the WebService license type. Because you
are creating a session, you can set the Session ID to an empty GUID.
b) Next create the CallSettings header. In this example, you use it to specify the Company ID used for the service
call. Be sure to replace “Epic06” in the sample below with a Company ID valid for your system.
abcCodeClient.Endpoint.EndpointBehaviors.Add(new
HookServiceBehavior(headers));
9. Set up the calls to the AbcCodeSvc service. First create an instance of an empty ABCCodeTableset to pass into the
service. Then call the GetNewABCCode method to create a new ABCCode record.
This call returns a new row in the tableset with default values, but it does not save the row to the database. When
you call a “GetNew” method on the Epicor service, the row returns with the RowMod field set to “A” (Add). The
code uses this value to select a new row from the ABCCode table in the tableset.
if (newRow != null)
{
10. You can now set the required fields and call the Update method on the service to save the record.
In this example, the code sets the ABCCode field for the new row to a “G” value. However for your code, use a
value that does not exist. This code goes between the curly braces after the if (newRow != null) statement you
previously added.
newRow.ABCCode = "G";
newRow.CountFreq = 30;
newRow.StockValPcnt = 100;
abcCodeClient.Update(ref ts);
11. Compile the code in Visual Studio by selecting the Build > Build Solution option. Correct any compilation errors.
12. Run the code by selecting the Debug > Start Debugging menu option. Remember you can change the value of the
bindingType variable to test the BasicHttpBinding and WSHttpBinding endpoint bindings.
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using AbcCodeServiceClientAzureAD.MessageHeaders;
| HTTP Transport | 32
7. Visual Studio creates an empty class for you named CustomMessageInspector. However before you do any work
on it, add another class to this file.
class CustomMessageInspector { }
class HookServiceBehavior
{
}
class CustomMessageInspector
{
}
/// <summary>
/// Initializes a new instance of the <see
cref="HookServiceBehavior"/> class.
/// </summary>
/// <param name="soapHeaders">The list of <see
cref="MessageHeader"/> to add to the SOAP message.</param>
public HookServiceBehavior(List<MessageHeader> soapHeaders)
{
_headers = soapHeaders;
}
Tip: You may notice that Visual Studio displays errors that refer to code in the ApplyClientBehavior method.
You can ignore these messages, as they will stop once you implement the CustomMessageInspector class.
d) Provide implementations for the four methods defined by the interface.
clientRuntime.ClientMessageInspectors.Add(new
CustomMessageInspector(_headers));
}
9. Now implement the CustomMessageInspector class. This class adds the SessionInfo header to the outgoing request
headers.
a) Specify the CustomMessageInspector class implements the IClientMessageInspector interface. This interface
defines a message inspector object that can be added to the MessageInspectors collection to view or modify
messages.
/// <summary>
/// Initializes a new instance of the <see
cref="CustomMessageInspector"/> class.
/// </summary>
/// <param name="soapHeaders">The list of <see
cref="MessageHeader"/> to add to the SOAP message.</param>
public CustomMessageInspector(List<MessageHeader> headers)
{
_headers = headers;
}
d) Enter the two methods required for the IClientMessageInspector interface. In this example, only add code to
the BeforeSendRequest method, as this method is where you place the SessionInfo header information.
/// <summary>
/// Enables inspection or modification of a message after a reply
message is received but prior to passing it back to the client application.
/// </summary>
/// <param name="reply">The message to be transformed into types
and handed back to the client application.</param>
/// <param name="correlationState">Correlation state data.</param>
/// <summary>
/// Enables inspection or modification of a message before a request
message is sent to a service.
/// </summary>
/// <param name="request">The message to be sent to the
service.</param>
/// <param name="channel">The WCF client object channel.</param>
| HTTP Transport | 34
/// <returns>
/// The object that is returned as the <paramref
name="correlationState " />argument of the <see
cref="M:System.ServiceModel.Dispatcher.IClientMessageInspector.AfterReceiveReply(System.ServiceModel.Channels.Message@,System.Object)"
/> method. This is null if no correlation state is used.The best practice
is to make this a <see cref="T:System.Guid" /> to ensure that no two
<paramref name="correlationState" /> objects are the same.
/// </returns>
public object BeforeSendRequest(ref Message request,
System.ServiceModel.IClientChannel channel)
{
foreach (var header in _headers)
{
request.Headers.Add(header);
}
return request;
}
using System.ServiceModel;
using System.ServiceModel.Channels;
using AbcCodeServiceClient2.MessageHeaders;
using AbcCodeServiceClientAzureAD.ABCCodeService;
2. Add Add function to create custom binding. This method can be added immediately after the Main method.
binding.Elements.Add(textEncoding);
| HTTP Transport | 35
binding.Elements.Add(transport);
return binding;
}
3. Add another helper method – GetClient. This method creates an instance of the client classes. Add the following
method directly after the GetHttpsTextEncodingAzureBinding method.
binding = GetHttpsTextEncodingAzureBinding();
client = (TClient)Activator.CreateInstance(typeof(TClient),
binding, endpointAddress);
return client;
}
You can also add a couple items to this code that help correct errors. If you receive operation timeout errors when calling
a service, adjust the timeout values. A binding has four timeout values:
• OpenTimeout
• CloseTimeout
• ReceiveTimeout
• SendTimeout
By default, each has a value of one minute. If you receive errors like “The open operation did not complete within
the allotted timeout…”, the service has exceeded the time period defined on a timeout. To correct this error, adjust the
timeout values on the binding. Place this code immediately after the ‘switch(bindingType)’ block:
In the above example, notice you set the timeout values to twelve minutes.
// replace 'localhost' with the fully qualified machine name where the services
are hosted.
UriBuilder builder = new UriBuilder("https", "localhost");
| HTTP Transport | 36
2. Set the Path property of the UriBuilder to the path of the service:
8. Make calls to the AbcCodeSvc service. First create an instance of an empty ABCCodeTableset to pass into the
service. Then call the GetNewABCCode method to create a new ABCCode record.
This call returns a new row in the tableset with default values, but it does not save the row to the database. When
you call a “GetNew” methods on the Epicor services, the row should return with the RowMod field set to “A”. The
code uses this to select a new row from the ABCCode table in the tableset.
abcCodeClient.GetNewABCCode(ref ts);
if (newRow != null)
{
9. You can now set the required fields and call the Update method on the service to save the record.
| HTTP Transport | 37
In this example, the code sets the ABCCode field for the new row to a “G” value. However for your code, use a
value that does not exist. This code goes between the curly braces after the if (newRow != null) statement you added
in the previous step.
newRow.ABCCode = "G";
newRow.CountFreq = 30;
newRow.StockValPcnt = 100;
abcCodeClient.Update(ref ts);
10. When you have added extended user defined (UD) columns to a table, these columns are exposed to web services
though a collection property on the individual rows. This property is called UserDefinedColumns; the property is
a name/value collection.
For example, you add two extended UD columns to the AbcCode table. One is a string column named ‘UDCol1_c’
and the other is an integer column named ‘UDCol2_c’. To access these columns through web services, enter the
following code:
11. Since you saved the record, you next fetch it from the server. This example demonstrates your record was saved by
the previous code. Replace the “G” in the call to GetByID with the code you entered in the previous step.
ts = null;
ts = abcCodeClient.GetByID("G");
if (ts != null && ts.ABCCode.Any())
{
}
12. Next update some values on the record and save it back to the server again. This code goes inside the curly braces
that appear after the if(ts != null && ts.ABCCode.Any()) code added in the previous step.
This code example demonstrates two important concepts:
• First, server business logic often requires both the ‘before’ and ‘updated’ versions of the row be sent. This requires
you make a copy of the row and then add it to the table before making changes.
• Second, when you update an existing record, set the RowMod field in the row to a “U” value. If you do not do
this, your changes are not saved. After saving the change, you null out the tableset and call GetByID again to
demonstrate the record was updated.
ts.ABCCode.Add(backupRow);
ts.ABCCode[0].CountFreq = 45;
ts.ABCCode[0].RowMod = "U";
abcCodeClient.Update(ref ts);
| HTTP Transport | 38
ts = null;
ts = abcCodeClient.GetByID("G");
if (ts != null && ts.ABCCode.Any())
{
Console.WriteLine("CountFreq = {0}", ts.ABCCode[0].CountFreq);
}
13. To finish the AbcCodeSvc service, delete the record. Place the following code immediately after the call to
Console.WriteLine and inside the curly braces that surround it.
To delete a record, set the RowMod field to a “D” value and call the Update method. Again you try to call the
GetByID method to retrieve the record. This time no record should exist, so a “record not found” error should get
thrown by the application server.
The following code demonstrates wrapping the service call in a try/catch block. You should always code defensively
and have code that can handle exceptions that may occur when calling the application server. Other possible values
for the ExceptionKindValue are “ServerException”, “BLException”, “SqlException”, “DuplicateRecord”,
“ConversionPending”, “VersionMismatch” and “UnknownException”.
ts.ABCCode[0].RowMod = "D";
abcCodeClient.Update(ref ts);
try
{
ts = abcCodeClient.GetByID("G");
}
catch (FaultException<Epicor.AbcCodeSvc.EpicorFaultDetail> ex)
{
if (ex.Detail.ExceptionKindValue.Equals("RecordNotFound",
StringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine("Record deleted.");
}
else
{
Console.WriteLine(ex.Message);
}
}
14. Lastly, call the LogOut method on the SessionModSvc. This releases the server session and makes sure you are not
needlessly consuming license seats.
This code goes after the closing curly brace that is tied to the “if(newRow != null)” block you added in step 10:
if (sessionId != Guid.Empty)
{
sessionModClient.Logout();
}
Console.ReadLine();
The full code for the Main method displays below for your reference:
builder.Path = "ERP102200/Erp/BO/AbcCode.svc";
ABCCodeSvcContractClient abcCodeClient =
GetClient<ABCCodeSvcContractClient, ABCCodeSvcContract>(
builder.Uri.ToString());
// the code from this point on is basically the same as our first
example
var ts = new ABCCodeTableset();
abcCodeClient.GetNewABCCode(ref ts);
ts = null;
ts = abcCodeClient.GetByID("G");
if (ts != null && ts.ABCCode.Any())
{
ABCCodeRow backupRow = new ABCCodeRow();
var fields =
backupRow.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
ts.ABCCode[0].CountFreq = 45;
ts.ABCCode[0].RowMod = "U";
abcCodeClient.Update(ref ts);
ts = null;
ts = abcCodeClient.GetByID("G");
if (ts != null && ts.ABCCode.Any())
{
Console.WriteLine("CountFreq = {0}", ts.ABCCode[0].CountFreq);
ts.ABCCode[0].RowMod = "D";
abcCodeClient.Update(ref ts);
try
{
ts = abcCodeClient.GetByID("G");
}
catch (FaultException<Epicor.AbcCodeSvc.EpicorFaultDetail> ex)
{
if (ex.Detail.ExceptionKindValue.Equals("RecordNotFound",
StringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine("Record deleted.");
}
else
{
Console.WriteLine(ex.Message);
}
}
}
}
}
if (sessionId != Guid.Empty)
{
sessionModClient.Logout();
}
Console.ReadLine();
}
4. Click the AzureADTokenHeader.cs and add the following custom using statements:
using System.ServiceModel.Channels;
using System.Xml;
5. Within the namespace AbcCodeServiceClientAzureAD.MessageHeaders {}, create new class to handle WCF header
for Azure AD token:
/// <summary>
/// WCF header to send Azure AD token to server
/// </summary>
class AzureADTokenHeader : MessageHeader
{
/// <summary>
/// Header name
/// </summary>
public override string Name
{
get { return "AzureADToken"; }
}
/// <summary>
/// Header namespace
/// </summary>
public override string Namespace
{
get { return "urn:epic:headers:AzureADTokenHeader"; }
}
/// <summary>
/// Code to wrie header into stream
/// </summary>
/// <param name="writer"></param>
/// <param name="messageVersion"></param>
protected override void OnWriteHeaderContents(XmlDictionaryWriter
writer, MessageVersion messageVersion)
{
writer.WriteElementString("Token",
@"http://schemas.datacontract.org/2004/07/Epicor.Hosting", GetToken());
}
/// <summary>
/// Get token value for the call
/// </summary>
/// <returns></returns>
private string GetToken()
{
return AzureADAuth.GetToken();
}
}
using System;
using System.Linq;
using System.Security;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
6. First, add the following Azure AD settings and adjust them accordingly:
/// <summary>
/// Directory ID - take it from Azure Active Directory properties
/// </summary>
private const string DirectoryID =
"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
/// <summary>
/// Application ID of the Epicor Web application
/// </summary>
private const string WebAppID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
/// <summary>
/// Application ID of the Epicor native application
/// </summary>
private const string NativeClientAppID =
"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
/// <summary>
/// Redirect URL specified for Epicor native application.
/// </summary>
private static readonly string RedirectURL = "https://localhost";
/// <summary>
/// Default scope for Azure AD Authentication.
/// </summary>
private static readonly string DefaultScope = "user_impersonation";
Tip: For a complete information on the above properties, see either of the below sources:
• Application Help: System Management > Working With System Management > System Administration Guide
> Manage Epicor ERP > Authentication (User Identity) > Azure AD Authentication
• Kinetic Installation guide: Appendices > Azure AD Authentication
.WithRedirectUri(RedirectURL)
.Build();
}
return _clientApp;
}
}
8. Add function to get the token. You may use either of the below approaches:
a) Show standard MSAL window to ask for name and password, when no valid token exists:
/// <summary>
/// Show dialog window so user can enter name and password, if
necessary
/// </summary>
/// <returns>Access token for the WCF header</returns>
private static async Task<string> InteractiveAzureLogon()
{
var azureAdapp = ClientApp;
try
{
// try to get token silently for the selected account
var authResult = await azureAdapp.AcquireTokenSilent(scopes,
firstAccount).ExecuteAsync().ConfigureAwait(false);
return authResult.AccessToken;
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilent.
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException:
{ex.Message}");
return authResult.AccessToken;
}
}
| HTTP Transport | 44
b) Specify Azure AD user name and password and obtain a token for the user. This approach can be used to avoid
user interaction.
/// <summary>
/// Get token without user interaction.
/// </summary>
/// <returns>Access token for the WCF header.</returns>
private static async Task<string>
AzureLogonForSavedUserAndPassword()
{
// get username and password for Azure AD user from settings.
if (!string.IsNullOrEmpty(password))
{
foreach (var c in password)
{
securePassword.AppendChar(c);
}
}
return securePassword;
}
Note: This approach does not show any user interface, therefore multi-factor authentication cannot be used. Without
administrator consent, the first time after applications are created, user logon must be done interactively, because
user consent should be received. If this was not done, then the example above fails with the following error: The
user or administrator has not consented to use the application.
| HTTP Transport | 45
Also, to use this approach, the Allow public client flowssetting should be set to yes in Authentication/Advanced
settings of the Native Application on the Azure AD portal. Otherwise, you will get an exception - AADSTS7000218:
The request body must contain the following parameter: 'client_assertion' or 'client_secret'.
using System;
using System.Linq;
using System.Security;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
namespace AbcCodeServiceClientAzureAD
{
/// <summary>
/// Example of Azure AD authentication using MSAL library.
/// </summary>
public static class AzureADAuth
{
// Settings to be adjusted according to your Azure AD installation
/// <summary>
/// Directory ID - take it from Azure Active Directory properties
/// </summary>
private const string DirectoryID =
"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
/// <summary>
/// Application ID of the Epicor Web application
/// </summary>
private const string WebAppID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
/// <summary>
/// Application ID of the Epicor native application
/// </summary>
private const string NativeClientAppID =
"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
/// <summary>
/// Redirect URL specified for Epicor native application.
/// </summary>
private static readonly string RedirectURL = "https://localhost";
/// <summary>
/// Default scope for Azure AD authentication.
/// </summary>
private static readonly string DefaultScope = "user_impersonation";
/// <summary>
/// Property to illustrate how token can be obtained - with our without
dialog window
/// </summary>
public static bool UseInteractiveLogon { get; set; } = true;
/// <summary>
/// Gets token for current user
/// </summary>
/// <returns>Access token for the WCF header</returns>
public static string GetToken()
{
if (string.IsNullOrEmpty(NativeClientAppID))
throw new Exception("No settings specified in AzureADAuth");
| HTTP Transport | 46
/// <summary>
/// Show dialog window so user can enter name and password, if necessary
/// </summary>
/// <returns>Access token for the WCF header</returns>
private static async Task<string> InteractiveAzureLogon()
{
var azureAdapp = ClientApp;
try
{
// try to get token silently for the selected account
var authResult = await azureAdapp.AcquireTokenSilent(scopes,
firstAccount).ExecuteAsync().ConfigureAwait(false);
return authResult.AccessToken;
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilent.
// This indicates you need to call AcquireTokenInteractive to
acquire a token
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException:
{ex.Message}");
return authResult.AccessToken;
}
}
/// <summary>
/// Get token without user interaction.
/// </summary>
/// <returns>Access token for the WCF header.</returns>
private static async Task<string> AzureLogonForSavedUserAndPassword()
| HTTP Transport | 47
{
// get username and password for Azure AD user from settings.
// Warning: MFA cannot be used and consent should be already
received for this user in the applications,
// Otherwise the error will be returned:
// AADSTS65001: The user or administrator has not consented to use
the application with ID ...
if (!string.IsNullOrEmpty(password))
{
foreach (var c in password)
{
securePassword.AppendChar(c);
}
}
return securePassword;
}
}
return _clientApp;
}
}
}
}
You can now compile the project in Visual Studio by selecting the Build > Build Solution option. Correct compilation
errors. Run the code by selecting the Debug > Start Debugging menu option.
Setting Up SSL
You have two options for setting up SSL in Internet Information Services (IIS):
• Obtain an SSL certificate from a known certificate authority (CA) such as VeriSign or GeoTrust.
• Create a self-signed certificate through the IIS Management console.
The following steps outline how to configure IIS to use a self-signed certificate.
3. Now from the Actions panel, click the "Create Self-Signed Certificate" option:
4. In the Create Self-Signed Certificate dialog box, enter a name for the certificate such as Epicor-Dev.
5. Click OK.
The certificate is created.
6. In the Server Certificates panel, right-click your new certificate; from the context menu, select the View option.
7. Now in the certificate dialog, select the Details tab.
8. Highlight the Subject property. Write down this value, as you will need it later.
b) Internet Explorer (IE) 7 and above will display an error page. Click the Continue to this website (not
recommended) link.
Tip: This error occurs because the self-signed certificate was issued by your computer, not by a trusted Certificate
Authority (CA). Internet Explorer 7 and above will trust the certificate if you add it to the list of Trusted Root
Certification Authorities in the certificates store on the local computer or in Group Policy for the domain.
<serviceCertificate findValue="L303415.americas.epicor.net"
storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"
/>
4. Change the findValue attribute to be the subject value you noted in the previous section.
5. Save the Web.config file.
2. Locate the system.serviceModel/bindings section. If they do not exist, add the following bindings to this section:
<basicHttpBinding>
<binding name="BasicHttp" maxReceivedMessageSize="2147483647">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
<readerQuotas maxArrayLength="2147483647" maxBytesPerRead="2147483647"
maxDepth="2147483647" maxStringContentLength="2147483647" />
</binding>
</basicHttpBinding>
<wsHttpBinding>
<binding name="SOAPHttp" maxReceivedMessageSize="2147483647">
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
<readerQuotas maxArrayLength="2147483647" maxBytesPerRead="2147483647"
maxDepth="2147483647" maxStringContentLength="2147483647" />
</binding>
</wsHttpBinding>
<!--Text encoding - Authentication: Azure Active Directory Token in the header
- Channel encrypted via https-->
<binding name="HttpsTextEncodingAzureChannel"
maxReceivedMessageSize="2147483647">
<textMessageEncoding>
<readerQuotas maxDepth="50" maxArrayLength="2147483647"
maxBytesPerRead="2147483647" />
</textMessageEncoding>
<httpsTransport maxReceivedMessageSize="2147483647"
maxBufferSize="2147483647" />
</binding>
BasicHTTPBinding Explanation
Since BasicHttpBinding communicates in plain text, make sure it uses the SSL protocol.
You do this by specifying transport security; this ensures the communication is encrypted. You also need to provide a
username/password token to specify a security mode of “TransportWithMessageCredential”. By setting the message
client credential type to “UserName”, the client must authenticate the server using a UserName credential.
The reader quotas element of the binding defines the constraints on the complexity of the SOAP messages processed
by the configured binding endpoints. Because you can retrieve large records through the Epicor application, it is
recommended you set these values to their maximum.
| Enable HTTP Endpoints | 54
WSHTTPBinding Explanation
If you use WSHttpBinding, specify the message is encrypted by default. You can then use http to communicate with
the server.
Do this by indicating the security mode is "message", as security is provided using SOAP message security. By default,
the SOAP body is Encrypted and Signed. This requires that an SSL certification is specified in the
system.serviceModel/behaviors/serviceBehaviors/behavior/serviceCredentials element in your web.config file (review
the Setting Up SSL section for more information). You specify the client credential type is a username and you also
specify the same reader quotas as in the BasicHttpBinding.
HttpsTextEncodingAzureChannel Explanation
This binding uses WCF header to send Azure AD tokens to the Epicor application server.
It is essential, that transport security is used, not message. This is to ensure that Azure AD token in WCF header is
transmitted securely.
| Index | 55
Index
A P
azureadauth, example #3 41 prerequisites 4
program class, example #1 10
program class, example #2 25
B program class, example #3 34
basichttpbinding 53
S
C ssl set up 48
conventions 4 ssl set up, connect application server 52
ssl set up, site binding 48
H
T
helper classes, example #1 7
helper classes, example #2 21 tcp transport, when to use 5
helper classes, example #3 31 token header, server request, example #3 48
http endpoints, enable 52
http endpoints, web.config 52 V
http transport 5
http transport, when to use 5 visual studio project, example #1 6
httpstextencodingazurechannel 54 visual studio project, example #2 19
visual studio project, example #3 30
M
W
main method, example #1 13
main method, example #2 28 wcf header class, example #3 40
main method, example #3 35 wcf services example #1 5, 6, 7, 10, 13
main method, full code example 17, 38 wcf services example #2 19, 20, 21, 25, 28
wcf services example #3 30, 31, 34, 35, 40, 41, 48
web services references, example #1 6
N web services references, example #2 20
nuget package, example #3 31 web services references, example #3 31
wshttpbinding 54