Full Disclosure: I’m one of the authors of the Celigo ServiceManager for NetSuite library mentioned in this post.
A lot of my team’s development tasks revolve around SuiteTalk WebServices API. Because of this we’ve always maintained wrapper library around SuiteTalk, called the Celigo Service Manager. And we’ve been quite active in keep it open source and (reasonably) up to date. Historically, this library was meant primarily for monolithic web or desktop applications built on the full .NET Framework. In such applications, the library was useful for it’s built in retry logic, connection pooling capabilities and for the overall unified API it presented.
Recently, we’ve ported this concept over to .NET Standard and have been quite actively releasing updates via the repository as well as NuGet. The library is not “officially” supported by Celigo, Inc. but follows more of an open development process. This blog post should serve as a primer in to why and how to use the Celigo ServiceManager for NetSuite.
Using VisualStudio Generated Stubs and Proxies
First off, here’s why you should consider using the library as opposed to just using the stubs generated by VisualStudio.
SuiteTalk WSDL doesn’t play nice with WCF Connected Services
There are few running issues with SuiteTalk and WCF Connected Service code generator. Few of them I’m aware of are:
- Web Service Reference Provider error: There was a problem reading the MetadataSet argument
- Unable to Import WSDL – Metadata contains a reference that cannot be resolved
- “Operation not Supported on this Platform” error attempting to make a request to a Web Service
- WCF Connected Service – Base class properties are not deserialized
The new Request/Response Wrappers
If you do manage to get the code to generate (using dotnet-svcutil for example), you’ll find that the generated client (NetSuitePortTypeClient
), have method signatures that look different from that of the concise ASMX style proxies that VisualStudio used to generate.
For example, it used to be that you could pre-configure your client with the user credentials and preferences, and simply call add(Record record)
to add a single record to NetSuite. The response would then contain a single WriteResponse
with the status
and a BaseRef
holding the Internal ID that was assigned to the record that was created.
In the new Connected Service proxy will instead hold a method like this:
Task<addResponse> addAsync(addRequest request)
Inspecting the addRequest
class you’d find that it expects the SOAP Header fields to be set in this request object along with the actual data you want to upload. Similarly, addResponse
wraps the WriteResponse
along with a documentInfo
property that would hold the JSESSIONID that you don’t really need when using request-level authentication.
Using the Celigo ServiceManager for NetSuite
The first obvious advantage of using the library is that you don’t need to go through the process of generating the stubs and proxies: Install the nuget package and you’ll find all the WSDL types under the SuiteTalk
namespace. Note that, at the time of this writing we are supporting SuiteTalk 2017.2. Our next major update will, likely be to target 2018.2.
Install-Package Celigo.ServiceManager.NetSuite
Note that if you don’t really care for the ServieManager functionality, and only need the stubs, you can just install our base package for SuiteTalk 2017.2:
Install-Package Celigo.SuiteTalk.2017_2
The INetSuiteClient Interface
When making WebService requests, you’d be working with the INetSuiteClient interface. It contains “wrapper” methods around the SuiteTalk API methods, exposing to you similar method contracts you had with the old ASMX style proxies. You’ll find the the full definition of this interface on GitHub.
Even with our legacy ServiceManager, something that we’ve been doing is to augment the VisualStudio generated WSDL types with custom interfaces. This of course makes a lot of sense to us, because of the type of dynamic applications we build, we require as much metadata as possible about the types that we are working on and adding these augmentations at compile time allows you to write more reusable code and not have to resort to runtime checks, especially those involving reflection. We’ve kept these augmentations open sourced hoping that they would prove useful to anyone else as well.
The custom interfaces we’ve added include:
ISearch
andISearch<T>
types that are applied toSearchRecord
types. Similarly, there areISearchAdvanced<>
,ISearchAdvancedRow
,ISearchColumnField
etc. With these types, you get write more concise reusable code. E.g.// Old Code var oldStyle = new TaskSearchAdvanced { columns = new TaskSearchRow { basic = new TaskSearchRowBasic { startDate = new[] { new SearchColumnDateField { customLabel = "startDate" } }, status = new[] { new SearchColumnEnumSelectField { customLabel = "status" } } } } }; // Becomes... var search = new TaskSearchAdvanced().CreateColumns( columns => columns.CreateBasic( b => b.SetColumns(new[] { nameof(b.startDate), nameof(b.status) }) ));
IReference
that is applied to allBaseRef
derived types such asRecordRef
,CustomRecordRef
,CustomTransactionRef
etc. The interface enable you to set or retrieve the Internal ID or External ID even if all you have is a reference to aBaseRef
without then doing an upcast or even a type check.
Making Requests to NetSuite
You can find the sample usage of ServiceManager
in the unit test project. In short, you need 2 things:
- An Application ID, that you’d generate on NetSuite by going to
Setup > Integration > Manage Integrations > New - A class that implements the
IPassportProvider
interface orITokenPassportProvider
interface. You can find implementations of these interfaces using that read the required data from environment variables in the unit tests folder.
Everything else is pretty straightforward.
var factory = new ClientFactory(config.ApplicationId); client = factory.CreateClient(config.PassportProvider); var serverTimeResult = await client.getServerTimeAsync(); serverTimeResult.status.isSuccess.Should().BeTrue(); serverTimeResult.serverTime.Year.Should().Be(DateTime.Now.Year);
Thanks Sameera,
I have this running in an Azure Function (Azure Function 2 with .Net Core 2.1) and had to make the following changes to the .csproj file to get it to run on my local system. (I think this is because of bugs in Azure functions)
”
Always
”
However, when I publish this to Azure and run it there I get a very cryptic “Could not load the specified file.” error.
I’m not sure if you have tried this or know a workaround but if you do let me know.
Thanks so much for making this available!
Many thanks, Stuart
LikeLike
Sorry can’t post the changes to the ,csproj file for some reason.
Had to set the Target Name ‘System.Http.WinHttpHandler.dll’ and ‘System.Private.ServiceModel.dll’ to copy to outputPath\bin for this to work.
Stuart
LikeLike
Hi Sameera,
Thanks heaps for this, I now have this working with Azure Functions but I am now getting the following timeout message.
System.TimeoutException: The HTTP request to ‘https://webservices.netsuite.com/services/NetSuitePort_2018_2’ has exceeded the allotted timeout of 00:01:00 while reading the response. The time allotted to this operation may have been a portion of a longer timeout.
I am logging in using user credentials and can’t see how to extend the timeout.
Can you let me know how to do this?
Kind regards,
Stuart Barnaby
LikeLike
Stuart,
Apologies for not seeing this in time. I hope you sorted this out.
For others looking for the same answer, following should work to increase the time out:
LikeLike
Hi Sameera,
I am trying to consume the soap web services of netsuite in .net core 2.2. While trying to call a method provided in the reference.cs, the method throws error : : ‘The remote server returned an unexpected response: (410) Gone.’
Here’s what I have tried :
Can you please tell me what is causing this issue?
LikeLike
Hi Prasanna,
I believe this has to do with the
getListAsync
overload method that you are using. Firstly, you should not use both passport and tokenPassport parameters. One of them should be null.Also, please note that we’ve stopped using the NetSuitePortTypeClient class directly because similar issues that we’ve run in to. Which is one the primary reasons why the ServiceManager library exists. If you look at the library, you’ll see that we’ve hidden away these passport, partnerInfo etc parameters and use alternative means of creating the SOAP header.
LikeLike
Do I need an Celigo subscription to be able to use this? Or this is complete open source?
LikeLike
Carlo,
It is completely open source and free to use. No Celigo subscription required.
LikeLike
Hi Sameera, We are loving your NetSuite libraries. They make talking to NetSuite from C# a lot easier. I am currently working on a project that needs additional NetSuite expertise. I was curious if you (or someone you would recommend) have availability to consult with us. Thanks!
LikeLike
Sorry for the (very) late reply Andrew. The CloudExtend team is focused on product development and doesn’t engage in consultation projects. Anything specific to the library could be answered in the Github issue tracker.
https://github.com/cloudextend/contrib-netsuite-servicemgr/issues
LikeLike
Do you have any examples on how to add a custom transaction using SOAP and c#?
LikeLike
Hi Mike,
Can you create a Github issue? Someone in the dev team will pick it up.
Use this link: https://github.com/cloudextend/contrib-netsuite-servicemgr/issues
LikeLike