I am trying to use DataContractResolver as an alternative to KnownTypes in WCF.
I have the following code and I've used it before on the server side. But on the client side, the code returns null when trying to find DataContractSerializerOperationBehavior in operation behaviors collection.
public override IMyService CreateProxy(Uri url)
{
ServiceEndpoint endpoint = CreateEndpoint(url);
var channelFactory = new ChannelFactory<IMyService>(endpoint);
InjectResolver(channelFactory.Endpoint);
return channelFactory.CreateChannel();
}
private void InjectResolver(ServiceEndpoint endpoint)
{
foreach (OperationDescription operation in endpoint.Contract.Operations)
{
var behavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
behavior.DataContractResolver = new DerivedTypeResolver(); // behavior is null here!
}
}
Why is the behavior missing?
UPDATE: I found out the real issue is that WCF was using XmlSerializer instead of DataContractSerializer. Is there a way to force a DataContractSerializer instead? Does WCF choose the serializer based on the wsdl? Considering I don't (yet) have the capacity to change the server side, what is my option? XmlSerializer behavior doesn't seem to have a similar option of resolving the type myself.
See here for example on how to create DataContractSerializerOperationBehavior if it does not exist:
private void DataContractBehavior()
{
WSHttpBinding b = new WSHttpBinding(SecurityMode.Message);
Uri baseAddress = new Uri("http://localhost:1066/calculator");
ServiceHost sh = new ServiceHost(typeof(Calculator), baseAddress);
sh.AddServiceEndpoint(typeof(ICalculator), b, "");
// Find the ContractDescription of the operation to find.
ContractDescription cd = sh.Description.Endpoints[0].Contract;
OperationDescription myOperationDescription = cd.Operations.Find("Add");
// Find the serializer behavior.
DataContractSerializerOperationBehavior serializerBehavior =
myOperationDescription.Behaviors.
Find<DataContractSerializerOperationBehavior>();
// If the serializer is not found, create one and add it.
if (serializerBehavior == null)
{
serializerBehavior = new DataContractSerializerOperationBehavior(myOperationDescription);
myOperationDescription.Behaviors.Add(serializerBehavior);
}
// Change the settings of the behavior.
serializerBehavior.MaxItemsInObjectGraph = 10000;
serializerBehavior.IgnoreExtensionDataObject = true;
sh.Open();
Console.WriteLine("Listening");
Console.ReadLine();
}
example from https://msdn.microsoft.com/en-us/library/system.servicemodel.description.datacontractserializeroperationbehavior.aspx
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
I have a Self-Hosted WCF Service and client.
The client does not have a service reference, I have linked it to the endpoint programmatically.
The bindings are set to BasicHttpBinding in both the client and service -
Service
Uri baseAddress = new Uri("http://localhost:8733/Design_Time_Addresses/DSCentralService/Service1/");
DSCentralService.Service1 contentServer = new DSCentralService.Service1();
//initialise the servicehost
centralSvrHost = new ServiceHost(typeof(DSCentralService.Service1), baseAddress);
//add bindings
centralSvrHost.AddServiceEndpoint(
typeof(DSCentralService.IService1),
new BasicHttpBinding(),
baseAddress
);
Client
serviceFactory = new ServiceFactory<DSCentralService.IService1>();
String serviceAddress="http://localhost:8733/Design_Time_Addresses/DSCentralService/Service1/";
iContentServer = serviceFactory.GetService(serviceAddress);
Service Factory Class
public class ServiceFactory<T> where T : class
{
private T _service;
public T GetService(string address)
{
return _service ?? (_service = GetServiceInstance(address));
}
private static T GetServiceInstance(string address)
{
BasicHttpBinding basicBinding = new BasicHttpBinding();
basicBinding.Name = "DSCentralSvr";
basicBinding.TransferMode = TransferMode.Streamed;
basicBinding.MessageEncoding = WSMessageEncoding.Mtom;
basicBinding.MaxReceivedMessageSize = 10067108864;
basicBinding.SendTimeout = new TimeSpan(0, 10, 0);
basicBinding.OpenTimeout = new TimeSpan(0, 10, 0);
basicBinding.CloseTimeout = new TimeSpan(0, 10, 0);
basicBinding.ReceiveTimeout = new TimeSpan(0, 10, 0);
EndpointAddress endpoint = new EndpointAddress(address);
return ChannelFactory<T>.CreateChannel(basicBinding, endpoint);
}
}
Yet upon debugging, I receive the common error of
The client and service bindings may be mismatched
There are no settings for bindings in any config files of either the client or service, to avoid conflicts with the programmatic settings.
Is there something I have missed, which is necessary when doing this programmatically? What is causing this mis-match?
You are hosting the service with a default BasicHttpBinding which means TransferMode Buffered and MessageEncoding Text.
In your client you are using Streamed and Mtom, respectively.
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();
}
I've been doing tests using the following sample from Microsoft :
http://msdn.microsoft.com/en-us/library/ff521581.aspx
it works, but it is a basichttp endpoint.
is there a way to make it a CustomBinding endpoint with binaryMessageEncoding?
Thanks,
Alex
This will create a custom binding using Http transport and binary encoding:
protected override Binding CreateBinding()
{
BinaryMessageEncodingBindingElement messageEncoding = new BinaryMessageEncodingBindingElement();
TransportBindingElement transport = new HttpTransportBindingElement();
BindingElementCollection bindingElements = new BindingElementCollection()
{
messageEncoding, transport
};
return new CustomBinding(bindingElements);
}