I have a problem configuring a ServiceBehavior for my WCF service.
Some background.
Basically I am developing a REST service WCF that is supposed to run on IIS.
I need to be able to log exceptions thrown by the service (I'm using log4net) and return HTTP status codes depending on the type of exception.
I want my service implementation to have a minimum knowledge of WCF related stuff, so I don't want to convert the exceptions to FaultException everywhere in the service.
So I figured out that adding my own IErrorHandler to the service host would be the best way to do it.
My problem is however that no matter what I try I can't seem to get the configuration for my custom ServiceBehavior right in Web.config.
Here is the relevant code.
Web config.
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
</system.webServer>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="UsingErrorLogBehavior">
<errorLogBehavior/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior>
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<extensions>
<behaviorExtensions>
<add name="errorLogBehavior"
type="MyNameSpace.Web.ErrorExtensionElement, MyNameSpace.Web"/>
</behaviorExtensions>
</extensions>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true"
automaticFormatSelectionEnabled="false"
defaultOutgoingResponseFormat="Json"
maxReceivedMessageSize="4194304" transferMode="Buffered" />
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel>
ErrorExtensionElement.
namespace MyNameSpace.Web
{
public class ErrorExtensionElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(ErrorServiceBehavior); }
}
protected override object CreateBehavior()
{
return new ErrorServiceBehavior();
}
}
}
ErrorServiceBehavior.
namespace MyNameSpace.Web
{
public class ErrorServiceBehavior : IServiceBehavior
{
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
{
channelDispatcher.ErrorHandlers.Add(new ExceptionModule());
}
}
public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
}
}
Where ExceptionModule implements IErrorHandler.
You have a <serviceBehavior> section named "UsingErrorLogBehavior", but no service configurations are referencing that section. You can either make that section the default service behavior (by not giving it a name, like you have for the endpoint behavior), or add a <service> element for your service which references that behavior:
<services>
<service name="YourNamespace.YourServiceName"
behaviorConfiguration="UsingErrorLogBehavior">
<endpoint address=""
binding="webHttpBinding"
contract="YourNamespace.YourContractName" />
</service>
</services>
Related
I'm going to implement basic authentication in WCF. I'm very new in all this stuff and my program is based on this series of articles http://leastprivilege.com/2008/01/11/http-basic-authentication-against-non-windows-accounts-in-iisasp-net-part-0-intro/ I do use webHttpBinding and HTTPS is on.
So the main idea is implementation of IHttpModule in this way:
When user requests some resource a module checks if Authorization header is present.
In case of Authorization is present, the module extracts the header's value, decodes and checks login and pass
In the other case the module sends a response with 401 code and a header "WWW-Authenticate".
Here is my implementation of the module:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Management;
using System.Text;
namespace MyProj_A
{
public class MyHTTPModule : IHttpModule
{
void IHttpModule.Dispose()
{
}
void IHttpModule.Init(HttpApplication context)
{
context.BeginRequest += Context_BeginRequest;
context.AuthenticateRequest += OnEnter;
context.EndRequest += OnLeave;
}
private void Context_BeginRequest(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
context.Response.Write("BeginRequest");
}
void OnEnter(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
if (IsHeaderPresent())
{
if (!AuthenticateUser())
{
DenyAccess();
}
}
else
{
// if anonymous requests are not allowed - end the request
DenyAccess();
}
}
bool IsHeaderPresent()
{
return HttpContext.Current.Request.Headers["Authorization"] != null;
}
bool AuthenticateUser()
{
string username = "", password = "";
string authHeader = HttpContext.Current.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic"))
{
// extract credentials from header
string[] credentials = ExtractCredentials(authHeader);
username = credentials[0];
password = credentials[1];
if (username.CompareTo("tikskit") == 0 && password.CompareTo("") == 0)
{
return true;
} else
{
return false;
}
}
else
{
return false;
}
}
private static void DenyAccess()
{
HttpContext context = HttpContext.Current;
context.Response.StatusCode = 401;
context.Response.End();
}
void OnLeave(object sender, EventArgs e)
{
// check if module is enabled
if (HttpContext.Current.Response.StatusCode == 401)
{
SendAuthenticationHeader();
}
}
private void SendAuthenticationHeader()
{
HttpContext context = HttpContext.Current;
context.Response.StatusCode = 401;
context.Response.AddHeader(
"WWW-Authenticate",
"Basic realm=\"yo-ho-ho\""
);
}
}
}
I publish it under IIS 7.5 on remote computer and connect to it with remote debugger from my Visual Studio. I set breakpoints at Context_BeginRequest, OnEnter and OnLeave.
Then I access to my WCF from a browser using URL and here is what happens:
After I inputted an URL and pressed the Enter Context_BeginRequest is fired
In VS I can see that the Authorization header isn't present
OnEnter is fired and eventually it assigns 401 code to the response
OnLeave is executed as well and it sets WWW-Authenticate to the response header
In the browser the standart login dialog is shown
I input the user name and password and press OK
Now Context_BeginRequest is fired again and I can see that Authorization header is present and consists a value like "Basic ", which is right
OnEnter isn't executed at all this time
OnLeave is fired but a value of HttpContext.Current.Response.StatusCode is 401 by some reason
Here 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.2" />
<httpRuntime targetFramework="4.5.2"/>
<customErrors mode="Off" />
</system.web>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="webBehavior">
<webHttp automaticFormatSelectionEnabled="false"/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="Default" >
<serviceMetadata httpGetEnabled="false" />
<serviceMetadata httpsGetEnabled="false"/>
<serviceAuthenticationManager authenticationSchemes="Basic"/>
<serviceCredentials>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="MyBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic"/>
</security>
</binding>
</webHttpBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
<service name="MyProj_A.Service1">
<endpoint address="" binding="webHttpBinding" contract="MyProj_A.IService1"
behaviorConfiguration="webBehavior"/>
<host>
<baseAddresses>
<add baseAddress="http://localhost/" />
</baseAddresses>
</host>
</service>
</services>
<diagnostics>
<endToEndTracing activityTracing="false" messageFlowTracing="true" propagateActivity="true"></endToEndTracing>
</diagnostics>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="MyHTTPModule"
type="MyProj_A.MyHTTPModule,MyProj-A"/>
</modules>
<directoryBrowse enabled="false"/>
</system.webServer>
</configuration>
So my questions are
1. Why OnEnter isn't fired second time, in 8, and how is 401 assigned in an item 9?
2. How to work around this behaviour, I mean do I need to move all the authentication processing from AuthenticateRequest (OnLeave) to BeginRequest (Context_BeginRequest) for example? Or maybe there is a better place for such processing?
Thanks!
Case is closed
I've forgotten to refer to binding configuration in endpoint configuration:
<endpoint address="" binding="webHttpBinding"
contract="MyProj_A.IService1"
behaviorConfiguration="webBehavior"
**bindingConfiguration="MyBinding"**/>
I try to write simple subscription service with soap and rest operations. This is my code:
SubsService.svc:
namespace SubscriptionService
{
public class SubsService : ISubsService
{
public string RegisterEvent(string serviceId, string name, string description)
{
return "ok";
}
public void UnregisterEvent(string serviceId, string name)
{
// TO DO
}
public void RiseEvent(string serviceId, string name)
{
// TO DO
}
public string EventsList(string token)
{
return token;
}
public void Subscribe(string token, string serviceId, string name)
{
// TO DO
}
public void UnSubscribe(string token, string serviceId, string name)
{
// TO DO
}
}
}
ISubsService.cs:
namespace SubscriptionService
{
[ServiceContract]
public interface ISubsService
{
// SOA
[OperationContract]
string RegisterEvent(string serviceId, string name, string description);
[OperationContract]
void UnregisterEvent(string serviceId, string name);
[OperationContract]
void RiseEvent(string serviceId, string name);
// REST
[OperationContract]
[WebGet(UriTemplate = "events?token={token}")]
string EventsList(string token);
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "subscribe?token={token}&serviceId={serviceId}&name={name}")]
void Subscribe(string token, string serviceId, string name);
[OperationContract]
[WebInvoke(Method = "GET", UriTemplate = "unsubscribe?token={token}&serviceId={serviceId}&name={name}")]
void UnSubscribe(string token, string serviceId, string name);
}
}
and 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>
<services>
<service name="SubscriptionService.SubsService">
<endpoint address="soap" binding="basicHttpBinding" contract="SubscriptionService.ISubsService"></endpoint>
<endpoint address="rest" binding="webHttpBinding" contract="SubscriptionService.ISubsService" behaviorConfiguration="web"></endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- 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>
<endpointBehaviors>
<behavior name="web">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</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>
When I try to access http://localhost:2354/SubsService.svc, I get error:
Operation 'RegisterEvent' of contract 'ISubsService' specifies multiple request body parameters to be serialized without any wrapper elements. At most one body parameter can be serialized without wrapper elements. Either remove the extra body parameters or set the BodyStyle property on the WebGetAttribute/WebInvokeAttribute to Wrapped.
But RegisterEvent has to be soap operation. What is wrong with my code? Something in web.config?
Hi a have WCF service libary with this configuration:
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<services>
<service behaviorConfiguration="Default" name="ComDocs.ControlServerServiceLibary.Concrete.TokenService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8080/TokenService" />
</baseAddresses>
</host>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<endpoint address="basic" binding="basicHttpBinding" contract="ComDocs.ControlServerServiceLibary.Abstract.ITokenService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Default">
<serviceMetadata httpGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
If I build it in debug, everything works fine on localhost. But if I make a Windows Service library with the same configuration:
public partial class TokenService : ServiceBase
{
ServiceHost _host = null;
public TokenService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
Trace.WriteLine("Starting Token Service...");
_host = new ServiceHost(typeof(TokenService));
_host.Open();
}
protected override void OnStop()
{
Trace.WriteLine("Shutting down Token Service...");
if (_host != null)
{
_host.Close();
_host = null;
}
}
}
Install it with InstallUtil and start it:
but error:
I suspect this line is the culprit.
_host = new ServiceHost(typeof(TokenService));
TokenService is your Windows service class, not your WCF service class.
I'm trying create and configure a Message Inspector to perform some authentication of a WCF Rest HTTP request. I'm using 4.0 so trying to steer clear of the WCF Starter Kit although I have managed to get an old RequestInterceptor working in the way I want. The problem with using RequestInterceptor is that I lost the automaticFormatSelectionEnabled features provided by WebHttpBehavior which I really want to keep.
So my question is how do I configure the Message Inspector in a way that I still use the WebHttpBehavior and keep it's features.
My web.config looks like this
<standardEndpoints>
<webHttpEndpoint>
<!-- the "" standard endpoint is used by WebServiceHost for auto creating a web endpoint. -->
<standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" />
<!-- Disable the help page for the directory end point-->
<standardEndpoint name="DirectoryEndpoint"/>
</webHttpEndpoint>
</standardEndpoints>
One way you can handle this is to create three objects.
The message inspector, responsible for analyzing the
request/response
The service behavior, automatically injects the inspector into the
pipeline
The configuration section, allows the behavior to be used in the
web.config
First create the message inspector by implementing IDispatchMessageInspector
and putting your validation code in the AfterReceiveRequest method:
public class HmacVerificationInspector : IDispatchMessageInspector
{
#region IDispatchMessageInspector Members
public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request,
System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
{
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
Message dupeRequest = buffer.CreateMessage();
ValidateHmac(dupeRequest);
buffer.Close();
return null;
}
public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply,
object correlationState)
{
}
#endregion
}
It's important to create a buffered copy of the message when reading it. Messages can only be opened once and not creating a copy will lead to problems down the pipe. My implementation of ValidateHmac throws an exception if it fails. This prevents the actual service from being called.
Second, create a behavior for your inspector. We'll use the behavior to inject the inspector into the WCF runtime. To create the behavior, derive a class from IEndpointBehavior so it looks like this
public class HmacVerificationBehavior : IEndpointBehavior
{
#region IEndpointBehavior Members
public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{
HmacVerificationInspector inspector = new HmacVerificationInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
}
public void Validate(ServiceEndpoint endpoint)
{
}
#endregion
}
Notice I create a new instance of my inspector (HmacVerificationInspector) and inject it programmatically into the runtime.
Finally, the last step is to create a configuration section. We can use this to apply the behavior in the web config (thus being able to turn it on and off via configuration). Create a new class and inherit from BehaviorExtensionElement and IServiceBehavior:
public class HmacVerificationConfigurationSection : BehaviorExtensionElement, IServiceBehavior
{
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase,
System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
#endregion
public override Type BehaviorType
{
get { return typeof(HmacVerificationBehavior); }
}
protected override object CreateBehavior()
{
return new HmacVerificationBehavior();
}
}
Now, to use the inspector, add the following to your web.config (you can set the name for your extension to whatever you want)
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="hmacVerification" type="NamespaceHere.HmacVerificationConfigurationSection, AssembleyHere, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<services>
<service name="MySecureService">
<endpoint address="" binding="webHttpBinding" contract="IMySecureService" behaviorConfiguration="web" />
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp automaticFormatSelectionEnabled="true" />
<hmacVerification />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
</system.serviceModel>
Couple of things, first you register the configuration section in the behavior extensions. Next, you use that configuration as an endpoint behavior which will then automatically inject the inspector and all requests to that endpoint will run through your inspector. If you want to turn off the inspector, remove the tag or select a different endpoint behavior. Note the use of the webHttp behavior also (which will allow you to keep automaticFormatSelectionEnabled.
Hope this helps
I am working on a WCF REST service that will be hosted within Azure and want to check the user id. To this end I have created a custom ServiceAuthorizationManager.
namespace SecureService
{
public class AccessControlServiceAuthorizationManager : ServiceAuthorizationManager
{
String serviceNamespace = String.Empty;
String acsHostname = String.Empty;
String trustedTokenPolicyKey = String.Empty;
String trustedAudience = String.Empty;
public AccessControlServiceAuthorizationManager()
{
try
{
serviceNamespace = RoleEnvironment.GetConfigurationSettingValue("serviceNamespace");
acsHostname = RoleEnvironment.GetConfigurationSettingValue("acsHostname");
trustedTokenPolicyKey = RoleEnvironment.GetConfigurationSettingValue("trustedTokenPolicyKey");
trustedAudience = RoleEnvironment.GetConfigurationSettingValue("trustedAudience");
}
catch
{
GenerateErrorResponse();
}
finally
{
}
} // end AccessControlServiceAuthorizationManager() Constructor
protected override bool CheckAccessCore(OperationContext operationContext)
{
String headerValue = WebOperationContext.Current.IncomingRequest.Headers[HttpRequestHeader.Authorization];
String token = String.Empty;
string[] nameValuePair = null;
TokenValidator validator = null;
if (String.IsNullOrEmpty(headerValue))
{
GenerateErrorResponse();
return false;
}
if (!headerValue.StartsWith("WRAP "))
{
GenerateErrorResponse();
return false;
}
nameValuePair = headerValue.Substring("WRAP ".Length).Split(new char[] { '=' }, 2);
if (nameValuePair.Length != 2 ||
nameValuePair[0] != "access_token" ||
!nameValuePair[1].StartsWith("\"") ||
!nameValuePair[1].EndsWith("\""))
{
GenerateErrorResponse();
return false;
}
token = nameValuePair[1].Substring(1, nameValuePair[1].Length - 2);
validator = new TokenValidator(acsHostname, serviceNamespace, trustedAudience, trustedTokenPolicyKey);
if (!validator.Validate(token))
{
GenerateErrorResponse();
return false;
}
return true;
}
public void GenerateErrorResponse()
{
}
}
}
My Web.config is as follows;
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<behaviors>
<serviceBehaviors>
<behavior name="Secure">
<serviceAuthorization serviceAuthorizationManagerType="SecureService.AccessControlServiceAuthorizationManager" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="webBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service name="SecureService.Demo">
<endpoint address="" behaviorConfiguration="webBehavior" binding="webHttpBinding" bindingConfiguration="" contract="SecureService.IDemo" />
<endpoint address="rest" behaviorConfiguration="webBehavior" binding="webHttpBinding" bindingConfiguration="" contract="SecureService.IDemo" />
</service>
</services>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
</system.webServer>
However the CheckAccessCore() method does not seem to be called when I call the service via IE. So the question is how can I ensure it is called and therefore ensure that my users are validated.
I have put a break point in the CheckAccessCore and it never seems to get hit.
And just to make things really interesting - I will need to call this webservice from within Silverlight.
Thanks in advance
Ignore your 'services' section in config. Try:
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint crossDomainScriptAccessEnabled="True" automaticFormatSelectionEnabled="true" helpEnabled="True"/>
</webHttpEndpoint>
</standardEndpoints>
Following the configuration pasted above, I see that service behavior "Secure" is missed to apply on Service. Please check it, if it's not a typo...