Alternate ways WCF Authentication with netTcpBinding binding - wcf

I'm writing a WCF server for an intranet application. I'm a newbie on WCF And have some queries on the Authentication. For this server the binding will netTcp, Its suggested to use netTcpBinding with Windows Authentication. But for my requirement I need a custom login where user will be validated with his own credentials not windows credentials.
I'm Planning to have a service contract implemented this way and validate the Login credentials
*[ServiceContract(SessionMode = SessionMode.Required)]
public interface Iservice
{
[OperationContract]
String Login(String username, String password);
}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession) ]
public class serviceclass : Iservice
{
String Login(String username, String password);
{
//Validate uname and password with DB.
if (validate)
return OperationContext.Current.SessionId;
else return String.Empty;
}
}*
Is this a good approach or is there a better approach to acheive this.
Please guide.

I would suggest to you to use the UserNamePasswordValidator for custom validation, it fits to your requirement.
You just need to inherit and implement validate from UserNamePasswordValidator class like this:
public class CustomUserNameValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
if (!(userName == "test1" && password == "1tset") && !(userName == "test2" && password == "2tset"))
{
// This throws an informative fault to the client.
throw new FaultException("Unknown Username or Incorrect Password");
// When you do not want to throw an infomative fault to the client,
// throw the following exception.
// throw new SecurityTokenException("Unknown Username or Incorrect Password");
}
}
}
You can replace static username and password and validate it in your data storage.
Service configuration :
<serviceBehaviors>
<behavior name="CustomValidator">
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType=
"MyAssembly.CustomUserNameValidator, MyAssembly"/>
</serviceCredentials>
</behavior>
<serviceBehaviors>
<netTcpBinding>
<binding name="tcpWithMessageSecurity">
<security mode="Message" >
<message clientCredentialType="UserName"/>
</security>
</binding>
</netTcpBinding>
For client side you can use Credentials to populate username and password.
proxy.ClientCredentials.UserName.UserName = "user";
proxy.ClientCredentials.UserName.Passsord = "password";

Related

how to find the service address in WCF client

I created a WCF service and client in same machine, the services address is wrote into Client's code, so I can easily find the service and create connection to service.
Then I try to deploy them into Intranet. The first problem is: how could Client find the address of server. In actual environment, customers can install service at any computer in Intranet, is there any way to let client find the server address?
WCF service could expose a specific endpoint as a discovery endpoint to all clients so that client could find where the service lies. You could even use UDP multicast to enable the service to be discovered by the client.
You could check the official document.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/wcf-discovery
I have made a demo, wish it is useful to you.
Server.
class Program
{
static void Main(string[] args)
{
using (ServiceHost sh=new ServiceHost(typeof(MyService)))
{
sh.Open();
Console.WriteLine("serivce is ready...");
Console.ReadLine();
sh.Close();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string SayHello();
}
public class MyService : IService
{
public string SayHello()
{
return "Hello, I am a Clown";
}
}
Server app.config
<system.serviceModel>
<services>
<service name="DiscoveryEndpoint20181024.MyService" behaviorConfiguration="mybehavior">
<endpoint address="http://10.157.18.188:4800" binding="wsHttpBinding" contract="DiscoveryEndpoint20181024.IService"></endpoint>
<endpoint kind="discoveryEndpoint" address="http://localhost:9999" binding="wsHttpBinding"></endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="mybehavior">
<serviceMetadata />
<serviceDiscovery />
</behavior>
</serviceBehaviors>
</behaviors>
Client.
class Program
{
static void Main(string[] args)
{
DiscoveryClient client = new DiscoveryClient("my_client");
client.ClientCredentials.Windows.ClientCredential.UserName = "administrator";
client.ClientCredentials.Windows.ClientCredential.Password = "abcd1234!";
FindCriteria crit = new FindCriteria(typeof(IService));
FindResponse resp = client.Find(crit);
if (resp != null && resp.Endpoints.Count > 0)
{
EndpointDiscoveryMetadata epaddrMtd = resp.Endpoints[0];
ChannelFactory<IService> factory = new ChannelFactory<IService>(new WSHttpBinding(), epaddrMtd.Address);
factory.Credentials.Windows.ClientCredential.UserName = "administrator";
factory.Credentials.Windows.ClientCredential.Password = "abcd1234!";
IService service = factory.CreateChannel();
var result=service.SayHello();
Console.WriteLine(result);
Console.ReadLine();
}
}
}
[ServiceContract]
public interface IService
{
[OperationContract]
string SayHello();
}
class DemoService : IService
{
public string SayHello()
{
OperationContext context = OperationContext.Current;
return $"the address:{OperationContext.Current.Channel.LocalAddress.Uri}";
}
}
Client.config
<system.serviceModel>
<client>
<endpoint name="my_client" kind="discoveryEndpoint" address="http://10.157.18.188:9999" binding="wsHttpBinding"></endpoint>
</client>
</system.serviceModel>

WCF Rest Token based authentication

I am working on WCF Rest application I need to implement the token based authentication in it.Please suggest me a perfect way to implement the token based authentication WCF Rest.
I was able to implement AAD Token based authentication in a WCF based SOAP service.
For this I leveraged WCF extensibility features - Message Inspector and Custom Invoker in the following way
Message Inspector : Using the message inspector, we extract the Bearer Token from the Authorization Header of the incoming request. Post this we perform the token validation using OIDC library to get the keys and config for Microsoft AAD. If token is validated, the operation is invoked and we get the response on the client side.
If the Token validation fails, we stop the request processing by using Custom Invoker and return 401 Unauthorized response to the caller with a custom error message.
public class BearerTokenMessageInspector : IDispatchMessageInspector
{
/// Method called just after request is received. Implemented by default as defined in IDispatchMessageInspector
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
WcfErrorResponseData error = null;
var requestMessage = request.Properties["httpRequest"] as HttpRequestMessageProperty;
if (request == null)
{
error = new WcfErrorResponseData(HttpStatusCode.BadRequest, string.Empty, new KeyValuePair<string, string>("InvalidOperation", "Request Body Empty."));
return error;
}
var authHeader = requestMessage.Headers["Authorization"];
try
{
if (string.IsNullOrEmpty(authHeader))
{
error = new WcfErrorResponseData(HttpStatusCode.Unauthorized, string.Empty, new KeyValuePair<string, string>("WWW-Authenticate", "Error: Authorization Header empty! Please pass a Token using Bearer scheme."));
}
else if (this.Authenticate(authHeader))
{
return null;
}
}
catch (Exception e)
{
error = new WcfErrorResponseData(HttpStatusCode.Unauthorized, string.Empty, new KeyValuePair<string, string>("WWW-Authenticate", "Token with Client ID \"" + clientID + "\" failed validation with Error Messsage - " + e.Message));
}
if (error == null) //Means the token is valid but request must be unauthorized due to not-allowed client id
{
error = new WcfErrorResponseData(HttpStatusCode.Unauthorized, string.Empty, new KeyValuePair<string, string>("WWW-Authenticate", "Token with Client ID \"" + clientID + "\" failed validation with Error Messsage - " + "The client ID: " + clientID + " might not be in the allowed list."));
}
//This will be checked before the custom invoker invokes the method, if unauthorized, nothing is invoked
OperationContext.Current.IncomingMessageProperties.Add("Authorized", false);
return error;
}
/// Method responsible for validating the token and tenantID Claim.
private bool Authenticate(string authHeader)
{
const string bearer = "Bearer ";
if (!authHeader.StartsWith(bearer, StringComparison.InvariantCultureIgnoreCase)) { return false; }
var jwtToken = authHeader.Substring(bearer.Length);
PopulateIssuerAndKeys();
var validationParameters = GenerateTokenValidationParameters(_signingKeys, _issuer);
return ValidateToken(jwtToken, validationParameters);
}
/// Method responsible for validating the token against the validation parameters. Key Rollover is
/// handled by refreshing the keys if SecurityTokenSignatureKeyNotFoundException is thrown.
private bool ValidateToken(string jwtToken, TokenValidationParameters validationParameters)
{
int count = 0;
bool result = false;
var tokenHandler = new JwtSecurityTokenHandler();
var claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out SecurityToken validatedToken);
result = (CheckTenantID(validatedToken));
return result;
}
/// Method responsible for sending proper Unauthorized reply if the token validation failed.
public void BeforeSendReply(ref Message reply, object correlationState)
{
var error = correlationState as WcfErrorResponseData;
if (error == null) return;
var responseProperty = new HttpResponseMessageProperty();
reply.Properties["httpResponse"] = responseProperty;
responseProperty.StatusCode = error.StatusCode;
var headers = error.Headers;
if (headers == null) return;
foreach (var t in headers)
{
responseProperty.Headers.Add(t.Key, t.Value);
}
}
}
NOTE - Please refer to this gist for complete Message Inspector code.
Custom Invoker - The job of custom Invoker is to stop the request flow to further stages of WCF Request processing pipeline if the Token is invalid. This is done by setting the Authorized flag as false in the current OperationContext (set in Message Inspector) and reading the same in Custom Invoker to stop the request flow.
class CustomInvoker : IOperationInvoker
{
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
// Check the value of the Authorized header added by Message Inspector
if (OperationContext.Current.IncomingMessageProperties.ContainsKey("Authorized"))
{
bool allow = (bool)OperationContext.Current.IncomingMessageProperties["Authorized"];
if (!allow)
{
outputs = null;
return null;
}
}
// Otherwise, go ahead and invoke the operation
return defaultInvoker.Invoke(instance, inputs, out outputs);
}
}
Here's the complete gist for Custom Invoker.
Now you need to inject the Message Inspector and the Custom Invoker to your WCF Pipeline using Endpoint Behavior Extension Element. Here are the gists for the class files to do this and a few other needed helper classes:
BearerTokenEndpointBehavior
BearerTokenExtensionElement
MyOperationBehavior
OpenIdConnectCachingSecurityTokenProvider
WcfErrorResponseData
The job is not done yet! You need to write a custom binding and expose a custom AAD endpoint in your web.config apart from adding the AAD Config Keys -
<!--List of AAD Settings-->
<appSettings>
<add key="AADAuthority" value="https://login.windows.net/<Your Tenant ID>"/>
<add key="AADAudience" value="your service side AAD App Client ID"/>
<add key="AllowedTenantIDs" value="abcd,efgh"/>
<add key="ValidateIssuer" value="true"/>
<add key="ValidateAudience" value="true"/>
<add key="ValidateIssuerSigningKey" value="true"/>
<add key="ValidateLifetime" value="true"/>
<add key="useV2" value="true"/>
<add key="MaxRetries" value="2"/>
</appSettings>
<bindings>
<wsHttpBinding>
<!--wsHttpBinding needs client side AAD Token-->
<binding name="wsHttpBindingCfgAAD" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" closeTimeout="00:30:00" openTimeout="00:30:00" receiveTimeout="00:30:00" sendTimeout="00:30:00">
<readerQuotas maxDepth="26214400" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/>
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<!--Exposing a new baseAddress/wssecureAAD endpoint which will support AAD Token Validation-->
<service behaviorConfiguration="ServiceBehaviorCfg" name="Service">
<!--wshttp endpoint with client AAD Token based security-->
<endpoint address="wsSecureAAD" binding="wsHttpBinding" bindingConfiguration="wsHttpBindingCfgAAD" name="ServicewsHttpEndPointAAD" contract="ServiceContracts.IService" behaviorConfiguration="AADEnabledEndpointBehavior"/>
</service>
</services>
<behaviors>
<endpointBehaviors> <!--Injecting the Endpoint Behavior-->
<behavior name="AADEnabledEndpointBehavior">
<bearerTokenRequired/>
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions> <!--Linking the BearerTokenExtensionElement-->
<add name="bearerTokenRequired" type="TokenValidator.BearerTokenExtensionElement, TokenValidator"/>
</behaviorExtensions>
</extensions>
Your WCF service should now accept AAD Tokens on this custom AAD endpoint and your tenants will be able to consume the same by just changing the binding and endpoint from their side. Note that you will need to add the tenant's client ID in the allowedTenantIDs list in web.config so as to authorize the tenant to hit your service.
Final Note - Though I have implemented Microsoft's AAD Based Authentication, you should be able to reuse the whole code to implement any OAuth based Identity Provider's Token Validation. You just need to change the respective keys for AADAuthority in web.config.
You can implement Bearer token authentication
using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web.Http;
[assembly: OwinStartup(typeof(ns.Startup))]
namespace ns
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
config.MessageHandlers.Add(new LogRequestAndResponseHandler());
}
Configure using of OAuthBearerAuthentication:
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/TokenService"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(3),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
And finally set the identity claims
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
try
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, "Name"));
identity.AddClaim(new Claim(ClaimTypes.Sid, "Sid"));
identity.AddClaim(new Claim(ClaimTypes.Role, "Role"));
context.Validated(identity);
}
catch (System.Exception ex)
{
context.SetError("Error....");
context.Response.Headers.Add("X-Challenge", new[] { ((int)HttpStatusCode.InternalServerError).ToString() });
}
}
}
}
}
It's the easiest solution and works like a charm!

Can a WCF REST Service support both Basic and Windows authentication?

I have a self hosted REST WCF Windows Service. I've got Basic Authentication working for the service, but I would like to also support Windows Authentication for clients which support it. Would I have to have a separate endpoint on a different port?
UPDATE: I've gotten something close to working in WCF 4.0. Here is the code, the issue I'm having now is that I can only seem to get NTLM working, which requires the user to type in their credentials, which nullifies any benefits to using Windows Auth.
I'm still not sure how to get Windows Authentication working, without requiring the user to enter their password in a second time.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Web;
using System.IdentityModel.Selectors;
namespace BasicAndNegotiateAuth
{
class Program
{
static void Main(string[] args)
{
Uri newUri = new Uri(new Uri("http://localhost/"), "/");
WebServiceHost webHost = new WebServiceHost(typeof(HelloWorldService), newUri);
// TransportCredentialOnly means we can use http
WebHttpBinding binding = new WebHttpBinding(WebHttpSecurityMode.Transport);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic | HttpClientCredentialType.Ntlm;
ServiceEndpoint ep = webHost.AddServiceEndpoint(typeof(IHelloWorld), binding, newUri);
WebHttpBehavior wb = new WebHttpBehavior();
ep.EndpointBehaviors.Add(wb);
ep.Behaviors.Add(new WebHttpCors.CorsSupportBehavior());
//ServiceAuthenticationBehavior sab = null;
//sab = webHost.Description.Behaviors.Find<ServiceAuthenticationBehavior>();
//if (sab == null)
//{
// sab = new ServiceAuthenticationBehavior();
// sab.AuthenticationSchemes = AuthenticationSchemes.Basic | AuthenticationSchemes.IntegratedWindowsAuthentication;
// host.Description.Behaviors.Add(sab);
//}
webHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
webHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
webHost.Open();
Console.ReadLine();
}
}
public class CustomUserNameValidator: UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
int i = 1;
}
}
[ServiceContract]
public interface IHelloWorld
{
[System.ServiceModel.OperationContract]
[System.ServiceModel.Web.WebGet(
UriTemplate = "/",
ResponseFormat = WebMessageFormat.Json)]
string GetHello();
}
public class HelloWorldService : IHelloWorld
{
public string GetHello()
{
ServiceSecurityContext ssc = ServiceSecurityContext.Current;
return "Hello World";
}
}
}
In .NET 4.5, you can support multiple authentication schemes on a single endpoint in WCF.
Here is an example of how you would do it in code for a Self-Hosted Service:
ServiceAuthenticationBehavior sab = null;
sab = serviceHost.Description.Behaviors.Find<ServiceAuthenticationBehavior>();
if (sab == null)
{
sab = new ServiceAuthenticationBehavior();
sab.AuthenticationSchemes = AuthenticationSchemes.Basic |
AuthenticationSchemes.Negotiate | AuthenticationSchemes.Digest;
serviceHost.Description.Behaviors.Add(sab);
}
else
{
sab.AuthenticationSchemes = AuthenticationSchemes.Basic |
AuthenticationSchemes.Negotiate | AuthenticationSchemes.Digest;
}
Alternatively, you can set it up in your config file like this:
<behaviors>
<serviceBehaviors>
<behavior name="limitedAuthBehavior">
<serviceAuthenticationManager authenticationSchemes=
"Negotiate, Digest, Basic"/>
<!-- ... -->
</behavior>
</serviceBehaviors>
</behaviors>
Then specify InheritedFromHost in your binding settings like this:
<bindings>
<basicHttpBinding>
<binding name="secureBinding">
<security mode="Transport">
<transport clientCredentialType="InheritedFromHost" />
</security>
</binding>
</basicHttpBinding>
</bindings>
See this article on MSDN: Using Multiple Authentication Schemes with WCF.

How to set up WorkflowService authentication?

I just need to secure my WF services. Can't find any resources on this. How to do it?
Already tried:
class Program
{
static void Main(string[] args)
{
using (WorkflowServiceHost host = new WorkflowServiceHost(new Workflow1(), new Uri("http://localhost/Test")))
{
host.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = System.ServiceModel.Security.UserNamePasswordValidationMode.Custom;
host.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new Test();
host.Open();
Console.Write("ready");
Console.ReadLine();
}
}
}
public class Test : UserNamePasswordValidator
{
public Test()
{
Console.Write("hit");
}
public override void Validate(string userName, string password)
{
Console.Write("never hit");
}
}
And a config
<bindings>
<wsHttpBinding>
<binding>
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<!--<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="myAssembly.Test, myAssembly" />
</serviceCredentials>-->
</behavior>
</serviceBehaviors>
Can't create a fixed name endpoint because they are dynamically created
UPDATE - I tried the configuration bellow and worked, but I want a more granular way to set what binding each service use
<protocolMapping>
<add scheme="http" binding="wsHttpBinding"/>
</protocolMapping>
We have an episode of Workflow TV that should help. Workflow TV - Workflow Services Security
As far as the messaging part this is just WCF so anything you can do with WCF should work here.
That said with workflow you typically need more fine grained control on all but the first request. For example all employees can start en expense report but only the employee who started a specific expense report can add expenses to it and submit it. You can do these kind of security checks using the WF Security Pack.
A little hackish, but works. Overrided WorkflowServiceHost in order to grab unknown contract names and added service endpoints for each one.
const string DEFAULT_WORKFLOW_SERVICE_BINDING_NAME = "WorkflowDefaultBinding";
static void Main(string[] args)
{
MyWorkflowServiceHost host = new MyWorkflowServiceHost(new CountingWorkflow2(), new Uri(hostBaseAddress));
foreach (var contractName in host.ImplementedContractsNames)
{
// now I'm able to choose which binding to use depending on a condition
var binding = new WSHttpBinding(DEFAULT_WORKFLOW_SERVICE_BINDING_NAME);
host.AddServiceEndpoint(contractName, binding, string.Empty);
}
}
And MyWorkflowServiceHost
public class MyWorkflowServiceHost : WorkflowServiceHost
{
public MyWorkflowServiceHost(Activity activity, params Uri[] baseAddresses)
: base(activity, baseAddresses)
{
}
private IDictionary<string, System.ServiceModel.Description.ContractDescription> _implementedContracts;
public IEnumerable<string> ImplementedContractsNames
{
get
{
foreach (var contract in _implementedContracts)
yield return contract.Key;
}
}
protected override System.ServiceModel.Description.ServiceDescription CreateDescription(out System.Collections.Generic.IDictionary<string, System.ServiceModel.Description.ContractDescription> implementedContracts)
{
System.ServiceModel.Description.ServiceDescription description = base.CreateDescription(out implementedContracts);
_implementedContracts = implementedContracts;
return description;
}
}
Adding a unamed WSHttpBinding and the following section on service model should work too, but for default configuration
<protocolMapping>
<add scheme="http" binding="wsHttpBinding"/>
</protocolMapping>

WCF - Streaming file upload over http

I am trying to build a WCF service that will allow my WPF desktop clients to upload files to a server.
I adapted a code sample from The Code Project (WCF Streaming: Upload/Download Files Over HTTP) and I've looked at several SO posts as well, but can't seem to get this working.
When I execute the code, it fails with a null reference exception at the point that the server tries to read the stream that has been passed through the interface.
At this point, I am rather lost and don't know how to fix this up. Any suggestions are appreciated.
Code samples follow:
CustomerDocumentModel is the data element that I pass through the WCF interface with the stream to read the client side file:
[DataContract]
[KnownType(typeof(System.IO.FileStream))]
public class CustomerDocumentModel : IDisposable
{
public CustomerDocumentModel()
{
}
public CustomerDocumentModel(string documentName, string path)
{
DocumentName = documentName;
Path = path;
}
[DataMember]
public string DocumentName;
[DataMember]
public string Path;
[DataMember]
public System.IO.Stream FileByteStream;
public void Dispose()
{
if (FileByteStream != null)
{
FileByteStream.Close();
FileByteStream = null;
}
}
}
IBillingService is the interface definition for my WCF service:
[ServiceContract]
public interface IBillingService
{
// other methods redacted...
[OperationContract]
void UploadCustomerDocument(CustomerDocumentModel model);
}
The class BillingService implements the WCF service:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class BillingService : IBillingService
{
// Other methods redacted ...
public void UploadCustomerDocument(CustomerDocumentModel model)
{
string path = HttpContext.Current.Server.MapPath(
String.Format("/Documents/{1}",
model.DocumentName));
using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
{
const int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int size = 0;
try
{
// The following Read() fails with a NullReferenceException
while ((size = model.FileByteStream.Read(buffer, 0, bufferSize)) > 0)
{
stream.Write(buffer, 0, size);
}
}
catch
{
throw;
}
finally
{
stream.Close();
model.FileByteStream.Close();
}
}
}
}
A few relevant bits from the web.config on my WCF web server:
<system.web>
<compilation debug="true" targetFramework="4.0" />
<httpRuntime maxRequestLength="2097151" useFullyQualifiedRedirectUrl="true" executionTimeout="360"/>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment
aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
<bindings>
<basicHttpBinding>
<binding name="userHttps" transferMode="Streamed" maxReceivedMessageSize="2147483647" maxBufferSize="2147483647">
<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
<security mode="None" />
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="">
<dataContractSerializer maxItemsInObjectGraph="2147483646"/>
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
The client is a WPF/MVVM app that creates a CustomerDocumentModel model, uses an OpenFileDialog to Open() the file stream and then passes the model to the UploadCustomerDocument method on WCF Service.
If I am missing any relevant details, please ask.
I know this rather very late reply for your question and I'm sure you must have resolved your problem as well. This could be helpful to someone else :-)
Use Messagecontract over Datacontract and only one MessageBodyMember with datatype Stream and rest all parameter are MessageHeader.
Here is the example:
[MessageContract]
public class CustomerDocumentModel : IDisposable
{
public CustomerDocumentModel(string documentName, string path)
{
DocumentName = documentName;
Path = path;
}
[MessageHeader]
public string DocumentName{get;set;}
[MessageHeader]
public string Path{get;set;}
[MessageBodyMember]
public System.IO.Stream FileByteStream{get;set;}
public void Dispose()
{
if (FileByteStream != null)
{
FileByteStream.Close();
FileByteStream = null;
}
}
}
Note: Make sure your in your configuration transfer mode is StreamedResponse, also you may want to change the MessageEncoding to MTOM for better performance.
public void UploadCustomerDocument(CustomerDocumentModel model)
{
var filename = //your file name and path;
using (var fs = new FileStream(filename, FileMode.Create))
{
model.FileByteStream.CopyTo(fs);
}
}
Your data type is what is making the streaming fail. This is documented on MSDN here: http://msdn.microsoft.com/en-us/library/ms731913.aspx
The relevant passage is:
Restrictions on Streamed Transfers
Using the streamed transfer mode causes the run time to enforce
additional restrictions.
Operations that occur across a streamed transport can have a contract
with at most one input or output parameter. That parameter corresponds
to the entire body of the message and must be a Message, a derived
type of Stream, or an IXmlSerializable implementation. Having a return
value for an operation is equivalent to having an output parameter.
Some WCF features, such as reliable messaging, transactions, and SOAP
message-level security, rely on buffering messages for transmissions.
Using these features may reduce or eliminate the performance benefits
gained by using streaming. To secure a streamed transport, use
transport-level security only or use transport-level security plus
authentication-only message security.
SOAP headers are always buffered, even when the transfer mode is set
to streamed. The headers for a message must not exceed the size of the
MaxBufferSize transport quota. For more information about this
setting, see Transport Quotas.