13

I have a requirement to access the HttpContext.Current from with-in a RESTful WCF service. I know I am able to achieve this by adding the following to config:

<serviceHostingEnvironment aspNetCompatibilityEnabled=”true” />

and using the following attribute on my service:

[AspNetCompatibilityRequirements(RequirementsMode 
    = AspNetCompatibilityRequirementsMode.Required)]

Here is my issue, I need to "spin up" an instance of the service in code for unit testing and therefore I cannot use config files to specify service bebaviours etc. At the moment my code looks like follows, but despite scouring the web I have been unable to work out how I set up a ServiceHostingEnvironment class and set the AspNetCompatibilityEnabled property to true without using config, can anyone help?

string serviceUrl = "http://localhost:8082/MyService.svc";

_host = new ServiceHost(typeof(MyService), new Uri[] { new Uri(serviceUrl) });

ServiceEndpoint serviceEndpoint 
    = _host.AddServiceEndpoint(typeof(IMyService), new WebHttpBinding(), string.Empty);

serviceEndpoint.Behaviors.Add(new WebHttpBehavior());

// Here's where I'm stuck, i need something like...
ServiceHostingEnvironmentSection shes = new ServiceHostingEnvironmentSection();
shes.AspNetCompatibilityEnabled = true;
_host.Add(shes);

_host.Open();

Any help is much appreciated and thanks in advance.

Oliver Pearmain
  • 19,885
  • 13
  • 86
  • 90

5 Answers5

5

You can totally do this, I don't know what these other answers are about, but they are way off!

Just do something like:

_host = new ServiceHost(...);
// Remove existing behavior as it is readOnly
for (int i = 0; i < _host.Description.Behaviors.Count; i++)
{
    if (_host.Description.Behaviors[i] is AspNetCompatibilityRequirementsAttribute)
    {
      _host.Description.Behaviors.RemoveAt(i);
      break;
    }
}
// Replace behavior with one that is configured the way you desire.
_host.Description.Behaviors.Add(new AspNetCompatibilityRequirementsAttribute { RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed });
_host.Open();

-- Edit This removes the existing behavior if it exists, and then adds a new behavior that has the mode you prefer. My example sets it to .Allowed, but you could of course set it to the mode you desire.

Austin Harris
  • 1,676
  • 1
  • 17
  • 22
  • This just sets if the service itself requires aspnet compatibility and not the Host. – Mhammad Chehab Jun 29 '15 at 10:00
  • How will that set the `AspNetCompatibilityEnabled` property to true? – drowa May 22 '17 at 20:34
  • This does not change the hosting environment setting, only changes the value indicating what the service requires. – MatteS Oct 29 '19 at 14:09
  • @MatteS You realize this question is 10 years old right? This answer solves the problem that was posted by the OP. If you are trying to do something different, and this doesn't work for what you are trying to do, then you should ask a new question. – Austin Harris Nov 16 '19 at 00:47
  • The question is the same I find, and still relevant apparently. – MatteS Nov 21 '19 at 11:12
  • The question was not about the ServiceHostingEnvironment. It was about AspNetCompatibilityRequirements attribute. Read the question again. – Austin Harris Nov 28 '19 at 04:28
1

To Elaborate on Austin Harris's answer:

You need to alter the behaviour of ServiceHost.
Because the attribute is read-only, you need to remove, and readd the behaviour to ServiceHost.
If you have a console application or Windows Service, you'll have this servicehost defined.

Something like this:

public static void Main()
{
  using (ServiceHost serviceHost = new ServiceHost(typeof(CalculatorService)))
  {
    try
    {
      // Open the ServiceHost to start listening for messages.
      serviceHost.Open();

        // The service can now be accessed.
      Console.WriteLine("The service is ready.");
      Console.WriteLine("Press <ENTER> to terminate service.");
      Console.ReadLine();

      // Close the ServiceHost.
      serviceHost.Close();
    }
    catch (TimeoutException timeProblem)
    {
      Console.WriteLine(timeProblem.Message);
      Console.ReadLine();
    }
    catch (CommunicationException commProblem)
    {
      Console.WriteLine(commProblem.Message);
      Console.ReadLine();
    }
  }
}

in which case Austin Harris' code would suffice (if he hadn't written Allowed instead of Required...).

However, if you have the WCF-Service integrated into an ASP.NET application, the tricky part is to get the ServiceHost.

The key is the factory attribute in the YOUR_SERVICE.svc markup file.

<%@ ServiceHost Factory="ApertureImportBelegung.DerivedFactory"  Language="VB" Debug="true" Service="ApertureImportBelegung.ImportBelegung" CodeBehind="Service1.svc.vb" %>

Then you need to write your own factory.
The below code is VB.NET, I'll leave it to the reader to translate it into C# (you'll need to set WITH_FORMS_AUTHENTICATION to true by the way, and C# ps: http://converter.telerik.com)

'Imports System.ServiceModel
Imports System.ServiceModel.Description
'Imports System.ServiceModel.Dispatcher
'Imports System.ServiceModel.Channels

'Imports System.ServiceModel.Configuration
Imports System.ServiceModel.Activation ' Add reference to assembly System.ServiceModel.Activation.dll



Public Class DerivedHost
    Inherits ServiceHost


    Public Sub New(t As Type, ParamArray baseAddresses() As Uri)
        MyBase.New(t, baseAddresses)
    End Sub


    Protected Overrides Sub OnOpening()
        'Me.Description.Behaviors.Add(New mys)
        'Me.Description.Add(New MyServiceBehavior())

        'Me.Description.Behaviors.Add(New WcfMessageLoggerExtension())
        MyBase.OnOpening()
    End Sub


End Class ' DerivedHost 



' http://msdn.microsoft.com/en-us/library/aa702697(v=vs.110).aspx
Public Class DerivedFactory
    Inherits ServiceHostFactory


    Protected Overrides Function CreateServiceHost(t As Type, baseAddresses As Uri()) As ServiceHost
        Return New DerivedHost(t, baseAddresses)
    End Function ' CreateServiceHost


    'Then in the CreateServiceHost method, we can do all of the 
    'things that we can do in a self-hosted case: 
    Public Overrides Function CreateServiceHost(service As String, baseAddresses As Uri()) As ServiceHostBase
        Application.ConfigData.ReadConfigData()

        ' The service parameter is ignored here because we know our service.
        Dim serviceHost As New ServiceHost(GetType(ImportBelegung), baseAddresses)
        ' System.ServiceModel.ServiceHostingEnvironment.AspNetCompatibilityEnabled = True

        ' http://stackoverflow.com/questions/13597408/wcf-message-inspector-is-not-working
        'Dim serviceHost As New System.ServiceModel.ServiceHost(GetType(ImportBelegung))
        'serviceHost.Description.Behaviors.Add(New WcfMessageLoggerExtension())


        ' http://stackoverflow.com/questions/5907791/how-to-programatically-create-a-wcf-service-and-its-metadata-on-the-same-url
        ' http://msdn.microsoft.com/en-us/library/system.servicemodel.servicehost(v=vs.110).aspx


        ' host.Open()
        'This example iterates through all the ServiceEndpoint objects and adds ConsoleMessageTracing as an endpoint behavior:
        For Each endpoint As ServiceEndpoint In serviceHost.Description.Endpoints
            'endpoint.Behaviors.Add(New WcfMessageLoggerExtension())
            'endpoint.Behaviors.Add(New ConsoleOutputBehaviorExtensionElement)

            endpoint.Behaviors.Add(New MessageInspector.ConsoleOutputBehavior)
            endpoint.Behaviors.Add(New HeaderInspector.ConsoleOutputHeaderBehavior)
        Next endpoint


        '  Ensure (in <system.serviceModel>)  aspNetCompatibilityEnabled="true" -->  <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
#Const WITH_FORMS_AUTHENTICATION = False

#If WITH_FORMS_AUTHENTICATION Then

        For i As Integer = 0 To serviceHost.Description.Behaviors.Count - 1 Step 1
            If TypeOf serviceHost.Description.Behaviors(i) Is AspNetCompatibilityRequirementsAttribute Then
                serviceHost.Description.Behaviors.RemoveAt(i)
                Exit For
            End If
        Next i

        serviceHost.Description.Behaviors.Add(New AspNetCompatibilityRequirementsAttribute() With {.RequirementsMode = AspNetCompatibilityRequirementsMode.Required})

#End If



        Return serviceHost
    End Function ' CreateServiceHost


End Class ' DerivedFactory 
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
1

After digging around with Reflector, I was able to set the AspNetCompatibilityEnabled flag using reflection. This approach has obvious drawbacks, but it did the job for me:

        // get the ServiceHostingEnvironmentSection by calling an internal static method
        var section = (ServiceHostingEnvironmentSection)typeof(ServiceHostingEnvironmentSection).GetMethod("UnsafeGetSection", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).Invoke(null, null);
        // set the read-only flag to false so values can be updated
        typeof(ServiceHostingEnvironmentSection).BaseType.BaseType.GetField("_bReadOnly", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(section, false);
        // set the AspNetCompatibilityEnabled value
        section.AspNetCompatibilityEnabled = true;

        // now one can add a Service Route
        routes.Add(new ServiceRoute("MyRoutePrefix", new ServiceHostFactory(), typeof(MyService)));
Adi
  • 106
  • 1
  • 6
0

It's an AppDomain-wide setting that you can set on the static ServiceHostingEnvironment class in System.ServiceModel:

 ServiceHostingEnvironment.AspNetCompatibilityEnabled = true;

This should be done before you create and open your service hosts.

Would have been nice - but it's a read-only setting, and the only way to set it appears to be through configuration :-(

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • Unfortunately that property has no setter. – Oliver Pearmain Nov 12 '09 at 11:28
  • Is there a workaround for this? It's amazing that you can almost completely configure WCF programmatically except for this particular case. I would call this a bug in WCF. Is there a way to at least tell your service to do it for you? – ATL_DEV Aug 03 '11 at 18:38
  • @marc_s: All you need to do is to set the service-factory (see my answer), though that's not site-wide, only service-wide. – Stefan Steiger Oct 08 '14 at 12:48
  • the aformentioned answer does not show how to set this specific value programmatically. – specializt Sep 20 '19 at 11:00
-3

Consider factoring out the explicit use of HttpContext.Current behind an interface that you can stub out during unit testing.

HttpContext.Current is only defined when your wcf service is hosted within an asp.net web application anyway - if some day you need to host it as an ordinary wcf service, HttpContext.Current is not going to be available.