Invoking a web service from a CRM 2011 plugin - c#

I have created a plugin that invokes an AX custom web service.
The web service should return a price given a product and a customer.
I am able to invoke the web service without problems outside CRM, but after including it in the plugin it stopped working.
The error message I get is:
Could not find default endpoint element that references contract 'AxIntegrationServices.PriceDiscService' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element.
Here is the code:
//retrieve the entity product as the input Entity
var entity = (Entity)context.InputParameters["Target"];
//Early bound entity
var oppProduct = new opportunityproduct(entity);
var quantity = (Decimal)oppProduct.quantity;
tracingService.Trace("Retrieving Opp with opp ID = {0}", oppProduct.opportunityid.Id.ToString());
//get the early bound opportunity containing the opportunity product
var opp = new opportunity(Helper.ActualEntity(oppProduct.opportunityid, service));
//get the early bound account entity that is the customer for the opportunity
tracingService.Trace("Retrieved, type = {0}", opp.name);
tracingService.Trace("Retrieving Account with accountID={0}", opp.customerid.Id.ToString());
Entity acc = Helper.ActualEntity(opp.customerid, service);
tracingService.Trace("Account retrieved");
var account = new account(acc);
//get the ax integration key for the account
tracingService.Trace("Retrieving Account AX key");
var accountAxKey = account.custom_axrecordid;
tracingService.Trace("Retrieving Product");
//get the early bound account entity that is the customer for the opportunity
var product = new product(Helper.ActualEntity(oppProduct.productid, service, new string[]{ "custom_axrecordid" }));
//get the integration key for the product
tracingService.Trace("Retrieving Product AX key");
var productAxKey = product.custom_axrecordid;
tracingService.Trace("Invoking web service");
PriceDiscServiceClient priceDiscServiceClient = new PriceDiscServiceClient();
CallContext callContext = new CallContext();
priceDiscServiceClient.ClientCredentials.Windows.ClientCredential.UserName = "xxx";
priceDiscServiceClient.ClientCredentials.Windows.ClientCredential.Password = "yyyy!";
priceDiscServiceClient.ClientCredentials.Windows.ClientCredential.Domain = "aaa";
PriceDiscServiceContract priceDiscServiceContract = priceDiscServiceClient.getPriceDiscSales(callContext, productAxKey, accountAxKey, quantity);
tracingService.Trace("Price :{0}",priceDiscServiceContract.Price);
tracingService.Trace("Markup :{0}", priceDiscServiceContract.Markup);
tracingService.Trace("PriceUnit :{0}", priceDiscServiceContract.PriceUnit);
tracingService.Trace("DiscAmount :{0}", priceDiscServiceContract.DiscAmount);
tracingService.Trace("DiscPct :{0}", priceDiscServiceContract.DiscPct);
oppProduct.priceperunit = priceDiscServiceContract.PriceUnit;
oppProduct.isproductoverridden = false;
oppProduct.ispriceoverridden = true;
The web service is located in the same network of the CRM environment and I am working through a VPN to connect to them.
Any ideas?

You should check your PriceDiscServiceClient constructor - it should accept a Binding and EndpointAddress so your code could look something like this:
//...
BasicHttpBinding binding = new BasicHttpBinding();
// configure Binding as needed (Timeout, etc.) ...
EndpointAddress endpoint = new EndpointAddress(endpointUri);
PriceDiscServiceClient client = new PriceDiscServiceClient(binding, endpoint);
//...
As James Wood already pointed out the next problem will be to populate endpointUri with a configurable value instead of hardcoding it into your Plugin.
I tend to prefer the plugin unsecure configration rather than roundtrip to a crm settings record for every time the plugin executes.
The link James Wood refers to is exactly the solution I'd choose to configure the endpoint address Uri.

If you code relies on configuration in the app.config as Filburt suggested then this approach is unlikely to work. When you add your plugin assembly to MSCRM the app.config is not included (its in a separate configuration file).
You wont be able to add any of the configuration in the app.config to the CRM app.config (because its not supported).
I would suggest whatever you are doing on the app.config moving into code within the plugin itself. Anything you are doing in the app.config you should be able to do in code as well.
If you need to retrieve settings values (e.g. connection strings) you might want to consider using a settings record in CRM and retrieving that information. Or alternatively using the plugins configuration section.

Related

TFS configuration policy creation using API in C#

I'm trying to create a new branch policy using Microsoft.TeamFoundation.Policy.WebApi Library.
my Code is:
variable info:
json - contains the setting in the policy configuration
connection - is the VSSConnection to our TFS server
TFSProject - is the project name in the TFS
the error i get the is not showing anything in my searches, i would appreciate some examples for how to create a new policy in TFS
code:
var json = "{\"statusName\": \"" + StatusNameForBlock + "\",\"statusGenre\": \"ci\",\"authorId\": null,\"invalidateOnSourceUpdate\": false,\"policyApplicability\": null,\"scope\": [{\"refName\": \"refs/heads/master\",\"matchKind\": \"Exact\"}]}";
JObject jToken = new JObject(JObject.Parse(json));
var newPolicy = new PolicyConfiguration();
var policyType = new PolicyTypeRef();
policyType.Id = Guid.NewGuid();
newPolicy.Type = policyType;
newPolicy.Settings = jToken;
var gitPolicyHttpClient = connection.GetClient<PolicyHttpClient>();
var policyCreated = gitPolicyHttpClient.CreatePolicyConfigurationAsync(newPolicy, TFSProject).Result;
exception: VssServiceException: Type with id '98813712-70a4-4937-b139-9a3654c9795f' does not exist
You could use Rest API to create a new branch policy.
POST https://{instance}/{collection}/{project}/_apis/policy/configurations/{configurationId}?api-version=5.0
Use these APIs to define policies for your projects. Configurations associate a type, such as "Required reviewers", with specific settings, such as "For pull requests with files named *.dll targeting the master branch in the xxx Git repository, add the Source-Controlled Binaries Team as a required reviewer".
Policy Examples for your reference.
For more details, you could also take a look at this blog-- Configuring standard policies for all repositories in Azure Repos

Programmatically implement WCF with Certificate

I am quite new to WCF and trying to get my head around the security. I am still reading and learning, but I came to a point where I got a working version of WCF with Certificate authentication. I know that the code has some weaknesses; however, my initial goal was to create communication using certificate authentication. Also, I wanted to create everything programmatically (no Web.config configurations for the services or clients). The reason for this is that the client should be able to link an Assembly (Class Library) and get access to the server. Also, I am loading the certificates from the file system (again, I know this is not secure). I would like to get a little bit feedback.
The following client snippet is creating an object that I can use to connect to the server. The anonymous type T is my service interface e.g. IService.
Here is my client implementation:
var url = "URL TO WS";
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.Message,
Message = {ClientCredentialType = MessageCredentialType.Certificate}
}
};
var endpoint = new EndpointAddress(url);
var channelFactory = new ChannelFactory<T>(binding, endpoint);
if (channelFactory.Credentials != null)
{
channelFactory.Credentials.ClientCertificate.Certificate =
new X509Certificate2(#"PATH\TO\Client.pfx"); // Client Certificate PRIVATE & PUBLIC Key
channelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None; // I know this is not good, but I dont have a valid certificate from a trusted entity
}
wcfClient = channelFactory.CreateChannel();
return wcfClient;
The service is a bit more complex. I use .svc files with their code-behind. If I understand the use of .svc files correctly, then I believe this is the entry point where the .NET framework creates a ServiceHost and automatically opens it? In my implementation I do not open the ServiceHost, I only implemented a ServiceHostFactoryBase and referenced it in the .svc Markup language. Look at the Factory section - this is the part where I implement my custom Host Factory.
<%# ServiceHost Language="C#" Debug="true"
Service="Service.Services.LevelService" CodeBehind="LevelService.svc.cs"
Factory="Service.Security.ServiceHostFactory.HostFactory" %>
And my custom Host Factory looks like this:
public class HostFactory : ServiceHostFactoryBase
{
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var serviceType = Type.GetType(constructorString);
if (serviceType.GetInterfaces().Count() != 1)
throw new NotImplementedException("The service can only have one implemented interface");
var interfaceType = serviceType.GetInterfaces()[0];
var myServiceHost = new ServiceHost(serviceType, baseAddresses);
var httpBinding = new WSHttpBinding();
httpBinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;
httpBinding.Security.Mode = SecurityMode.Message;
myServiceHost.Credentials.ServiceCertificate.Certificate = new X509Certificate2(#"PATH\TO\Server.pfx");
myServiceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
myServiceHost.Credentials.ClientCertificate.Authentication.CustomCertificateValidator = new MyX509CertificateValidator();
myServiceHost.Credentials.ClientCertificate.Certificate = new X509Certificate2(#"PATH\TO\Client.cer");
myServiceHost.AddServiceEndpoint(interfaceType, httpBinding, String.Empty);
return myServiceHost;
}
}
The custom validator doess't do much yet, but here it is as well:
public class MyX509CertificateValidator : X509CertificateValidator
{
public override void Validate(X509Certificate2 certificate)
{
// Check that there is a certificate.
if (certificate == null)
{
throw new ArgumentNullException("certificate");
}
// Check that the certificate issuer matches the configured issuer.
//throw new SecurityTokenValidationException("Certificate was not issued by a trusted issuer");
}
}
If I understand correctly, the Server has ONLY the PUBLIC key of the client registered since I only reference the .cer file.
My big question is now, if I would like to get anything like this on a production server - and lets assume nobody will actually get the executables (including the certificates), would this be a possible solution to keep unwanted people out of my webservice? Basically, I don't want anybody else consuming my webservice - only if you have the proper certificate. Also, how much of an issue is the part where I set on the client:
CertificateValidationMode = X509CertificateValidationMode.None
I know there are many questions - but overall, I would like to know if I made some fundamental mistakes in this implementation.
Ok,
after going through a lot of tutorials and demo applications, I figured out that the best way to go ahead is actually using the Certificate Store on Windows. However, I still might consider a hybrid solution where the Server has the certificates in the Certificate store and the client has it embedded in a resource. If you are struggling with WCF and Certificates, have a look at those links:
IIS7 Permissions Overview - ApplicationPoolIdentity
I was able to create Transport as well as Message secured WCF web services. I would suggest to READ the linked articles because there is so much information that will make you understand certificates and their usage. Especially when dealing with self-singed certificates!
I ended up implementing wsHttpBinding using Message Security Mode + Client Certificate with ChainTrust.
Hope this will help someone else!

Acumatica Web Services API Login

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.

"Invalid ChangeSet : Id must be unique for each entry" error when inserting records from client using Ria Services SOAP endpoint

I have a Silverlight application that uses Ria Services. I now wish to run a different client application (console application) that will perform certain scheduled day-end operations on the server. To do this and avoid duplication I have decided to expose the Ria Services DomainContext as a Web Service using SOAP as described here and here so that I can re-use the entities and relationships as set up in Ria Services without having to duplicate it in my client.
In my client I have successfully added the Service Reference and I am successfully able to query the Web Service to get results back. I am also able to perform an "insert" statement for a single new database entry. Unfortunately, the moment I add more than one item to my set of entries to update I get an error "Invalid ChangeSet : Id must be unique for each entry".
The following is my code:
DomainServiceSoapClient service = new DomainServiceSoapClient();
List<DomainServices.ChangeSetEntry> changesToSave = new List<DomainServices.ChangeSetEntry>();
foreach (string name in myListOfNames)
{
Person newPerson = new Person() {Name = name};
DomainServices.ChangeSetEntry entry = new DomainServices.ChangeSetEntry { Entity = newPerson, Operation = DomainServices.DomainOperation.Insert };
changesToSave.Add(entry);
}
service.SubmitChanges(changesToSave.ToArray<DomainServices.ChangeSetEntry>());
The error occurs on the last line of the code. As a test, if I replace this last line with the following line (which means that I only have ONE entry in the array I am submitting) it works and I don't get the error.
service.SubmitChanges(new DomainServices.ChangeSetEntry[] {changesToSave.ToArray<DomainServices.ChangeSetEntry>()[0]});
How can I fix this so that SubmitChanges works when there is more than one entry I wish to submit?
you have to use like, I assume that Person is your table.
DomainServiceSoapClient service = new DomainServiceSoapClient();
foreach (string name in myListOfNames)
{
Person newPerson = new Person() {Name = name};
service.Person.Add(newPerson);
}
service.SubmitChanges();

How to have unique entity framework connection strings in each user configuration file

I have a situation when some clients see the server by its local IP and some by global. So that for some of them IP is 10.0.0.4 and for some 94.44.224.132. I use ClickOnce for deployment and Entity Framework to generate the DB mapping. Now ive connection string setting in my user settings section and for each user i store his own one. After that for entity context's construction i do the following:
SomeEntities context = new SomeEntities(new EntityConnection("metadata=res://*/DBModel.csdl|res://*/DBModel.ssdl|res://*/DBModel.msl;provider=System.Data.SqlClient;provider connection string=\"" + Properties.Settings.Default.ServerLocalConnectionString + "\""));
But there are some problems with Open/Close and Command execution after such approach. Is there some right way to store individual connection strings for every client and not overwrite them with deployment(ClickOnce is preferable)?
Found answer here.
EntityConnectionStringBuilder ecb = new EntityConnectionStringBuilder();
if (serverName=="Local")
{
ecb.ProviderConnectionString = Properties.Settings.Default.ServerLocalConnectionString;
}
else
{
ecb.ProviderConnectionString = Properties.Settings.Default.ServerConnectionString;
}
ecb.Metadata = "res://*/";
ecb.Provider = "System.Data.SqlClient";
SomeEntities context = new SomeEntities(new EntityConnection(ecb.ToString());
It seems this works. And now i can deploy the application and leave to the user the decision to which server does he want to connect and leave that configuration after updates, because its written in user's app.config.

Categories