Acumatica Web Services API Login - c#

I am attempting to perform some basic integration using Acumatica's web services. Unfortunatly, I'm having problems logging in. According to their documentation, this process should look something like:
apitest.Screen context = new apitest.Screen();
context.CookieContainer = new System.Net.CookieContainer();
context.AllowAutoRedirect = true;
context.EnableDecompression = true;
context.Timeout = 1000000;
context.Url = "http://localhost/WebAPIVirtual/Soap/APITEST.asmx";
LoginResult result = context.Login("admin", "E618");
Simple enough. However, after creating and importing a WSDL file from Acumatica into Visual Studio, I found I don't have a Screen object. I do, however have a ScreenSoapClient object, which has a similar Login() method.
ScreenSoapClient context = new Acumatica.ScreenSoapClient("ScreenSoap");
LoginResult result = context.Login("username", "password");
That part works. In fact, the LoginResult give me a session ID. However, if I try to make any calls to the service, such as:
CR401000Content cr401000 = context.CR401000GetSchema();
I get an error: System.Web.Services.Protocols.SoapException: Server was unable to process request. ---> PX.Data.PXNotLoggedInException: Error #185: You are not currently logged in.
While the version of Acumatica we're using does appear to be slightly newer, I'm unsure why the Screen() object isn't available. Consequently, if I try a bad username/password, Login() does fail (as it should). From what I can the tell, the ScreenSoapClient class is using service model details from web.config, so it's getting the endpoint address and other details there.
Is there something I'm missing or doing wrong?

As i see, you use WCF to create your service reference.
So you should enable cookies in service binding:
var binding = new BasicHttpBinding()
{
AllowCookies = true
};
var address = new EndpointAddress("http://localhost/WebAPIVirtual/Soap/APITEST.asmx");
var c = new ServiceReference1.ScreenSoapClient(binding, address);
Or, you can use old asmx web service reference (http://msdn.microsoft.com/en-us/library/bb628649.aspx).
Then everything will be same as in Acumatica`s documentation.

As noted in a comment above, I was able to make contact with a representative from Acumatica. He had me remove then recreate the service references in our project and try again. That apparently did the trick and the "Error #185: You are not currently logged in" error went away.

Related

Generated WCF SOAP client uses current user for windows authentication instead of given credentials

I'm kind of new to the whole WCF and SOAP topic so please be kind.
I'm using a generated SOAP Client with .net6. In another project we successfully worked with the same Web Service using the old .net Framework 2.0 Web References and the same credentials.
Strange enough everything seemed to work fine at first. Until I realized, that it does not use the given credentials to authenticate. Instead it authenticates with my own domain user.
I also tried to get it to work with explicitly setting the binding with a BasicHttpBinding but I only could get the same broken logic to work or I got various authentication/protocol/security errors.
So it seems the authentication is basically working. It just doesn't use the provided credentials. So my question is: How can I configure it to work with the provided identity?
I also found out that it might have anything to do with a cached Windows token. But how can I get rid of it. How to prevent caching in the first place?
EDIT:
Specified the variable types explicitly.
string url = "http://someServer/AdministrationService.asmx";
AdministrationServiceSoapClient client = new AdministrationServiceSoapClient(
AdministrationServiceSoapClient.EndpointConfiguration.AdministrationServiceSoap,
url);
WindowsClientCredential credential = client.ClientCredentials.Windows;
credential.ClientCredential.UserName = "username";
credential.ClientCredential.Password = "password";
credential.ClientCredential.Domain = "DOMAIN";
GetServerInfoRequest getServerInfoRequest = new GetServerInfoRequest
{
// some stuff set here
};
GetServerInfoRequest getServerInfoReply = await client.GetServerInfoAsync(getServerInfoRequest);
As far as I know, BasicHttpBinding has security disabled by default, but can be added setting the BasicHttpSecurityMode to a value other than None in the constructor. It can be configured according to the instructions in BasicHttpBinding and BasicHttpBinding Constructors.
By default, setting up client credentials involves two steps: determining the type of client credential required by the service and specifying an actual client credential, as described in this document.
After waiting a day it is working. It seems that the cached credentials became invalid somehow.
Strange enough the simple service creation from above is not working anymore. Instead I have to use the following.
var client = new AdministrationServiceSoapClient(
new BasicHttpBinding()
{
Security = new BasicHttpSecurity()
{
Mode = BasicHttpSecurityMode.TransportCredentialOnly,
Message = new BasicHttpMessageSecurity()
{
ClientCredentialType = BasicHttpMessageCredentialType.UserName,
},
Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.Windows,
ProxyCredentialType = HttpProxyCredentialType.Windows,
}
},
},
new EndpointAddress(url));

SOAP error when connecting to NetSuite web services: "Namespace prefix ' soapenv' not defined"

I am getting the following error when connecting to a NetSuite production account, through the Suitetalk API:
I don't have problems connecting to the Sandbox account for this client. I am connecting through a C# WCF project. I don't believe the problem is with the c# project, since this code is being used in Production with many other clients.
It seems to me like the SOAP message being returned is incorrectly formatted - there seems to be a line break before the 'soapenv' element in the SOAP message. I am getting this error when creating a "get" request against the API(using passport login). This error occurs on any API call though, I did try simply logging in through the API as well.
I have double checked the login details and account information for this client and everything seems in orders. Besides, if this information is incorrect, I should be getting authentication errors - not malformed SOAP messages.
Any help will be appreciated, thanks!
It turns out that I needed to use the webservices.na3.netsuite WSDL. I was under the impression that the regular "webservices.netsuite" WSDL would direct any requests to the correct server.
So when connecting to a NetSuite account through SuiteTalk, be sure to make use of the correct WSDL and specify the correct endpoint along with your login credentials. You can check which server your account is hosted on by looking at the URL when logged into your NetSuite account.
Update
I made use of the newest 'DataCenterAwareNetSuiteService' class to dynamically get the correct data center for the current account that I am trying to connect to:
class DataCenterAwareNetSuiteService : NetSuiteService
{
private System.Uri OriginalUri;
public DataCenterAwareNetSuiteService(string account, bool doNotSetUrl)
: base()
{
OriginalUri = new System.Uri(this.Url);
if (account == null || account.Length == 0)
account = "empty";
if (!doNotSetUrl)
{
//var temp = getDataCenterUrls(account);
DataCenterUrls urls = getDataCenterUrls(account).dataCenterUrls;
Uri dataCenterUri = new Uri(urls.webservicesDomain + OriginalUri.PathAndQuery);
this.Url = dataCenterUri.ToString();
}
}
public void SetAccount(string account)
{
if (account == null || account.Length == 0)
account = "empty";
this.Url = OriginalUri.AbsoluteUri;
DataCenterUrls urls = getDataCenterUrls(account).dataCenterUrls;
Uri dataCenterUri = new Uri(urls.webservicesDomain + OriginalUri.PathAndQuery);
this.Url = dataCenterUri.ToString();
}
}
The above is called like so:
new DataCenterAwareNetSuiteService("*account number*", false);
With the latest version of NetSuite, some changes have been made to URLs. For instance, now you can have more than one SandBox URL. Because of this, the URL format has changed. The account number used when authenticating is also now different. For sandboxes the account Id is now passed up as ACCOUNTNUMBER_SANDBOXID, for example 12345678_SB1.
You can determine the URLs for the SOAP and REST services by using the datacenterurls endpoint and supplying the account # you would like to determine the URLS for.
https://rest.netsuite.com/rest/datacenterurls?account=YOUR_ACCOUNT_NUMBER
The functionality below is based on the answer from #Charl above.
I have made a couple changes below that provides the same functionality without using inheritance.
This may be a simpler implementation for a newer programmer who does not know how to use an inherited class.
var accountId = "1234567"; // Insert your account ID here
var Service = new NetSuiteService();
Service.Url = new Uri(Service.getDataCenterUrls(accountId).dataCenterUrls.webservicesDomain + new Uri(Service.Url).PathAndQuery).ToString();

CRM Context Object persists connection even when creating new instance

I have a system in place for a WCF Service, in which I take in some credentials from the client. I then try to authenticate with CRM using these credentials. If the authentication fails, I use a pre-defined service account, with the credentials stored in web.config.
What I have found is, no matter what, the first set of credentials used persists for any further requests, no matter how much I tear down the first object. I even instantiate new objects, wrap each context in a using statement, etc.
I have watered the code down into a simple 'connect, retry' block, and this suffers the same issue. The code is as follows:
try
{
var connection = new CrmConnection();
connection.ServiceUri = new Uri("https://my.crm.dynamics.com/");
connection.ClientCredentials = new ClientCredentials();
connection.ClientCredentials.UserName.UserName = "removed1";
connection.ClientCredentials.UserName.Password = "removed1";
using (var crm = new CrmOrganizationServiceContext(connection))
{
var req = new Microsoft.Crm.Sdk.Messages.WhoAmIRequest();
var resp = (Microsoft.Crm.Sdk.Messages.WhoAmIResponse)crm.Execute(req);
}
}
catch (Exception ex) { }
try
{
var connection = new CrmConnection();
connection.ServiceUri = new Uri("https://my.crm.dynamics.com/");
connection.ClientCredentials = new ClientCredentials();
connection.ClientCredentials.UserName.UserName = "removed2";
connection.ClientCredentials.UserName.Password = "removed2";
using (var crm = new CrmOrganizationServiceContext(connection))
{
var req = new Microsoft.Crm.Sdk.Messages.WhoAmIRequest();
var resp = (Microsoft.Crm.Sdk.Messages.WhoAmIResponse)crm.Execute(req);
}
}
catch (Exception ex) { }
Assume that removed1 is incorrect and removed2 is correct. The second call will fail instantly with a token exception, saying invalid credentials. If removed1 is correct, and removed2 is not, the first will authenticate and get the WhoAmIRequest fine. Then, removed2 should fail, but it does not, as it seems to still hold the connection using the old credentials. The invalid credentials still allows the service to make requests. Not good!
The bizarre thing is, the code for the authentication is in a separate project. I have included this project in a simple console application, and everything works fine. I can only assume this is something to do with the WCF service and the way it holds connections. I've tried manually disposing, calling garbage collection, setting to null, etc. I've also tried using web config connection strings called by name (hard coded 2 test ones), tried manually creating the connection string settings with unique names, using CrmConnection.Parse(), etc.
I have even copy pasted the code i'm using directly into a console application, and it works fine. Due to this, I am convinced it is to do with the behavior of a WCF service, and not the code itself. I set the class to have the behavior of
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
But no luck. If it is of any importance, this code is running in a message inspector class which implements IDispatchMessageInspector.
How can I ensure that I can get a fresh session? Thanks.
You are using the default constructor of the CrmConnection class. When doing that, your connection is cached by name. This name is supposed to be the name of the ConnectionStringSettings, but using this constructor that property is never being set and keeps its default value, thus always returning the first connection object created.
Just use another overload of the constructors, e.g. that using a connection string or accepting the service url, credentials etc.
The CrmConnection class was designed to offer an easy way to create connectionstrings in config files, similar to database connection strings. It had its issues and has been removed from the Dynamics CRM 2016 SDK.

Exchange Web Services Autodiscover non default link

I am writing a piece of software that runs on a utility device on a customers network, but not on the domain. The autodiscover service is not available off domain the same as it is either on the domain or even on the internet. None of the ways the service works by default will find it according to the docs, but the customer's IT staff tells me, supposedly :/ , it will all work if I can access Autodiscover at the link they gave me. Is there any way to override the default approach and pass it this url to autodiscover from? Hardcoding the link to /exchange.asmx is not an option nor is adding this device to the domain.
I am reusing, and now tweaking, a tried and true piece of software that has been deployed many times, but this situation is a first.
Using the EWS Managed API you may be able to do it using the AutodiscoverService class. It has a constructor that takes the URI of the Autodiscover service as a parameter.
Your code should look something like this. Note that I disable SCP lookup as you are not on a domain. I have not actually tried this code but give it a try:
AutodiscoverService ads = new AutodiscoverService(new Uri("..."));
ads.EnableScpLookup = false;
ads.Credentials = new NetworkCredential(...);
ads.RedirectionUrlValidationCallback = delegate { return true; };
GetUserSettingsResponse grResp = ads.GetUserSettings("someemail#domain.com", UserSettingName.ExternalEwsUrl);
Uri casURI = new Uri(grResp.Settings[UserSettingName.ExternalEwsUrl].ToString());
var service = new ExchangeService()
{
Url = casURI,
Credentials = ads.Credentials,
};

PayPal SOAP API - Version is not supported

I use .Net, and Visual Studio 2010.
I downloaded the WSDLs and corrected the maxOccurs error, and adding the WSDL as a Web Reference works just fine.
The problem occurs when I call SetExpressCheckout. The error simply says, Version is not supported. I have checked the version of the WSDL, which is 76.0 - and should be correct AFAIK.
The Web Service endpoint being used is this: https://api.sandbox.paypal.com/2.0/
Is there anywhere I need to specify the version, or is the service endpoint being used wrong?
If anyone has the same problem, you need to specify the version:
PayPalAPIAASoapBinding api = new PayPalAPIAASoapBinding();
// Service Provider's API Credentials
api.RequesterCredentials = new CustomSecurityHeaderType();
api.RequesterCredentials.Credentials = new UserIdPasswordType();
api.RequesterCredentials.Credentials.Username = this.Username;
api.RequesterCredentials.Credentials.Password = this.Password;
api.RequesterCredentials.Credentials.Signature = this.ApiSignature;
// The merchant's PayPal e-mail address (3rd party authentication)
api.RequesterCredentials.Credentials.Subject = this.CustomerId;
SetExpressCheckoutReq req = new SetExpressCheckoutReq();
req.SetExpressCheckoutRequest = new SetExpressCheckoutRequestType();
req.SetExpressCheckoutRequest.SetExpressCheckoutRequestDetails = new SetExpressCheckoutRequestDetailsType();
req.SetExpressCheckoutRequest.Version = "74.0";
Right now the Sandbox runs in v 74.0, and production in 76.0. PayPal doesn't always run same versions across their environments.

Categories