2

I'm using Fileless Activation, here is my full web.config on the server side, which has two endpoints:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="entityFramework" 
             type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
             requirePermission="false" />
  </configSections>
  <connectionStrings>
    <add name="RedStripe"
         connectionString="Data Source=S964;Initial Catalog=MyDatabase;Persist Security Info=True;User ID=sa;Password=***;MultipleActiveResultSets=True"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
      <parameters>
        <parameter value="mssqllocaldb" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
  <system.web>
    <customErrors mode="Off"/>
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment>
      <!-- where virtual .svc files are defined -->
      <serviceActivations>     
        <add service="Company.Project.Business.Services.AccountClassService" 
             relativeAddress="Account/AccountClassService.svc" 
             factory="Company.Project.WebHost.CustomServiceHostFactory"/>

        <add service="Company.Project.Business.Services.AccountService"
             relativeAddress="Account/AccountService.svc"
             factory="Company.Project.WebHost.CustomServiceHostFactory"/>

      </serviceActivations>
    </serviceHostingEnvironment>
  </system.serviceModel>
</configuration>

Here is my CustomServiceHostFactory:

public class CustomServiceHostFactory : ServiceHostFactory
{
    protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
    {
        return new CustomServiceHost(serviceType, baseAddresses);
    }
}

And here is my CustomServiceHost:

public class CustomServiceHost : ServiceHost
{        
    public CustomServiceHost(Type serviceType, Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    {
    }

    protected override void InitializeRuntime()
    {
        AddServiceDebugBehavior();
        AddWcfMessageLoggingBehavior();
        AddGlobalErrorHandlingBehavior();
        AddServiceCredentialBehavior();
        AddEndpoints();
        ConfigureThrottling();
        base.InitializeRuntime();
    }

    private void AddEndpoints()
    {
        var wsHttpBinding = WcfHelpers.ConfigureWsHttpBinding();

        foreach (Uri address in BaseAddresses)
        {
            var endpoint = new ServiceEndpoint(
                ContractDescription.GetContract(Description.ServiceType),
                wsHttpBinding, new EndpointAddress(address));

            AddServiceEndpoint(endpoint);

            //adding mex
            AddServiceMetadataBehavior();
            AddServiceEndpoint(
                ServiceMetadataBehavior.MexContractName,
                MetadataExchangeBindings.CreateMexHttpBinding(),
                address.AbsoluteUri + "/mex");

            break;
        }
    }
    private void AddGlobalErrorHandlingBehavior()
    {
        var errorHanlderBehavior = Description.Behaviors.Find<GlobalErrorBehaviorAttribute>();

        if (errorHanlderBehavior == null)
        {
            Description.Behaviors.Add(new GlobalErrorBehaviorAttribute(typeof(GlobalErrorHandler)));
        }
    }

    private void AddServiceCredentialBehavior()
    {
        var credentialBehavior = Description.Behaviors.Find<ServiceCredentials>();

        if (credentialBehavior == null)
        {
            var customAuthenticationBehavior = new ServiceCredentials();
            customAuthenticationBehavior.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
            customAuthenticationBehavior.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNamePasswordValidator();
            Description.Behaviors.Add(customAuthenticationBehavior);
        }
    }
    private void AddServiceDebugBehavior()
    {
        var debugBehavior = Description.Behaviors.Find<ServiceDebugBehavior>();

        if (debugBehavior == null)
        {
            Description.Behaviors.Add(
                new ServiceDebugBehavior() {IncludeExceptionDetailInFaults = true});
        }
        else
        {
            if (!debugBehavior.IncludeExceptionDetailInFaults)
                debugBehavior.IncludeExceptionDetailInFaults = true;
        }
    }
    private void AddServiceMetadataBehavior()
    {
        var metadataBehavior = Description.Behaviors.Find<ServiceMetadataBehavior>();

        if (metadataBehavior == null)
        {
            ServiceMetadataBehavior serviceMetadataBehavior = new ServiceMetadataBehavior();
            serviceMetadataBehavior.HttpsGetEnabled = true;
            Description.Behaviors.Add(serviceMetadataBehavior);
        }
    }
    private void AddWcfMessageLoggingBehavior()
    {
        var messageInspectorBehavior = Description.Behaviors.Find<WcfMessageInspector>();

        if (messageInspectorBehavior == null)
        {
            Description.Behaviors.Add(new WcfMessageInspector());
        }
    }
    private void ConfigureThrottling()
    {
        var throttleBehavior = Description.Behaviors.Find<ServiceThrottlingBehavior>();

        if (throttleBehavior != null) return;

        throttleBehavior = new ServiceThrottlingBehavior
        {
            MaxConcurrentCalls = 100,
            MaxConcurrentInstances = 100,
            MaxConcurrentSessions = 100
        };

        Description.Behaviors.Add(throttleBehavior);
    }
}

Finally here is the WcfHelper where the binding is defined. This is in a shared location so I can programmatically configure the client side binding using the same:

public class WcfHelpers
{
    public static WSHttpBinding ConfigureWsHttpBinding()
    {
        return new WSHttpBinding
        {
            Name = "myWSHttpBinding",                
            OpenTimeout = new TimeSpan(0, 10, 0),
            CloseTimeout = new TimeSpan(0, 10, 0),
            SendTimeout = new TimeSpan(0, 10, 0),
            MaxBufferPoolSize = 104857600,
            MaxReceivedMessageSize = 104857600,
            Namespace = Constants.RedStripeNamespace,
            ReaderQuotas = new XmlDictionaryReaderQuotas()
            {
                MaxDepth = 104857600,
                MaxStringContentLength = 104857600,
                MaxArrayLength = 104857600,
                MaxBytesPerRead = 104857600,
                MaxNameTableCharCount = 104857600
            },
            Security =
            {
                Mode = SecurityMode.TransportWithMessageCredential,
                Message = { ClientCredentialType = MessageCredentialType.UserName }
            }
        };

    }
}

When I publish this WebHost project and try to browse to one of the two addreses like so: https://myserver/Project/Account/AccountService.svc I get the following error:

The provided URI scheme 'http' is invalid; expected 'https'. Parameter name: context.ListenUriBaseAddress

I notice that in the CustomServiceHost AddEndpoints() method, when looping over BaseAddresses, if I hardcode an address there like so: https://myserver/Project/Account/AccountService.svc I can then browse to it successfully. How do the BaseAddresses get built when using fileless activation and relative addressing? Where can I specify they use https (where it seems they are using http now)?

Thanks in advance.


Edit 1: This will fix the problem but seems like a total hack, where do I specify https using fileless activation so the relative address builds with https?

var endpoint = new ServiceEndpoint(ContractDescription.GetContract(Description.ServiceType),
wsHttpBinding, new EndpointAddress(address.OriginalString.Replace("http:", "https:")));

Edit 2: I think I'm gaining an understanding of what is going on here. Thank you @Andreas K for pointing me in the right direction. If I go into IIS and look at the bindings for the site, there are multiple as indicated by the image:enter image description here

I put some code to write to a database inside my AddEndpoints() method when looping over BaseAddresses. When I try to use the browser to get to the service like so: https://my.server.local/Project/Account/AccountService.svc, TWO entries are created in the database. http://my.server.local/Project/Account/AccountService.svc https://my.server.local/Project/Account/AccountService.svc

Thus, it seems the IIS SITE BINDING is being picked up. However, now I'm not sure why there aren't more entries in the database for the BaseAddresses. Where are the net.pipe, net.tcp, etc?

BBauer42
  • 3,549
  • 10
  • 44
  • 81

3 Answers3

2

It turns out the BaseAddresses come from the IIS binding as mentioned in my Update 2, and again thanks to @Andreas K for pointing me to the right direction. In IIS I have one website with multiple applications under it. I have both http and https enabled on those bindings. I have updated my AddEndpoings() method in the CustomServiceHost to look like this:

private void AddEndpoints()
{
    var wsHttpBinding = WcfHelpers.ConfigureWsHttpBinding();

    foreach (var address in BaseAddresses.Where(a => a.Scheme == Uri.UriSchemeHttps))
    {
        var endpoint = new ServiceEndpoint(
            ContractDescription.GetContract(Description.ServiceType),
            wsHttpBinding, 
            new EndpointAddress(address));

        AddServiceEndpoint(endpoint);
        AddServiceMetadataBehavior();
    }
}

Since other applications under the site need http, my BaseAddresses always contains two (http and https). I needed to manually filter the http ones since I don't want to expose them for this particular site. Now that I know HOW they are being populated I am satisfied. Thanks all.

BBauer42
  • 3,549
  • 10
  • 44
  • 81
1

Try this:

WebHttpBinding binding = new WebHttpBinding();
binding.Security.Mode = WebHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None; // no password

// If you are not using IIS, you need to bind cert to port
Process proc = new Process();
proc.StartInfo.FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "netsh.exe");
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.StartInfo.Arguments =
    string.Format("http add sslcert ipport={0}:{1} certhash={2} appid={{{3}}}", ip, port, cert.Thumbprint, Guid.NewGuid());
proc.Start();
proc.WaitForExit();

To get a cert do the following (note cert must be in cert store):

X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
cert = store.Certificates.Find(X509FindType.FindBySubjectName, certSubject, false)[0];

This will work without IIS. If you are using IIS you don't need to bind cert to port (I think)

Zeus82
  • 6,065
  • 9
  • 53
  • 77
  • Try where? I HAVE to use WsHttpBinding and TransportWithMessageCredential, so not sure how this applies? And yes, I am using IIS. – BBauer42 May 04 '15 at 16:07
  • Can you replace your WsHttpBinding with a WebHttpBinding? And configure it as in the example I provide? – Zeus82 May 04 '15 at 16:09
  • Ok, I changed my WsHttpBinding to use your WebHttpBinding, re-publish to IIS and get the same error. "The provided URI scheme 'http' is invalid; expected 'https'." – BBauer42 May 04 '15 at 16:15
  • Where is it getting the URI? Is that from your web.config? Make sure your URI starts with HTTPS – Zeus82 May 04 '15 at 16:25
  • That is my question. I'm using file-less activation like stated in the post. I just provide the relative address in the tag. I don't know how the BaseAddresses get populated and how to change them to use https. – BBauer42 May 04 '15 at 17:09
1

I found this on the msdn:

File-less Activation Although .svc files make it easy to expose WCF services, an even easier approach would be to define virtual activation endpoints within Web.config, thereby removing the need for .svc files altogether. In WCF 4, you can define virtual service activation endpoints that map to your service types in Web.config. This makes it possible to activate WCF services without having to maintain physical .svc files (a.k.a. “file-less activation”). The following example shows how to configure an activation endpoint:

<configuration>
  <system.serviceModel>
    <serviceHostingEnvironment>
      <serviceActivations>
        <add relativeAddress="Greeting.svc" service="GreetingService"/>
      </serviceActivations>
    </serviceHostingEnvironment>
  </system.serviceModel>
</configuration>

With this in place, it’s now possible to activate the GreetingService using a relative path of “Greeting.svc” (relative to the base address of the Web application). In order to illustrate this, I’ve created an IIS application on my machine called “GreetingSite”, which I assigned to the “ASP.NET v4.0” application pool, and mapped it to the GreetingService project directory that contains the web.config shown above. Now I can simply browse to http://localhost/GreetingSite/Greeting.svc without actually having a physical .svc file on disk. Figure 9 shows what this looks like in the browser.

I hope this can help you

Andreas K
  • 222
  • 1
  • 11
  • Yes I have all of that working, and am publishing to IIS. In my post you'll see I have a serviceActivations section with two services defined, each with a relativeAddress, service, and factory value set. My factory is defining the wsHttpBinding with Security.Mode = TransportWithMessageCredential and Security.Message.ClientCredentialType = MessageCredentialType.Username. This should cause the Schem to be https, but in my CustomServiceHost the BaseAddresses are always http. How do I set them to https? – BBauer42 May 05 '15 at 12:10
  • Also, see edit 1 in the post if you have't already. I can fix the issue but it seems like a hack. – BBauer42 May 05 '15 at 12:11
  • you host your wcf service on a IIS. So the IIS provides a binding to the "outside". Please check this binding. And yes the edit is like a hack :P – Andreas K May 05 '15 at 12:16
  • In IIS I have Require SSL checked and ClientCertificates set to "Accept". You can see the binding I'm using in the OP, unless there is some other binding I don't know about? – BBauer42 May 05 '15 at 13:18
  • 1
    Yes the, IIS has its own binding. I think if you use the relative address for the WCF service, the url is just set together from the IIS binding + webapp + service. Can you please take a look to this menue in the IIS console: https://www.sslshopper.com/assets/images/host-headers-iis7/step2-site-bindings.png – Andreas K May 05 '15 at 13:22
  • In that window I see type: https, IP address is populated, port 443, SSL cert is populated with my cert, all looks correct to me. – BBauer42 May 05 '15 at 13:46
  • hmm yes, this seems correct. let me think about it. Together we will figure it out. – Andreas K May 05 '15 at 13:57
  • In this article: https://msdn.microsoft.com/en-us/magazine/cc163412.aspx look at Figure 2 Using Relative URIs and chapter IIS Addressing Considerations. I hope there you will find the solution... – Andreas K May 05 '15 at 14:18
  • please see my updated post Edit 2, thanks for the help so far, getting there. – BBauer42 May 05 '15 at 15:51
  • Hi, so i think you just figuerd it out. Like I said, ist the IIS binding. To your Question: there are only http and https addressess mapped to your IIS host. I would appreciate, if you could accept my answer as the correct one. – Andreas K May 05 '15 at 16:18
  • Yes, thanks! I've posted and marked a more thorough answer myself, but have awarded you the bounty for pointing me in the right direction. Thanks. – BBauer42 May 05 '15 at 17:24