使用 HTTPS 以编程方式配置 ServiceHost 端点?
Programmatically configure ServiceHost endpoints with HTTPS?
我正在使用无文件激活,这是我在服务器端的完整 web.config,它有两个端点:
<?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>
这是我的 CustomServiceHostFactory:
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new CustomServiceHost(serviceType, baseAddresses);
}
}
这是我的 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);
}
}
最后是定义绑定的 WcfHelper。这是在共享位置,因此我可以使用相同的方式以编程方式配置客户端绑定:
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 }
}
};
}
}
当我发布这个 WebHost 项目并尝试浏览到两个地址之一时,如下所示:
https://myserver/Project/Account/AccountService.svc
我收到以下错误:
The provided URI scheme 'http' is invalid; expected 'https'. Parameter
name: context.ListenUriBaseAddress
我注意到在 CustomServiceHost AddEndpoints() 方法中,当循环遍历 BaseAddresses 时,如果我像这样硬编码地址:
https://myserver/Project/Account/AccountService.svc
然后我可以成功浏览到它。使用无文件激活和相对寻址时如何构建 BaseAddresses?我在哪里可以指定他们使用 https(他们现在似乎在使用 http)?
提前致谢。
编辑 1: 这将解决问题,但看起来完全是 hack,我在哪里使用无文件激活指定 https,以便使用 https 构建相对地址?
var endpoint = new ServiceEndpoint(ContractDescription.GetContract(Description.ServiceType),
wsHttpBinding, new EndpointAddress(address.OriginalString.Replace("http:", "https:")));
编辑 2: 我想我正在了解这里发生的事情。感谢@Andreas K 为我指明了正确的方向。如果我进入 IIS 并查看站点的绑定,如图所示有多个:
在 BaseAddresses 上循环时,我在 AddEndpoints() 方法中放置了一些代码来写入数据库。当我尝试使用浏览器访问服务时:https://my.server.local/Project/Account/AccountService.svc,在数据库中创建了两个条目。
http://my.server.local/Project/Account/AccountService.svc
https://my.server.local/Project/Account/AccountService.svc
因此,IIS SITE BINDING 似乎正在被拾取。但是,现在我不确定为什么数据库中没有更多的 BaseAddresses 条目。 net.pipe、net.tcp 等在哪里?
试试这个:
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();
要获得证书,请执行以下操作(注意证书必须在证书存储中):
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
cert = store.Certificates.Find(X509FindType.FindBySubjectName, certSubject, false)[0];
这无需 IIS 即可运行。如果您使用的是 IIS,则不需要将证书绑定到端口(我认为)
我在 msdn 上找到了这个:
无文件激活
尽管 .svc 文件可以轻松公开 WCF 服务,但更简单的方法是在 Web.config 内定义虚拟激活端点,从而完全不需要 .svc 文件。
在 WCF 4 中,您可以在 Web.config 中定义映射到您的服务类型的虚拟服务激活端点。这使得激活 WCF 服务成为可能,而无需维护物理 .svc 文件(a.k.a。“无文件激活”)。以下示例显示了如何配置激活端点:
<configuration>
<system.serviceModel>
<serviceHostingEnvironment>
<serviceActivations>
<add relativeAddress="Greeting.svc" service="GreetingService"/>
</serviceActivations>
</serviceHostingEnvironment>
</system.serviceModel>
</configuration>
有了这个,现在可以使用“Greeting.svc”的相对路径激活 GreetingService (相对于 Web 应用程序的基地址) .为了说明这一点,我在我的机器上创建了一个名为“GreetingSite”的 IIS 应用程序,我将其分配给“ASP.NET v4.0”应用程序池,并将其映射到包含web.config 如上所示。现在我可以简单地浏览到 http://localhost/GreetingSite/Greeting.svc,而实际上磁盘上没有物理 .svc 文件。图 9 显示了它在浏览器中的样子。
希望对您有所帮助
事实证明,BaseAddresses 来自我的更新 2 中提到的 IIS 绑定,再次感谢@Andreas K 为我指明了正确的方向。在 IIS 中,我有一个网站,其下有多个应用程序。我在这些绑定上同时启用了 http 和 https。我已将 CustomServiceHost 中的 AddEndpoings() 方法更新为如下所示:
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();
}
}
由于站点下的其他应用程序需要http,所以我的BaseAddresses总是包含两个(http和https)。我需要手动过滤 http 的,因为我不想为这个特定站点公开它们。现在我知道他们是如何被填充的,我很满意。谢谢大家
我正在使用无文件激活,这是我在服务器端的完整 web.config,它有两个端点:
<?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>
这是我的 CustomServiceHostFactory:
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new CustomServiceHost(serviceType, baseAddresses);
}
}
这是我的 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);
}
}
最后是定义绑定的 WcfHelper。这是在共享位置,因此我可以使用相同的方式以编程方式配置客户端绑定:
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 }
}
};
}
}
当我发布这个 WebHost 项目并尝试浏览到两个地址之一时,如下所示: https://myserver/Project/Account/AccountService.svc 我收到以下错误:
The provided URI scheme 'http' is invalid; expected 'https'. Parameter name: context.ListenUriBaseAddress
我注意到在 CustomServiceHost AddEndpoints() 方法中,当循环遍历 BaseAddresses 时,如果我像这样硬编码地址: https://myserver/Project/Account/AccountService.svc 然后我可以成功浏览到它。使用无文件激活和相对寻址时如何构建 BaseAddresses?我在哪里可以指定他们使用 https(他们现在似乎在使用 http)?
提前致谢。
编辑 1: 这将解决问题,但看起来完全是 hack,我在哪里使用无文件激活指定 https,以便使用 https 构建相对地址?
var endpoint = new ServiceEndpoint(ContractDescription.GetContract(Description.ServiceType),
wsHttpBinding, new EndpointAddress(address.OriginalString.Replace("http:", "https:")));
编辑 2: 我想我正在了解这里发生的事情。感谢@Andreas K 为我指明了正确的方向。如果我进入 IIS 并查看站点的绑定,如图所示有多个:
在 BaseAddresses 上循环时,我在 AddEndpoints() 方法中放置了一些代码来写入数据库。当我尝试使用浏览器访问服务时:https://my.server.local/Project/Account/AccountService.svc,在数据库中创建了两个条目。 http://my.server.local/Project/Account/AccountService.svc https://my.server.local/Project/Account/AccountService.svc
因此,IIS SITE BINDING 似乎正在被拾取。但是,现在我不确定为什么数据库中没有更多的 BaseAddresses 条目。 net.pipe、net.tcp 等在哪里?
试试这个:
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();
要获得证书,请执行以下操作(注意证书必须在证书存储中):
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
cert = store.Certificates.Find(X509FindType.FindBySubjectName, certSubject, false)[0];
这无需 IIS 即可运行。如果您使用的是 IIS,则不需要将证书绑定到端口(我认为)
我在 msdn 上找到了这个:
无文件激活 尽管 .svc 文件可以轻松公开 WCF 服务,但更简单的方法是在 Web.config 内定义虚拟激活端点,从而完全不需要 .svc 文件。 在 WCF 4 中,您可以在 Web.config 中定义映射到您的服务类型的虚拟服务激活端点。这使得激活 WCF 服务成为可能,而无需维护物理 .svc 文件(a.k.a。“无文件激活”)。以下示例显示了如何配置激活端点:
<configuration>
<system.serviceModel>
<serviceHostingEnvironment>
<serviceActivations>
<add relativeAddress="Greeting.svc" service="GreetingService"/>
</serviceActivations>
</serviceHostingEnvironment>
</system.serviceModel>
</configuration>
有了这个,现在可以使用“Greeting.svc”的相对路径激活 GreetingService (相对于 Web 应用程序的基地址) .为了说明这一点,我在我的机器上创建了一个名为“GreetingSite”的 IIS 应用程序,我将其分配给“ASP.NET v4.0”应用程序池,并将其映射到包含web.config 如上所示。现在我可以简单地浏览到 http://localhost/GreetingSite/Greeting.svc,而实际上磁盘上没有物理 .svc 文件。图 9 显示了它在浏览器中的样子。
希望对您有所帮助
事实证明,BaseAddresses 来自我的更新 2 中提到的 IIS 绑定,再次感谢@Andreas K 为我指明了正确的方向。在 IIS 中,我有一个网站,其下有多个应用程序。我在这些绑定上同时启用了 http 和 https。我已将 CustomServiceHost 中的 AddEndpoings() 方法更新为如下所示:
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();
}
}
由于站点下的其他应用程序需要http,所以我的BaseAddresses总是包含两个(http和https)。我需要手动过滤 http 的,因为我不想为这个特定站点公开它们。现在我知道他们是如何被填充的,我很满意。谢谢大家