WCF Service service call to stream endpoint fails on 3rd call - c#

quick overview of what I've got here:
WCF Soap service is running over HTTPS, with Message credential of type certificate. I've got 2 endpoints that I use (besides mex), 1 for my normal service calls, the other for streaming files. Here's my web.config for the service (edited some names of items):
<bindings>
<wsHttpBinding>
<binding name="wsHttpEndpointBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="Certificate" />
</security>
</binding>
</wsHttpBinding>
<basicHttpBinding>
<binding name="streamBinding" transferMode="Streamed" messageEncoding="Mtom" maxReceivedMessageSize="2147483646">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="Certificate" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="Services.Behavior" name="MyInterface">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttpEndpointBinding" name="wsHttpEndpoint" contract="IMyInterface" />
<endpoint address="stream" binding="basicHttpBinding" bindingConfiguration="streamBinding" name="streamEndpoint" contract="IMyInterface" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Service.Behavior">
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<dataContractSerializer maxItemsInObjectGraph="2147483646" />
<serviceCredentials>
<serviceCertificate findValue="CN=Server.Temp.Dev" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
The service has a method, DownloadDocument, that I'm testing. Here's the signature:
DocumentDownloadResponse DownloadDocument(DocumentDownloadRequest request);
It's worth noting the the service throws Exceptions when the data passed in is not valid, and DownloadDocument catches those exceptions and passes back the error message in the response object.
The DocumentDownloadRequest looks like this:
[MessageContract]
public class DocumentDownloadRequest
{
[MessageHeader]
public string SecurityToken { get; set; }
[MessageHeader]
public string LoginName { get; set; }
[MessageHeader]
public string DocumentId { get; set; }
}
And DownloadDocumentResponse:
[MessageContract]
public class DocumentDownloadResponse : ServiceResponse<Stream>
{
public DocumentDownloadResponse()
{
Data = Stream.Null;
}
[MessageHeader(MustUnderstand = true)]
public bool Success { get; set; }
[MessageHeader(MustUnderstand = true)]
public string ErrorMessage { get; set; }
[MessageBodyMember(Order = 1)]
public Stream Data { get; set; }
}
Here's how I call it from the client:
var soapServiceClient = new SoapServiceClient("streamEndpoint");
bool success;
Stream stream;
string errorMessage =
soapServiceClient.DownloadDocument(documentId, loginName, securityToken, out success, out stream);
serviceClient.Close();
Where SecurityToken and LoginName are items that need to validates. What's strange is that from my test client, when I call DownloadDocument with valid data, I am able to download the file perfectly as many times as I want. However, if I pass in an invalid LoginName or SecurityToken, I get error messages indicating incorrect data (as expected). If I pass in invalid data 3 times, however, the client times out. Running the service locally I don't get this issue, everythign runs as expected. Strangely enough, whenI run with fiddler open, I don't get this issue. When I run with the service on my dev server, I have the problems.
The configuration on the dev server matches what I run locally. Using the SvcTraceTool I don't see any errors, except that it only documents the first 2 successful calls, and not the one that failed. It almost makes me think the endpoint just closed itself somehow.
Cliffs:
1) Service with 2 endpoints, one of if which is streaming (the one I'm concerned with).
2) Able to use streaming endpoint to call method to Download files with valid data
3) Service correctly catches bad data 2 times, hangs the 3rd time. No logs in SvcTraceTool, client times out.
Any ideas?
Thanks

In order to answer Rodrigo, I figured I'd post more details:
First off, wrap your generated proxy class in something sort of like this to handle error properly:
public class ProxyWrapper<TServiceClientType, TResultType>
where TServiceClientType : class, ICommunicationObject
{
private static string _endPoint;
public ProxyWrapper(string endPoint = "")
{
_endPoint = endPoint;
}
public TResultType Wrap(Func<string, TServiceClientType> constructWithEndpoint,
Func<TServiceClientType, TResultType> codeBlock)
{
TResultType result = default(TResultType);
TServiceClientType client = default(TServiceClientType);
try
{
client = constructWithEndpoint(_endPoint);
result = codeBlock(client);
client.Close();
}
catch (Exception)
{
if (client != null)
{
client.Abort();
}
throw;
}
return result;
}
}
I then have a client class that wraps around the service calls. Here's the DownloadDocument method:
public MyServiceResponse<Stream> DownloadDocument(string loginName,
string documentId)
{
var proxyWrapper = new MyProxyWrapper<DocumentDownloadResponse>(StreamEndpoint);
DocumentDownloadResponse response =
proxyWrapper.Wrap((client) =>
{
Stream data;
bool success;
string errorMessage = client.DownloadDocument(documentId, loginName,
out success,
out data);
return new DocumentDownloadResponse
{
Data = data,
Success = success,
ErrorMessage = errorMessage
};
});
var result = new MyServiceResponse<Stream>
{
Success = response.Success,
ErrorMessage = response.ErrorMessage
};
if (!response.Success)
{
result.Data = null;
response.Data.Close();
}
else
{
result.Data = response.Data;
}
return result;
}
Note: MyProxyWrapper inherits from ProxyWrapper and specified the WCF client proxy class. Now the actual call looks like this:
var myClient = new MyClient();
var downloadDocumentResponse = myClient.DownloadDocument(someId);
using (
Stream output =
File.OpenWrite(someFilePath))
{
downloadDocumentResponse.Data.CopyTo(output, 2048);
downloadDocumentResponse.Data.Close();
}
Note the two areas that that I call .Close() on the stream, once after writing the file, and once is response.Success == false.

Related

Is it possible to use both a certificate and UserName credential type with a service? C#

I am working on a project that makes requests to an API (which I think is WCF). The API requires an X509 certificate as a credential to be able to make requests. After referencing the service for the client and trying to make some requests, I was told that my requests were also missing a UserName header credential in order to authenticate with the API. Is it possible to use both types of credentials in a request and if so would anyone know how to set it up? Sorry if this post is poorly made, this is my first post on this website.
Edit: The username header that I need is at the SOAP message level. A header in the actual SOAP xml request.
This is my program code (with personal info taken out):
using System;
using System.Security.Cryptography.X509Certificates;
using EFM_User_Service_Test.EfmUserService;
namespace EFM_User_Service_Test
{
class Program
{
static void Main(string[] args)
{
X509Certificate2 cert = new X509Certificate2("path to pfx file", "password for file");
EfmUserServiceClient client = new EfmUserServiceClient();
client.ClientCredentials.ClientCertificate.Certificate = cert;
client.ClientCredentials.UserName.UserName = "test#email.com";
client.ClientCredentials.UserName.Password = "test password";
client.Open();
AuthenticateRequestType req = new AuthenticateRequestType();
req.Email = "test#email.com";
req.Password = "test password";
AuthenticateResponseType response = client.AuthenticateUser(req);
string user_id = "";
if (response.Error != null && response.Error.ErrorCode != "0")
{
Console.WriteLine(response.Error.ErrorCode);
Console.WriteLine(response.Error.ErrorText);
} else
{
Console.WriteLine("{0} {1} is now signed in",response.FirstName,response.LastName);
Console.WriteLine("authenticated user id: "+response.UserID);
user_id = response.UserID;
}
GetUserRequestType user_req = new GetUserRequestType();
user_req.UserID = user_id;
var user_response = client.GetUser(user_req);
if (user_response.Error != null)
{
Console.WriteLine("Error Code: "+user_response.Error.ErrorCode);
Console.WriteLine("Error Text: "+user_response.Error.ErrorText);
} else
{
Console.WriteLine(user_response.User.Email);
Console.WriteLine(user_response.User.FirstName);
Console.WriteLine(user_response.User.LastName);
Console.WriteLine(user_response.User.Email);
}
client.Close();
}
}
}
and this is my App.config file for the program which seemed to be automatically generated when I added the service reference
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IEfmUserService" messageEncoding="Mtom">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="Certificate" />
</security>
</binding>
<binding name="BasicHttpBinding_IEfmUserService1" messageEncoding="Mtom">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="in place of actual address"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IEfmUserService"
contract="EfmUserService.IEfmUserService" name="BasicHttpBinding_IEfmUserService" />
</client>
</system.serviceModel>
</configuration>
In time I solved this by adding a ServiceBehavior to my app.config file. Note that in my case it's a WPF application.
<configuration>
<system.serviceModel>
<extensions>
<behaviorExtensions>
<!-- Add this -->
<add name="basicAuthenticationBehavior" type="ProjectNamespace.Behaviors.BasicAuthenticationBehaviorExtensionElement, ProjectAssemblyName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<!-- Add this -->
<endpointBehaviors>
<behavior name="basicAuthenticationEndpointBehavior">
<basicAuthenticationBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<!-- After adding your service reference, your bindings appear here -->
<binding name="DMSinterfaceSoap">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<!-- After adding your service reference, your endpoints appear here -->
<endpoint address="https://soa.example.com/dmsinterface/DMS_service.asmx" binding="basicHttpBinding" behaviorConfiguration="basicAuthenticationEndpointBehavior" ... />
</client>
</system.serviceModel>
<configuration>
The BasicAuthenticationBehaviorExtensionElement looks like this
// Add System.Configuration reference to your ExecutingAssembly
internal class BasicAuthenticationBehaviorExtensionElement : BehaviorExtensionElement
{
/// <summary>Set this property if you want to send specific headers along with each request</summary>
public static TCredentials Credentials { internal get; set; }
public override Type BehaviorType
{
get { return typeof(BasicAuthenticationEndpointBehavior); }
}
protected override object CreateBehavior()
{
return new BasicAuthenticationEndpointBehavior();
}
}
class BasicAuthenticationEndpointBehavior : IEndpointBehavior
{
public BasicAuthenticationEndpointBehavior()
{
}
public void Validate(ServiceEndpoint endpoint) { }
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
clientRuntime.MessageInspectors.Add(new BasicAuthenticationClientMessageInspector());
}
}
class BasicAuthenticationClientMessageInspector : IClientMessageInspector
{
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
if (BasicAuthenticationBehaviorExtensionElement.Credentials != null)
{
SetRequestHeader(ref request, "USERID", BasicAuthenticationBehaviorExtensionElement.Credentials.Credential.UserName);
SetRequestHeader(ref request, "PSWD", BasicAuthenticationBehaviorExtensionElement.Credentials.Credential.Password);
}
return null;
}
private void SetRequestHeader(ref Message request, string key, string value)
{
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
var httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (httpRequestMessage != null)
{
httpRequestMessage.Headers[key] = (value ?? string.Empty);
}
else
{
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(key, (value ?? string.Empty));
request.Properties[HttpRequestMessageProperty.Name] = httpRequestMessage;
}
}
else
{
var httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(key, (value ?? string.Empty));
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
}
public void AfterReceiveReply(ref Message reply, object correlationState) { }
}
This adds the authentication header to each request, based on the object stored in static variable.
Then you can send the soap call with the authentication certificate:
#region Get Certificate
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
var foundCerts = store.Certificates.Find(X509FindType.FindBySerialNumber, credential.CertificateSerial, false);
if (foundCerts.Count == 0)
throw new Exceptions.NoValidCertificateException();
#endregion
#region Setup client
// Check the App.config for available endpoint configuration names
var client = new MySoapService.DMSinterfaceSoapClient("DMSinterfaceSoap.0");
if (client.Endpoint.Binding is System.ServiceModel.BasicHttpBinding basicBinding)
{
// DMSinterfaceSoap and DMSinterfaceSoap.0 are BasicHttpBindings
// The ClientCredentialType is by default set to None. You should be able to combine HttpClientCredentialTypes but sadly that's not possible.
basicBinding.Security.Transport.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.Certificate;
}
else if (client.Endpoint.Binding is System.ServiceModel.Channels.CustomBinding customBinding)
{
// DMSinterfaceSoap12 and DMSinterfaceSoap12.0 are CustomBindings
// Whatever additional configuration needs to be performed
}
#endregion
// Set the USERID and PSWD header
BasicAuthenticationBehaviorExtensionElement.Credentials.Credential = credential;
// Send certificate along with request
client.ClientCredentials.ClientCertificate.Certificate = foundCerts[0];
var error = client.GetResult(req, out res);
// Clear the USERID and PSWD header
BasicAuthenticationBehaviorExtensionElement.Credentials.Credential = null;

WCF protobuf endpoint 400 bad request

I have WCF service with DataContract json serialization. I would like to add service endpoints to consume Protobuf data messages.
I tried to use nugget package ProtoBuf.Services.WCF.
Added endpoint via web.config configuration. However, every request on protobuf endpoint with address "proto" returns 400 Bad request. Web.config sample is written bellow. Endpoint with default address "" works properly.
Get method:
HTTP 200 OK http://localhost:65460/BeaconService.svc/GetData
HTTP 400 BAD REQUEST: http://localhost:65460/BeaconService.svc/proto/GetData
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding transferMode="Streamed">
<security mode="None" />
</binding>
</webHttpBinding>
<basicHttpBinding>
<binding messageEncoding="Mtom">
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<extensions>
<behaviorExtensions>
<add name="protobuf" type="ProtoBuf.ServiceModel.ProtoBehaviorExtension, protobuf-net" />
</behaviorExtensions>
</extensions>
<services>
<service behaviorConfiguration="DefaultServiceBehavior" name="Services.BeaconService">
<endpoint address="" behaviorConfiguration="httpBehavior" binding="webHttpBinding" contract="Services.IBeaconService" />
<endpoint address="proto" behaviorConfiguration="protoBehavior" binding="basicHttpBinding" contract="Services.IBeaconService" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="protoBehavior">
<protobuf />
</behavior>
<behavior name="httpBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</system.serviceModel>
Please, which part of configuration is defective. Eventualy, what is proper way to call Get method on "proto" WCF endpoint to avoid HTTP 400 Bad request message?
Unfortunately, I have failed to implement ProtoBuf.Services.WCF and decided to use another approach. In general, WCF by default uses DataContractSerializer.
After reading this article, I realized It is possible to replace this serializer with another one, f.e. protobuf serializer in this library. So I created behavior extension, which replaces DataContractSerializer with my custom ProtobufSerializer. In configuration added another endpoint, which has set behavior extension to use my custom ProtobufSerializer.
WebHttpBehavior:
public class ProtobufBehavior : WebHttpBehavior
{
protected override IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
return new ProtobufDispatchFormatter(operationDescription);
}
protected override IDispatchMessageFormatter GetReplyDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
return new ProtobufDispatchFormatter(operationDescription);
}
}
Dispatch formatter:
namespace Services.Extension.ProtobufSerializationExtension
{
public class ProtobufDispatchFormatter : IDispatchMessageFormatter
{
OperationDescription operation;
bool isVoidInput;
bool isVoidOutput;
public ProtobufDispatchFormatter(OperationDescription operation)
{
this.operation = operation;
this.isVoidInput = operation.Messages[0].Body.Parts.Count == 0;
this.isVoidOutput = operation.Messages.Count == 1 || operation.Messages[1].Body.ReturnValue.Type == typeof(void);
}
public void DeserializeRequest(Message message, object[] parameters)
{
if (!message.IsEmpty)
{
XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
bodyReader.ReadStartElement("Binary");
byte[] rawBody = bodyReader.ReadContentAsBase64();
MemoryStream ms = new MemoryStream(rawBody);
using (StreamReader sr = new StreamReader(ms))
for (int i = 0; i < parameters.Length; i++)
parameters[i] = Serializer.Deserialize(operation.Messages[i].Body.Parts[i].Type, sr.BaseStream);
}
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
byte[] body;
using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms))
{
Serializer.Serialize(sw.BaseStream, result);
sw.Flush();
body = ms.ToArray();
}
Message replyMessage = Message.CreateMessage(messageVersion, operation.Messages[1].Action, new RawBodyWriter(body));
replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
return replyMessage;
}
class RawBodyWriter : BodyWriter
{
internal static readonly byte[] EmptyByteArray = new byte[0];
byte[] content;
public RawBodyWriter(byte[] content)
: base(true)
{
this.content = content;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("Binary");
writer.WriteBase64(content, 0, content.Length);
writer.WriteEndElement();
}
}
}
}
Extension element:
namespace Services.Extension.ProtobufSerializationExtension
{
public class ProtobufSerializationServiceElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(ProtobufBehavior); }
}
protected override object CreateBehavior()
{
return new ProtobufBehavior();
}
}
}
Web config:
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding transferMode="Streamed">
<security mode="None" />
</binding>
</webHttpBinding>
</bindings>
<extensions>
<behaviorExtensions>
<add name="protobufExtension" type="Services.Extension.ProtobufSerializationExtension.ProtobufSerializationServiceElement, Services" />
</behaviorExtensions>
</extensions>
<services>
<service behaviorConfiguration="DefaultServiceBehavior" name="Services.BeaconService">
<endpoint address="" behaviorConfiguration="httpBehavior" binding="webHttpBinding" contract="Services.IBeaconService" />
<endpoint address="proto" behaviorConfiguration="protoBehavior" binding="webHttpBinding" contract="Services.IBeaconService" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="protoBehavior">
<webHttp/>
<protobufExtension/>
</behavior>
<behavior name="httpBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</system.serviceModel>
Services.Extension.ProtobufSerializationExtension is name of my custom namespace inside application structure. Hope this helps someone.

Can I get/set data into WCF Service by Host?

I have WCF Service hosted on WindowsServiceHost (to communicate WindowsFormsApp <> WindowsServiceHost)
Is there any way get data from WCFService to WindowsServiceHost?
And in other way (set data from WindowsServiceHost to WCFService)
That is what have i done:
I've made a project of WCF Service Library, implemented interface, contracts etc.
I created new project - Windows service and added reference to project from #1 and to System.ServiceModel
Configured app.conf:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="netTcp">
<security mode="Message">
</security>
</binding>
</netTcpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="mexBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="mexBehavior" name="KSPDJOBWinWCFService.KSPDJOBWinWCFService" >
<endpoint address="KSPDJOBWinWCFService" binding="netTcpBinding" contract="KSPDJOBWinWCFService.IKSPDJOBWinWCFService" bindingConfiguration="netTcp" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8079"/>
<add baseAddress="net.tcp://localhost:8090"/>
</baseAddresses>
</host>
</service>
</services>
I've hosted the WCF in OnStart method of Windows Service
protected override void OnStart(string[] args)
{
host = new ServiceHost(typeof(KSPDJOBWinWCFService.KSPDJOBWinWCFService));
host.Open();
}
Added new solution with WinformsClient app (as WCF Client) and tested communication - all working fine.
The problem is when i send a value from WinFormsClient to WCF Service, and want to read it from Windows Service aplication
Thanks for any Help.
You could hold the WCF service instance in a global variable and work with events. In this sample the WCF Service KSPDJOBWinWCFService exposes an event EventA and the Service Host will handle it. This is the place where you can process the values sent by your WCF Client.
public partial class Service : ServiceBase
{
private ServiceHost _host;
private KSPDJOBWinWCFService _instance;
protected override void OnStart(string[] args)
{
try
{
_instance = new KSPDJOBWinWCFService();
_instance.EventA += HandleEventA;
_host = new ServiceHost(_instance);
_host.Open();
}
catch (Exception ex)
{
// Logging
}
}
public void HandleEventA(object sender, CustomEventArgs e)
{
// do whatever you want here
var localVar = e.Value;
}
protected override void OnStop()
{
try
{
if (_instance != null)
{
_instance.Dispose();
}
_host.Close();
}
catch (Exception ex)
{
// Logging
}
}
}
The WCF Service then fires this event together with the values sent from the WCF client:
public class KSPDJOBWinWCFService : IKSPDJOBWinWCFService
{
public event EventHandler<CustomEventArgs> EventA;
public bool SomeWcfOperation(int value)
{
EventA?.Invoke(this, new CustomEventArgs(value));
return true;
}
}
Create event args that fulfill your needs:
public class CustomEventArgs : EventArgs
{
public int Value { get; set; }
public CustomEventArgs(int value)
{
Value = value;
}
}
You can also expose values with public properties in your WCF Service. But events are also necessary.

How to limit secure protocol in wcf service

I need to write WCF service using TLS 1.2.I need to use only this security protocol and (as i think) refuse connections with other secure protocol types. I have created certificate. Bind it to port. Https works well. I read everywhere that i need to write next string of code:
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Ok, i wrote it, but had no effect. Service side code:
using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Net;
using System.ServiceModel;
using System.Runtime.Serialization;
using static System.Console;
namespace ConsoleHost
{
public class DistributorValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
throw new SecurityTokenException("Both username and password required");
if (userName != "login" || password != "pass")
throw new FaultException($"Wrong username ({userName}) or password ");
}
}
public class Service1 : IService1
{
public string GetData(int value)
{
return $"You entered: {value}";
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
if (composite == null)
{
throw new ArgumentNullException(nameof(composite));
}
if (composite.BoolValue)
{
composite.StringValue += "Suffix";
}
return composite;
}
}
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
[OperationContract]
CompositeType GetDataUsingDataContract(CompositeType composite);
}
[DataContract]
public class CompositeType
{
[DataMember]
public bool BoolValue { get; set; } = true;
[DataMember]
public string StringValue { get; set; } = "Hello ";
}
class Program
{
static void Main(string[] args)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServiceHost host = new ServiceHost(typeof(Service1));
host.Open();
WriteLine("Press any key to stop server...");
ReadLine();
}
}
}
App.config contains:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<system.serviceModel>
<services>
<service name="ConsoleHost.Service1">
<host>
<baseAddresses>
<add baseAddress = "https://localhost:8734/Service1/" />
</baseAddresses>
</host>
<endpoint address="" binding="wsHttpBinding" contract="ConsoleHost.IService1" bindingConfiguration="securityBinding">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="securityBinding">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" />
<message clientCredentialType="UserName" />
<!--establishSecurityContext="false" />-->
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="False" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="ConsoleHost.DistributorValidator,ConsoleHost"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Client side code:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
try
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
Service1Client client = new Service1Client();
client.ClientCredentials.UserName.UserName = "login";
client.ClientCredentials.UserName.Password = "pass";
Console.WriteLine(client.GetData(10));
}
catch (Exception ex)
{
Console.WriteLine("Exception: " + ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine("Inner: " + ex.InnerException.Message);
if (ex.InnerException.InnerException != null)
Console.WriteLine("Inner: " + ex.InnerException.InnerException.Message);
}
}
Console.ReadLine();
}
}
}
As you can see on service side i have set security protocol to Tls 1.2. On client side i have set security protocol to Ssl3. I am waiting that service will refuse client connection, because server must work and accept clients who will work with only Tls 1.2 security protocol. But i'm not getting this result. Client connects and works well. What's the problem?
I understand that i can change some settings on IIS to use only Tls 1.2. But i am making self hosting wcf service and that's the problem.
It can't be done for server using ServicePointManager.SecurityProtocol option, that's used to connection to sth through a specific security protocol. You can't turn off some security protocol for an separate application, you able to allow or dissallow connections for whole server. If u want disable all protocols except TLS 1.2 u have to open registry windows and find out the next key:
HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\
And set the next values for each protocol in key [Server]: DisabledByDefault = 1, Enabled = 0
How to enable TLS

Deserialize JSON object sent from Android app to WCF webservice

I'm trying to send a JSON object to my webservice method, the method is defined like this:
public String SendTransaction(string trans)
{
var json_serializer = new JavaScriptSerializer();
Transaction transObj = json_serializer.Deserialize<Transaction>(trans);
return transObj.FileName;
}
Where I want to return the FileName of this JSON string that I got as a parameter.
The code for the android application:
HttpPost request = new HttpPost(
"http://10.118.18.88:8080/Service.svc/SendTransaction");
request.setHeader("Accept", "application/json");
request.setHeader("Content-type", "application/json");
// Build JSON string
JSONStringer jsonString;
jsonString = new JSONStringer()
.object().key("imei").value("2323232323").key("filename")
.value("Finger.NST").endObject();
Log.i("JSON STRING: ", jsonString.toString());
StringEntity entity;
entity = new StringEntity(jsonString.toString(), "UTF-8");
entity.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
"application/json"));
entity.setContentType("application/json");
request.setEntity(entity);
// Send request to WCF service
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpResponse response = httpClient.execute(request);
HttpEntity httpEntity = response.getEntity();
String xml = EntityUtils.toString(httpEntity);
Log.i("Response: ", xml);
Log.d("WebInvoke", "Status : " + response.getStatusLine());
I only get a long html file out, which tells me The server has encountered an error processing the request. And the status code is HTTP/1.1 400 Bad Request
My Transaction class is defined in C# like this:
[DataContract]
public class Transaction
{
[DataMember(Name ="imei")]
public string Imei { get; set; }
[DataMember (Name="filename")]
public string FileName { get; set; }
}
How can I accomplish this in the right way?
EDIT, this is my web.config
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5"/>
</system.web>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="httpBehavior">
<webHttp />
</behavior >
</endpointBehaviors>
<serviceBehaviors>
<behavior name="">
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
<services>
<service name="Service.Service">
<endpoint address="" behaviorConfiguration="httpBehavior" binding="webHttpBinding" contract="Service.IService"/>
</service>
</services>
<protocolMapping>
<add binding="webHttpBinding" scheme="http" />
</protocolMapping>
<!--<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />-->
</system.serviceModel>
<system.webServer>
<!-- <modules runAllManagedModulesForAllRequests="true"/>-->
<!--
To browse web app root directory during debugging, set the value below to true.
Set to false before deployment to avoid disclosing web app folder information.
-->
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
#Tobias, This is not an answer. But since it was a little bit long for comment, I post it here. Maybe it can help to diagnose your problem. [A full working code].
public void TestWCFService()
{
//Start Server
Task.Factory.StartNew(
(_) =>{
Uri baseAddress = new Uri("http://localhost:8080/Test");
WebServiceHost host = new WebServiceHost(typeof(TestService), baseAddress);
host.Open();
},null,TaskCreationOptions.LongRunning).Wait();
//Client
var jsonString = new JavaScriptSerializer().Serialize(new { xaction = new { Imei = "121212", FileName = "Finger.NST" } });
WebClient wc = new WebClient();
wc.Headers.Add("Content-Type", "application/json");
var result = wc.UploadString("http://localhost:8080/Test/Hello", jsonString);
}
[ServiceContract]
public class TestService
{
[OperationContract]
[WebInvoke(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped)]
public User Hello(Transaction xaction)
{
return new User() { Id = 1, Name = "Joe", Xaction = xaction };
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public Transaction Xaction { get; set; }
}
public class Transaction
{
public string Imei { get; set; }
public string FileName { get; set; }
}
}

Categories

Resources