You are on page 1of 57

Epicor ERP

Epicor WCF Services Developer


Guide
10.2.700
Disclaimer
This document is for informational purposes only and is subject to change without notice. This document and its
contents, including the viewpoints, dates and functional content expressed herein are believed to be accurate as of its
date of publication. However, Epicor Software Corporation makes no guarantee, representations or warranties with
regard to the enclosed information and specifically disclaims any applicable implied warranties, such as fitness for a
particular purpose, merchantability, satisfactory quality or reasonable skill and care. As each user of Epicor software is
likely to be unique in their requirements in the use of such software and their business processes, users of this document
are always advised to discuss the content of this document with their Epicor account manager. All information contained
herein is subject to change without notice and changes to this document since printing and other important information
about the software product are made or published in release notes, and you are urged to obtain the current release
notes for the software product. We welcome user comments and reserve the right to revise this publication and/or
make improvements or changes to the products or programs described in this publication at any time, without notice.
The usage of any Epicor software shall be pursuant to an Epicor end user license agreement and the performance of
any consulting services by Epicor personnel shall be pursuant to Epicor's standard services terms and conditions. Usage
of the solution(s) described in this document with other Epicor software or third party products may require the purchase
of licenses for such other products. Where any software is expressed to be compliant with local laws or requirements
in this document, such compliance is not a warranty and is based solely on Epicor's current understanding of such laws
and requirements. All laws and requirements are subject to varying interpretations as well as to change and accordingly
Epicor cannot guarantee that the software will be compliant and up to date with such changes. All statements of
platform and product compatibility in this document shall be considered individually in relation to the products referred
to in the relevant statement, i.e., where any Epicor software is stated to be compatible with one product and also
stated to be compatible with another product, it should not be interpreted that such Epicor software is compatible
with both of the products running at the same time on the same platform or environment. Additionally platform or
product compatibility may require the application of Epicor or third-party updates, patches and/or service packs and
Epicor has no responsibility for compatibility issues which may be caused by updates, patches and/or service packs
released by third parties after the date of publication of this document. Epicor® is a registered trademark and/or
trademark of Epicor Software Corporation in the United States, certain other countries and/or the EU. All other
trademarks mentioned are the property of their respective owners. Copyright © Epicor Software Corporation 2020.
All rights reserved. Not for distribution or republication. Information in this document is subject to Epicor license
agreement(s).

DOC0091E9
10.2.700
Revision: October 16, 2020 5:49 a.m.
Total pages: 57
sys.ditaval
Epicor WCF Services Developer Guide Contents

Contents
Introduction............................................................................................................................4
Audience.........................................................................................................................................................4
Prerequisites....................................................................................................................................................4
Conventions....................................................................................................................................................4
Overview.................................................................................................................................5
When to Use HTTP Transport...........................................................................................................................5
When to Use the TCP Transport.......................................................................................................................5
HTTP Transport.......................................................................................................................6
WCF Services Example #1 - Calling Epicor Web Services Using BasicHttpBinding or WSHttpBinding..................6
Create the Visual Studio Project................................................................................................................6
Add the Web Service References...............................................................................................................7
Add Helper Classes...................................................................................................................................8
Add Functionality to the Program Class...................................................................................................11
Code the Main Method..........................................................................................................................14
WCF Services Example #2 - Using SOAP Call Headers.....................................................................................20
Create the Visual Studio Project..............................................................................................................20
Add the Web Service References.............................................................................................................21
Add Helper Classes.................................................................................................................................22
Add Functionality to the Program Class...................................................................................................27
Code The Main Method..........................................................................................................................30
WCF Services Example #3 - Azure AD token authentication............................................................................32
Create the Visual Studio Project..............................................................................................................32
Add NuGet Package...............................................................................................................................32
Add the Web Service Reference..............................................................................................................32
Add Helper Classes.................................................................................................................................33
Add Functionality to the Program Class...................................................................................................36
Code the Main Method..........................................................................................................................37
Add Message Header..............................................................................................................................42
Add AzureADAuth Class.........................................................................................................................43
Add Token Header to the Server Request................................................................................................48
Setting Up SSL......................................................................................................................49
Create Site Binding........................................................................................................................................49
Connect the Application Server......................................................................................................................53
Enable HTTP Endpoints........................................................................................................54
Update the Web.Config File...........................................................................................................................54
BasicHTTPBinding Explanation........................................................................................................................55
WSHTTPBinding Explanation..........................................................................................................................55
HttpsTextEncodingAzureChannel Explanation................................................................................................55

Epicor ERP | 10.2.700 3


Introduction Epicor WCF Services Developer Guide

Introduction

The Epicor ERP application connects to internet applications through Windows Communication Foundation (WCF)
services. If you need to move Epicor data to these services, review this guide.
The documentation describes how to consume the Epicor WCF-Services, how to configure the web services for
different scenarios, and other information developers may find useful when working with Epicor Web Services.
This guide is written for the .NET 4 platform.

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:
static void Main(string[] args)
{
string myString = "Test";

4 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide Overview

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.

When to Use HTTP Transport

Microsoft states the following about when you should use HTTP Transport:
HTTP is a request/response protocol between clients and servers. The most common application consists of
Web-browser clients that communicate with a Web server. The client sends a request to a server, which listens
for client request messages. When the server receives a request, it returns a response, which contains the status
of the request. If successful it returns optional data, such as a Web page, an error message, or other information.
In WCF, the HTTP transport binding is optimized for interoperability with legacy non-WCF systems. If all
communicating parties are using WCF, the TCP-based binding is faster.

When to Use the TCP Transport

Microsoft states the following about when you should use TCP Transport:
TCP is a connection-based, stream-oriented delivery service with end-to-end error detection and correction.
Connection-based means that a communication session between hosts is established before exchanging data.
A host is any device on a TCP/IP network identified by a logical IP address.
TCP provides reliable data delivery and ease of use. Specifically TCP notifies the sender of packet delivery,
guarantees that packets are delivered in the same order in which they are sent, retransmits lost packets, and
ensures that data packets are not duplicated. Note that this reliable delivery applies between two TCP/IP nodes.
This delivery method is not the same as WS-ReliableMessaging, which applies between endpoints, no matter
how many intermediate nodes they may include. The WCF TCP transport is optimized for the scenario where
both ends of the communication use WCF. This binding is the fastest WCF binding for scenarios that involve
communicating between different machines.

Epicor ERP | 10.2.700 5


HTTP Transport Epicor WCF Services Developer Guide

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.

WCF Services Example #1 - Calling Epicor Web Services Using BasicHttpBinding


or WSHttpBinding

This section describes the process through which Epicor web services are consumed. This process is illustrated
through an ABCCode service example.
You will create a sample C# project that consumes the ABCCode service using either BasicHttpBinding or
WSHttpBinding. To illustrate this example, the Epicor application server is located on a machine named ‘localhost’
and the application name is ‘ERP100500’. When you see these values in the code samples, replace them with
values that match your environment.

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.

Create the Visual Studio Project


® ®
1. Launch Microsoft Visual Studio .

2. Select the File > New > Project menu item.

3. In the New Project dialog box, verify Visual C# is selected in the tree view.

4. Next in the Detail panel, select Console Application.

5. For the project Name, enter AbcCodeServiceClient.

6 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

6. Select the location where you will save the project.

7. Click OK.

Add the Web Service References

You need to reference the SessionMod and AbcCode services. The SessionMod service creates a session on the
server that persists between method calls. This service also changes certain session properties, such as the company
or site you are working against. To change the company you call the SessionMod SetCompany method; to change
the site you call the SessionMod SetPlant method. The AbcCode service maintains the ABC codes.

1. In the Solution Explorer, right-click the AbcCodeServiceClient project item and select the Add> Service
Reference… menu item.
The Add Service Reference dialog box displays.

2. In the Address field, enter the URL to your SessionMod service. For this example, the URL is the
http://localhost/ERP100500/ICE/Lib/SessionMod.svc value.

3. Click Go.
The details for the service display.

4. For the Namespace field, enter Epicor.SessionModSvc.

5. Click OK.
The reference is added to your project.

Epicor ERP | 10.2.700 7


HTTP Transport Epicor WCF Services Developer Guide

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.

Add Helper Classes

One feature of the Epicor application server is the client can institute a session on the server. Any session level
properties the user may set persist between each server call as long as each call uses the same session ID. To do
this, you provide a SessionInfo SOAP header. You can provide this header through multiple methods.
The process described below does not require you take a dependency on any Epicor assemblies. These steps note
where you could use the Epicor provided classes in place of the classes you enter through code.

1. In the Solution Explorer, right-click the AbcCodeServiceClient project item.

2. Select the Add > Class… menu item.


The Add New Item dialog box displays. On the List view, the Class item should be selected by default.

3. Next in the Name field, enter CustomMessageInspector.

4. Click Add.

5. Return to the Solution Explorer and click on the CustomMessageInspector.cs file.


This opens the file in the Editor window.

6. At the top of the file you see the standard using statements. You need to add the following custom using
statements to this list:
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

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:
class SessionInfoHeader: MessageHeader

8 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

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:
protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWri
ter writer, MessageVersion messageVersion)
{
writer.WriteElementString("SessionID", @"http://schemas.datacontract
.org/2004/07/Epicor.Hosting", SessionId.ToString());
writer.WriteElementString("UserID", @"http://schemas.datacontract.or
g/2004/07/Epicor.Hosting", EpicorUserId);
}

public override string Name


{
get { return "SessionInfo"; }
}

public override string Namespace

{
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:
public Guid SessionId { get; set; }
public string EpicorUserId { get; set; }

9. Next update the HookServiceBehavior class. 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.
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.


class HookServiceBehavior : IEndpointBehavior

b. After the opening curly brace, add two private variables that hold the user’s Session ID and Epicor User
ID.
class HookServiceBehavior : IEndpointBehavior
{
private Guid _sessionId;
private string _epicorUserId;

Epicor ERP | 10.2.700 9


HTTP Transport Epicor WCF Services Developer Guide

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.
public HookServiceBehavior(Guid SessionId, string EpicorUserId)
{
_sessionId = SessionId;
_epicorUserId = EpicorUserId;
}
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.


public void AddBindingParameters(ServiceEndpoint endpoint, BindingParamet
erCollection bindingParameters){ }

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime c


lientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new CustomMessageInspector
(_sessionId, _epicorUserId));
}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, Endpoi


ntDispatcher endpointDispatcher) { }

public void Validate(ServiceEndpoint endpoint) { }

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.
class CustomMessageInspector : IClientMessageInspector

b. Add two private class level variables that hold the Epicor session Id and user Id.
class CustomMessageInspector : IClientMessageInspector
{

private Guid _sessionID;


private string _epicorUserId;

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.
public CustomMessageInspector(Guid SessionId, string EpicorUserId)
{
_sessionID = SessionId;
_epicorUserId = EpicorUserId;
}

10 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

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.
public void AfterReceiveReply(ref Message reply, object correlationState)
{ }

public object BeforeSendRequest(ref Message request, System.ServiceModel.


IClientChannel channel)
{
if (_sessionID != null && _sessionID != Guid.Empty)
{
var sessionHeader = new SessionInfoHeader() { SessionId = _sess
ionID, EpicorUserId = _epicorUserId };
request.Headers.Add(sessionHeader);
}
return request;
}

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.

Add Functionality to the Program Class

You next add code to the Program class provided in the Program.cs code file. You add three helper methods to
this class that are called from the Main method. These methods do the work of creating an instance of the service
client classes you defined when you added the service references.

1. Since this project demonstrates the ability to call into the Epicor services using either BasicHttpBinding or
WSHttpBinding, you start by defining an enumeration that specifies which binding to use.
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.

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;

Epicor ERP | 10.2.700 11


HTTP Transport Epicor WCF Services Developer Guide

b. Now enter the GetWsHttpBinding method. This method can be added immediately after the Main
method.
private static WSHttpBinding GetWsHttpBinding()
{
var binding = new WSHttpBinding();

const int maxBindingSize = Int32.MaxValue;


binding.MaxReceivedMessageSize = maxBindingSize;
binding.ReaderQuotas.MaxDepth = maxBindingSize;
binding.ReaderQuotas.MaxStringContentLength = maxBindingSize;
binding.ReaderQuotas.MaxArrayLength = maxBindingSize;
binding.ReaderQuotas.MaxBytesPerRead = maxBindingSize;
binding.ReaderQuotas.MaxNameTableCharCount = maxBindingSize;
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialTyp
e.UserName;

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.
public static BasicHttpBinding GetBasicHttpBinding()
{

var binding = new BasicHttpBinding();

const int maxBindingSize = Int32.MaxValue;


binding.MaxReceivedMessageSize = maxBindingSize;
binding.ReaderQuotas.MaxDepth = maxBindingSize;
binding.ReaderQuotas.MaxStringContentLength = maxBindingSize;
binding.ReaderQuotas.MaxArrayLength = maxBindingSize;
binding.ReaderQuotas.MaxBytesPerRead = maxBindingSize;
binding.ReaderQuotas.MaxNameTableCharCount = maxBindingSize;
binding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCr
edential;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCred
entialType.UserName;

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 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.

12 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

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.
private static TClient GetClient<TClient, TInterface>(
string url,
string username,
string password,
EndpointBindingType bindingType)
where TClient : ClientBase<TInterface>
where TInterface : class
{
Binding binding = null;
TClient client;

var endpointAddress = new EndpointAddress(url);

switch (bindingType)
{
case EndpointBindingType.BasicHttp:
binding = GetBasicHttpBinding();
break;
case EndpointBindingType.SOAPHttp:
binding = GetWsHttpBinding();
break;
}

client = (TClient)Activator.CreateInstance(typeof(TClient), binding, e


ndpointAddress);

if (!string.IsNullOrEmpty(username) && (client.ClientCredentials != nu


ll))
{
client.ClientCredentials.UserName.UserName = username;
client.ClientCredentials.UserName.Password = password;
}

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
• 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

Epicor ERP | 10.2.700 13


HTTP Transport Epicor WCF Services Developer Guide

this error, adjust the timeout values on the binding. Place this code immediately after the ‘switch(bindingType)’
block:
TimeSpan operationTimeout = new TimeSpan(0, 12, 0);
binding.CloseTimeout = operationTimeout;
binding.ReceiveTimeout = operationTimeout;
binding.SendTimeout = operationTimeout;
binding.OpenTimeout = operationTimeout;
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:
var endpointAddress = new EndpointAddress(url);
With this line:
DnsEndpointIdentity endpointIdentity = new DnsEndpointIdentity("L303415.america
s.epicor.net");
var endpointAddress = new EndpointAddress(new Uri(url), endpointIdentity);
You will also need to add a reference to the System.IdentityModel assembly.

Code the Main Method

Now code the Main method and call the Epicor services. This example demonstrates how to initiate a server
session and associate that session with your server calls. It also demonstrates how to create, save , modify, and
delete a record.

1. If you use a self-signed certificate in IIS, be sure to include the following code. This code speeds up calls to
the services.
ServicePointManager.ServerCertificateValidationCallback += (sender, certifi
cate, 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:
EndpointBindingType bindingType = EndpointBindingType.SOAPHttp;
Tip If you run the Epicor SaaS environment, you typically select the EndpointBindingType.BasicHttp
option instead of the EndpointBindingType.SOAPHttp defined in this example.

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.

14 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

Tip This example uses a plain string to hold the password. In production code, you store the password
though a SecureString instance.

string epicorUserID = "manager";


string epiorUserPassword = "manager";

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”.
string scheme = "http";
if (bindingType == EndpointBindingType.BasicHttp)
{
scheme = "https";
}

UriBuilder builder = new UriBuilder(scheme, "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<SessionModSvcContr
actClient, 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.
Guid sessionId = Guid.Empty;
sessionId = sessionModClient.Login();

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, Sessi
onModSvcContract>(
builder.Uri.ToString(),
epicorUserID,
epiorUserPassword,
bindingType);

Epicor ERP | 10.2.700 15


HTTP Transport Epicor WCF Services Developer Guide

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(ses
sionId, epicorUserID));
abcCodeClient.Endpoint.EndpointBehaviors.Add(new HookServiceBehavior(sessio
nId, 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:
string plantID, plantName, workstaionID, workstationDescription, employeeID
, coutryGroupCode, countryCode, tenantID;
sessionModClient.SetCompany("NewCompanyID",
out plantID,
out plantName,
out workstaionID,
out workstationDescription,
out employeeID,
out coutryGroupCode,
out countryCode,
out tenantID);

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.
var ts = new ABCCodeTableset();
abcCodeClient.GetNewABCCode(ref ts);
var newRow = ts.ABCCode.Where(n => n.RowMod.Equals("A", StringComparison.In
variantCultureIgnoreCase)).FirstOrDefault();

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.

16 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

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:
newRow.UserDefinedColumns[“UDCol1_c”] = “This is a test.”;
newRow.UserDefinedColumns[“UDCol2_c”] = 10;

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.
ABCCodeRow backupRow = new ABCCodeRow();
var fields = backupRow.GetType().GetProperties(BindingFlags.Instance | Bind
ingFlags.Public);
foreach(var field in fields)
{
if (field.PropertyType == typeof(System.Runtime.Serialization.Extensio
nDataObject))
{
continue;
}
var fieldValue = field.GetValue(ts.ABCCode[0]);
field.SetValue(backupRow, fieldValue);
}

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.

Epicor ERP | 10.2.700 17


HTTP Transport Epicor WCF Services Developer Guide

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", StringCompar
ison.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:
static void Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback += (sender, certif
icate, chain, errors) => { return true; };

EndpointBindingType bindingType = EndpointBindingType.SOAPHttp;

string epicorUserID = "manager";


string epiorUserPassword = "Epicor123";

string scheme = "http";


if (bindingType == EndpointBindingType.BasicHttp)
{
scheme = "https";
}

UriBuilder builder = new UriBuilder(scheme, "localhost");

18 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

builder.Path = "ERP100500/Ice/Lib/SessionMod.svc";
SessionModSvcContractClient sessionModClient = GetClient<SessionModSvcCont
ractClient, SessionModSvcContract>(
builder.Uri.ToString(),
epicorUserID,
epiorUserPassword,
bindingType);

builder.Path = "ERP100500/Erp/BO/AbcCode.svc";
ABCCodeSvcContractClient abcCodeClient = GetClient<ABCCodeSvcContractClien
t, ABCCodeSvcContract>(
builder.Uri.ToString(),
epicorUserID,
epiorUserPassword,
bindingType);

Guid sessionId = Guid.Empty;


sessionId = sessionModClient.Login();

builder.Path = "ERP100500/Ice/Lib/SessionMod.svc";
sessionModClient = GetClient<SessionModSvcContractClient, SessionModSvcCon
tract>(builder.Uri.ToString(),epicorUserID,epiorUserPassword,bindingType);

sessionModClient.Endpoint.EndpointBehaviors.Add(new HookServiceBehavior(se
ssionId, epicorUserID));
abcCodeClient.Endpoint.EndpointBehaviors.Add(new HookServiceBehavior(sessi
onId, epicorUserID));

var ts = new ABCCodeTableset();


abcCodeClient.GetNewABCCode(ref ts);

var newRow = ts.ABCCode.Where(n => n.RowMod.Equals("A", StringComparison.I


nvariantCultureIgnoreCase)).FirstOrDefault();

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.Inst
ance | BindingFlags.Public);
foreach(var field in fields)
{
if (field.PropertyType == typeof(System.Runtime.Serializati
on.ExtensionDataObject))
{
continue;
}

var fieldValue = field.GetValue(ts.ABCCode[0]);


field.SetValue(backupRow, fieldValue);
}
ts.ABCCode.Add(backupRow);

ts.ABCCode[0].CountFreq = 45;
ts.ABCCode[0].RowMod = "U";
abcCodeClient.Update(ref ts);

Epicor ERP | 10.2.700 19


HTTP Transport Epicor WCF Services Developer Guide

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", S
tringComparison.InvariantCultureIgnoreCase))
{
Console.WriteLine("Record deleted.");
}
else
{
Console.WriteLine(ex.Message);
}
}
}
}
}

if (sessionId != Guid.Empty)
{
sessionModClient.Logout();
}
Console.ReadLine();
}

WCF Services Example #2 - Using SOAP Call Headers

This section describes an alternate approach to calling Epicor web services.


This example uses SOAP call headers to specify call settings and license details. It uses the same requirements as
WCF Services Example #1. Through this alternate method, you do not need to create an instance of the SessionMod
service to log in and out of the application or to change the current company.

Create the Visual Studio Project


® ®
1. Launch Microsoft Visual Studio .

2. From the menu, click File > New > Project.


The New Project window displays.

3. Now on the tree view, verify that Visual C# branch is selected.

4. Next in the center Detail panel, select the Console Application option.

5. For the project Name, enter AbcCodeServiceClient2.

20 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

6. Select the location where you will save the project.

7. Click OK.

Add the Web Service References

You need to reference the SessionMod and AbcCode services.


The SessionMod service creates a session on the server that persists between method calls. This service also
changes certain session properties, such as the company or site you call through the web service. To change the
company, you call the SessionMod SetCompany method; to change the site you call the SessionMod SetPlant
method. The AbcCode service maintains the ABC codes.

1. Click on the Solution Explorer tab.

2. Right-click the AbcCodeServiceClient2 icon; from the context menu, select the Add Service Reference…
option.
The Add Service Reference dialog box displays. Through this window, you add reference to the AbcCode
service.

3. Enter the Address for this service. For example, you could enter:
http://localhost/ERP100500/Erp/BO/AbcCode.svc

4. Click Go.

The details for the service display.

Epicor ERP | 10.2.700 21


HTTP Transport Epicor WCF Services Developer Guide

5. For the Namespace field, enter Epicor.AbcCodeSvc.

6. Click OK.

The reference is added to your project.

Add Helper Classes

You next add four helper classes to the project.


These helper classes define the custom SOAP headers you use and the classes you implement. Then you can add
these headers to your web services calls.
The first two classes are similar to the classes you added in the first AbcCodeServiceClient sample. You only make
some slight modifications.

1. Navigate to the Solution Explorer.

2. Right-click the AbcCodeServiceClient2 icon; from the context menu; select the Add > Class… option.
The Add New Item dialog box displays. On the List view, the Class icon is selected by default.

3. Next in the Name field, enter CustomMessageInspector.

4. Click Add.
The CustomMessageInspector.cs code should displays in an Editor tab. If it does not, do the following:

a. Return to the Solution Explorer.

b. Click on the CustomMessageInspector.cs file.


This opens the file in a separate Editor tab.

5. At the top of the file you see the standard using statements. You need to add the following custom using
statements to this list:
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.

22 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

• 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:


class HookServiceBehavior : IEndpointBehavior

b. Now after the opening curly brace, add a private variable to hold the custom headers.:
class HookServiceBehavior : IEndpointBehavior
{
private readonly List<MessageHeader> _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.
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. Now implement the AddBindingParameters, ApplyClientBehavior, ApplyDispatchBehavior, and


Validate methods. These methods are defined by the interface.
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParamet
erCollection bindingParameters){ }

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime c


lientRuntime)

{
clientRuntime.ClientMessageInspectors.Add(new CustomMessageInspector
(_headers));
}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispa


tcher endpointDispatcher) { }

public void Validate(ServiceEndpoint endpoint) { }

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.
class CustomMessageInspector : IClientMessageInspector

Epicor ERP | 10.2.700 23


HTTP Transport Epicor WCF Services Developer Guide

b. Now add a private class level variable that holds the custom headers:
class CustomMessageInspector : IClientMessageInspector
{

private readonly List<MessageHeader> _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:
public CustomMessageInspector(Guid SessionId, string EpicorUserId)
{

_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:
public void AfterReceiveReply(ref Message reply, object correlationState)
{ }

public object BeforeSendRequest(ref Message request, System.ServiceModel.


IClientChannel channel)
{
foreach (var header in _headers)
{
request.Headers.Add(header);
}

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.

24 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

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;

b. Now indicate that the class inherits from the MessageHeader:


class LicenseHeader : MessageHeader
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:
public override string Name
{
get { return "License"; }
}

public override string Namespace


{
get { return "urn:epic:headers:License"; }
}

protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWri


ter writer, MessageVersion messageVersion)
{
writer.WriteElementString("Claimedlicense", @"http://schemas.
datacontract.org/2004/07/Epicor.Hosting", Claimedlicense);
writer.WriteElementString("SessionID", @"http://schemas.datac
ontract.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:
public Guid SessionId { get; set; }

public string Claimedlicense { get; set; }

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.

Epicor ERP | 10.2.700 25


HTTP Transport Epicor WCF Services Developer Guide

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;

public string Plant { get; set; } = string.Empty;

public string Language { get; set; } = string.Empty;

public string FormatCulture { get; set; } = string.Empty;

public Nullable<Int16> TimezoneOffset { get; set; }

protected override void OnWriteHeaderContents(System.Xml.XmlDiction


aryWriter writer, MessageVersion messageVersion)
{
writer.WriteElementString("Company", @"http://schemas.datacontr
act.org/2004/07/Epicor.Hosting", Company);
writer.WriteElementString("Plant", @"http://schemas.datacontrac
t.org/2004/07/Epicor.Hosting", Plant);
writer.WriteElementString("Language", @"http://schemas.datacont
ract.org/2004/07/Epicor.Hosting", Language);
writer.WriteElementString("FormatCulture", @"http://schemas.dat
acontract.org/2004/07/Epicor.Hosting", FormatCulture);
if (TimezoneOffset.HasValue)
{
writer.WriteElementString("TimezoneOffset", @"http://schema
s.datacontract.org/2004/07/Epicor.Hosting", TimezoneOffset.ToString());
}
}

/// <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

26 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

{
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.

Add Functionality to the Program Class

You next add code to the Program class contained in the Program.cs code file.
You add three helper methods to this class called from the Main method. These methods create an instance of
the service client classes you defined when you added the service references.

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.
private static WSHttpBinding GetWsHttpBinding()
{
var binding = new WSHttpBinding();
const int maxBindingSize = Int32.MaxValue;
binding.MaxReceivedMessageSize = maxBindingSize;
binding.ReaderQuotas.MaxDepth = maxBindingSize;
binding.ReaderQuotas.MaxStringContentLength = maxBindingSize;
binding.ReaderQuotas.MaxArrayLength = maxBindingSize;
binding.ReaderQuotas.MaxBytesPerRead = maxBindingSize;
binding.ReaderQuotas.MaxNameTableCharCount = maxBindingSize;
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.
UserName;

return binding;
}

4. Name the second method GetBasicHttpBinding; this method creates a new, properly configured instance
of the BasicHttpBinding class.

Epicor ERP | 10.2.700 27


HTTP Transport Epicor WCF Services Developer Guide

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.
public static BasicHttpBinding GetB
asicHttpBinding()
{
var binding = new BasicHttpBinding();

const int maxBindingSize = Int32.MaxValue;


binding.MaxReceivedMessageSize = maxBindingSize;
binding.ReaderQuotas.MaxDepth = maxBindingSize;
binding.ReaderQuotas.MaxStringContentLength = maxBindingSize;
binding.ReaderQuotas.MaxArrayLength = maxBindingSize;
binding.ReaderQuotas.MaxBytesPerRead = maxBindingSize;
binding.ReaderQuotas.MaxNameTableCharCount = maxBindingSize;
binding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCred
ential;
binding.Security.Message.ClientCredentialType = BasicHttpMessageCreden
tialType.UserName;

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
private static TClient GetClient<TClient, TInterface>(
string url,
string username,
string password,
EndpointBindingType bindingType)
where TClient : ClientBase<TInterface>
where TInterface : class
{
Binding binding = null;
TClient client;

var endpointAddress = new EndpointAddress(url);

switch (bindingType)
{
case EndpointBindingType.BasicHttp:
binding = GetBasicHttpBinding();
break;

28 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

case EndpointBindingType.SOAPHttp:
binding = GetWsHttpBinding();
break;
}

client = (TClient)Activator.CreateInstance(typeof(TClient), binding, e


ndpointAddress);

if (!string.IsNullOrEmpty(username) && (client.ClientCredentials != nu


ll))
{
client.ClientCredentials.UserName.UserName = username;
client.ClientCredentials.UserName.Password = password;
}

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.
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(bindi
ngType) block:
TimeSpan operationTimeout = new TimeSpan(0, 12, 0);
binding.CloseTimeout = operationTimeout;
binding.ReceiveTimeout = operationTimeout;
binding.SendTimeout = operationTimeout;
binding.OpenTimeout = operationTimeout;
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:
var endpointAddress = new EndpointAddress(url);

Epicor ERP | 10.2.700 29


HTTP Transport Epicor WCF Services Developer Guide

With this line:


DnsEndpointIdentity endpointIdentity = new DnsEndpointIdentity("L303415.ame
ricas.epicor.net");
var endpointAddress = new EndpointAddress(new Uri(url), endpointIdentity);
You will also need to add a reference to the System.IdentityModel assembly.

Code The Main Method

Now code the Main method to call the Epicor services. This example demonstrates how to create the custom
headers you coded and apply them to your service client.

1. If you use a self-signed certificate in IIS, be sure to include the following code. This code speeds up calls to
the services:
ServicePointManager.ServerCertificateValidationCallback += (sender, certifi
cate, chain, errors) => { return true; };
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:
EndpointBindingType bindingType = EndpointBindingType.SOAPHttp;

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.
string epicorUserID = "manager";
string epiorUserPassword = "manager";
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”.
string scheme = "http";
if (bindingType == EndpointBindingType.BasicHttp)
{
scheme = "https";
}

UriBuilder builder = new UriBuilder(scheme, "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

30 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

, ABCCodeSvcContract>(
builder.Uri.ToString(),
epicorUserID,
epiorUserPassword,
bindingType);

7. Add a list to hold the custom headers:


List<MessageHeader> headers = new List<MessageHeader>();

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.
headers.Add(new LicenseHeader() { Claimedlicense = "00000003-B615-4300-95
7B-34956697F040", SessionId = Guid.Empty });

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.
headers.Add(new CallSettings() { Company = "Epic06" });

c. Now add the headers to the client instance.


abcCodeClient.Endpoint.EndpointBehaviors.Add(new HookServiceBehavior(hea
ders));

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.
var ts = new ABCCodeTableset();
abcCodeClient.GetNewABCCode(ref ts);
var newRow = ts.ABCCode.Where(n => n.RowMod.Equals("A", StringComparison.In
variantCultureIgnoreCase)).FirstOrDefault();

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);
This completes the code for the Main method.

Epicor ERP | 10.2.700 31


HTTP Transport Epicor WCF Services Developer Guide

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.

WCF Services Example #3 - Azure AD token authentication

This section describes the process of using WCF Services with Azure AD authentication.

Create the Visual Studio Project


® ®
1. Launch Microsoft Visual Studio .

2. Select the File > New > Project menu item.

3. In the New Project dialog box, verify Visual C# is selected in the tree view.

4. Next in the Detail panel, select Console Application.

5. For the project Name, enter AbcCodeServiceClientAzureAD.

6. Select the location where you will save the project.

7. Click OK.

Add NuGet Package


First, add the Active Directory Authentication Library (ADAL) NuGet package to the project.

1. In the Solution Explorer, right-click the AbcCodeServiceClientAzureAD project item and select Manage
NuGet Packages... menu item.

2. Click Browse and search for Microsoft.IdentityModel.Clients.ActiveDirectory package.

3. Click Install and step through the wizard.

4. Verify the following Microsoft.Identity references are added to your project.

Add the Web Service Reference


You need to reference the AbcCode service that maintains the ABC codes.

1. In the Solution Explorer, right-click the AbcCodeServiceClientAzureAD project item and select the Add>
Service Reference… menu item.
The Add Service Reference dialog box displays.

32 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

2. In the Address field, enter the URL to your SessionMod service. For this example, the URL is the
http://localhost/ERP102200/Erp/BO/AbcCode.svc value.

3. Click Go.
The details for the service display.

4. For the Namespace field, enter Epicor.AbcCodeSvc and click OK.

Add Helper Classes

One feature of the Epicor application server is the client can institute a session on the server. Any session level
properties the user may set persist between each server call as long as each call uses the same session ID. To do
this, you provide a SessionInfo SOAP header. You can provide this header through multiple methods.
The process described below does not require you take a dependency on any Epicor assemblies. These steps note
where you could use the Epicor provided classes in place of the classes you enter through code.

1. In the Solution Explorer, right-click the AbcCodeServiceClientAzureAD project item.

2. Select the Add > Class… menu item.


The Add New Item dialog box displays. On the List view, the Class item should be selected by default.

3. Next in the Name field, enter CustomMessageInspector.

4. Click Add.

5. Return to the Solution Explorer and click on the CustomMessageInspector.cs file.


This opens the file in the Editor window.

6. At the top of the file you see the standard using statements. You need to add the following custom using
statements to this list:
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using AbcCodeServiceClientAzureAD.MessageHeaders;

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 { }
Add the following HookServiceBehavior class above the CustomMessageInspector class:
class HookServiceBehavior
{
}
class CustomMessageInspector
{
}

8. Next update the HookServiceBehavior class. 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.

Epicor ERP | 10.2.700 33


HTTP Transport Epicor WCF Services Developer Guide

• 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.


class HookServiceBehavior : IEndpointBehavior

b. After the opening curly brace, add:


private readonly List<MessageHeader> _headers;

c. Now create a constructor method for the class:


/// <summary>
/// Initializes a new instance of the <see cref="HookServiceBehav
ior"/> class.
/// </summary>
/// <param name="soapHeaders">The list of <see cref="MessageHeade
r"/> 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.


#region IEndpointBehavior methods
public void AddBindingParameters(ServiceEndpoint endpoint, Bindin
gParameterCollection bindingParameters) { }

public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientR


untime clientRuntime)
{
clientRuntime.ClientMessageInspectors.Add(new CustomMessageIn
spector(_headers));
}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, Endpo


intDispatcher endpointDispatcher) { }

public void Validate(ServiceEndpoint endpoint) { }


#endregion

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.
class CustomMessageInspector : IClientMessageInspector

34 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

b. Add the following variable:


class CustomMessageInspector : IClientMessageInspector
{
private readonly List<MessageHeader> _headers;

c. Add a constructor method for the class:


/// <summary>
/// Initializes a new instance of the <see cref="CustomMessageIns
pector"/> class.
/// </summary>
/// <param name="soapHeaders">The list of <see cref="MessageHeade
r"/> 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 applicati
on.
/// </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
>
public void AfterReceiveReply(ref Message reply, object correlati
onState) { }

/// <summary>
/// Enables inspection or modification of a message before a requ
est 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>
/// <returns>
/// The object that is returned as the <paramref name="correlatio
nState " />argument of the <see cref="M:System.ServiceModel.Dispatcher.IC
lientMessageInspector.AfterReceiveReply(System.ServiceModel.Channels.Mess
age@,System.Object)" /> method. This is null if no correlation state is u
sed.The best practice is to make this a <see cref="T:System.Guid" /> to e
nsure that no two <paramref name="correlationState" /> objects are the sa
me.
/// </returns>
public object BeforeSendRequest(ref Message request, System.Servi
ceModel.IClientChannel channel)
{
foreach (var header in _headers)
{
request.Headers.Add(header);
}

//Get AzureAD token and add it to request header


request.Headers.Add(new AzureADTokenHeader());

return request;
}

Epicor ERP | 10.2.700 35


HTTP Transport Epicor WCF Services Developer Guide

Add Functionality to the Program Class

You next add code to the Program class provided in the Program.cs code file. You add three helper methods to
this class that are called from the Main method. These methods do the work of creating an instance of the service
client classes you defined when you added the service references.

1. 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.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.
public static CustomBinding GetHttpsTextEncodingAzureBinding()
{
var binding = new CustomBinding();
//Specify message encoding as text and set its quotas
var textEncoding = new TextMessageEncodingBindingElement
{
ReaderQuotas =
{
MaxArrayLength = int.MaxValue,
MaxDepth = 50,
MaxBytesPerRead = int.MaxValue,
MaxStringContentLength = int.MaxValue
}
};
//Specify secure https transport
var transport = new HttpsTransportBindingElement
{
MaxReceivedMessageSize = int.MaxValue,
MaxBufferSize = int.MaxValue
};

binding.Elements.Add(textEncoding);
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.
private static TClient GetClient<TClient, TInterface>(
string url)
where TClient : ClientBase<TInterface>
where TInterface : class
{
Binding binding = null;
TClient client;

var endpointAddress = new EndpointAddress(url);

binding = GetHttpsTextEncodingAzureBinding();

client = (TClient)Activator.CreateInstance(typeof(TClient), bin


ding, endpointAddress);

36 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

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:
TimeSpan operationTimeout = new TimeSpan(0, 12, 0);
binding.CloseTimeout = operationTimeout;
binding.ReceiveTimeout = operationTimeout;
binding.SendTimeout = operationTimeout;
binding.OpenTimeout = operationTimeout;
In the above example, notice you set the timeout values to twelve minutes.

Code the Main Method

Now code the Main method and call the Epicor services. This example demonstrates how to initiate a server
session and associate that session with your server calls. It also demonstrates how to create, save , modify, and
delete a record.

1. Inside the Main method, enter an UriBuilder code line that creates the URL for the service:
// replace 'localhost' with the fully qualified machine name where the serv
ices are hosted.
UriBuilder builder = new UriBuilder("https", "localhost");

2. Set the Path property of the UriBuilder to the path of the service:
//change 'ERP102200' to the name of your appserver
builder.Path = "ERP102200/Erp/BO/AbcCode.svc";
ABCCodeSvcContractClient abcCodeClient = GetClient<ABCCodeSvcContractClient
, ABCCodeSvcContract>(builder.Uri.ToString());

3. Create the custom headers to add to the web service call:


List<MessageHeader> headers = new List<MessageHeader>();

4. Set the call to use an appropriate web service license:


// available license types and thier GUID value are:
// DefaultUser" = "00000003 - B615 - 4300 - 957B - 34956697F040"
// "WebService" = "00000003-9439-4B30-A6F4-6D2FD4B9FD0F"
// "WebServiceShared" = "00000003-231B-4E2B-9B47-13F87EA9A0A3"

// set the call to use a web service license


headers.Add(new LicenseHeader() { Claimedlicense = "00000003-B615-4300-957B
-34956697F040", SessionId = Guid.Empty });

Epicor ERP | 10.2.700 37


HTTP Transport Epicor WCF Services Developer Guide

5. Update CallSettings header as needed:


// use the CallSettings header to specify company, plant, etc.
// replace 'Epic06' with a company ID valid for your system
headers.Add(new CallSettings() { Company = "Epic06" });

6. Add an endpoint behavior to the endpoint being used by the service:


// add an endpoint behavior to the endpoint being used by the service.
abcCodeClient.Endpoint.EndpointBehaviors.Add(new HookServiceBehavior(header
s));

7. Test interactive vs hardcoded logon into AzureAD:


//This is property for test interactive vs hardcoded logon into AzureAD.
//Try it with both values to see the difference
AzureADAuth.UseInteractiveLogon = true;

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.
var ts = new ABCCodeTableset();

abcCodeClient.GetNewABCCode(ref ts);

var newRow = ts.ABCCode.FirstOrDefault(n => n.RowMod.Equals("A"


, StringComparison.InvariantCultureIgnoreCase));

if (newRow != null)
{

9. 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);

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:
newRow.UserDefinedColumns[“UDCol1_c”] = “This is a test.”;
newRow.UserDefinedColumns[“UDCol2_c”] = 10;

38 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

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.
ABCCodeRow backupRow = new ABCCodeRow();
var fields = backupRow.GetType().GetProperties(BindingFlags.Instance | Bind
ingFlags.Public);
foreach(var field in fields)
{
if (field.PropertyType == typeof(System.Runtime.Serialization.Extensio
nDataObject))
{
continue;
}
var fieldValue = field.GetValue(ts.ABCCode[0]);
field.SetValue(backupRow, fieldValue);
}

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);
}

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);

Epicor ERP | 10.2.700 39


HTTP Transport Epicor WCF Services Developer Guide

try
{
ts = abcCodeClient.GetByID("G");
}
catch (FaultException<Epicor.AbcCodeSvc.EpicorFaultDetail> ex)
{
if (ex.Detail.ExceptionKindValue.Equals("RecordNotFound", StringCompar
ison.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:
static void Main(string[] args)
{

// replace 'localhost' with the fully qualified machine name where


the services are hosted.
UriBuilder builder = new UriBuilder("https", "localhost");

//change 'ERP102200' to the name of your appserver


builder.Path = "ERP102200/Erp/BO/AbcCode.svc";
ABCCodeSvcContractClient abcCodeClient = GetClient<ABCCodeSvcContra
ctClient, ABCCodeSvcContract>(
builder.Uri.ToString());

// create the custom headers to add to the web service call.


List<MessageHeader> headers = new List<MessageHeader>();

// available license types and thier GUID value are:


// DefaultUser" = "00000003 - B615 - 4300 - 957B - 34956697F040"
// "WebService" = "00000003-9439-4B30-A6F4-6D2FD4B9FD0F"
// "WebServiceShared" = "00000003-231B-4E2B-9B47-13F87EA9A0A3"

// set the call to use a web service license


headers.Add(new LicenseHeader() { Claimedlicense = "00000003-B615-4
300-957B-34956697F040", SessionId = Guid.Empty });

// use the CallSettings header to specify company, plant, etc.


// replace 'Epic06' with a company ID valid for your system
headers.Add(new CallSettings() { Company = "Epic06" });

// add an endpoint behavior to the endpoint being used by the servi


ce.

40 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

abcCodeClient.Endpoint.EndpointBehaviors.Add(new HookServiceBehavio
r(headers));

//This is property for test interactive vs hardcoded logon into Azu


reAD.
//Try it with both values to see the difference
AzureADAuth.UseInteractiveLogon = true;

// the code from this point on is basically the same as our first e
xample
var ts = new ABCCodeTableset();

abcCodeClient.GetNewABCCode(ref ts);

var newRow = ts.ABCCode.FirstOrDefault(n => n.RowMod.Equals("A", St


ringComparison.InvariantCultureIgnoreCase));
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.Inst
ance | BindingFlags.Public);
foreach(var field in fields)
{
if (field.PropertyType == typeof(System.Runtime.Serializati
on.ExtensionDataObject))
{
continue;
}

var fieldValue = field.GetValue(ts.ABCCode[0]);


field.SetValue(backupRow, fieldValue);
}
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);

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", S
tringComparison.InvariantCultureIgnoreCase))

Epicor ERP | 10.2.700 41


HTTP Transport Epicor WCF Services Developer Guide

{
Console.WriteLine("Record deleted.");
}
else
{
Console.WriteLine(ex.Message);
}
}
}
}
}

if (sessionId != Guid.Empty)
{
sessionModClient.Logout();
}
Console.ReadLine();
}

Add Message Header

1. First, create a new folder in the project to store message headers.

a. In the Solution Explorer, right-click the AbcCodeServiceClientAzureAD project item and select Add >
New Folder.

b. Name the folder MessageHeaders.

2. Right-click the MessageHeaders folder and select Add > Class.

3. For Name, enter AzureADTokenHeader and click Add.

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"; }
}

42 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

/// <summary>
/// Code to wrie header into stream
/// </summary>
/// <param name="writer"></param>
/// <param name="messageVersion"></param>
protected override void OnWriteHeaderContents(XmlDictionaryWriter w
riter, MessageVersion messageVersion)
{
writer.WriteElementString("Token", @"http://schemas.datacontrac
t.org/2004/07/Epicor.Hosting", GetToken());
}

/// <summary>
/// Get token value for the call
/// </summary>
/// <returns></returns>
private string GetToken()
{
return AzureADAuth.GetToken();
}
}

Add AzureADAuth Class

Implement AzureADAuth class to handle Azure AD token creation and management.

1. In the Solution Explorer, right-click the AbcCodeServiceClientAzureAD project item.

2. Select the Add > Class… menu item.


The Add New Item dialog box displays. On the List view, the Class item should be selected by default.

3. Next in the Name field, enter AzureADAuth.

4. Click Add.

5. Adjust the using statements to read the following:


using System;
using System.Globalization;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

6. First, add the following Azure AD settings and adjust them accordingly:
//Settings to be adjusted according your Azure AD installation

/// <summary>
/// Directory ID - take it from Azure Active Directory properties
/// </summary>
private const string DirectoryID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXX
XXXXX";
/// <summary>
/// Application ID of the Epicor Web application
/// </summary>
private const string WebAppID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX
XX";
/// <summary>
/// Application ID of the Epicor native application
/// </summary>

Epicor ERP | 10.2.700 43


HTTP Transport Epicor WCF Services Developer Guide

private const string NativeClientAppID = "XXXXXXXX-XXXX-XXXX-XXXX-X


XXXXXXXXXXX";
/// <summary>
/// Redirect URL specified for Epicor native application.
/// </summary>
private static readonly Uri redirectURL = new Uri("https://localhos
t");

/// <summary>
/// Azure AD logon URL, used for any tenancy.
/// </summary>
private const string aadInstance = "https://login.microsoftonline.c
om/{0}";
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
• Epicor ERP 10 Installation guide: Appendices > Azure AD Authentication

7. Add property to hold Authentication context instance. This will manage all necessary token processing
internally, without an intervention:
private static AuthenticationContext _authContext;
/// <summary>
/// Property to store authentication context for the Azure AD token
s
/// </summary>
private static AuthenticationContext AuthContext
{
get
{
if (_authContext == null)
{
var authority = string.Format(CultureInfo.InvariantCult
ure, aadInstance, DirectoryID);

_authContext = new AuthenticationContext(authority);


}
return _authContext;
}
}

8. Add function to get the token. You may use wither of the below approaches:

a. Show ADAL standard window to ask for name and password, when no valid token exists:
/// <summary>
/// Get token for the 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
");

//Specify that dialog should be shown only if prompt for user


credentials is necessary
var platformParameters = new PlatformParameters(PromptBehavio
r.Auto);

44 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

//use AuthContext to get token. ADAL will internally handle t


oken caching and refreshing
AuthenticationResult result = AuthContext.AcquireTokenAsync(
WebAppID,
NativeClientAppID,
RedirectURL,
platformParameters
).Result;

//Return AccessToken, which should be sent to Epicor App Serv


er as WCF header
return result?.AccessToken;
}

b. Specify Azure AD user name and password and obtain a token the user. This approach can be used to
avoid user interaction.
/// <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
");

//get username and password from settings. Consent should be


already received for this user in the applications
const string userName = "USER@tenant.onmicrosoft.com";
const string password = "Password";

var userCredentials = new UserPasswordCredential(userName, pa


ssword);

//use AuthContext to get token for this user. ADAL will inte
rnally handle token caching and refreshing
var result = AuthContext.AcquireTokenAsync(WebAppID, NativeCl
ientAppID, userCredentials).Result;

//Return AccessToken, which should be sent to Epicor App Serv


er as WCF header
return result?.AccessToken;
}

Note 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 will fail with the error:
The user or administrator has not consented to use the application.

The full example code displays below for your reference:


using System;
using System.Globalization;
using Microsoft.IdentityModel.Clients.ActiveDirectory;

namespace AbcCodeServiceClientAzureAD
{
/// <summary>
/// Example of Azure AD authentication using ADAL.NET
/// </summary>
public static class AzureADAuth

Epicor ERP | 10.2.700 45


HTTP Transport Epicor WCF Services Developer Guide

{
//Settings to be adjusted according your Azure AD installation

/// <summary>
/// Directory ID - take it from Azure Active Directory properties
/// </summary>
private const string DirectoryID = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX
X";
/// <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-XXXXX
XXXXXXX";
/// <summary>
/// Redirect URL specified for Epicor native application.
/// </summary>
private static readonly Uri RedirectURL = new Uri("https://localhost");

/// <summary>
/// Azure AD logon URL, used for any tenancy.
/// </summary>
private const string AadInstance = "https://login.microsoftonline.com/{
0}";

/// <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");

//This value is used to illustrate how token can be obtained


if (UseInteractiveLogon)
{
return InteractiveAzureLogon();
}
else
{
return AzureLogonForSavedUserAndPassword();
}
}

/// <summary>
/// Show dialog window so user can enter name and password, if necessar
y
/// </summary>
/// <returns>Access token for the WCF header</returns>
private static string InteractiveAzureLogon()
{
//Specify that dialog should be shown only if prompt for user crede

46 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide HTTP Transport

ntials is necessary
var platformParameters = new PlatformParameters(PromptBehavior.Auto
);

//use AuthContext to get token. ADAL will internally handle token c


aching and refreshing
AuthenticationResult result = AuthContext.AcquireTokenAsync(
WebAppID,
NativeClientAppID,
RedirectURL,
platformParameters
).Result;

//Return AccessToken, which should be sent to Epicor App Server as


WCF header
return result?.AccessToken;
}

private static string AzureLogonForSavedUserAndPassword()


{
//get username and password for Azure AD user from settings.
//Warning: 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 t
he application with ID ...
const string userName = "USER@tenant.onmicrosoft.com";
const string password = "Password";

var userCredentials = new UserPasswordCredential(userName, password


);

//use AuthContext to get token for this user. ADAL will internally
handle token caching and refreshing
var result = AuthContext.AcquireTokenAsync(WebAppID, NativeClientAp
pID, userCredentials).Result;

//Return AccessToken, which should be sent to Epicor App Server as


WCF header
return result?.AccessToken;
}

private static AuthenticationContext _authContext;


/// <summary>
/// Property to store authentication context for the Azure AD tokens
/// </summary>
private static AuthenticationContext AuthContext
{
get
{
if (_authContext == null)
{
var authority = string.Format(CultureInfo.InvariantCulture,
AadInstance, DirectoryID);

_authContext = new AuthenticationContext(authority);


}
return _authContext;
}
}
}
}

Epicor ERP | 10.2.700 47


HTTP Transport Epicor WCF Services Developer Guide

Add Token Header to the Server Request


Last step is to update public object BeforeSendRequest(ref Message request, System.ServiceModel.IClientChannel
channel) in the CustomMessageInspector class.

1. Click the CustomMessageInspector.cs to open the method.

2. Add the following line at the end, right before return request;:
//Get AzureAD token;
request.Headers.Add(new AzureADTokenHeader());

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.

48 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide Setting Up SSL

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.

Create Site Binding

1. Open the Internet Information Services (IIS) Manager console. Click Start > All Programs >
Administrative Tools > Internet Information Services (IIS) Manager.

2. Navigate to the tree view; select the server node and then double-click the Server Certificates icon in the
list view:

3. Now from the Actions panel, click the "Create Self-Signed Certificate" option:

Epicor ERP | 10.2.700 49


Setting Up SSL Epicor WCF Services Developer Guide

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.

9. You next create an SSL Binding.

a. Select a site in the tree view (for example, ‘Default Web Site’).

b. Now in the Actions pane, select the Bindings option.


This launches the Site Bindings editor. Use this window to create, edit, and delete bindings for your
Web site.

50 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide Setting Up SSL

c. To add your new SSL binding, click the Add… button.

d. The default settings for a new binding are set to HTTP on port 80. From the Type drop-down list, select
the https option.

e. From the SSL Certificate drop-down list, select the certificate you created in the previous steps.

f. To save your new binding, click OK.

10. Now verify the SSL binding works.

a. In the Actions pane, under the Browse Web Site section, click the link associated with the https binding.

Epicor ERP | 10.2.700 51


Setting Up SSL Epicor WCF Services Developer Guide

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.

c. You should see a page similar to this example:

52 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide Setting Up SSL

Connect the Application Server

Now that you have IIS configured to use SSL, you make your appserver aware of this certificate.

1. In Notepad open the Web.config file associated with you application server.

2. Locate the system.serviceModel/behaviors/serviceBehaviors/behavior/serviceCredentials element.

3. Add a serviceCertificate element to the serviceCredentials element. The serviceCertificate element looks
like the following:
<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.

Epicor ERP | 10.2.700 53


Enable HTTP Endpoints Epicor WCF Services Developer Guide

Enable HTTP Endpoints

If you need clients to connect to your Epicor application server using HTTP, configure endpoint bindings in your
web.config file so they use this method of transport. The following topics describe how you enable HTTP endpoints.

Update the Web.Config File

This section describes how to configure the BasicHttpBinding to use the https, the WSHttpBinding to use plain
http secured through X509 certificates and HttpsTextEncodingAzureChannel to work with Azure Active Directory
tokens.

1. Open the web.config for your application server in Notepad or a similar text editor.

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>

3. Locate the system.serviceModel/protocolMapping section and add or verify your endpoints:


<remove scheme="https" />
<add scheme="https" binding="basicHttpBinding" bindingConfiguration="B
asicHttp" />
<remove scheme="http" />
<add scheme="http" binding="wsHttpBinding" bindingConfiguration="SOAPH
ttp" />

54 Epicor ERP | 10.2.700


Epicor WCF Services Developer Guide Enable HTTP Endpoints

<remove scheme="https" />


<add scheme="https" binding="customBinding" bindingConfiguration="Http
sTextEncodingAzureChannel" />

4. Save the web.config file.


Note Only one binding per scheme is possible, in order to support more bindings, a separate AppServer
needs to be created.

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.

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.

Epicor ERP | 10.2.700 55


Index Epicor WCF Services Developer Guide

Index
A P
azureadauth, example #3 43 prerequisites 4
program class, example #1 11
program class, example #2 27
B program class, example #3 36
basichttpbinding 55
S
C ssl set up 49
conventions 4 ssl set up, connect application server 53
ssl set up, site binding 49

H
T
helper classes, example #1 8
helper classes, example #2 22 tcp transport, when to use 5
helper classes, example #3 33 token header, server request, example #3 48
http endpoints, enable 54
http endpoints, web.config 54 V
http transport 6
http transport, when to use 5 visual studio project, example #1 6
httpstextencodingazurechannel 55 visual studio project, example #2 20
visual studio project, example #3 32
M
W
main method, example #1 14
main method, example #2 30 wcf header class, example #3 42
main method, example #3 37 wcf services example #1 6, 7, 8, 11, 14
main method, full code example 18, 40 wcf services example #2 20, 21, 22, 27, 30
wcf services example #3 32, 33, 36, 37, 42, 43, 48
web services references, example #1 7
N web services references, example #2 21
nuget package, example #3 32 web services references, example #3 32
wshttpbinding 55

56 Epicor ERP | 10.2.700


Additional information is available at the Education and
Documentation areas of the EPICweb Customer Portal. To access
this site, you need a Site ID and an EPICweb account. To create an
account, go to http://support.epicor.com.

You might also like