WCF Session Id changes with each call - c#

I am trying to track and use a session id between calls to a WCF service however after each call by the same WCF client instance I receive two different session ids.
Here is the service contract, specifying that sessions are required:
[ServiceContract(SessionMode=SessionMode.Required)]
public interface IMonkeyService
{
[OperationContract(ProtectionLevel=System.Net.Security.ProtectionLevel.None, IsOneWay = true, IsInitiating = true, IsTerminating = false)]
void Init();
[OperationContract(ProtectionLevel = System.Net.Security.ProtectionLevel.None, IsOneWay = false, IsInitiating = false, IsTerminating = false)]
string WhereAreMyMonkies();
[OperationContract(ProtectionLevel = System.Net.Security.ProtectionLevel.None, IsOneWay = true, IsInitiating = false, IsTerminating = true)]
void End();
}
Here is the service implementation:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, IncludeExceptionDetailInFaults=true)]
public class BestMonkeyService : IMonkeyService
{
public void Init() { ;}
public void End() { ;}
public string WhereAreMyMonkies()
{
return "Right here";
}
}
Here is the service being opened:
_Service = new ServiceHost(typeof(BestMonkeyService));
var binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.None;
binding.ReliableSession.Enabled = true;
string uriAddress = "http://localhost:8000/MONKEY_SERVICE";
var endpoint = _Service.AddServiceEndpoint(typeof(IMonkeyService), binding, uriAddress);
_Service.Open();
Here is the client configuration
<system.serviceModel>
<client>
<endpoint address="http://localhost:8000/MONKEY_SERVICE" bindingConfiguration="wsHttpBindingConfiguration"
binding="wsHttpBinding" contract="IMonkeyService" />
</client>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBindingConfiguration">
<security mode="None" />
<reliableSession enabled="true" />
</binding>
</wsHttpBinding>
</bindings>
</system.serviceModel>
The call by the client which is kept open after the first call:
var client = new MonkeyClient();
client.Init();
client.WhereAreMyMonkies();
client.WhereAreMyMonkies();
client.End();
I am getting an ActionNotSupportedException
The message with Action 'http://tempuri.org/IMonkeyService/WhereAreMyMonkies' 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).
I have both service and client configured using the same binding and security. What am I missing?

As explained here, because the default value of IsInitiating parameter is true each of your calls started a new session.
You need something like this:
[OperationContract(IsInitiating=false, IsTerminating=false)]
string WhereAreMyMonkies();
By default a session is initiated when channel is opened. You can also add methods that explicitly create and terminate a session (as explained in the documentation).

WSHttpBinding doesn't support sessions without reliable messaging or security sessions.
See the answer to How to enable Session with SSL wsHttpBinding in WCF

Related

How to read System.serviceModel in Class Library with Proxy implementation of WCF service

I am trying to read service configuration in Class Library from the app.config file of the library. but getting below exception
Could not find endpoint element with name 'WSHttpBinding_IUsers' and contract 'ISGP.Plugins.SolveService.IUsers'
in the ServiceModel client configuration section.
This might be because no configuration file was found for your application,
or because no endpoint element matching this name could be found in the client element.
I tried ti implement the below code to read the config and create the client
public class ServiceManager
{
public static T CreateServiceClient<T>(string configName)
{
// string _assemblyLocation = Assembly.GetExecutingAssembly().Location;
var configuration = ConfigurationManager.OpenMappedExeConfiguration(
new ExeConfigurationFileMap
{
ExeConfigFilename = "app.config"
}, ConfigurationUserLevel.None);
ConfigurationChannelFactory<T> channelFactory = new ConfigurationChannelFactory<T>(configName, configuration, null);
var client = channelFactory.CreateChannel();
return client;
}
}
Call this method as below
usersClient= (UsersClient)ServiceManager.CreateServiceClient<IUsersChannel>("WSHttpBinding_IUsers");
usersClient.ClientCredentials.UserName.UserName = userName;
usersClient.ClientCredentials.UserName.Password = password;
But it throws the above exception the app.config file is as below
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IUsers">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" />
<message clientCredentialType="UserName" establishSecurityContext="false" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://xxxxxx16.prod.xxxxx.local/WebServices/v3/Users.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IUsers"
contract="ISGP.Plugins.SolveService.IUsers" name="WSHttpBinding_IUsers" />
</client>
</system.serviceModel>
Please pass the endpoint parameter in the channel factory constructor.
ConfigurationChannelFactory channelFactory = new
ConfigurationChannelFactory(configName, configuration, null);
You can refer to the following code.
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = "app.config";
Configuration newConfiguration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
ConfigurationChannelFactory<IService> factory = new ConfigurationChannelFactory<IService>("endpoint1", newConfiguration, new EndpointAddress("http://localhost:8000/servicemodelsamples/service"));
factory.Credentials.UserName.UserName = "jack";
factory.Credentials.UserName.Password = "123456";
IService client1 = factory.CreateChannel();
https://learn.microsoft.com/en-us/dotnet/framework/wcf/samples/configuration-channel-factory
Feel free to let me know if there is anything I can help with.

What is the .NET equivalent of this PHP web service call?

I am attempting to utilise an API, and the providers can only offer a PHP sample, which I have summarised as follows (with sensitive data removed):
<?php
ini_set('default_socket_timeout', 1600);
$options = array(
'login' => 'myusername',
'password' => 'mypassword',
'trace' => 1
);
$url = 'https://supplierurl/ws?wsdl';
$soapClient = new \SoapClient($url, $options);
$params = array('12345');
try{
$details = $soapClient->getData($params);
var_dump($details->paramdetails);
}
catch(\Exception $e){
echo "Last request headers:<br>\n".$soapClient->__getLastRequestHeaders()."<br><br>\n";
echo "Last request:<br>\n".$soapClient->__getLastRequest()."<br><br>\n";
echo "Last response headers:<br>\n".$soapClient->__getLastResponseHeaders()."<br><br>\n";
echo "Last response:<br>\n".$soapClient->__getLastResponse()."<br><br>\n";
}
?>
I have successfully run this on my development machine and get back the data as expected.
I have attempted to use this service in .NET by adding a service reference using the url provided, which generates the proxy code as expected, giving me a configuration as below:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="mybinding">
<security mode="Transport">
<transport clientCredentialType="Basic"/>
</security>
</binding>
<binding name="mybinding1" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://supplierurl/ws"
binding="basicHttpBinding" bindingConfiguration="mybinding"
contract="API.portType" name="API.port" />
</client>
</system.serviceModel>
And test code as below:
API.portClient client = new API.portClient();
client.ClientCredentials.UserName.UserName = "myusername";
client.ClientCredentials.UserName.Password = "mypassword";
API.GetDataResponse response = client.getData(new string[] { "12345" });
The code executes with no exceptions thrown, but response is null. If I change the username or password to something invalid, I get an exception, indicating that the credentials side of things is working.
If anyone can point me in the right direction, it would be much appreciated!
Some further information, if I add this as a web reference it works, which gets me moving for now, although I'd still like to know how to make it work in the first instance. Code for using a web reference:
WebReference.customerV4Service svc = new WebReference.customerV4Service();
svc.Credentials = new System.Net.NetworkCredential("myusername", "mypassword");
WebReference.GetDataResponse resp = svc.getData(new string[] { "12345" });

WCF - Setting Policy for UsernameToken

I received an updated WSDL for a service I'm consuming which has below Policy added
<wsp:Policy wssutil:Id="UsernameToken">
<ns0:SupportingTokens xmlns:ns0="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200512">
<wsp:Policy>
<ns0:UsernameToken ns0:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200512/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<ns0:WssUsernameToken10 />
</wsp:Policy>
</ns0:UsernameToken>
</wsp:Policy>
</ns0:SupportingTokens>
</wsp:Policy>
I have updated my reference by right clicking the Service Reference --> Configure Service option inside Visual Studio. This generated a customBinding replacing my previous basicHttpBinding
<customBinding>
<binding name="myBindingName">
<!-- WsdlImporter encountered unrecognized policy assertions in ServiceDescription 'http://ouaf.oracle.com/webservices/cm/CM-CustConnAcctDetailExtract': -->
<!-- <wsdl:binding name='CM-CustConnAcctDetailExtractSoapBinding'> -->
<!-- <ns0:SupportingTokens xmlns:ns0="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200512">..</ns0:SupportingTokens> -->
<textMessageEncoding messageVersion="Soap11" />
<httpTransport />
</binding>
</customBinding>
Do I need to use this customBinding only? Or is there any option in basicBinding that makes it work?
If I use my basicBinding with TransportWithMessageCredential, I get below error:
The provided URI is Invalid; HTTPS is expected
I ran this using SoapUI. In addition to UserName and Passwrod, I had to supply WSS-PasswordType as PasswordText. Without supplying this parameter, I get an error in SoapUI
Error on verifying message against security policy Error code:1000
I'm not sure how to supply WSS-PasswordType in my basicHttpBinding.
My basicHttpBinding is as below
protected BasicHttpBinding GetBinding()
{
return new BasicHttpBinding()
{
Name = "BasicHttpBinding",
ReceiveTimeout = new TimeSpan(0, 0, 2, 0),
SendTimeout = new TimeSpan(0, 0, 2, 0),
Security =
{
Mode = BasicHttpSecurityMode.TransportCredentialOnly,
Transport =
{
ClientCredentialType = HttpClientCredentialType.Basic,
ProxyCredentialType = HttpProxyCredentialType.None,
Realm = ""
},
Message =
{
ClientCredentialType = BasicHttpMessageCredentialType.UserName,
AlgorithmSuite = SecurityAlgorithmSuite.Default
}
},
MaxBufferSize = Int32.MaxValue,
MaxReceivedMessageSize = Int32.MaxValue,
ReaderQuotas = new XmlDictionaryReaderQuotas()
{
MaxBytesPerRead = 8192
}
};
}
I'm able to work this through by changing my binding to Custom Binding
protected CustomBinding GetCustomBinding()
{
var customBinding = new CustomBinding() { Name = "CustomBinding" };
customBinding.Elements.Add(new TextMessageEncodingBindingElement() { MessageVersion = MessageVersion.Soap11 });
var securityBindingElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
securityBindingElement.AllowInsecureTransport = true;
securityBindingElement.EnableUnsecuredResponse = true;
securityBindingElement.IncludeTimestamp = false;
customBinding.Elements.Add(securityBindingElement);
customBinding.Elements.Add(new HttpTransportBindingElement());
return customBinding;
}

How authenticate signed-in user via self-hosted WCF REST API

I have a self-hosted WCF RESTful API that exposes some functionality that I don't want exposed to unauthorized users. All administrators must be signed in using a custom ASP.NET membership provider to call the REST API. Currently I just send a API key which is unsecure as it can be seen by all. All calls to the REST API is done via jQuery. I'm not using TLS/SSL or other transport security mechanisms. All REST API calls are done against the same server/domain, so there are no cross-domain calls or JSONP stuff going on.
My question is, what is the best practice in my case for securing my REST API? Perhaps I should use OAuth for this - the more I read about OAuth the more it seems it is not for my scenario with jQuery.
IVeraCMS.cs:
[ServiceContract]
public interface IVeraCMS {
[OperationContract]
[WebInvoke(Method = "GET",
BodyStyle = WebMessageBodyStyle.WrappedRequest,
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json)]
string PerformanceCounter(string API_Key);
}
VeraCMS.cs:
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerCall,
IncludeExceptionDetailInFaults = false, MaxItemsInObjectGraph = 1000)]
public class VeraCMS : IVeraCMS
{
public string PerformanceCounter(string API_Key)
{
if (ConfigurationManager.AppSettings["API_key"] != API_Key)
throw new SecurityException("Access denied");
var procPercentage = new PerformanceCounter("Processor", "% Processor Time", "_Total");
procPercentage.NextValue();
var memPercentage = new PerformanceCounter("Memory", "Available MBytes");
memPercentage.NextValue();
const int samplingIntervalMs = 100;
Thread.Sleep(samplingIntervalMs);
var json = "{" + String.Format("\"ProcTime\":\"{0}%\",\"AvailMemory\":\"{1}MB\"" ,
procPercentage.NextValue().ToString(), memPercentage.NextValue().ToString()
) + "}";
return json;
}
}
}
Web.config:
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="VeraWAF.WebPages.Interfaces.VeraCMS.Endpoint.Binding" maxReceivedMessageSize="4096" crossDomainScriptAccessEnabled="true" />
</webHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="VeraWAF.WebPages.Interfaces.VeraCMS.Service.Behavior"
name="VeraWAF.WebPages.Interfaces.VeraCMS">
<endpoint address="" behaviorConfiguration="VeraWAF.WebPages.Interfaces.VeraCMS.Endpoint.Behavior"
binding="webHttpBinding" bindingConfiguration="VeraWAF.WebPages.Interfaces.VeraCMS.Endpoint.Binding"
contract="VeraWAF.WebPages.Interfaces.IVeraCMS" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="VeraWAF.WebPages.Interfaces.VeraCMS.Endpoint.Behavior">
<webHttp defaultOutgoingResponseFormat="Json" />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="VeraWAF.WebPages.Interfaces.VeraCMS.Service.Behavior">
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
You can do basic HTTP authentication like this:
WebServiceHost secureHost
secureHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
secureHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new ClientValidator(username, password);
// Need to reference System.IdentityModel
public class ClientValidator : UserNamePasswordValidator
{
private readonly string _password;
private readonly string _username;
public ClientValidator(string username, string password)
{
_password = password;
_username = username;
}
public override void Validate(string userName, string password)
{
if (userName != _username || (password != _password))
{
WebFaultException rejectEx = new WebFaultException(HttpStatusCode.Unauthorized);
rejectEx.Data.Add("HttpStatusCode", rejectEx.StatusCode);
throw rejectEx;
}
}
}
Just keep in mind that your username and password can be easily sniffed if you are not using SSL. You can change the Validate method to fetch username and password from DB or some other service.

C# WCF Web Api 4 MaxReceivedMessageSize

I am using the WCF Web Api 4.0 framework and am running into the maxReceivedMessageSize has exceeded 65,000 error.
I've updated my webconfig to look like this but because I am uisng the WCF Web Api I think this isn't even used anymore as I am no longer using a webHttpEndpoint?
<standardEndpoints>
<webHttpEndpoint>
<!--
Configure the WCF REST service base address via the global.asax.cs file and the default endpoint
via the attributes on the <standardEndpoint> element below
-->
<standardEndpoint name=""
helpEnabled="true"
automaticFormatSelectionEnabled="true"
maxReceivedMessageSize="4194304" />
</webHttpEndpoint>
Where do I specify MaxReceivedMessageSize in the new WCF Web Api?
I've also tried a CustomHttpOperationHandlerFactory to no avail:
public class CustomHttpOperationHandlerFactory: HttpOperationHandlerFactory
{
protected override System.Collections.ObjectModel.Collection<HttpOperationHandler> OnCreateRequestHandlers(System.ServiceModel.Description.ServiceEndpoint endpoint, HttpOperationDescription operation)
{
var binding = (HttpBinding)endpoint.Binding;
binding.MaxReceivedMessageSize = Int32.MaxValue;
return base.OnCreateRequestHandlers(endpoint, operation);
}
}
the maxReceivedMessageSize is a property you have to define on the binding you´re using. WCF in .Net 4 introduced the simplified configuration, so if you don´t configure anything, default values will be used.
The example below is valid for the wshttpBinding, you have the ammend it according to your used binding and register it in your web.config (assuming you´re using an IIS hosted service) in the servicemodel-binding section.
<wsHttpBinding>
<binding name="CalculatorBinding" maxBufferPoolSize="2000000" maxReceivedMessageSize="2000000000" >
<security mode="Transport" >
<transport clientCredentialType="Windows" />
</security>
<readerQuotas maxDepth="2000000" maxStringContentLength="2000000"
maxArrayLength="2000000"
maxBytesPerRead="2000000"
maxNameTableCharCount="2000000" />
</binding>
</wsHttpBinding>
HTH
Dominik
If you're trying to do this programatically (via using MapServiceRoute with HttpHostConfiguration.Create) the way you do it is like this:
IHttpHostConfigurationBuilder httpHostConfiguration = HttpHostConfiguration.Create(); //And add on whatever configuration details you would normally have
RouteTable.Routes.MapServiceRoute<MyService, NoMessageSizeLimitHostConfig>(serviceUri, httpHostConfiguration);
The NoMessageSizeLimitHostConfig is an extension of HttpConfigurableServiceHostFactory that looks something like:
public class NoMessageSizeLimitHostConfig : HttpConfigurableServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var host = base.CreateServiceHost(serviceType, baseAddresses);
foreach (var endpoint in host.Description.Endpoints)
{
var binding = endpoint.Binding as HttpBinding;
if (binding != null)
{
binding.MaxReceivedMessageSize = Int32.MaxValue;
binding.MaxBufferPoolSize = Int32.MaxValue;
binding.MaxBufferSize = Int32.MaxValue;
binding.TransferMode = TransferMode.Streamed;
}
}
return host;
}
}
If you are hosting in IIS, you can set the values where ever you define the route (in my case, the global.asax). If you have TransferMode set to buffered (the default), you will also need to set MaxBufferSize to the same value as MaxReceivedMessageSize.
protected void Application_Start()
{
var config = new HttpConfiguration();
config.MaxReceivedMessageSize = int.MaxValue;
config.MaxBufferSize = int.MaxValue;
RouteTable.Routes.MapServiceRoute<MyService>("api", config);
}
This is how you can do it in code
var endpoint = ((HttpEndpoint)host.Description.Endpoints[0]); //Assuming one endpoint
endpoint.TransferMode = TransferMode.Streamed;
endpoint.MaxReceivedMessageSize = 1024 * 1024 * 10; // Allow files up to 10MB
If you create a your own custom host, derived from the standard one, there is a method that you can overload to configure the HttpEndpoint.

Categories

Resources