I have a problem... I explored whole internet and I don't know what I'm doing wrong.
Problem: WCF web service, .Net Framework 3.5, 2 different type of clients (Handheld device and usual computer)
What I'm trying to do is to create 2 diferent endpoints, one with basicBinding (For SOAP request) and other with wsBinding (for usual computers)
So I go thru web.config and I created 2 different bindings, related with 2 different endpoints:
<bindings>
<basicHttpBinding>
<binding name="BasicBinding" openTimeout="00:10:00" receiveTimeout="23:59:00"
sendTimeout="23:59:00" messageEncoding="Text" />
</basicHttpBinding>
<wsHttpBinding>
<binding name="DefaultBinding" openTimeout="00:10:00" receiveTimeout="23:59:59"
sendTimeout="23:59:59">
<reliableSession inactivityTimeout="01:00:00" />
<security mode="None">
<transport clientCredentialType="None" />
<message clientCredentialType="None" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="qtswsdl.QTS_ServiceBehavior"
name="qtswsdl.QTS_Service">
<endpoint address="http://host.com/service.svc/ForHh"
binding="basicHttpBinding" bindingConfiguration="BasicBinding"
name="handHeldEndPoint" contract="qtswsdl.QTSPort" />
<endpoint address="http://host.com/service.svc/ForCp"
binding="wsHttpBinding" bindingConfiguration="DefaultBinding"
name="coProcessorEndPoint" contract="qtswsdl.QTSPort" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="qtswsdl.QTS_ServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
<serviceThrottling maxConcurrentCalls="2147483647"maxConcurrentSessions="2147483647"
maxConcurrentInstances="2147483647" />
</behavior>
</serviceBehaviors>
</behaviors>
So when I try to send SOAP messages to "http://host.com/service.svc/ForHh" Im getting a "HTTP 400 - Bad Request" (the /ForCp is also not working)
I tried with custom clients, with WcfTestClient.exe and I was not able to fin what is happening
Any tip or suggestion?
Thanks for your time
EDIT:
After enable Svc trace I got a couple of exceptions:
<Message>The message with Action 'http://Host.com/Service.svc/ForHh' cannot be processed at the receiver, due to a ContractFilter mismatch at the EndpointDispatcher. This may be because of either a contract mismatch (mismatched Actions between sender and receiver) or a binding/security mismatch between the sender and the receiver. Check that sender and receiver have the same contract and the same binding (including security requirements, e.g. Message, Transport, None).</Message>
Funny thing is that I'm sending the SOAP request programatically. It's my understanding that if I'm sending SOAP request programatically i don't need to define any contract because it is being send using SOAP 1.1 by default.
Code that sends the request is the following code:
private string SendRequestAndGetAnswerFromWebService(string methodName, string requestXml){
StringBuilder soapRequest = new StringBuilder("<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
soapRequest.Append(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" ");
soapRequest.Append("xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body>");
soapRequest.Append(requestXml);//Body
soapRequest.Append("</soap:Body></soap:Envelope>");
WebRequest webRequest = WebRequest.Create(#"http://Host.com/Service.svc/" + methodName);
HttpWebRequest httpRequest = (HttpWebRequest)webRequest;
httpRequest.Method = "POST";
httpRequest.ContentType = "text/xml; charset=ascii";
httpRequest.Headers.Add("SOAPAction: " + #"http://Host.com/Service.svc/Service.svc/" + methodName);
httpRequest.ProtocolVersion = HttpVersion.Version11;
httpRequest.Credentials = CredentialCache.DefaultCredentials;
httpRequest.Timeout = 7000;
httpRequest.ContentLength = System.Text.Encoding.ASCII.GetByteCount(soapRequest.ToString());
Stream requestStream = httpRequest.GetRequestStream();
//Create Stream and send Request
StreamWriter streamWriter = new StreamWriter(requestStream, Encoding.ASCII);
streamWriter.Write(soapRequest.ToString());
//Send the request
streamWriter.Close();
//Get the Response
HttpWebResponse wr = (HttpWebResponse)httpRequest.GetResponse();
StreamReader srd = new StreamReader(wr.GetResponseStream());
string resulXmlFromWebService = srd.ReadToEnd();
return resulXmlFromWebService;
}
Maybe my assumption about contract and soap messages sent programatically are wrong...
Generally, this is very bad practice -- you should NOT be manually authoring SOAP requests to WCF services this way. It is better if you were to take advantage of the out-of-the-box WCF request/reply paradigms instead of resorting to setting up headers manually.
What I would recommend is to create an alternative example where you DO achieve the same thing out-of-the-box (with perhaps DataContracts, OperationContracts, and ServiceContracts on the client-end, perhaps taking advantage of WebHttpBehavior and WebHttpBinding). Examine the exact messages that are sent and received in that scenario, and closely examine, character by character, how those messages are different from the ones you're sending here.
Also, it's hard to tell what else you could be doing wrong from just the detail you've provided.
Here is what I suggest:
Enable tracing on the service side, generate tracing logs, and analyze with SvcTraceViewer. To do this, follow the instructions at this MSDN article on using the service trace viewer. Notice what's different between the first and second time.
Turn on debug exceptions. This is done by turning in includeExceptionDetailInFaults, which you can do by following the instructions here. Notice what's different between the first and second time.
Use Fiddler to monitor the wire traffic on both the client side and the service side.
Generally, once you do this, you should plenty of more info on what's going funky at the service side and can diagnose the issue pretty quickly. Try it, and please report back! :)
Related
I have a WCF endpoint that exposes a API with a basicHttpBinding. This biding is set to use security mode TransportWithMessageCredentialand UserName for clientCredentialType.
Because security is implemented at message level, at the WCF, the IIS needs to allow anonymous access. And so, wsdl can be obtain without providing any credentials.
How to force authentication to get the service metadata?
Here the current service configuration looks like (from web.config)
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="secure">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="secure" name="someProject.MyService">
<endpoint binding="basicHttpBinding" contract="someProject.IService" bindingConfiguration="secure" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="secure">
<serviceMetadata httpsGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
I try the obvious, to set a specific binding for the metatada, by using service behavior configuration:
<behavior name="secure">
<serviceMetadata httpsGetEnabled="true" httpsGetBinding="basicHttpBinding" httpsGetBindingConfiguration="transportSecure" />
</behavior>
//and add the new binding
<basicHttpBinding>
<binding name="transportSecure">
<security mode="Transport">
<message clientCredentialType="UserName" />
</security>
</binding>
</basicHttpBinding>
But it is not supported. It throws this:
MessageVersion 'Soap11 (http://schemas.xmlsoap.org/soap/envelope/)
AddressingNone
(http://schemas.microsoft.com/ws/2005/05/addressing/none)' is not
supported in this scenario. Only MessageVersion 'EnvelopeNone
(http://schemas.microsoft.com/ws/2005/05/envelope/none) AddressingNone
(http://schemas.microsoft.com/ws/2005/05/addressing/none)' is
supported.
I don't understand this error or how to get around it.
Normally we will not disclose our metadata in the production environment,But if you want to enable metadata, we can use https binding to protect the metadata.
1.Configure a port with an appropriate X.509 certificate. The certificate must come from a trusted authority, and it must have an intended use of "Service Authorization." You must use the HttpCfg.exe tool to attach the certificate to the port.
2.Create a new instance of the ServiceMetadataBehavior class.
3.Set the HttpsGetEnabled property of the ServiceMetadataBehavior class to true.
4.Set the HttpsGetUrl property to an appropriate URL. Note that if you specify an absolute address, the URL must begin with the scheme https://. If you specify a relative address, you must supply an HTTPS base address for your service host. If this property is not set, the default address is "", or directly at the HTTPS base address for the service.
5.Add the instance to the behaviors collection that the Behaviors property of the ServiceDescription class returns, as shown in the following code.
ServiceMetadataBehavior sb = new ServiceMetadataBehavior();
sb.HttpsGetEnabled = true;
sb.HttpsGetUrl = new Uri("https://myMachineName:8036/myEndpoint");
myServiceHost.Description.Behaviors.Add(sb);
myServiceHost.Open();
This is authentication enabled on WCF, you can also enable windows authentication on IIS, both methods can protect metadata.
But in the production environment, I do not recommend that you enable metadata, because this will lead to the risk of metadata leakage.The call of WCF service can also be called through the channel factory. In this case, we can call WCF service without knowing the metadata of the server.
For more information on how to protect metadata, you can refer to this link:
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-secure-metadata-endpoints?redirectedfrom=MSDN
I have a WCF service where client applications can connect via a MSMQ:
[ServiceContract(Namespace="http://example.com", SessionMode = SessionMode.NotAllowed)]
public interface IMyService
{
[OperationContract(IsOneWay = true)]
void Update(DataSet ds);
}
and then:
string queueName = ConfigurationManager.AppSettings["QueueName"];
NetMsmqBinding binding = new NetMsmqBinding("MyBinding");
if (!MessageQueue.Exists(#".\private$\" + queueName))
{
MessageQueue.Create(#".\private$\" + queueName, binding.ExactlyOnce);
}
ServiceHost msmqHost = new ServiceHost(typeof(MyService));
msmqHost.AddServiceEndpoint(typeof(IMyService), binding, "net.msmq://localhost/private/" + queueName);
with the following configuration:
<system.serviceModel>
<bindings>
<netMsmqBinding>
<binding name="MyBinding" durable="false" exactlyOnce="false" maxReceivedMessageSize="20000000">
<security mode="None" />
<readerQuotas maxDepth="32" maxStringContentLength="543192" maxArrayLength="2147483647" maxBytesPerRead="4096" maxNameTableCharCount="8456384" />
</binding>
</netMsmqBinding>
</bindings>
<services>
<service name="MyService" behaviorConfiguration="MsMqBehavior" />
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MsMqBehavior">
<serviceThrottling maxConcurrentCalls="50" maxConcurrentSessions="50" maxConcurrentInstances="50" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
I have the service with the same configuration already in use without problems on other installations. But now on a new installation I receive only messages from some clients (9 actually - there are 31). The messages I receive are always from the same servers. I can't find an error message anywhere (Windows Event Log (Client/Server), WCF Trace file) and also the MSMQ state says "connected" on the client machines that don't send messages. The dead letter queues are also empty.
The messages must get lost somewhere between the MSMQ Client and Server (I stopped my app and on the server queue I received only messages from the nine Clients - same behaviour if I enable journaling).
Any help would be appreciated
Update
I've used performance counters to monitor the queue. The session counter shows the correct value of 31 sessions. Also the incoming message counter shows correct values. However if I stop the app or enable journaling only a part of the messages are stored in the queue.
The problem comes through cloning the server as described in this blog entry: http://blogs.msdn.com/b/johnbreakwell/archive/2007/02/06/msmq-prefers-to-be-unique.aspx
Basically it says there that you must not clone servers with MSMQ feature turned on. If you do so you either have to re-install the MSMQ feature on your client machines or do a registry change:
1.Stop the MSMQ Service
2.Delete the QMId value completely
3.Add a SysPrep DWORD (Under HKLM\Software\Microsoft\MSMQ\Parameters) and set it to 1
4.Start the MSMQ Service
Architecture
I have a simple example web service that exposes two operations by ServiceContract and OperationContract, nothing fancy. This service should be consumed by an Adobe Flex 4 client. Unfortunately Flex can just handle SOAP 1.1 (and not SOAP 1.2), so I have to use the BasicHttpBinding on WCF side. To secure the access to the web service I've to use Basic Authentication, because it's the only authentication method both sides (WCF and Flex) understand. Basic Authentication goes along with SSL to encrypt the transport. I run the service in IIS Express with Visual Studio 2012.
WCF service configuration
Web.config
<system.serviceModel>
<services>
<service name="UserAuthentication.AuthenticationService"
behaviorConfiguration="AuthenticationServiceBehavior">
<endpoint address=""
binding="basicHttpBinding"
bindingConfiguration="AuthenticationBinding"
contract="UserAuthentication.IAuthenticationService" />
<endpoint contract="IMetadataExchange"
binding="mexHttpBinding"
address="mex" />
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="AuthenticationBinding" maxReceivedMessageSize="65536">
<!-- Use SSL (Transport) and MessageCredential by Username (referencing behaviors/serviceBehaviors/behavior/serviceCredentials) -->
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" proxyCredentialType="None" />
<message clientCredentialType="UserName" />
</security>
<readerQuotas maxArrayLength="65536" maxBytesPerRead="65536" maxStringContentLength="65536"/>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="AuthenticationServiceBehavior">
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
<!-- Use Custom DistributorValidator for Basic Authentication -->
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="UserAuthentication.DistributorValidator,UserAuthentication"/>
<!--<serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />-->
</serviceCredentials>
<!-- For Debug purpose: #see http://intrepiddeveloper.wordpress.com/2008/08/07/security-event-logging-auditing/ -->
<serviceSecurityAudit auditLogLocation="Application" serviceAuthorizationAuditLevel="Failure" messageAuthenticationAuditLevel="Failure" suppressAuditFailure="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
DistributedValidator.cs
Should be used to authenticate the user by username and password from Basic Authentication.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
namespace UserAuthentication
{
public class DistributorValidator : UserNamePasswordValidator
{
/* Throw exeption to deny access for user */
public override void Validate(string userName, string password)
{
if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
throw new SecurityTokenException("Username and password required");
if( userName.Equals("user") == false || password.Equals("secretpwd") == false)
throw new FaultException(string.Format("Wrong username ({0}) or password ", userName));
}
}
}
Start service with SSL in IIS Express
Select project in Solution Explorer press F4 to open the properties panel
Set property SSL enabled to True
To run project press F11 (HTTP version of the page should open in your browser)
Right click on the IIS Express icon in your task bar tray and select the HTTPS version of your page
You can now open the WSDL file of the service via HTTPS
Consuming web service with Flex
Connect to web service as described in the Adobe documentation. This works fine so far and the service has been created in the Data/Services panel of the Flash Builder.
Problem
Test the web service through the Test Operation panel in the Flash Builder, the result is the HTML source code from https://localhost:44301/AuthenticationService.svc and not an expected SOAP message.
Trying the same web service and operation with the free version of SoapUI, the result is this SOAP envelope:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<s:Fault>
<faultcode xmlns:a="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">a:InvalidSecurity</faultcode>
<faultstring xml:lang="de-AT">An error occurred when verifying security for the message.</faultstring>
</s:Fault>
</s:Body>
</s:Envelope>
In addition a MessageSecurityException is logged to the Windows Event Viewer:
Message authentication failed.
Service: https://localhost:44301/AuthenticationService.svc
Action: http://tempuri.org/IAuthenticationService/GetData
ClientIdentity:
ActivityId: <null>
MessageSecurityException: Security processor was unable to find a security header in the message. This might be because the message is an unsecured fault or because there is a binding mismatch between the communicating parties. This can occur if the service is configured for security and the client is not using security.
In both cases (Flex and SoapUI) the custom DistributorValidator is never touched, so the problem is placed deeper in the magic of WCF.
Question
Is there any possibility to run a WCF service with BasicHttpBinding and Basic Authentication that play nicely together with Adobe Flex?
You need to mess with the headers to get basic HTTP auth to work with HTTPService.
It would look something like this when making the call from Flex...
var encoder:Base64Encoder = new Base64Encoder();
encoder.insertNewLines = false; // or else your header may fail...
encoder.encode("user_name:user_pass");
service.headers = {Authorization:"Basic " + encoder.toString()};
service.send(); //where servie is an instance of HTTPService
I have the following problem. Let me describe the steps I took so far...
I created a new WCF Service Application in Visual Studio
I then updated the project via Nuget to get the latest web http libs (webapi.dll)
I then created a service method that looks like this
`
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(Method="POST", UriTemplate="{value}")]
string GetData(int value, Stream inputDocument);
}
`
Now attempting to view the my .svc in the browswer results in an error that says "For request in operation GetData to be a stream the operation must have a single parameter whose type is Stream"
I know this is an issue with configuration, I just don't know what needs to change in web.config Mind you, this seems to have been a common problem in WCF before the new HTTP support, I'm somewhat surprised that this doesn't work out of the box with the new APIs.
Any pointers?
Thanks
[EDIT] I've included my config...
<system.serviceModel>
<services>
<service name="MyService.Service" behaviorConfiguration="serviceBehaviour">
<endpoint behaviorConfiguration="endPointBehaviour" address="" binding="webHttpBinding" contract="MyService.IService"/>
</service>
</services>
<bindings>
<webHttpBinding>
<binding transferMode="Streamed" name="webHttpBinding" />
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="endPointBehaviour">
<webHttp/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="serviceBehaviour">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
You are mixing up the new WCF Web API stuff with the old WCF REST stuff. Take a look at the HttpHelloResource sample as the simplest example of how to run a Web API service under IIS, or my blog post for an even simpler example of a service running in a console.
As for accepting a stream I think your simplest option would be an operation like this:
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(Method="POST", UriTemplate="{value}")]
string GetData(int value, HttpRequestMessage request);
}
and you can get the stream by doing
var stream = request.Content.ContentReadStream
Ok, so it seems the error message was taking me down the wrong path. I think that error message needs to be far more descriptive. Basically there's nothing wrong my code at all, it just doesn't make sense to point my browser to the .svc file as the service is not quite a WCF service. I learmt this by going ahead and accessing the service via code. And it works. Thanks for the help
I have a asp.net website that is hosting a WCF service. This service is then accessed from a desktop app. In my service the HttpContext is always null during the execution of the Validate method in my implementation of the UserNamePasswordValidator class. I'm using Username as the client credential type. I need access to the http context in order to get the Url the service was accessed from in order to validate the username and password correctly as the site can be accessed using different Urls and each one has a different user store.
The following attribute on the class that contains the method that will be called after the validator class (and the validator class as well)
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
I have a service is configured as follows:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttpSecurityOptions">
<security mode="Message">
<message clientCredentialType="UserName" establishSecurityContext="true" negotiateServiceCredential="true"/>
<transport clientCredentialType="Certificate" proxyCredentialType="None"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="SecurityServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFServer.MyAuthenticator" includeWindowsGroups="false"/>
<serviceCertificate findValue="myurl.com" x509FindType="FindBySubjectName" storeLocation="LocalMachine" storeName="My"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="SecurityServiceBehavior" name="Test.WCF.Actions">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttpSecurityOptions" contract="WCFServer.IActions"/>
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
I've seen the HttpContext is not initialised on first call bug but this happens to me for every call I make to the service, even when I call the same method on the same connection more than once
Edit: clarified question to answer marc_s's comment and Aliostad's question
Edit: Added following links that suggest the http context should not be null
http://blogs.msdn.com/b/wenlong/archive/2006/01/23/516041.aspx
http://msdn.microsoft.com/en-us/library/aa702682(v=VS.90).aspx
Can anyone lend me a hand with this please? I'd rather not have to put the site's Url in the appSettings config section for all my sites.
The problem is that you want to access HttpContext from Validate method. As I understand internal WCF implementation Validate method runs in different thread. By design this thread doesn't have access to any context available to main thread processing the request. In Validate method you can't access any WCF based context (OperationContext, ServiceSecurityContext, etc.) so I think it will be the same with HttpContext.
UserNamePasswordValidator's validate method is executed before asp.net pipeline is initialized. So the HttpContext is null. Try using OperationContext instead.
I am not clear on what you are trying to do.
aspNetCompatibilityEnabled only makes sense - as far as I know - when you are using new WCF REST API which does not require a binding configuration. Binding in WCF REST is managed by ASP.NET MVC routing.
If you use configuration API to set up a classic binding, then you are not using the new feature hence "no aspNetCompatibilityEnabled for you"!
So finally I thought of a workaround. I pass the url that the service is running in to the UserNamePasswordValidator.Validate though the username parameter. I use the format $username$|$siteurl$. Then at the server I separate the two. One thing to note is the ServiceSecurityContext.Current.PrimaryIdentity.Name property will then contain $username$|$siteurl$ for the rest of the request so you have to split it into its component everytime you want to access it.
Just to clarify why I need to do this. Our system can run multiple sites with different urls on the same home directory, each with separate authentication that is tied to the url. So without the url I can't authenticate the request. I had been using an appSetting key to provide the url but that meant each site had to have its own home directory.