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.
Related
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>
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!
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>
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.
I'm trying to create a windows service along with WCF. We don't want to use IIS to handle the proxying of the service. I've gotten the service registered and started. I created a simple console application to call the service, but it times out.
I have a .NET DLL containing functions that call a locally running application to create an alarm record. I created a forms application that uses AlarmLib.dll and it is able to make the call and insert the alarm record.
It appears to be a permissions issue. I get the following exception:
> Unhandled Exception: System.ServiceModel.EndpointNotFoundException:
> Could not co nnect to http://localhost:8000/ServiceModel/service. TCP
> error code 10061: No co nnection could be made because the target
> machine actively refused it 127.0.0.1:
> 8000. ---> System.Net.WebException: Unable to connect to the remote
> server ---> System.Net.Sockets.SocketException: No connection could
> be made because the tar get machine actively refused it
> 127.0.0.1:8000
> at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot,
> SocketAddre ss socketAddress) at
> System.Net.Sockets.Socket.InternalConnect(EndPoint remoteEP) at
> System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure,
> Sock et s4, Socket s6, Socket& socket, IPAddress& address,
> ConnectSocketState state, IAsyncResult asyncResult, Int32 timeout,
> Exception& exception) --- End of inner exception stack trace
> at System.Net.HttpWebRequest.GetRequestStream(TransportContext&
> context) at System.Net.HttpWebRequest.GetRequestStream() at
> System.ServiceModel.Channels.HttpOutput.WebRequestHttpOutput.GetOutputStre
> am() --- End of inner exception stack trace ---
I ran VS2010 as an administrator. Any other settings for the service that I can modify?
Thanks,
John
Windows service code:
using System.ServiceProcess;
using System.Text;
using System.ServiceModel;
using System.Configuration;
using System.Configuration.Install;
using AlarmLib;
namespace Test.ServiceModel
.WindowsServices {
//
Define a service contract.
[ServiceContract(Namespace = "http://ServiceModel.WindowsServices")]
public interface IAlarmLib {
[OperationContract]
bool CreateNoDataAlarm(string well, string run, string record, string description, string selectedVariable);
}
// Implement the IAlarmLib service contract in a service class.
public class AlarmLibService : IAlarmLib {
// Implement the IAlarmLib methods.
public bool CreateNoDataAlarm(string well, string run, string record, string description, string sel
ectedVariable) {
AlarmUserConfiguration newalarm = new AlarmUserConfiguration();
// The Machine name should be the machine which is running the client application
// and which calls the webservice. This should not be the name of machine hosting
// webservice.
newalarm.MachineName = System.Environment.MachineName;
newalarm.AlarmType = AlarmTypes.NoData;
newalarm.TimeInSeconds = 30;
DateTime CreationTime = DateTime.Now;
newalarm.Name = "NoDataAlarm " + CreationTime.ToString();
PrimaryKey key = new PrimaryKey();
key.Well = "Well ID 1";
key.Run = "1600";
key.Record = "DGR";
key.Desc = "Realtime";
key.SelectedVariable = "Gamma Ray A";
newalarm.PrimaryKeys = key;
// Add any of the following activities.
/*"All"
"Trip Out"
"Trip In"
"Circulating"
"Drilling On Bottom"
"Drilling Off Bottom"*/
newalarm.TDActivities.Add("Drilling On Bottom");
bool bStatus = AlarmUtilities.AddNewAlarm(newalarm, "-Local-");
return bStatus;
}
}
public class AlarmLibWindowsService : ServiceBase {
public ServiceHost serviceHost = null;
public AlarmLibWindowsService() {
// Name the Windows Service
ServiceName = "AlarmLibWS";
}
public static void Main() {
ServiceBase.Run(new AlarmLibWindowsService());
}
// Start the Windows service.
protected override void OnStart(string[] args) {
if (serviceHost != null) {
serviceHost.Close();
}
// Create a ServiceHost for the AlarmLibService type and
// provide the base address.
serviceHost = new ServiceHost(typeof(AlarmLibService));
// Open the ServiceHostBase to create listeners and start
// listening for messages.
serviceHost.Open();
}
protected override void OnStop() {
if (serviceHost != null) {
serviceHost.Close();
serviceHost = null;
}
}
}
// Provide the ProjectInstaller class which allows
// the service to be installed by the Installutil.exe tool
[RunInstaller(true)]
public class ProjectInstaller : Installer {
private ServiceProcessInstaller process;
private ServiceInstaller service;
public ProjectInstaller() {
process = new ServiceProcessInstaller();
process.Account = ServiceAccount.LocalSystem;
service = new ServiceInstaller();
service.ServiceName = "AlarmLibWS";
Installers.Add(process);
Installers.Add(service);
}
}
}
App.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<!-- This section is optional with the new configuration model
introduced in .NET Framework 4. -->
<service name="ServiceModel.WindowsServices.AlarmLibService" behaviorConfiguration="AlarmLibServiceBehavior">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000/ServiceModel/service"/>
</baseAddresses>
</host>
<!-- this endpoint is exposed at the base address provided by host: http://localhost:8000/ServiceModel/service -->
<endpoint address=""
binding="wsHttpBinding"
contract="ServiceModel.WindowsServices.IAlarmLib" />
<!-- the mex endpoint is exposed at http://localhost:8000/ServiceModel/service/mex -->
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="AlarmLibServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="False"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Console app:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TestAlarmLibConsoleApp {
class Program {
static void Main(string[] args) {
ServiceReference1.AlarmLibClient client = new ServiceReference1.AlarmLibClient();
bool result = client.CreateNoDataAlarm("Well ID 1", "1100", "DGR", "Realtime", "Gamma Ray A");
Console.WriteLine("result = " + bool.TrueString);
}
}
}
Here is the code in forms app that works:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using AlarmLib;
namespace TestCallingAlarmLib {
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
bool result = CreateNoDataAlarm();
Application.Run(new Form1());
}
static bool CreateNoDataAlarm() {
AlarmUserConfiguration newalarm = new AlarmUserConfiguration();
// The Machine name should be the machine which is running the client application
// and which calls the webservice. This should not be the name of machine hosting
// webservice.
newalarm.MachineName = System.Environment.MachineName;
newalarm.AlarmType = AlarmTypes.NoData;
newalarm.TimeInSeconds = 30;
DateTime CreationTime = DateTime.Now;
newalarm.Name = "NoDataAlarm " + CreationTime.ToString();
PrimaryKey key = new PrimaryKey();
key.Well = "Well ID 1";
key.Run = "1100";
key.Record = "DGR";
key.Desc = "Realtime";
key.SelectedVariable = "Gamma Ray A";
newalarm.PrimaryKeys = key;
// Add any of the following activities.
/*"All"
"Trip Out"
"Trip In"
"Circulating"
"Drilling On Bottom"
"Drilling Off Bottom"*/
newalarm.TDActivities.Add("Drilling On Bottom");
bool bStatus = AlarmUtilities.AddNewAlarm(newalarm, "-Local-");
return bStatus;
}
}
}
Check your Client Access Policy, make sure you have the ports opened up on the server and double check your firewall. Every single time I get this error it's related to one of those.