I am calling a 3rd party SOAP service (a Magento webstore) in ASP.NET MVC4. When importing the web service reference, all of the service methods are automatically implemented by Visual Studio, eg the login soap method is implemented as
public string login(string username, string apiKey) {
object[] results = this.Invoke("login", new object[] {
username,
apiKey});
return ((string)(results[0]));
}
But when I call this method, this.Invoke sends a POST with this user-agent header automagically added:
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0;
MS Web Services Client Protocol 4.0.30319.18444)
This header tells the 3rd party that the user agent is IE6. And many sites automatically block IE6 with a message saying something to the effect of "We do not support IE6. Go get a real browser and try again"
So the soap call breaks but only because the 3rd party site thinks we are using IE6, not because there is anything wrong with the soap call. If we could change this header to mimic the UA string of a modern web browser then this problem would not exist.
So how then do you change the UA string used by SoapHttpClientProtocol method calls? It all happens inside of the this.Invoke method, which is part of the .NET core.
EDIT:
The object this in the above autogenerated code above is a subclass of SoapHttpClientProtocol, so yes I could just manually write the user agent in there myself:
public string login(string username, string apiKey) {
this.UserAgent = "something, anything except for IE6";
object[] results = this.Invoke("login", new object[] {
username,
apiKey});
return ((string)(results[0]));
}
BUT, this is autogenerated code and will be overwritten anytime the 3rd party updates their service (for Magento it is quite frequently), and I would have to manually add it to every single autogenerated function (a lot of them). So it's not practical to just write this.UserAgent = "not IE6" here, it needs to be a more useful solution.
The generated Web Service reference class derives itself from SoapHttpClientProtocol, something like this:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.18408")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Web.Services.WebServiceBindingAttribute(Name="MyGeneratedWebServiceSoap", Namespace="http://www.killroy.com/webservices/")]
public partial class MyGeneratedWebService : System.Web.Services.Protocols.SoapHttpClientProtocol
{
...
}
SoapHttpClientProtocol has a read/write UserAgent property, so what you could do is derive from this class again and customize the user agent like this (this way you can automatically replace all instance creations of the original class by the new one):
public class SuperWs: MyGeneratedWebService
{
public SuperWs()
{
UserAgent = "Mozilla/5.0 (Killroy was here)";
}
}
is the autogenerated class a partial class?
When it is a partial class then you should create an own extention to the generated class like "myWebservice_partial.cs", rename the class to:
public partial class "GENERATEDCLASSNAME"{}
and define/ override the constructor. within this you can set your UserAgent. This is updatesave.
This code is untested and written from my brain. I don`t know now if you have to innerhit from the SoapHttpClientProtocol (See Comment)
E.G.
FileName: WsClass_partial.cs
public partial class WsClass /* :SoapHttpClientProtocol */ {
public WsClass(string useragent):this(){
this.UserAgent = useragent;
}
}
Related
Disclaimer: I have not worked with SOAP web services ever. At all. Not even a little bit. So the concept of channels and WCF scaffolding has got me a bit confused, hence I'm here.
I have been asked to integrate to a SOAP XML web service which uses basic authentication. (e.g. Authorization header, Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx <- which is a Base64 encoded username:password). My project is in .NET Core using C#.
I have used Visual Studio WCF connected service discovery to produce scaffolding code which has served me very well for instantiating the required objects etc, however my issue is I've been asked to use Basic authentication, and I have no idea where to inject this code into the scaffolding that's been produced. I have worked with basic authentication before, so I understand 'how' to do it, for things like REST APIs etc. Just username:password, base64 encode and add to Authorization header. However, I am unsure how to do this for this scaffolded SOAP web service.
The code that i believe can be injected into every request, to add your custom headers, is:
using (OperationContextScope scope = new OperationContextScope(IContextChannel or OperationContext)
{
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = new HttpRequestMessageProperty()
{
Headers =
{
{ "MyCustomHeader", Environment.UserName },
{ HttpRequestHeader.UserAgent, "My Custom Agent"}
}
};
// perform proxy operations...
}
The OperationContextScope expects either an IContextChannel or OperationContext. I am stuck as to what to add here. If I look at my scaffolded code, I can find the 'client' for the web service, here:
public partial class POSUserInformationManagerV1_2Client : System.ServiceModel.ClientBase<McaEndpointPosUserInformation.POSUserInformationManagerV1_2>, McaEndpointPosUserInformation.POSUserInformationManagerV1_2
And I can find the 'channel' here, but it's just another interface, that doesn't have any contracts specified?
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.0.2")]
public interface POSUserInformationManagerV1_2Channel : McaEndpointPosUserInformation.POSUserInformationManagerV1_2, System.ServiceModel.IClientChannel
{
}
I looked up ChannelBase, and it seems like it should accept a variety of objects that implement one or another channel interface (including IClientChannel, which the scaffolded POSUserInformationManagerV1_2Channel uses)
protected class ChannelBase<T> : IDisposable, IChannel, ICommunicationObject, IOutputChannel, IRequestChannel, IClientChannel, IContextChannel, IExtensibleObject<IContextChannel> where T : class
{
protected ChannelBase(ClientBase<T> client);
[SecuritySafeCritical]
protected IAsyncResult BeginInvoke(string methodName, object[] args, AsyncCallback callback, object state);
[SecuritySafeCritical]
protected object EndInvoke(string methodName, object[] args, IAsyncResult result);
But I'm still stuck on what I can put into the OperationContextScope to connect it appropriately to the 'channel'. I've tried POSUserInformationManagerV1_2Client and the relevent Channel interface, but neither will convert to an IContextChannel. Does anyone have any ideas/thoughts?
EDIT: Here is where I am trying to inject the code to add the Auth HTTP header:
public System.Threading.Tasks.Task<McaEndpointPosUserInformation.requestUserInformationResponse> requestUserInformationAsync(McaEndpointPosUserInformation.requestUserInformation request)
{
using (OperationContextScope scope = new OperationContextScope(request)
{
OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = new HttpRequestMessageProperty()
{
Headers =
{
{ "MyCustomHeader", Environment.UserName },
{ HttpRequestHeader.UserAgent, "My Custom Agent"}
}
};
// perform proxy operations...
}
return base.Channel.requestUserInformationAsync(request);
}
The issue turned out to be not setting up the transport security to be 'Basic' through the use of:
// Set the binding. Without this, the WCF call will be made as anonymous
var binding = new BasicHttpsBinding();
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
I spent several hours reading through many different examples and documentation for setting up a portable area with ASP.NET MVC, with the intent of sharing a common login page with authentication for multiple applications. I got it all together and it works nicely so far, but one thing I'm having trouble with is the use of message bus. I see it is a way of communicating between the Host and Portable components, but I don't see a clear way of how to do this.
For instance; if my Portable login is successful, how do I tell the Host so it can do something (set a cookie, redirect to a specific page, etc.)? Also, if I want to send something to the Portable (like the title or Assembly Version of the Host application) how would I do that? I haven't tried anything yet because I cannot seem to find a complete example.
I got it all figured out. There was a MvcContrib source code archive that I was unable to download since Google Chrome was blocking the .zip file, but I was able to get it using Internet Explorer.
Here are the important bits after adapting it to my application. Hopefully this can help someone. I did my best to format my answer properly, this is my first time posting on StackOverflow:
In the Portable class library
Create a LoginResult class using the ICommandResult interface
public class LoginResult: MvcContrib.PortableAreas.ICommandResult
{
public bool Success { get; set; }
public string Message { get; set; }
public string Username { get; set; }
}
Create a LoginMessage class that also uses the ICommandResult interface with LoginResult. The LoginMessage class has a property for a LoginViewModel I use in my Login.cshtml view (it has Username, Password, and some other additional fields I needed for the view)
public class LoginMessage : ICommandMessage<LoginResult>
{
public LoginResult Result { get; set; }
public LoginViewModel Input { get; set; }
}
In the HttpPost action of the Login controller, create an instance of LoginMessage, passing in the LoginViewModel from the login view, and send it to the Host with MvcContrib.Bus.Send
[HttpPost]
public ActionResult Login(LoginViewModel mdl)
{
// TODO: Do basic auth here first, then send to Host for additional validation
// Create and send message to the Host
var message = new LoginMessage { Input = mdl, Result = new LoginResult() };
MvcContrib.Bus.Send(message);
if (message.Result.Success)
{
// Redirect to defaultUrl set in the Host's web.config
FormsAuthentication.RedirectFromLoginPage(mdl.Username, false);
}
return View("Login", "_Layout", mdl);
}
Note: LoginMessage sets a new empty LoginResult and then waits for Success. The Success is set by the Host (shown below). I do this because certain Host applications have specific additional logic that only apply to that application, so I let the Host do what it needs and return to the Portable to let it know if it passed or failed. Eventually, I will have the basic authentication logic in the Portable first and then let the Host do the extra work, but for the sake of this example I am keeping it simple.
In the Host web application (which has a reference to my Portable dll)
Create a handler for the Portable.LoginMessage so we can read it in the Host. Note that IsValidLogin is where I will eventually do my additional authentication logic to see if the user is valid
public class LoginHandler : MvcContrib.PortableAreas.MessageHandler<Portable.LoginMessage>
{
public override void Handle(Portable.LoginMessage message)
{
if (IsValidLogin(message.Input.Username, message.Input.Password))
{
message.Result.Success = true;
message.Result.Username = message.Input.Username;
}
else
{
message.Result.Message = "Username or Password was incorrect";
}
}
private bool IsValidLogin(string username, string password)
{
// TODO: Replace with actual authentication
return username.Equals("admin") && password.Equals("password");
}
}
In the web.config, set the defaultUrl that the Portable will redirect to in the controller I described earlier, when message.Result.Success is True. You do not need to be using Forms Authentication, the mode can be set to None, but you need to have the defaultUrl for this to work.
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Portable/Login" defaultUrl="~/Home/Index" />
</authentication>
</system.web>
That's it! This was a great exercise and learning experience for me. I am still figuring out the second part of my question where I need to send information to the Portable first (like the application title and assembly version) but I'm thinking I can do pretty much the same thing but in reverse, where I send an ICommandMessage to the Portable when my Host starts up (global.asax).
I have a solution which includes a thick client (implemented using CefSharp for the majority of the user interface), and the javascript application needs to execute some C# logic in the application hosting the CEF browser. I considered using WebView.RegisterJsObject(), but I can write less glue code if I can just use $.ajax() from the html pages.
I already have ServiceStack set up for the web services and the web client in this solution. I'd like to route requests from the CEF browser to a local ServiceStack host (without actually using http).
Here's some psuedo code to illustrate what I would like to do:
public partial class MainWindow : IRequestHandler {
WebView _webView;
CefSharpServiceStackHost _serviceHost;
public MainWindow() {
// initialize CefSharp...
_webView.RequestHandler = this;
// initialize ServiceStackHost...
}
// other IRequestHandler methods...
// method this intercepts ajax calls from the CEF browser
public bool OnBeforeResourceLoad(IWebBrowser browser, IRequestResponse requestResponse) {
// translate CefSharp.IRequestResponse to ServiceStack.IRequest or HttpRequest
// should execute HelloService.Any() for the requestResponse.Url = "/hello/Zach"
var response = _serviceHost.ExecuteService(Translate(requestResponse));
requestResponse.RespondWith(response.Stream);
return false;
}
}
[Route("/hello/{Name}")]
public class Hello {
public string Hello { get; set; }
}
public class HelloService {
public object Any(Hello request) { // ... }
}
The part I can't figure out is how to extend ServiceStackHost so I can pass some sort of request object to it. Is this even possible?
This might be a stupid answer, but why not just use http anyway? The web is so heavily based on it that things actually gets easier if you use it even in cases like this (where it isn't really necessary).
If this isn't OK, you can implement a custom scheme handler that routes requests to foo://bar to your C# code, and do whatever you like. The CefSharp.Wpf.Example has an example custom scheme handler, so it should help you along the way.
What you're after sounds similar to how MQ Servers execute services in ServiceStack by simply routing messages to:
ServiceController.ExecuteMessage(IMessage)
There are a number of other API's on ServiceController you can use to execute requests in ServiceStack:
//Execute the Request DTO with an empty Request context:
object Execute(object requestDto)
//Execute the Request DTO with the supplied Request context:
object Execute(object requestDto, IRequest request)
For the IRequest context, you can use the built-in BasicRequest class, or your own that implements IRequest.
I have a WCF Host with something like this:
[ServiceContract]
public interface IMountToOs
{
[OperationContract]
char GetMountDriveLetter();
[OperationContract]
MyTestClass MyTest();
}
public class MyTestClass
{
public string A { get; set; }
public string B { get; set; }
}
Client
private IMountToOs _proxy;
public IMountToOs Proxy
{
get
{
if (_proxy == null)
{
NetTcpBinding binding = new NetTcpBinding();
binding.MaxReceivedMessageSize = 2147483647;
binding.OpenTimeout = TimeSpan.FromMilliseconds(50000);
EndpointAddress address = new EndpointAddress("net.tcp://localhost:1234/MountToOsHost");
//_proxy = new MountToOsClient(binding, address);
ChannelFactory<IMountToOs> factory = new ChannelFactory<IMountToOs>(binding);
_proxy = factory.CreateChannel(address);
}
return _proxy;
}
}
While I can access
MessageBox.Show("Okay - " + Proxy.GetMountDriveLetter());
I can't call this method:
MessageBox.Show("Okay - " + Proxy.MyTest().A);
The complete extension is not working. But only while using it in an extension. Even if I insert a Messagebox in the first line of the extension it is not hit. I don't know why. It seems to run a pre-check and find the call of the custom class which is refused or so...
If I use a winform or so there is no problem.
.net 3.5
curious is that I have a break-point and a message of the hosts side. So I see that the method is not called
Update
now I moved the wcf-call in the Load Method of the extension and get a exception:
System.MissingMethodException: method not found:
"Contracts.Interfaces.MyTestClass
Contracts.Interfaces.IMountToOs.MyTest()".
My winform test and this extension use the same interface so that the method should known from both. no contract or so is outdated
According to what I found here and in the comments of the post: "For creating dynamic service proxy using client channel factory method, you will need datacontracts of the service. If you don't have datacontracts but you have the service URL, then you could use reflection to create proxy at runtime and call the service method."
Seems that the MyTestClass type is not known on the client side, so I think you could use reflection, or share the class between the client and server or much more simple, use the datacontract attribute.
Also, found something on MSDN that says something like this:
"When to use a proxy?
We create proxy using svcutil.exe. The output of this tool gives a proxy class and makes corresponding changes to the application configuration file. If you have a service that you know is going to be used by several applications or is generic enough to be used in several places, you'll want to continue using the generated proxy classes. We use proxy in WCF to be able to share the service contract and entities with the client. Proxies have several restrictions like they need to have gets and sets , contructors can't be exposed , methods other than the service contract cannot be exposed, repetition of code, everytime that we add/modify a service contract/data contract/message contract we need to re-generate the proxy for the client.
When to use ChannelFactory
The other option is using the ChannelFactory class to construct a channel between the client and the service without the need of a proxy . In some cases, you may have a service that is tightly bound to the client application. In such a case, it makes sense to reference the Interface DLL directly and use ChannelFactory to call your methods using that. One significant advantage of the ChannelFactory route is that it gives you access to methods that wouldn't otherwise be available if you used svcutil.exe..
When to use a ChannelFactory vs Proxy class?
A DLL is helpful if the client code is under you control and you'd like to share more than just the service contract with the client -- such as some utility methods associated with entities and make the client & the service code more tightly bound. If you know that your entities will not change much and the client code is less, then a DLL would work better than a proxy. If the client to your service is external to the system, such as API, it makes sense to use a proxy, because it makes sharing the contract easier by giving a code file rather than a DLL."
We cant see the class
MountToOsClient: IMountToOs
So we can only assume it is ok.
[DataContract] // Missing
public class MyTestClass
{
[DataMember] // Missing
public string A { get; set; }
[DataMember] // Missing
public string B { get; set; }
}
MountToOsClient can not expose Mytestclass without these attributes.
I am building a service in Windows Workflow Foundation 4.0 in Visual Studio designer mode.
How do I retrieve client IP and request headers in WF, VS Designer mode?
Seems like what you want to do is put an InvokeMethod activity (this is in the Primitives section of the Toolbox) in your workflow in the designer. There you specify a class type and the method to be called. Inside this method you can call the OperationContext class to get the client address and the request headers like so:
public class Class1 {
public static void SomeMethod() {
EndpointAddress clientAddress = OperationContext.Current.Channel.RemoteAddress;
MessageHeaders headers = OperationContext.Current.RequestContext.RequestMessage.Headers;
// Do something with the address and / or headers...
return;
}
}
The way to get the WCF details from the incoming request is to implement the IReceiveMessageCallback and add that class to the NativeActivityContext.Properties. In the OnReceiveMessage() function you will receive the WCF OperationContext allowing you to retreive any data you like from there.