I have a web api with controller methods that use rest http(post/get calls).
Clients consume this API with normal httpclient calls and I pass an http response back.
My use case is I have a legacy method that needs to make a call to another server. This method currently uses WCF and contract binding but I don't want to use WCF in this API project.
Is there a way that I can still call these methods using just WEB API or do I have to mix architectures (Web api with WCF)?
Here is the normal method call
First I initialize the proxy
var proxy = GetAccountProxy();
public static AcountClient GetAccountProxy()
{
var client = new AccountClient();
client.ClientCredentials.ClientCertificate.SetCertificate(...);
return client;
}
I connect to a method on the other server through the proxy
var accountInfo = proxy.GetAccountInfo(xmlAccount);
public string AccountInfo(string sXml){
AccountLookup val = new AccountLookup();
val.Body = new AccountLookupRequestBody();
val.Body.XML = sXML;
AccountLookupResponse retVal = ((AccountLookupResponse)(this)).AccountLookup(val);
return retVal;
}
In my webconfig the endpoints look like this
<endpoint address="https://www.mylookup.com/AccountLookupWS/AccountLookupWS.svc/wshttp" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IAccountLookupWS" contract="AccountLookupWS.IAccountLookupWS" name="WSHttpBinding_IAccountLookupWS1" />
So my question is can I just call this endpoint using a normal rest httpclient call and have the same result?
Uri baseUrl = new Uri("https://www.mylookup.com/AccountLookupWS/AccountLookupWS.svc/wshttp");
IRestClient client = new RestClient(baseUrl);
IRestRequest request = new RestRequest("GetAccountInfo", Method.GET)
request.AddParameter("XmlAccount", sXml);
IRestResponse<dynamic> response = client.Execute<dynamic>(request);
if (response.IsSuccessful)
{
response.Data.Write();
}
else
{
Console.WriteLine(response.ErrorMessage);
}
Console.WriteLine();
In general it is possible to access wcf with webrequest, but it depends on the wcf service implementation. Check out the WebGet and/or WebInvoke attributes https://learn.microsoft.com/en-us/dotnet/api/system.servicemodel.web.webgetattribute?view=netframework-4.8 and start from there.
It is work to be done on WCF side, after it is done properly, you can access your wcf as normal rest services.
It depends on your WCF server bindings. You see, HTTP/s protocol implementation is just possible module of WCF, a part, just like any other protocol out there - it is just called binding. Different bindings means same bindings should be on client side, otherwise they don't understand each other.
For example if server tells:
use gzip on my data which I send over wire
then I xor my data with 666 if first bit is set true
then use SSL to protect it
then send it over TCP
Then client should do the same thing in reverse. This is WCF and it's flexibility for you which opened hell gates for researchers and developers.
As I said, if your server supports HTTP bindings, without extra stuff - you are good. Use http client or billion other HTTP classes. If not - port your server protocol bindings to NET Core and use them.
Related
I am creating client for soap service on .Net Core. Code was generated from wsdl
https://testfinance.post.ee/finance/erp/erpServices.wsdl
Code which should send request
var client = new ErpDataExchangeClient(
ErpDataExchangeClientBase.EndpointConfiguration.ErpDataExchangeSoap11);
var eInvoiceRequest = new EInvoiceRequest()
{
authPhrase = "10****:rskzsbkqdlmlmaeoyhmzeyttacozypxbbwqudna***********",
E_Invoice = GetEinvoice()
};
var result = client.EInvoiceAsync(eInvoiceRequest).Result;
GetEinvoice() - returns XMl document
On runtime I get an exception:
System.InvalidOperationException: 'Contract requires Duplex, but
Binding 'BasicHttpBinding' doesn't support it or isn't configured
properly to support it.'
Service should be fine. This is a big company API.
Nothing was changed, used only automatically generated code.
What should I do to be able make API calls for this SOAP service
I have a requirement to secure a streamed WCF net.tcp service endpoint using WIF. It should authenticate incoming calls against our token server. The service is streamed because it is designed to transfer large amounts of data n stuff.
This appears to be impossible. And if I can't get around the catch, my Christmas will be ruined and I'll drink myself to death in a gutter while merry shoppers step over my slowly cooling body. Totes serious, you guys.
Why is this impossible? Here's the Catch-22.
On the client, I need to create a channel with the GenericXmlSecurityToken I get from our token server. No problemo.
// people around here hate the Framework Design Guidelines.
var token = Authentication.Current._Token;
var service = base.ChannelFactory.CreateChannelWithIssuedToken(token);
return service.Derp();
Did I say "no problemo"? Problemo. In fact, NullReferenceException style problemo.
"Bro, " I asked the Framework, "do you even null check?" The Framework was silent, so I disassembled and found that
((IChannel)(object)tChannel).
GetProperty<ChannelParameterCollection>().
Add(federatedClientCredentialsParameter);
was the source of the exception, and that the GetProperty call was returning null. So, WTF? Turns out that if I turn on Message security and set the client credential type to IssuedToken then this property now exists in the ClientFactory (protip: There is no "SetProperty" equivalent in IChannel, the bastard).
<binding name="OMGWTFLOL22" transferMode="Streamed" >
<security mode="Message">
<message clientCredentialType="IssuedToken"/>
</security>
</binding>
Sweet. No more NREs. However, now my client is faulted at birth (still love him, tho). Digging through WCF diagnostics (protip: make your worst enemies do this after crushing them and driving them before you but right before enjoying the lamentations of their women and children), I see it's because of a security mismatch between the server and client.
The requested upgrade is not supported by 'net.tcp://localhost:49627/MyService'. This could be due to mismatched bindings (for example security enabled on the client and not on the server).
Checking the host's diags (again: crush, drive, read logs, enjoy lamentations), I see this is true
Protocol Type application/ssl-tls was sent to a service that does not support that type of upgrade.
"Well, self," I says, "I'll just turn on Message security on the host!" And I do. If you want to know what it looks like, it's an exact copy of the client config. Look up.
Result: Kaboom.
The binding ('NetTcpBinding','http://tempuri.org/') supports streaming which cannot be configured together with message level security. Consider choosing a different transfer mode or choosing the transport level security.
So, my host cannot be both streamed and secured via tokens. Catch-22.
tl;dr: How can I secure a streamed net.tcp WCF endpoint using WIF???
WCF has gotchas in a few areas with streaming (I'm looking at you, MTOM1) due to a fundamental issue in how it fails to perform preauthentication the way most people would think that should work (it only affects subsequent requests for that channel, not the first request) Ok, so this is not exactly your issue but please follow along as I will get to yours at the end. Normally the HTTP challenge works like this:
client hits server anonymously
server says, sorry, 401, I need authentication
client hits server with authentication token
server accepts.
Now, if you ever try to enable MTOM streaming on an WCF endpoint on the server, it will not complain. But, when you configure it on the client proxy (as you should, they must match bindings) it will explode in a fiery death. The reason for this is that the above sequence of events that WCF is trying to prevent is this:
client streams 100MB file to server anonymously in a single POST
server says sorry, 401, I need authentication
client again streams 100MB file to server with an authentication header
server accepts.
Notice that you just sent 200MB to the server when you only needed to send 100MB. Well, this is the problem. The answer is to send the authentication on the first attempt but this is not possible in WCF without writing a custom behaviour. Anyway, I digress.
Your Problem
First up, let me tell you that what you're trying is impossible2. Now, in order for you to stop spinning your wheels, let me tell you why:
It strikes me that you are now wandering in a similar class of problem. If you enable message level security, the client must load the entire stream of data into memory before it can actually close out the message with the usual hash function and xml signature required by ws-security. If it has to read the entire stream to sign the single message (which is not really a message, but it's a single continuous stream) then you can see the problem here. WCF will have to stream it once "locally" to compute the message security, then stream it again to send it to the server. This is clearly a silly thing, so WCF does not permit message level security for streaming data.
So, the simple answer here is that you should send the token either as a parameter to the initial web service, or as a SOAP header and use a custom behaviour to validate it. You cannot use WS-Security to do this. Frankly, this is not just a WCF issue - I cannot see how it could practically work for any other stacks.
Solving the MTOM Problem
This is just for an example how I solved my MTOM streaming issue for basic authentication, so perhaps you could take the guts of this and implement something similar for your issue. The crux of it is that in order to enable your custom message inspector, you have to disable all notion of security on the client proxy (it remains enabled on the server,) apart from transport level (SSL):
this._contentService.Endpoint.Behaviors.Add(
new BasicAuthenticationBehavior(
username: this.Settings.HttpUser,
password: this.Settings.HttpPass));
var binding = (BasicHttpBinding)this._contentService.Endpoint.Binding;
binding.Security.Mode = BasicHttpSecurityMode.Transport; // SSL only
binding.Security.Transport.ClientCredentialType =
HttpClientCredentialType.None; // Do not provide
Note that I have turned off transport security here because I will be providing that myself using a message inspector and custom behaviour:
internal class BasicAuthenticationBehavior : IEndpointBehavior
{
private readonly string _username;
private readonly string _password;
public BasicAuthenticationBehavior(string username, string password)
{
this._username = username;
this._password = password;
}
public void AddBindingParameters(ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
var inspector = new BasicAuthenticationInspector(
this._username, this._password);
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
}
internal class BasicAuthenticationInspector : IClientMessageInspector
{
private readonly string _username;
private readonly string _password;
public BasicAuthenticationInspector(string username, string password)
{
this._username = username;
this._password = password;
}
public void AfterReceiveReply(ref Message reply,
object correlationState) { }
public object BeforeSendRequest(ref Message request,
IClientChannel channel)
{
// we add the headers manually rather than using credentials
// due to proxying issues, and with the 101-continue http verb
var authInfo = Convert.ToBase64String(
Encoding.Default.GetBytes(this._username + ":" + this._password));
var messageProperty = new HttpRequestMessageProperty();
messageProperty.Headers.Add("Authorization", "Basic " + authInfo);
request.Properties[HttpRequestMessageProperty.Name] = messageProperty;
return null;
}
}
So, this example is for anyone who is suffering with the MTOM issue, but also as a skeleton for you to implement something similar to authenticate your token generated by the primary WIF-secured token service.
Hope this helps.
(1) Large Data and Streaming
(2) Message Security in WCF (see "disadvantages.")
I am using a ServiceStack client to call a webservice as follows:
var client = new JsonServiceClient(apiUrl);
var url = "/V1/MyApiCall";
var response = client.Post<MyApiCallResponse>(url, "foo=" + request.foo + "&bar=" + request.bar);
This generally works well, however I need to change the Content-Type header. By default (and for most other calls I am making from the service) this needs to be application/json, but in this particular case it needs to be application/x-www-form-urlencoded.
client.ContentType does not implement a setter, so how can I change the Content-Type header?
Don't use Servicestack's C# Clients to call 3rd party API's. You're using a JSON client which as expected sends JSON. You can use ServiceStack's built-in HTTP Utils if you need to call 3rd Party APIs, look at the POSTing data examples, e.g:
var response = url.PostToUrl(new { foo = request.foo, bar = request.bar },
acceptContentType = "application/json")
.FromJson<MyApiCallResponse>();
I have created a simple web api controller in mvc4 containing 4 methods (one for each CRUD operation). I'm able to use fiddler to test that the methods in my controller work.
I'm now trying to make a unit test to prove that these work. I've managed to serialize my client side object into json format, but now how do I use this string of json to actually invoke my methods?
If it helps, I am using Json.NET to serialize my client object - although I don't think this extention actually handles the delivery and retreival of it to the server.
Your unit tests should be written against the controller - so you don't need to make an actual HTTP request to unit test your Web API code, you just call the methods.
From a design perspective, if you want a restful Web API, the client should be able to send a standard HTTP message without having to serialize the request.
This is the kind of approach I have used to post an object to a restful Web API:
HttpResponseMessage response;
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://url_to_service");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var responseTask = client.PostAsJsonAsync("api/resource/somethingelse", someObjectToPost).Result;
responseTask.Wait();
response = responseTask.Result;
if (response.IsSuccessStatusCode)
{
var contentTask = response.Content.ReadAsAsync<SomeResponseType>();
contentTask.Wait();
SomeResponseType responseContent = contentTask.Result;
}
else
{
//Handle error.
}
In this case, someObjectToPost is your client-side object, though you can leave it to Web API to serialize it for you. In the above example I am assuming the reponse is of fictional type SomeResponseType - you can also use ReadAsStringAsync if the response is expected to be plain text.
The code presented here by nick_w is correct. You need to use HttpClient object. And as Steve Fenton mentioned, to create unit test you don't want to do it - rather test directly against controller. But for the functional test you can do it. I've done same thing. I've created helper class so I need only to call one of Http helper methods, depending if it is GET or POST, etc. that I do. This helper uses generic types so it operates with any types that being passed.
I'm making the following call in jQuery, using jsonp as my data format, that I'd like to make directly in Silverlight:
$.getJSON('https://api.wordstream.com/authentication/login?callback=?',
{ 'username': un,
'password': pw
}, function (loginResults) {
API_SESSION_ID = loginResults['data']['session_id'];
$.getJSON('https://api.wordstream.com/keywordtool/get_keywords?callback=?',
{ 'session_id': API_SESSION_ID,
'seeds': keyword,
'max_results': 20
}, function (keywordResults) {
for (i = 0; i < +keywordResults['data'].length; i++) {
Keywords[i] = keywordResults['data'][i][0];
}
return(Keywords);
});
});
I tried something like this to handle the first $.getJSON (authenticating & returning my auth token):
WebClient downloader = new WebClient();
WebRequest.RegisterPrefix("https://", System.Net.Browser.WebRequestCreator.ClientHttp);
var client = new WebClient();
client.Credentials = new NetworkCredential("username", "password");
client.UseDefaultCredentials = false;
client.DownloadStringCompleted += new
DownloadStringCompletedEventHandler(client_DownloadStringCompleted);
client.DownloadStringAsync(loginEndpoint);
When I try and run this I get the following error inside my downloadstringcompleted eventhandler:
{System.Security.SecurityException: Security error.
at System.Net.Browser.ClientHttpWebRequest.InternalEndGetResponse(IAsyncResult asyncResult)
at System.Net.Browser.ClientHttpWebRequest.<>c__DisplayClass5.<EndGetResponse>b__4(Object sendState)
at System.Net.Browser.AsyncHelper.<>c__DisplayClass4.<BeginOnUI>b__1(Object sendState)}
I've used WCF Ria Services in EF & SOAP services via .asmx files in the past, so I'm not a total stranger to the idea of web services. I am not sure if I need to be using the clientaccesspolicy or crossdomain files or not.
Any ideas on how to proceed?
Thanks,
Scott
What you trying to do in this series of questions has become clearer to me now.
Unless api.wordstream.com includes a ClientAccessPolicy xml (or the Flash equivalent) you will not be able to make requests to this api from Silverlight.
You have two options:
Call into Javascript to make these requests on behalf of the Silverlight app.
Create WCF service to on your server to make these requests on behalf od the Silverlight app.
I would recommend the first approach, however don't use getJSON. Instead use the standard ajax api in JQuery to fetch the JSON content asynchronously. When the final JSON content is available (still in string form) call into Silverlight from Javasript passing in the string.
What would be preferable is to create the appropriate set of .NET classes and collections that match the data from the api. You could then use DataContractJsonSerialializer to deserialize the received string into instances of your classes.
Sometimes creating a class structure can be a bit of a burden. Another approach is to use the set of objects in the System.Json namespace starting with JsonValue.Parse to load up the set of JsonObjects from the string. You can now navigate around the returned data using these `son objects and Linq where necessary.
Did a little digging and a test.
When you are calling an external domain, the cross-domain issue will occur and that's why you are seeing the Security error.
Remember that this is a Web application after all, it does run inside the browser!
To enable Silverlight to reach outside it's domain, give this article a try if you are doing a self hosted app.
http://blogs.msdn.com/b/carlosfigueira/archive/2008/03/07/enabling-cross-domain-calls-for-silverlight-apps-on-self-hosted-web-services.aspx