Programmatically configure ServiceHost endpoints with HTTPS? - wcf

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:
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?

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.

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)

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

Related

I get an access error when attempting to initialize raven db

Here's sample code:
private static Raven.Client.Embedded.EmbeddableDocumentStore _documentStore;
public static Raven.Client.Embedded.EmbeddableDocumentStore documentStore
{
get
{
if (_documentStore == null)
{
_documentStore = new Raven.Client.Embedded.EmbeddableDocumentStore
{
DataDirectory = "App_Data/RavenDbData",
UseEmbeddedHttpServer = true
};
_documentStore.Initialize();
}
return _documentStore;
}
}
The exception message looke like this when the _documentStore.Initialize(); line is called:
System.Net.HttpListenerException: The process cannot access the file because it is being used by another process
It turns out that this exception gets thrown if port 8080 is used by anything else. The fix was to add this bit to the web.config to change the port number (pick any port number)
<appSettings>
<add key="Raven/Port" value="8082"/>
</appSettings>

WCF service proxy doesn't refresh binding settings from config file

My application changes end-point settings at run-time and persists the changes to config file. But when I create a new service proxy instance, the end-point settings are the ones that were before the update. How do I force the proxy to get new settings?
You will need to detect if your configuration file has been updated be it your main app.config / web.config or any external configuration file you are using via configSource.
If a change is detected you will need to refresh the system.serviceModel configuration section:
ConfigurationManager.RefreshSection("system.serviceModel/client");
Existing Channels and ChannelFactories will not pick up the changes so they will need to be closed and new ones created.
The following example shows this in action:
[TestFixture]
class When_trying_to_change_service_endpoints_on_the_fly
{
[Test]
public void Should_use_the_new_endpoint()
{
var someService1 = Substitute.For<ISomeWebService>();
var someService2 = Substitute.For<ISomeWebService>();
var serviceHost1 = CreateServiceHost(someService1, new Uri("http://localhost:50001"));
var serviceHost2 = CreateServiceHost(someService2, new Uri("http://localhost:50002"));
serviceHost1.Open();
serviceHost2.Open();
UpdateEndpointInConfig(new Uri("http://localhost:50001"));
var channelFactory = new ChannelFactory<ISomeWebService>("TestReloadConfig");
var channel1 = channelFactory.CreateChannel();
channel1.ServiceMethod();
((IChannel)channel1).Close();
channelFactory.Close();
UpdateEndpointInConfig(new Uri("http://localhost:50002"));
channelFactory = new ChannelFactory<ISomeWebService>("TestReloadConfig");
var channel2 = channelFactory.CreateChannel();
channel2.ServiceMethod();
((IChannel)channel2).Close();
serviceHost1.Close();
serviceHost2.Close();
someService1.Received(1).ServiceMethod();
someService2.Received(1).ServiceMethod();
}
private static void UpdateEndpointInConfig(Uri endpointAddress)
{
var configFile = new ExeConfigurationFileMap();
configFile.ExeConfigFilename = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;
var config = ConfigurationManager.OpenMappedExeConfiguration(configFile, ConfigurationUserLevel.None);
var serviceModelConfig = (ServiceModelSectionGroup) config.GetSectionGroup("system.serviceModel");
serviceModelConfig.Client.Endpoints[0].Address = endpointAddress;
config.Save();
ConfigurationManager.RefreshSection("system.serviceModel/client");
}
private ServiceHost CreateServiceHost<TService>(TService service, Uri baseUri)
{
var serviceHost = new ServiceHost(service, new Uri[0]);
serviceHost.Description.Behaviors.Find<ServiceDebugBehavior>().IncludeExceptionDetailInFaults = true;
serviceHost.Description.Behaviors.Find<ServiceBehaviorAttribute>().InstanceContextMode = InstanceContextMode.Single;
serviceHost.AddServiceEndpoint(typeof(TService), new BasicHttpBinding(), baseUri);
return serviceHost;
}
}
[ServiceContract]
public interface ISomeWebService
{
[OperationContract]
void ServiceMethod();
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<client>
<endpoint address="http://localhost:50000" binding="basicHttpBinding" contract="Junk.ISomeWebService" name="TestReloadConfig" />
</client>
</system.serviceModel>
</configuration>
If you managed the endpoint configurations another way you could update any ChannelFactory instance manually as you have access to the Endpoint and Binding properties.

RavenB select database

var store = new DocumentStore()
{
Url = #"http://localhost"
};
store.Initialize();
Blog blog = new Blog()
{
Title = "Hello RavenDB",
Category = "RavenDB",
Content = "This is a blog about RavenDB",
Comments = new BlogComment[]{
new BlogComment() { Title = "Unrealistic", Content= "This example is unrealistic"},
new BlogComment() { Title = "Nice", Content= "This example is nice"}
}
};
using (IDocumentSession session = store.OpenSession())
{
session.Store(blog);
session.SaveChanges();
}
The above code saves data to the default database. (It is a web application.) But I want it save data to another database that I created the raven management studio (web page). Where do i specify the database name? Also please tell me how I can save the connection string with the database name in the config file. This is how I would save it to config file without the database name
<connectionStrings>
<add name="Local" connectionString="DataDir = ~\Data"/>
<add name="Server" connectionString="Url = http://localhost:8080"/>
</connectionStrings>
All of your questions are explained in the documentation:
new DocumentStore
{
ConnectionStringName = "Local"
}
<connectionStrings>
<add name="Local" connectionString="DataDir=~\Data;Database=MyDatabaseName"/>
<add name="Server" connectionString="Url=http://localhost:8080;Database=MyDatabaseName"/>
</connectionStrings>
The other answers are ok, but for efficiency you really only want one instance of DocumentStore for your application, unless you are running multiple Raven servers and then it would be acceptable to have one per server.
If you are just connecting to different databases on the same server, you should use:
var store = new DocumentStore(...your connection string or inline options...);
using (var session = store.OpenSession("the database name")
{
...
}
You can keep your connection strings data as you shown, best with the databases names at the end:
<connectionStrings>
<add name="Core" connectionString="Url=http://localhost:8082/databases/Core"
providerName="My primary database." />
<add name="Backup" connectionString="Url=http://localhost:8082/databases/Backup"
providerName="My backup stuff." />
</connectionStrings>
Next you can implement singleton class which will keep all your handlers for defined sources, for example:
public class DocumentStoreProvider : IDocumentStoreProvider
{
private static readonly IDictionary<string, IDocumentStore> _documentStores = new Dictionary<string, IDocumentStore>();
private static readonly DocumentStoreProvider _instance = new DocumentStoreProvider();
private DocumentStoreProvider()
{
var connectionStrings = ConfigurationManager.ConnectionStrings;
foreach (ConnectionStringSettings connectionString in connectionStrings)
{
var name = connectionString.Name;
var connection = connectionString.ConnectionString;
IDocumentStore currentStore = new DocumentStore { ConnectionStringName = name };
currentStore.Initialize();
currentStore.DatabaseCommands.EnsureDatabaseExists(name);
IndexCreation.CreateIndexes(Assembly.Load("Your.Assembly.Containing.Indexes"), currentStore);
_documentStores.Add(name, currentStore);
}
}
public static DocumentStoreProvider Instance
{
get { return _instance; }
}
public IDocumentStore GetDocumentStore(string key)
{
return _documentStores[key];
}
}
The usage can be following:
using (var session = DocumentStoreProvider.Instance.GetDocumentStore("Backup").OpenSession())
{
/* do stuff for chosen database... */
session.Store(something);
session.SaveChanges();
}

WCF REST WebHttpBinding cookie handling

The problem is WCF client doesn't respect server cookie (doesn't set it on the next request). Here is how I create the client:
WebChannelFactory<IPostService> factory = new WebChannelFactory<IPostService>(
new WebHttpBinding() {AllowCookies = true},
new Uri("http://10.6.90.45:8081"));
_proxy = factory.CreateChannel();
Settings AllowCookies to false has no effect too.
What I did for now is wrote a simple IClientMessageInspector to persist the server cookie between requests, but I really don't want to do this, there should be a way to handle cookie in a standard way.
The custom inspector I'm using now, which is working as expected, but I'm looking for a "clean" solution:
public class CookieInspector : IClientMessageInspector
{
private string _cookie;
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
if(_cookie != null && request.Properties.ContainsKey("httpRequest"))
{
HttpRequestMessageProperty httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty;
if(httpRequest != null)
{
httpRequest.Headers["Cookie"] = _cookie;
}
}
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
if (reply.Properties.ContainsKey("httpResponse"))
{
HttpResponseMessageProperty httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
if(httpResponse != null)
{
string cookie = httpResponse.Headers.Get("Set-Cookie");
if (cookie != null) _cookie = cookie;
}
}
}
}
How to add cookie on a HttpTransportBindingElement
see this page it has complete answer to your question
Don't know if this was "fixed" in more recent .NET version (I'm on 4.5), or if the OP had other problems that were keeping this from working, but the allowCookies setting DOES work for this purpose.
It is working for me.... I also have "aspnetcompatabilitymode" set on the server.
<webHttpBinding>
<binding name="webHttpEndpointBinding" allowCookies="true">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</webHttpBinding>

System.Data.EntityClient not recognized, by code.but recognized by web.config

When I simply run the project, data is get from server no problems, but there's a requirement of connection string to be in code.
According to material on net I did the following, but just cannot solve the error.
Error
The specified store provider cannot be found in the configuration, or is not valid.
domainservice
public class DomainService1 : LinqToEntitiesDomainService<EMPLOYEEEntities>
{
public override void Initialize(DomainServiceContext context)
{
EntityConnectionStringBuilder builder = new EntityConnectionStringBuilder();
builder.Provider = "System.Data.EntityClient";
builder.ProviderConnectionString = #"Data Source=A-63A9D4D7E7834\THIRD;Initial Catalog=EMPLOYEE;Integrated Security=True;";
builder.Metadata = string.Format(#"res://*/{0}.csdl|
res://*/{0}.ssdl|
res://*/{0}.msl", "Model1");
this.ObjectContext.Connection.ConnectionString = builder.ConnectionString;
base.Initialize(context);
}
web.config
<connectionStrings>
<add name="EMPLOYEEEntities"
connectionString="metadata=res://*/Model1.csdl|res://*/Model1.ssdl|res://*/Model1.msl;provider=System.Data.SqlClient;provider connection string="Data Source=A-63A9D4D7E7834\THIRD;Initial Catalog=EMPLOYEE;Integrated Security=True;MultipleActiveResultSets=True""
providerName="System.Data.EntityClient" />
</connectionStrings>
The "System.Data.EntityClient" is a combination of many namespaces/libs therefore it is not recognized.
the right way would be,
public IQueryable<Table1> GetTable1()
{
// Specify the provider name, server and database.
string providerName = "System.Data.SqlClient";
string serverName = #"A-63A9D4D7E7834\THIRD";
string databaseName = "EMPLOYEE";
// Initialize the connection string builder for the
// underlying provider.
SqlConnectionStringBuilder sqlBuilder =
new SqlConnectionStringBuilder();
// Set the properties for the data source.
sqlBuilder.DataSource = serverName;
sqlBuilder.InitialCatalog = databaseName;
sqlBuilder.IntegratedSecurity = true;
sqlBuilder.MultipleActiveResultSets=True;
// Build the SqlConnection connection string.
string providerString = sqlBuilder.ToString();
// Initialize the EntityConnectionStringBuilder.
EntityConnectionStringBuilder entityBuilder =
new EntityConnectionStringBuilder();
//Set the provider name.
entityBuilder.Provider = providerName;
// Set the provider-specific connection string.
entityBuilder.ProviderConnectionString = providerString;
// <add name="EMPLOYEEEntities" connectionString="metadata=res://*/Models.Model1.csdl|res://*/Models.Model1.ssdl|res://*/Models.Model1.msl;provider=System.Data.SqlClient;provider connection string="Data Source=A-63A9D4D7E7834\THIRD;Initial Catalog=EMPLOYEE;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />
// Set the Metadata location.
entityBuilder.Metadata = #"res://*/Models.Model1.csdl|
res://*/Models.Model1.ssdl|
res://*/Models.Model1.msl";
Console.WriteLine(entityBuilder.ToString());
// EntityConnection conn = new EntityConnection(entityBuilder.ToString());
/*
using (EntityConnection conn =
new EntityConnection(entityBuilder.ToString()))
{
conn.Open();
Response.Write("this is a web application");
Console.WriteLine("Just testing the connection.");
conn.Close();
}*/
this.ObjectContext.Connection.ConnectionString = entityBuilder.ToString();
// this.ObjectContext.Connection.Open();
// this.ObjectContext.Connection.BeginTransaction();
// this.ObjectContext.Table1.OrderBy(q => q.ID);
IQueryable<Table1> results = this.ObjectContext.Table1.OrderBy(q => q.ID);
// this.ObjectContext.Connection.Close();
return results;
}