Is it possible to handle "Cross Origin Resource Sharing" requests for a WCF service while enforcing Windows Authentication?
My scenario:
I have set up a self hosted WCF service exposed through a webHttpBinding.
This service is supposed to be called directly from the browser using jQuery. Practically speaking, this would limit me to using either the basicHttpBinding or the webHttpBinding. In this case, I'm using the webHttpBinding for calling the service operations.
The HTML pages (that will call the WCF service) are served from a web-server on the same machine but on a different port than the WCF service. This means I'll need CORS support to get this working in Firefox, Chrome, ...
Users must authenticate using Windows authentication when calling the WCF service. To this end, I have configured my webHttpBinding to use the transport security mode "TransportCredentialsOnly".
The W3C dictates that CORS should be used in such cases.
Simply stated, this means that the browser will detect that I am doing a cross-domain request. Before actually sending the request to my WCF service, it will send a so-called "preflight" request to my WCF service URL. This preflight request uses the HTTP method "OPTIONS" and asks whether the originating URL (= the webserver that served my HTML) is allowed to send the request to my service URL. The browser then expects an HTTP 200 response (= "OK") before sending the actual request to my WCF service. Any other reply from my service will prevent the actual request from being sent.
CORS is not built into WCF at this time, so I've used WCF extension points to add CORS compatibility.
The services section of the App.Config for my self-hosted service:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MyApp.DefaultServiceBehavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="True"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="MyApp.DefaultEndpointBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="MyApp.DefaultWebHttpBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service
name="MyApp.FacadeLayer.LookupFacade"
behaviorConfiguration="MyApp.DefaultServiceBehavior"
>
<endpoint
contract="MyApp.Services.ILookupService"
binding="webHttpBinding"
bindingConfiguration="MyApp.DefaultWebHttpBinding"
address=""
behaviorConfiguration="MyApp.DefaultEndpointBehavior"
>
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost/Temporary_Listen_Addresses/myapp/LookupService"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
I have implemented an IDispatchMessageInspector that replies to preflight messages:
public class CORSSupport : IDispatchMessageInspector
{
private Dictionary<string, string> requiredHeaders;
public CORSSupport(Dictionary<string, string> requiredHeaders)
{
this.requiredHeaders = requiredHeaders ?? new Dictionary<string, string>();
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
HttpRequestMessageProperty httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty;
if (httpRequest.Method.ToUpper() == "OPTIONS")
instanceContext.Abort();
return httpRequest;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
HttpRequestMessageProperty httpRequest = correlationState as HttpRequestMessageProperty;
HttpResponseMessageProperty httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
foreach (KeyValuePair<string, string> item in this.requiredHeaders)
httpResponse.Headers.Add(item.Key, item.Value);
string origin = httpRequest.Headers["origin"];
if (origin != null)
httpResponse.Headers.Add("Access-Control-Allow-Origin", origin);
if (httpRequest.Method.ToUpper() == "OPTIONS")
httpResponse.StatusCode = HttpStatusCode.NoContent;
}
}
This IDispatchMessageInspector is registered through a custom IServiceBehavior attribute.
I call my service through jQuery like so:
$.ajax(
{
url: 'http://localhost/Temporary_Listen_Addresses/myapp/LookupService/SomeLookup',
type: 'GET',
xhrFields:
{
withCredentials: true
}
}
)
.done(function () { alert('Yay!'); })
.error(function () { alert('Nay!'); });
This works in IE10 and Chrome (I get a message box saying "Yay!"), but not in Firefox. In Firefox, I get a "Nay!" and a HTTP 401 (unauthorized) error.
This 401 is due to the "Windows Authentication" that I have set up in my service configuration. The way authentication works is the browser first sends a request without any authentication info. The server then replies back with HTTP 401 (unauthorized) indicating the authentication method to use. The browser would then normally re-submit the request including the user-credentials (after which the request would proceed normally).
Unfortunately, it seems the W3C has indicated that credentials should not be passed into the CORS preflight messages. Hence, WCF replies back with a HTTP 401. It seems that Chrome somehow does send the credentials in the preflight request header (which is actually incorrect, according to W3C specs), while Firefox does not.
Furthermore, the W3C recognizes only the HTTP 200 response to a preflight requests: any other response (such as the HTTP 401 I receive) simply means the CORS request failed and the actual request may not be submitted...
I don't know how to get this (simple) scenario working. Can anyone help?
Gotten a bit further.
With .NET 4.5, it is possible to support multiple authentication schemes for a single endpoint. This allowed me to define both Windows authentication and anonymous authentication simultaneously:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MyApp.DefaultServiceBehavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="True"/>
<serviceAuthenticationManager authenticationSchemes="Negotiate, Anonymous"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="MyApp.DefaultEndpointBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="MyApp.DefaultWebHttpBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="InheritedFromHost"/>
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service
name="MyApp.FacadeLayer.LookupFacade"
behaviorConfiguration="MyApp.DefaultServiceBehavior"
>
<endpoint
contract="MyApp.Services.ILookupService"
binding="webHttpBinding"
bindingConfiguration="MyApp.DefaultWebHttpBinding"
address=""
behaviorConfiguration="MyApp.DefaultEndpointBehavior"
>
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost/Temporary_Listen_Addresses/myapp/LookupService"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
This way, my IDispatchMessageInspector does get called and I can properly handle the preflight messages for all browsers.
I then wanted to adapt my IDispatchMessageInspector to enforce authentication for any request other than the preflights:
public class CrossOriginResourceSharingMessageInspector : IDispatchMessageInspector
{
private Dictionary<string, string> requiredHeaders;
public CrossOriginResourceSharingMessageInspector(Dictionary<string, string> requiredHeaders)
{
this.requiredHeaders = requiredHeaders ?? new Dictionary<string, string>();
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
HttpRequestMessageProperty httpRequestHeader = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
if (httpRequestHeader.Method.ToUpper() == "OPTIONS")
instanceContext.Abort();
else if (httpRequestHeader.Headers[HttpRequestHeader.Authorization] == null)
instanceContext.Abort();
return httpRequestHeader;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
HttpRequestMessageProperty httpRequestHeader = correlationState as HttpRequestMessageProperty;
HttpResponseMessageProperty httpResponseHeader = reply.Properties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
foreach (KeyValuePair<string, string> item in this.requiredHeaders)
httpResponseHeader.Headers.Add(item.Key, item.Value);
string origin = httpRequestHeader.Headers["origin"];
if (origin != null)
httpResponseHeader.Headers.Add("Access-Control-Allow-Origin", origin);
string method = httpRequestHeader.Method;
if (method.ToUpper() == "OPTIONS")
{
httpResponseHeader.StatusCode = HttpStatusCode.NoContent;
}
else if (httpRequestHeader.Headers[HttpRequestHeader.Authorization] == null)
{
httpResponseHeader.StatusDescription = "Unauthorized";
httpResponseHeader.StatusCode = HttpStatusCode.Unauthorized;
}
}
}
Again, this seems to work for IE and Chrome, but not for Firefox. Preflight is now ok for Firefox, but it seems Firefox is not re-submitting the request after I replied with a HTTP 401 when the actual request didn't contain user credentials. In fact, I would expect Firefox to send the credentials along with the GET request immediately (seeing as I added "withCredentials: true" in my jQuery AJAX request; Chrome does seem to do this correctly, though).
What am I doing wrong?
Eureka (kind of). It seems that Firefox didn't like the "Negotiate" authentication I specified for my service. It seems to work when I change the authentication scheme from "Negotiate, Anonymous" to "Ntlm, Anonymous":
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MyApp.DefaultServiceBehavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="True"/>
<serviceAuthenticationManager authenticationSchemes="Ntlm, Anonymous"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="MyApp.DefaultEndpointBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="MyApp.DefaultWebHttpBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="InheritedFromHost"/>
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service
name="MyApp.FacadeLayer.LookupFacade"
behaviorConfiguration="MyApp.DefaultServiceBehavior"
>
<endpoint
contract="MyApp.Services.ILookupService"
binding="webHttpBinding"
bindingConfiguration="MyApp.DefaultWebHttpBinding"
address=""
behaviorConfiguration="MyApp.DefaultEndpointBehavior"
>
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost/Temporary_Listen_Addresses/myapp/LookupService"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
I thought Firefox supported the "Negotiate" scheme... Anyone has any idea why it didn't work?
Related
I'm trying to enable CORS using WCF.
But I get Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access. The response had HTTP status code 404.
This is my Global.asax
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
HttpContext.Current.Response.End();
}
}
This is my web.config
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="TransportSecurity">
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="RESTAPI03.testAspNetAjaxBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="RESTAPI03.testAspNetAjaxBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
<service name="RESTAPI03.test">
<endpoint address="" behaviorConfiguration="RESTAPI03.testAspNetAjaxBehavior"
binding="webHttpBinding" bindingConfiguration="TransportSecurity" contract="RESTAPI03.Hotels" />
</service>
</services>
<standardEndpoints>
<webScriptEndpoint>
<standardEndpoint crossDomainScriptAccessEnabled="true"></standardEndpoint>
</webScriptEndpoint>
</standardEndpoints>
</system.serviceModel>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*"/>
<add name="Access-Control-Allow-Headers" value="Content-Type, Accept" />
<add name="Access-Control-Allow-Methods" value="POST,GET,OPTIONS" />
<add name="Access-Control-Max-Age" value="1728000" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
this is code in my application project
const httpOptions = {
headers: new HttpHeaders({
'Access-Control-Allow-Origin' : '*'
})
}
return this.httpClient.get('https://www.example/1' httpOptions ).subscribe(data => {
console.log("s");
console.log(JSON.stringify(data));
}, err => {
console.log("err => ");
console.log( JSON.stringify(err));
});
but still not working. What did I missed?
A web application executes a cross-origin HTTP request when it
requests a resource that has a different origin (domain, protocol, or
port) from its own.
CORS (Cross Origin Resource Sharing) is a way by which web browsers implement SOP (Same Origin Policy) which says that only the same origin is allowed to access a resource of the server.
Workaround: Use native http plugin. Because CORS and SOP policies are enforced by browsers only, therefore you will not get CORS errors on other http clients like native http calls and POSTman.
https://ionicframework.com/docs/native/http
Why this happens:
Suppose a login form is returned by www.example.com/login. Now, the origin becomes www.example.com
When you will click on submit, the browser will not immediately request the server. It will first verify whether the server is taking a request from this origin ( due to SOP policy followed by browsers) by sending a preflight OPTIONS request.
If server is allowing the request from this origin, then server will send some CORS headers including ACCESS-CONTROL-ALLOW-ORIGIN header.
Read more at: https://ionicframework.com/docs/faq/cors
If server says yes(by sending valid CORS headers), browser will submit the form with the Origin header set to 'www.example.com' along with login parameters.
In other cases, you may have a Login API on www.example.com/api/login server and you are sending a POST request from a different source, http://localhost in case of IONIC Apps
Since the origin is different, you are required to set CORS headers on your Server so that any request made from the http://localhost is allowed. But there are some factors to keep in mind. Like, setting ACCESS-CONTROL-ALLOW-ORIGIN to '*' doesnot work when you send request with credentials.
I recommend that you should read this document about CORS and its policies. it will help you in the future.
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
At first, I suspect there is something wrong with your configuration used for creating Restful style WCF service.
Pay attention to the name property of the service section and the contract property of the endpoint section.
<service name="RESTAPI03.test">
<endpoint address="" behaviorConfiguration="RESTAPI03.testAspNetAjaxBehavior"
binding="webHttpBinding" bindingConfiguration="TransportSecurity" contract="RESTAPI03.Hotels" />
</service>
The name property of the service should be a qualified name of the service implemented class and the contract property should be a qualified name of the service contract, like this.
<service name="WcfService3.Service1" behaviorConfiguration="mybehavior">
<endpoint address="" binding="webHttpBinding" bindingConfiguration="mybinding" contract="WcfService3.IService1" behaviorConfiguration="rest"/>
</service>
Please refer to my configuration with the ProtocolMapping feature. It works on both HTTP and HTTPS protocol and supports your service irrespective of any service contract.
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="httpsbinding">
<security mode="Transport">
<transport clientCredentialType="None"></transport>
</security>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior>
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<protocolMapping>
<add binding="webHttpBinding" scheme="http"></add>
<add binding="webHttpBinding" scheme="https" bindingConfiguration="httpsbinding"/>
</protocolMapping>
</system.serviceModel>
Secondly, there usually are two ways to enable CORS in Restful WCF service.
1. Add a global.asax file to the WCF project and the below code
snippets to Application_BeginRequest event.
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Cache-Control", "no-cache");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With,Accept");
HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
HttpContext.Current.Response.End();
}
}
Adding a global.asax file and modify the Application_BeginRequest event.
if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
{
Response.End();
}
Subsequently, add the following configuration to the Web.config file.
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*"/>
<add name="Access-Control-Allow-Headers" value="content-type" />
<add name="Access-Control-Allow-Methods" value="GET,POST,PUT,DELETE,OPTIONS" />
</customHeaders>
</httpProtocol>
</system.webServer>
Feel free to let me know if the problem still exists.
I have a .Net c# client that needs to consume a Java web service from a third party. They require both a client cert and user name and password. I have the cert set up but constantly get 401 Unauthorized because I don't think the username and password are actually being attached to the request. It seems like WCF expects one or the other but not both cert and username/password. Surely I'm missing something.
<bindings>
<binding name="CC2WebSoap">
<security mode="Transport">
<transport clientCredentialType="Certificate" />
</security>
</binding>
</bindings>
<client>
<endpoint address="https://url_goes_here.com"
binding="basicHttpBinding"
bindingConfiguration="CC2WebSoap"
contract="acontract"
name="CC2WebSoap"
behaviorConfiguration="SecureClientBehavior"/>
</client>
<behaviors>
<endpointBehaviors>
<behavior name="SecureClientBehavior">
<clientCredentials>
<clientCertificate findValue="mythumbprint" storeLocation="LocalMachine" storeName="My" x509FindType="FindByThumbprint"/>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
try
{
CC2WebSoap client= new CC2WebSoapClient("CC2WebSoap");
client.ClientCredentials.UserName.UserName = "username";
client.ClientCredentials.UserName.Password = "password";
request = BuildRequest();
response = client.DoSomething(request);
}
catch(Exception e){ // Always get 401 exception here. }
This turned out to be reasonably simple by adding a MessageInspector and the related classes to get WCF to attach the username and password to the headers before each request. Specifically, I followed the advice in the blog post below exactly.
Using a MessageInspector To modify HTTP Headers
Long story short:
My WCF clients should be able to provide both username and certificate to a service hosted in IIS, where I should use that information to validate requests using a custom policies.
Complete story:
I have the need to authenticate some WCF clients to verify if they can execute operations.
We have two kinds of clients: WPF applications and a web application. We would like to do the following:
The web application uses a certificate trusted by the service so that it is recognized as a special user with all permissions (the web application already verifies permissions by itself and we wouldn't like to touch it by now)
The WPF clients authenticate themselves with username/password provided by the user
In the implementation of the operations, I would like to verify if the certificate was provided (then I recognize the "super user"), otherwise fallback to username/password authentication.
Services are hosted in IIS 7 and we need to use NetTcpBinding.
I was able to implement the username validation, but the problem is that the AuthorizationContext inspected by the service contains only identity information, and not the certificate.
The following code is used on the client side to initialize the creation of channels (from a spike I'm using to test the solution):
var factory = new ChannelFactory<T>(this.Binding, address);
var defaultCredentials = factory.Endpoint.Behaviors.Find<ClientCredentials>();
factory.Endpoint.Behaviors.Remove(defaultCredentials);
var loginCredentials = new ClientCredentials();
loginCredentials.ServiceCertificate.Authentication.CertificateValidationMode =
X509CertificateValidationMode.None;
loginCredentials.UserName.UserName = username;
loginCredentials.UserName.Password = password;
if (useCertificate)
{
loginCredentials.SetCertificate();
}
factory.Endpoint.Behaviors.Add(loginCredentials);
return factory.CreateChannel();
With the SetCertificate extension being implemented like this:
public static void SetCertificate(this ClientCredentials loginCredentials)
{
loginCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SecureWcfClient");
}
This is the configuration of the web application hosting the services:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="SecureBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<serviceCertificate findValue="Test"
storeLocation="LocalMachine"
storeName="My"
x509FindType="FindBySubjectName" />
<clientCertificate>
<authentication certificateValidationMode="Custom" customCertificateValidatorType="AuthenticationProtectedService.Security.CertificateValidator, AuthenticationProtectedService.Security"/>
</clientCertificate>
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="AuthenticationProtectedService.Security.UserNamePassValidator, AuthenticationProtectedService.Security" />
</serviceCredentials>
<serviceAuthorization serviceAuthorizationManagerType="AuthenticationProtectedService.Security.CertificateAuthorizationManager, AuthenticationProtectedService.Security"/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netTcpBinding>
<binding>
<security mode="None"/>
</binding>
<binding name="SecureNetTcp">
<security mode="Message">
<message clientCredentialType="UserName"/>
</security>
</binding>
</netTcpBinding>
</bindings>
<service
name="AuthenticationProtectedService.Services.OneWayServiceB"
behaviorConfiguration="SecureBehavior">
<endpoint
address=""
binding="wsHttpBinding"
contract="AuthenticationProtectedService.ServiceModel.IOneWayServiceB">
</endpoint>
</service>
<service
name="AuthenticationProtectedService.Services.DuplexServiceB" behaviorConfiguration="SecureBehavior">
<endpoint
address=""
binding="netTcpBinding"
bindingConfiguration="SecureNetTcp"
contract="AuthenticationProtectedService.ServiceModel.IDuplexServiceB">
</endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"/>
</service>
</services>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
Finally, this is the implementation of the custom authorization manager (I also tried with a custom certificate validator but the function was never run)
public class CertificateAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{
if (!base.CheckAccessCore(operationContext))
{
return false;
}
string thumbprint = GetCertificateThumbprint(operationContext);
// I'd need to verify the thumbprint, but it is always null
return true;
}
private string GetCertificateThumbprint(OperationContext operationContext)
{
foreach (var claimSet in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
{
foreach (Claim claim in claimSet.FindClaims(ClaimTypes.Thumbprint, Rights.Identity))
{
string tb = BitConverter.ToString((byte[])claim.Resource);
tb = tb.Replace("-", "");
return tb;
}
}
return null;
}
}
I think that the problem could be in the clientCredentialType property of the nettcpbinding.Security.Message node on the service configuration, but I don't see the option to use both Certificate and Username withing the Message security.
Any help appreciated, thanks
Remark: a specific goal of the project is to have very low level impact on server setup and in general in the system, so also SSL should be avoided if possible.
try out this link http://msdn.microsoft.com/en-us/library/ms733099.aspx ...it might resolve your issue where in you can have different binding configuration for same binding type and associate the same to different endpoints as per your need.
I came across a page on MSDN explaining transaction in WCF Services here. I tweaked the binding settings and used netTcpBinding. Here is the serviceModel section of my app.config file:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="netTcpBindingConfiguration1" transactionFlow="true">
<security mode="Message" />
</binding>
</netTcpBinding>
</bindings>
<services>
<service name="OrderingService.OrderService">
<clear />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"
listenUriMode="Explicit">
</endpoint>
<endpoint address="net.tcp://localhost:8880/OrderingService"
binding="netTcpBinding" bindingConfiguration="netTcpBindingConfiguration1"
contract="OrderingService.IOrderService" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8888/OrderingService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
I created a windows application as the client of the service. I used netstat command to see the TCP connections between the client and the service (hosted in a console application). I realized for each operation (which was a button click in my client app that places a new order by invoking the methods of the service's proxy class), a new connection is created and all previous connections still remain ESTABLISHED. Obviously, this is not an ideal condition. I wondered what I did wrong and what setting or configuration would work out this problem by reducing the number of connections to only one. By the way, the service class that implements the service interface has InstanceContextMode set to PerSession. Here are the contract interface and the service class:
[ServiceContract(SessionMode=SessionMode.Required)]
public interface IOrderService
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.NotAllowed)]
List<Customer> GetCustomers();
[OperationContract]
[TransactionFlow(TransactionFlowOption.NotAllowed)]
List<Product> GetProducts();
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
string PlaceOrder(Order order);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
string AdjustInventory(int productId, int quantity);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
string AdjustBalance(int customerId, decimal amount);
}
[ServiceBehavior(TransactionIsolationLevel = IsolationLevel.Serializable,
TransactionTimeout = "00:00:20",
InstanceContextMode = InstanceContextMode.PerSession,
TransactionAutoCompleteOnSessionClose = true)]
public class OrderService : IOrderService
{...}
Here is the code the uses the proxy class in the client app:
using (TransactionScope scope = new TransactionScope())
{
try
{
proxy = new OrderServiceClient("NetTcpBinding_IOrderService");
result = proxy.PlaceOrder(order);
MessageBox.Show(result);
result = proxy.AdjustInventory(product.ProductId, quantity);
MessageBox.Show(result);
result = proxy.AdjustBalance(customer.CustomerId, product.Price * quantity);
MessageBox.Show(result);
proxy.Close();
scope.Complete();
}
catch (Exception exc)
{
MessageBox.Show("Error occurred: " + exc.Message);
}
}
With regards to the TCP connection remaining ESTABLISHED - are you calling .Close() on your instance of the client when you are finished with it?
If you want to use a single connection you should change the instance context mode to 'Single' and reuse the connection you establish in the client to process all your service calls. This suits an architecture where you want to maintain state within your service.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Service : IService
{
}
I found this link very helpful when I was learning about context modes in WCF: CodeProject link
As you are currently using PerSession context mode you should be able to limit it to a single connection by adding a setting for maxConcurrentSessions in your behaviors section. You can do it like this:
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" />
<serviceDebug includeExceptionDetailInFaults="False" />
<serviceThrottling maxConcurrentSessions="1" />
</behavior>
</serviceBehaviors>
This would only be a good idea if you have a single client.
I want to make a web service with the following properties:
It uses WCF and .NET 4.0
It is hosted in IIS7
It is RESTful
It's okay to keep the default output behaviour of collecting and handling WebFaultExceptions etc
It has a single call that
eats naked HTTP POST of potentially huge binary files (should preferably not be kept in memory!)
accepts a Stream as an input
outputs a Stream
uses an UriTemplate for matching (there will be more calls soon)
wants the streams to be completely raw and NOT have IIS or WCF try to be smart by handling the content type in any way
The problem is that IIS and/or WCF keep interfering regarding the Content-Type, insisting on returning
415 Cannot process the message because the content type '...' was not the expected type 'text/xml; charset=utf-8'
no matter what the content type was. Can you spot any errors I have made below?
[ServiceContract]
public interface IRenderService
{
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "/render", BodyStyle = WebMessageBodyStyle.Bare)]
Stream Render(Stream input);
}
With the following snippets from Web.config:
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpRuntime maxRequestLength="500000000" />
</system.web>
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding
name="FileStreamConfiguration"
transferMode="Streamed"
maxReceivedMessageSize="500000000"
maxBufferSize="500000000"
openTimeout="00:25:00"
closeTimeout="00:25:00"
sendTimeout="00:25:00"
receiveTimeout="00:25:00" />
</webHttpBinding>
</bindings>
<services>
<service name="RenderService" behaviorConfiguration="RenderServiceBehavior">
<endpoint address="" binding="webHttpBinding" contract="RenderServer.IRenderService" bindingConfiguration="FileStreamConfiguration" behaviorConfiguration="RenderEndpointBehaviour" >
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="RenderServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="RenderEndpointBehaviour">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
I want to always get the raw contents of the HTTP POST body, and fetch headers from WebOperationContext.Current.IncomingRequest manually if I deem that necessary, and IIS/WCF should completely ignore all aspects of the request besides parsing it and sending it to my code. I'll use WebOperationContext.Current.OutgoingResponse to set aspects of the output as I see fit, also manually.
This is so easy to do with the new WCF Web API library. See http://wcf.codeplex.com I have a sample on my blog which I will post once the power comes back on :-)
The interface looks like this,
[ServiceContract]
public interface IRenderService{
[WebInvoke(Method = "POST", UriTemplate = "/render")]
HttpResponseMessage Render(HttpRequestMessage input);
}