Get calling assembly name inside WCF service - wcf

I'm trying to achieve the following:
whenever a call to service is performed I want to be able to identify the client.
I thought about getting the calling assembly name by iterating over stack trace
but I failed to get the client assembly name.
Sample code:
private List<System.Reflection.Assembly> GetCallingAssemblies()
{
List<System.Reflection.Assembly> assemblies = new List<System.Reflection.Assembly>();
StackTrace stackTrace = new StackTrace(0, true);
for (int i = 0; i < stackTrace.FrameCount; i++)
{
StackFrame stackFrame = stackTrace.GetFrame(i);
System.Reflection.MethodBase methodBase = stackFrame.GetMethod();
Type type = methodBase.ReflectedType;
System.Reflection.Assembly assembly;
if (type != null)
{
assembly = System.Reflection.Assembly.GetAssembly(type);
if (assemblies.Contains(assembly) == false)
{
assemblies.Add(assembly);
}
}
}
return assemblies;
}

I must be missing something: you're trying to identify the client through assemblies? Why not use authentication?
Besides, who says the client even has assemblies? It may be a Java client, or some other platform.

When your client calls a WCF service, all that goes between the two is the serialized message - the method to call and all the parameters to pass in.
There is no other connection at runtime between server and client. The server cannot "reach back" and look at the client - there is no connection.
All your service can look at is the serialized message, and any message headers. So if you really really need this (what do you need it for??) then you need to make sure the client puts a marker / identification of some sort as a message header into the call.

Related

Why does WCF ignore my TokenProvider?

I have a BizTalk WCF-Custom receive location to which I have added a custom behavior:
public class SasTokenProviderEndpointBehavior : BehaviorExtensionElement, IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(sharedAccessSecretName, sharedAccessKey);
bindingParameters.Add(new TransportClientEndpointBehavior { TokenProvider = tokenProvider });
}
}
}
parameter setup code omitted for brevity
This is adapted from a sample found at https://code.msdn.microsoft.com/How-to-integrate-BizTalk-07fada58#content - this author is widely respected in the BizTalk community and code of this kind has been in use for some years. All I am doing is adapting the method he uses, that is proven to work, to substitute a different TokenProvider.
I can see through debugging that this code runs and the TransportClientEndpointBehavior with correct parameters is added to the channel. However when the BizTalk receive location polls Service Bus, I see the following in the event log:
The adapter "WCF-Custom" raised an error message. Details "System.UnauthorizedAccessException: 40102: Missing authorization token, Resource:sb://[namespace].servicebus.windows.net/[queue]. TrackingId:452c2534-d3e6-400f-874f-09be324e9e11_G27, SystemTracker:[namespace].servicebus.windows.net:[queue], Timestamp:12/1/2016 11:38:56 AM ---> System.ServiceModel.FaultException: 40102: Missing authorization token, Resource:sb://[namespace].servicebus.windows.net/[queue]. TrackingId:452c2534-d3e6-400f-874f-09be324e9e11_G27, SystemTracker:[namespace].servicebus.windows.net:[queue], Timestamp:12/1/2016 11:38:56 AM
I cannot see any reason that the Azure Service Bus endpoint would return this error message except that because the token provider is not being used. Why would the channel ignore the TokenProvider and what do I have to do to pass the token correctly?
edit:
I have inspected the raw WCF message traffic for the port in question as well as one using the SB-Messaging adapter, which works as expected. The difference is that the SB-Messaging adapter's messages contain a SOAP header like:
<Authorization xmlns="http://schemas.microsoft.com/servicebus/2010/08/protocol/">SharedAccessSignature sr=[really long encoded string]</Authorization> and my custom binding port's messages do not. So it is true that the problem is a missing Authorization SOAP header; but the question persists - why isn't the channel adding this header?
edit #2:
I have decompiled Microsoft.ServiceBus.dll and I believe I've found the class that actually creates the WCF messsage, Microsoft.ServiceBus.Messaging.Sbmp.SbmpMessageCreator. It has this method:
private Message CreateWcfMessageInternal(string action, object body, bool includeToken, string parentLinkId, RetryPolicy policy, TrackingContext trackingContext, RequestInfo requestInfo)
{
Message message = Message.CreateMessage(this.messageVersion, action, body);
MessageHeaders headers = message.Headers;
headers.To = this.logicalAddress;
string sufficientClaims = this.GetSufficientClaims();
if (this.linkInfo != null)
{
if (!string.IsNullOrEmpty(this.linkInfo.TransferDestinationEntityAddress))
{
SecurityToken authorizationToken = this.GetAuthorizationToken(this.linkInfo.TransferDestinationEntityAddress, sufficientClaims);
if (authorizationToken != null)
{
SimpleWebSecurityToken webSecurityToken = (SimpleWebSecurityToken) authorizationToken;
if (webSecurityToken != null)
this.linkInfo.TransferDestinationAuthorizationToken = webSecurityToken.Token;
}
}
this.linkInfo.AddTo(headers);
}
if (includeToken)
{
ServiceBusAuthorizationHeader authorizationHeader = this.GetAuthorizationHeader(sufficientClaims);
if (authorizationHeader != null)
headers.Add((MessageHeader) authorizationHeader);
}
if (this.messagingFactory.FaultInjectionInfo != null)
this.messagingFactory.FaultInjectionInfo.AddToHeader(message);
if (!string.IsNullOrWhiteSpace(parentLinkId))
message.Properties["ParentLinkId"] = (object) parentLinkId;
if (trackingContext != null)
TrackingIdHeader.TryAddOrUpdate(headers, trackingContext.TrackingId);
MessageExtensionMethods.AddHeaderIfNotNull<RequestInfo>(message, "RequestInfo", "http://schemas.microsoft.com/netservices/2011/06/servicebus", requestInfo);
return message;
}
So thinking about it logically, there are two reasons the Authorization header would be missing:
includeToken is false (Why would this be so?)
GetAuthorizationHeader() returns null (Why?)
edit #3:
I have compiled and run the example code and this works. The only significant difference between my code and his is that mine includes a line which calls out to Azure Key Vault:
var kv = new KeyVaultClient(this.GetAccessToken);
var key = kv.GetSecretAsync(this.KeyVaultUri.AbsoluteUri, this.SharedAccessSecretName).Result;
var sharedAccessKey = key.Value;
var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(
this.SharedAccessSecretName,
sharedAccessKey);
bindingParameters.Add(new TransportClientEndpointBehavior { TokenProvider = tokenProvider });
This is an asynchronous method that returns a Task. Can it be that blocking on the result of this Task somehow doesn't do what would be expected in certain situations, and this is messing up the configuration of the WCF channel somehow? As I said, I am certain this code runs and assigns the TokenProvider. I am now merely not certain when it runs.
D'OH!
I had neglected to realise that the very old version of Microsoft.ServiceBus.dll we still have in the solution for interop with the (equally old) on premises version of Service Bus (Service Bus for Windows Server) was the one referenced by my project. For whatever reason this version just doesn't do what it's supposed to, and doesn't give any indication that it's bypassing the intended behaviour. Updating to have the current NuGet package for Service Bus fixes the problem.

Passing values in Header

We are consuming an external web service (WCF) in our AX2012 project. We followed the procedure described in the following blog.
We are implementing security by passing the token in the header. However, what i am not sure of is how to do this in AX2012.
the sample code for getting the token is
static void myTestServiceWSDL(Args _args)
{
myServiceWSDL.Proxies.Service.ServiceClient service;
myServiceWSDL.Proxies.Service.LoginData LoginData;
str token;
System.Exception ex;
System.Type type;
try
{
type = CLRInterop::getType('myServiceWSDL.Proxies.Service.ServiceClient');
service = AifUtil::createServiceClient(type);
LoginData = new myServiceWSDL.Proxies.Service.LoginData();
LoginData.set_uName("test");
LoginData.set_pwd("test");
token=service.Login(LoginData);
info(token);
}
catch(Exception::CLRError)
{
ex = CLRInterop::getLastException();
info(CLRInterop::getAnyTypeForObject(ex.ToString()));
}
}
The token comes back fine which confirms the code is working.
Now the question is how to do i set header values for the message.
If it was C# i would have done
using (MemberMasterClient proxy = new MemberMasterClient())
{
using (OperationContextScope scope
= new OperationContextScope(proxy.InnerChannel))
{
// set the message in header
MessageHeader header =
MessageHeader.CreateHeader("SourceApplication",
"urn:spike.WCFHeaderExample:v1",
"WCFClient Application 2");
OperationContext.Current.OutgoingMessageHeaders.Add(header);
Console.WriteLine("Membership Details");
Console.WriteLine("Henry's - {0}", proxy.GetMembership("Henry"));
}
}
}
Could any one let me know how to do the equivalent in X++
One idea which has been on my mind is to write an assembly in C# which can then be called in AX2012. Will give that a go, but the idea is to code this in X++ in AX2012
The only thing you do differently in X++ is creating the proxy using the Aif utility. So basically, your C# example you listed, the only difference would be the proxy = new MemberMasterClient() which goes through AIF. All the other code you can take into X++ as-is (except for the "using"). You just need to have the right assemblies reference in the AOT, and use the full namespace in the code.
Alternatively, as you mentioned, you can just code it all in C# and call that from AX :-)

Issue while running web service using batch job?

I have consumed a web service using visual studio and used managed code to call that in AX 2012.
Now if I am running the code in a simple job as:
static void CurrencyService(Args _args)
{
CurrencyConvert.Currency_Convert.CurrencyServiceClient convertcurrency;
CurrencyConvert.Currency_Convert.Currency currency;
System.ServiceModel.Description.ServiceEndpoint endPoint;
System.Type type;
System.Exception ex;
str s1;
try
{
type = CLRInterop::getType('CurrencyConvert.Currency_Convert.CurrencyServiceClient');
convertcurrency = AifUtil::createServiceClient(type);
endPoint = convertcurrency.get_Endpoint();
// endPoint.set_Address(new System.ServiceModel.EndpointAddress("http://localhost/HelloWorld"));
currency = convertcurrency.GetConversionRate(CurrencyConvert.Currency_Convert.CurrencyCode::AUD,CurrencyConvert.Currency_Convert.CurrencyCode::INR );
info(strFmt('%1', CLRInterop::getAnyTypeForObject(currency.get_Rate())));
}
catch(Exception::CLRError)
{
ex = CLRInterop::getLastException();
info(CLRInterop::getAnyTypeForObject(ex.ToString()));
}
}
Above job is working fine and producing results in a infolog.
Now, if a same piece of code is written under a class for batchjob(extending Runbasebatch class) as we normally do for any batch job, it is throwing an error as:
Microsoft.Dynamics.Ax.Xpp.ErrorException: Exception of type
'Microsoft.Dynamics.Ax.Xpp.ErrorException' was thrown.
at Dynamics.Ax.Application.BatchRun.runJobStatic(Int64 batchId) in
BatchRun.runJobStatic.xpp:line 38
at BatchRun::runJobStatic(Object[] )
at
Microsoft.Dynamics.Ax.Xpp.ReflectionCallHelper.MakeStaticCall(Type
type, String MethodName, Object[] parameters)
at BatchIL.taskThreadEntry(Object threadArg)
Other batch jobs except which used web services are working properly.
I have already tried many things such as : RunOn property of a class is set as "server" etc.
This is the case with each web service we have consumed.
Does anybody have a proper solution for this??
I am assuming that this is the same as on the Dynamics Ax community site post. So reading there, the error is not related to batch but to the following: "Could not find default endpoint element that references contract 'Currency_Convert.ICurrencyService' in the ServiceModel client configuration section.
This is because the endpoint is being searched in the AX32.exe.config file and this is not the one you need. You need to get it from the config file associated with your DLL.
To do this, you need to construct you client differently in AX. You need to use the AIF util because that way, the right config is used. Example:
type= CLRInterop::getType('DynamicsAxServices.WebServices.ZipCode.USAZipCodeServiceRef.PostalCodeServiceClient');
postalServiceClient = AifUtil::createServiceClient(type);
Apart from that, there is also an extra thing to whatch for. Separate environments would require different URL's and this can be solved by manually specifying your endpoint address and let it use a system parameter. (that way you can specify different configurations for DEV/TEST/PROD) (Note: below the endpoint address is hard coded and that should be a parameter)
static void Consume_GetZipCodePlaceNameWithEndPoint(Args _args)
{
DynamicsAxServices.WebServices.ZipCode.USAZipCodeServiceRef.PostalCodeServiceClient postalServiceClient;
DynamicsAxServices.WebServices.ZipCode.USAZipCodeServiceRef.PostalCodepostalCode;
System.ServiceModel.Description.ServiceEndpointendPoint;
System.ServiceModel.EndpointAddressendPointAddress;
System.Exceptionexception;
System.Typetype;
;
try
{
// Get the .NET type of the client proxy
type = CLRInterop::getType('DynamicsAxServices.WebServices.ZipCode.USAZipCodeServiceRef.PostalCodeServiceClient');
// Let AifUtil create the proxy client because it uses the VSAssemblies path for the config file
postalServiceClient = AifUtil::createServiceClient(type);
// Create and endpoint address, This should be a parameter stored in the system
endPointAddress = new System.ServiceModel.EndpointAddress ("http://www.restfulwebservices.net/wcf/USAZipCodeService.svc");
// Get the WCF endpoint
endPoint = postalServiceClient.get_Endpoint();
// Set the endpoint address.
endPoint.set_Address(endPointAddress);
// Use the zipcode to find a place name
postalCode = postalServiceClient. GetPostCodeDetailByPostCode("10001"); // 10001 is New York
// Use the getAnyTypeForObject to marshal the System.String to an Ax anyType
// so that it can be used with info()
info(strFmt('%1', CLRInterop::getAnyTypeForObject(postalCode.get_ PlaceName())));
}
catch(Exception::CLRError)
{
// Get the .NET Type Exception
exception = CLRInterop::getLastException();
// Go through the inner exceptions
while(exception)
{
// Print the exception to the infolog
info(CLRInterop::getAnyTypeForObject(exception.ToString()));
// Get the inner exception for more details
exception = exception.get_InnerException();
}
}
}
I was getting the same issue, finally its resolved.
Login to AOS machine with AOS service account and check if you can browse internet. If not then you need to set proxy for internet in IE.
So basically under AOS account, process could not connect to Webservice provider.
I have resolved this issue. I just end session all online user and stop/start AOS after doing ful cil. Maybe deleting XPPIL and Appl files helps before start the AOS service.

gSOAP instance of .NET service

I have a linux c++ client (via gSOAP) to WCF c# server. The WCF c# service contains list of objects, on which some action is executed. Each time i call some function on service, the new object is created, action on that object is executed and that object lands into list in service. at the end i am calling another function on service, which loops over all objects in a list and executes another call on them. this works as intended on c#, with both client and service pure WCF.
it works different via gSOAP. each time i call a first function on service via gSOAP, that action is executed and list is updated. but it is each time new service. so basically i am dealing each time with new service. i do not wont serialize/deserialize object itself, to have it on inux side.
any ideas how to solve this?
on c# side i have something like (unnecessery details skipped)
class Service : IService
{
List list = new List();
void func1(int i)
{
Class1 c = new Class1(i);
c.create();
list.Add(c);
}
void func2()
{
foreach(Class1 c in list)
{
c.close();
}
}
}
on gSOAP side i have something like
Proxy service (endpoint);
service.func1(1);
service.func1(2);
//...
service.func2();
as i said problem is: when func2() is executed it operates on empty list, meaning gSOAP object of Proxy service does not contain c# object of service.
Help, help!
ps.
the solution is found: container made "static" does the trick.

System.ServiceModel.ClientBase connected to Service

I have the following code:
public partial class MyServiceClient : System.ServiceModel.ClientBase<...
if (m_MyClient == null)
m_MyClient = new MyServiceClient
("BasicHttpBinding_IMyService", remoteAddress);
WriteOutput("Successfully connected to service");
My question is how do i know that my client actually connected to the service at this point? I would like to display a message of either failure or success.
When you've created the client, and no exception like EndpointNotFoundException has occured - then you are "connected" to the service which really means: the communication channel between the client and the service is ready to be used for sending messages back and forth. That's all there is - there's nothing on the server side yet to really handle your calls (except for the channel listener which will get activated if a message arrives).
You can also check the client channel's .State property - ideally, it should be Opened at that point:
Use this if you're deriving from ClientBase<T>
m_MyClient.State == CommunicationState.Opened
or this is you're using the standard client class generated by the Add Service Reference functionality in Visual Studio:
(m_MyClient as IClientChannel).State == CommunicationState.Opened
After realizing what i mentioned in my above comment, i realized the answer to my question was as follows:
In my ServiceContract i added the following:
[OperationContract]
bool IsAlive();
Whose implentation simply looks as follows:
public bool IsAlive()
{
return true;
}
Then changed my code as follows:
m_MyClient = new MyServiceClient("BasicHttpBinding_IMyService", remoteAddress);
try
{
m_MyClient.IsAlive();
}
catch (EndpointNotFoundException)
{
WriteOutput("Unable to connect to service");
m_MyClient = null;
}
if (m_MyClient != null)
WriteOutput("Successfully connected to service");