I have a large number of WCF services that are hosted on the IIS.
All these services have the same system.serviceModel configuration. They all have the same binding configuration, behavior configuration and the only thing that is different is the service's contract which is placed in a different custom configuration file for a different use.
Now, Every change I do in the system.serviceModel section needs to get done across all the services and that's annoying.
Since I create my WCF clients with custom C# code and update them to fit the services, I was thinking about somehow creating the service's system.serviceModel via C# code somehow (and every change will be a dll update or something).
So, I guess it's possible to create the service via code.
I also guess that it can be done with creating a custom ServiceHostFactory, but I couldn't really find the place I can choose the service's binding.
What's the best way to achieve something like this?
There is a code example from msdn that seems to fit your question: I'll copy it here for reference.
The binding
// Specify a base address for the service
String baseAddress = "http://localhost/CalculatorService";
// Create the binding to be used by the service.
BasicHttpBinding binding1 = new BasicHttpBinding();
and the endpoint
using(ServiceHost host = new ServiceHost(typeof(CalculatorService)))
{
host.AddServiceEndpoint(typeof(ICalculator),binding1, baseAddress);
Now, this is an other example with a ServiceFactory
public class ServiceHostFactory :
System.ServiceModel.Activation.ServiceHostFactory
{
protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var host = base.CreateServiceHost(serviceType, baseAddresses);
host.Description.Behaviors.Add(ServiceConfig.ServiceMetadataBehavior);
ServiceConfig.Configure((ServiceDebugBehavior)host.Description.Behaviors[typeof(ServiceDebugBehavior)]);
return host;
}
}
with a config done in code:
public static class ServiceConfig
{
public static Binding DefaultBinding
{
get
{
var binding = new BasicHttpBinding();
Configure(binding);
return binding;
}
}
public static void Configure(HttpBindingBase binding)
{
if (binding == null)
{
throw new ArgumentException("Argument 'binding' cannot be null. Cannot configure binding.");
}
binding.SendTimeout = new TimeSpan(0, 0, 30, 0); // 30 minute timeout
binding.MaxBufferSize = Int32.MaxValue;
binding.MaxBufferPoolSize = 2147483647;
binding.MaxReceivedMessageSize = Int32.MaxValue;
binding.ReaderQuotas.MaxArrayLength = Int32.MaxValue;
binding.ReaderQuotas.MaxBytesPerRead = Int32.MaxValue;
binding.ReaderQuotas.MaxDepth = Int32.MaxValue;
binding.ReaderQuotas.MaxNameTableCharCount = Int32.MaxValue;
binding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
}
public static ServiceMetadataBehavior ServiceMetadataBehavior
{
get
{
return new ServiceMetadataBehavior
{
HttpGetEnabled = true,
MetadataExporter = {PolicyVersion = PolicyVersion.Policy15}
};
}
}
public static ServiceDebugBehavior ServiceDebugBehavior
{
get
{
var smb = new ServiceDebugBehavior();
Configure(smb);
return smb;
}
}
public static void Configure(ServiceDebugBehavior behavior)
{
if (behavior == null)
{
throw new ArgumentException("Argument 'behavior' cannot be null. Cannot configure debug behavior.");
}
behavior.IncludeExceptionDetailInFaults = true;
}
}
Related
I'm trying to unittest a WCF app.
Here what I've tried:
public class Service1 : IService1
{
public string GetData(int value)
{
string val = ConfigurationManager.AppSettings["mykey"].ToString();
return string.Format("You entered: {0}. mykey={1}", value, val);
}
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
ServiceHost serviceHost = null;
try
{
string url = "http://localhost:56666/Serivce1";
var baseAddress = new Uri(url);
serviceHost = new ServiceHost(typeof(Service1), baseAddress);
Binding binding = new WSHttpBinding(SecurityMode.None);
serviceHost.AddServiceEndpoint(typeof(IService1), binding, "Service1");
var smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
serviceHost.Description.Behaviors.Add(smb);
serviceHost.Open(); // if this fails, you have to run Visual Studio as admin
BasicHttpBinding myBinding = new BasicHttpBinding();
ChannelFactory<IService1> myChannelFactory = new ChannelFactory<IService1>(myBinding);
EndpointAddress myEndpoint = new EndpointAddress(url);
IService1 wcfClient = myChannelFactory.CreateChannel(myEndpoint);
string s = wcfClient.GetData(39);
serviceHost.Close();
}
finally
{
if (serviceHost != null)
{
((IDisposable)serviceHost).Dispose();
}
}
}
}
}
When I call wcfClient.GetData(39);
I get this error:
System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://localhost:56666/Serivce1 that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: The remote server returned an error: (404) Not Found.
Any ideas why I'm getting this error and how to make it work?
I got it working but making the bindings consistent and fixing the issue related to the configuration of the WCF not getting loaded with a not-so-nice workaround:
public class Service1 : IService1
{
public string GetData(int value)
{
string val = "Nothing";
if(ConfigurationManager.AppSettings != null && ConfigurationManager.AppSettings["mykey"] != null) // when normally run
{
val = ConfigurationManager.AppSettings["mykey"].ToString();
}
else // when run via unittest:
{
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = "web.config";
Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
ConfigurationManager.RefreshSection("appSettings"); // this does not work, so I I wrote the loop:
foreach (KeyValueConfigurationElement kv in configuration.AppSettings.Settings)
{
ConfigurationManager.AppSettings[kv.Key] = kv.Value;
}
if (ConfigurationManager.AppSettings["mykey"] != null)
{
val = ConfigurationManager.AppSettings["mykey"].ToString();
}
}
return string.Format("You entered: {0}. mykey={1}", value, val);
}
}
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
ServiceHost serviceHost = null;
try
{
string url = "http://localhost:56669";
var baseAddress = new Uri(url);
serviceHost = new ServiceHost(typeof(Service1), baseAddress);
BasicHttpBinding binding = new BasicHttpBinding();
serviceHost.AddServiceEndpoint(typeof(IService1), binding, "");
var smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
serviceHost.Description.Behaviors.Add(smb);
// enabling server side exception details to help debug:
var behavior = serviceHost.Description.Behaviors.Find<ServiceDebugBehavior>();
behavior.IncludeExceptionDetailInFaults = true;
serviceHost.Open(); // if this fails, you have to run Visual Studio as admin
ChannelFactory<IService1> myChannelFactory = new ChannelFactory<IService1>(binding);
EndpointAddress myEndpoint = new EndpointAddress(url);
IService1 wcfClient = myChannelFactory.CreateChannel(myEndpoint);
string result = wcfClient.GetData(39);
Assert.AreEqual(string.Format("You entered: {0}. mykey=myval", 39), result);
serviceHost.Close();
}
finally
{
if (serviceHost != null)
{
((IDisposable)serviceHost).Dispose();
}
}
}
}
Any suggestions to improve the configuration loading would be welcome.
Here is a simple Service Fabric stateless service with WCF communication and it's client - a console app. It works well on the local cluster, client gets responce from the service. But I don't know how to communicate with a service if I deploy it in the cloud. What should I do to access it from console app?
SF Stateless service with WCF communications:
Contract:
[ServiceContract]
public interface IPresidentialService
{
[OperationContract]
Task<string> GetInfo();
}
Service:
internal sealed class PresidentialService : StatelessService, IPresidentialService
{
public PresidentialService(StatelessServiceContext context) : base(context)
{
}
public Task<string> GetInfo() => Task.FromResult($"Node {Context.NodeContext.NodeName} operating");
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new[]
{
new ServiceInstanceListener(context =>
new WcfCommunicationListener<IPresidentialService>(wcfServiceObject: this, serviceContext: context,
endpointResourceName: "WcfServiceEndpoint",
listenerBinding: WcfUtility.CreateTcpListenerBinding()))
};
}
}
}
Client console app:
WCF client:
public class PresidentialServiceClient : ServicePartitionClient<WcfCommunicationClient<IPresidentialService>>
{
public PresidentialServiceClient(
ICommunicationClientFactory<WcfCommunicationClient<IPresidentialService>> communicationClientFactory,
Uri serviceUri, ServicePartitionKey partitionKey = null,
TargetReplicaSelector targetReplicaSelector = TargetReplicaSelector.Default, string listenerName = null,
OperationRetrySettings retrySettings = null) : base(communicationClientFactory, serviceUri, partitionKey,
targetReplicaSelector, listenerName, retrySettings)
{
}
public Task<string> GetInfo() => InvokeWithRetryAsync(client => client.Channel.GetInfo());
}
Client App:
private static void Main(string[] args)
{
var binding = WcfUtility.CreateTcpClientBinding();
var partitionResolver = ServicePartitionResolver.GetDefault();
var wcfClientFactory =
new WcfCommunicationClientFactory<IPresidentialService>(binding,
servicePartitionResolver: partitionResolver);
var serviceUri = new Uri("fabric:/Application5/PresidentialService");
var client = new PresidentialServiceClient(wcfClientFactory, serviceUri, ServicePartitionKey.Singleton);
do
{
Console.WriteLine(client.GetInfo().Result);
Console.ReadKey();
} while (true);
}
Added to ServiceManifest.xml:
<Endpoints>
<Endpoint Name="WcfServiceEndpoint" />
</Endpoints>
UPDATE
Changed ServicePartitionResolver:
var partitionResolver = new ServicePartitionResolver("sfapp.westeurope.cloudapp.azure.com:19000");
Still not works.
UPDATE
Added a load balancer rule for TCP port 777.
When the service is running in the cloud, you can't use the default resolver.
The default ServicePartitionResolver assumes that the client is
running in same cluster as the service. If that is not the case,
create a ServicePartitionResolver object and pass in the cluster
connection endpoints.
Try something like
ServicePartitionResolver resolver = new ServicePartitionResolver("mycluster.cloudapp.azure.com:19000");
https://learn.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-services-communication-wcf
I've a Duplex TCP IP WCF service. I'm currently unit-testing it.
In everyone of my test, I setup a new server, create a new ChannelFactory, create the InstanceContext and do the call.
Then I trigger the event(it's a Mock on the server side), and the server give me this exception when it tries to reach the client:
Exception thrown: 'System.ObjectDisposedException' in mscorlib.dll
Additional information: Cannot access a disposed object.
Important point, this happens ONLY when I run all the tests in a row(sequentially executed but in the same execution).
There is nothing special about my service:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IMyServiceCallback))]
public interface IMyService{
[OperationContract]
void SomeVariousMethods();
}
[ServiceContract]
public interface IMyServiceCallback
{
[OperationContract]
void HandleMessageFromServer(String message);
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class MyService : IMyService{
public MyService(ISomeServerComponent component){
component.OnMessage += OnMessageReceived;
}
public void SomeVariousMethods(){
//...
}
private void OnMessageReceived(object sender, EventArgs<String> e){
IMyServiceCallback callback = OperationContext.Current.GetCallbackChannel<IMyServiceCallback>();
callBack.HandleMessageFromServer(e.Data);//Crash here
}
}
And here is how I'm currently UnitTesting it(not exactly, I've a lot of this that has been extracted in some helpers:
[TestFixture]
public class MyServiceTest:IMyServiceCallback{
private Mock<ISomeServerComponent> _mock;
[OneTimeSetUp]
public void Setup(){
//... Creating a mock for the ISomeServerComponent that the MyService receives
}
[Test]
public void TestSomeVariousMethods(){
string serviceName = nameof(TestSomeVariousMethods);
using(ServiceHost host = CreateServer(_mock.Object,serviceName)){
using (IMyService service = CreateClient(serviceName, this)){
service.SomeVariousMethods();
}
}
}
[Test]
public void TestCallback(){
string serviceName = nameof(TestSomeVariousMethods);
using(ServiceHost host = CreateServer(_mock.Object,serviceName)){
using (IMyService service = CreateClient(serviceName, this)){
_mock.TriggerCallBack();
//Assert-that-the-flag-has-been-set
}
}
}
public void HandleMessageFromServer(String msg){
//Flag that this method has been called
}
private ServiceHost CreateServer(ISomeServerComponent mock, string serviceName){
UnityServiceHost serviceHost = new UnityServiceHost(m_container);//This extends ServiceHost to be able to inject some objects to my services
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
binding.ReliableSession.Enabled = true;
binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
binding.MaxBufferPoolSize = Int64.MaxValue;
binding.MaxBufferSize = Int32.MaxValue;
binding.MaxReceivedMessageSize = Int32.MaxValue;
Uri uri = new Uri(String.Format("net.tcp://{0}:{1}/{2}", IPAddress.Any, 9999, serviceName));
ServiceEndpoint serviceEndpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(IMyService)), binding, uri);
serviceEndpoint.EndpointBehaviors.Add(new ProtoEndpointBehavior());
serviceHost.AddServiceEndpoint(serviceEndpoint);
return serviceHost;
}
private IMyService CreateClient(string serviceName, IMyServiceCallback callback){
UnityServiceHost serviceHost = new UnityServiceHost(m_container);//This extends ServiceHost to be able to inject some objects to my services
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
binding.ReliableSession.Enabled = true;
binding.Security.Transport.ClientCredentialType = TcpClientCredentialType.None;
binding.MaxBufferPoolSize = Int64.MaxValue;
binding.MaxBufferSize = Int32.MaxValue;
binding.MaxReceivedMessageSize = Int32.MaxValue;
Uri uri = new Uri(String.Format("net.tcp://{0}:{1}/{2}", IPAddress.Loopback, 9999, serviceName));
InstanceContext context = new InstanceContext(callBack);
DuplexChannelFactory channelFactory = new DuplexChannelFactory<T>(context, binding, new EndpointAddress(uri));
return channelFactory.CreateChannel()
}
}
Funny part is that all of this works when I'm ONLY running TestCallback test, but if I run all the test of the class, it fails, like if the second time, the InstanceContext was not creating properly the callback.
Any idea how to avoid this?
I finally found the issue. I feel a little bit stupid, but in fact, in the Service implementation, I was not unregistering from the OnMessage correctly, so when the event was triggered, the previous service instance were trying to communicate with the already closed client.
I've seen several examples on Internet setting up an NInject Factory modifying svc file. I don't have this svc file (or I don't know where's it).
I've a slight WCF service:
IService interface:
[ServiceContract]
public interface IFileSystemPluginService
{
[OperationContract]
void saveConfiguration(Configuration.Configuration configuration, string userId);
[OperationContract]
Configuration.Configuration getConfiguration(string userId);
}
IService implementation:
public class Service : IService
{
private IUserConfigurable userConfigurablePlugin;
public Service(IUserConfigurable configurablePlugin)
{
this.configurablePlugin = configurablePlugin;
}
}
In order to kick the service off I'm setting configuration up in code:
private void initializeWCFService()
{
string baseAdress = "xxx";
Uri baseAddressUri = new Uri(baseAdress);
this.serviceHost = new System.ServiceModel.ServiceHost(typeof(Service), baseAddressUri);
this.serviceHost.AddServiceEndpoint(
typeof(IService),
new System.ServiceModel.WSHttpBinding(),
string.Empty
);
System.ServiceModel.Description.ServiceMetadataBehavior smb = new System.ServiceModel.Description.ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
smb.HttpGetUrl = new Uri(baseAdress);
this.serviceHost.Description.Behaviors.Add(smb);
this.serviceHost.Open();
}
I need to inject a IUserConfigurable object in Service constructor.
I have the following scenario going on:
A windows "fat client" application is connecting to a WCF webservice. Both, client and webservice use exact the same binding, which looks like this:
private static NetTcpBinding Message_Security_UserName_Credentials()
{
NetTcpBinding binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
binding.PortSharingEnabled = true;
return binding;
}
The client sends "custom" client credentials to the webservice. The custom client credential class is this:
public class CustomClientCredentials : ClientCredentials
{
public CustomClientCredentials()
{
AuthorizationToken = String.Empty;
this.ClientCertificate.Certificate = Certificates.ClientPFX;
this.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.Custom;
this.ServiceCertificate.Authentication.CustomCertificateValidator = new CustomClientX509CertificateValidator("CN");
}
private string authorizationtoken;
public string AuthorizationToken
{
get
{
return this.authorizationtoken;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
this.authorizationtoken = value;
}
}
public String Name
{
set
{
this.UserName.UserName = value;
}
}
public String Password
{
set
{
this.UserName.Password = value;
}
}
protected CustomClientCredentials(CustomClientCredentials other)
: base(other)
{
this.AuthorizationToken = other.AuthorizationToken;
}
protected override ClientCredentials CloneCore()
{
return new CustomClientCredentials(this);
}
}
In short, the process of sending the custom client credentials to the service looks like this:
ChannelFactory<ILoginService> factory = new ChannelFactory<ILoginService> (binding, endpointaddress);
factory.Endpoint.Behaviors.Remove<ClientCredentials>();
CustomClientCredentials credentials = new CustomClientCredentials() {Name = this.User.EMail, Password = this.User.Password, AuthorizationToken = String.Empty};
factory.Endpoint.Behaviors.Add(credentials);
ILoginService client = factory.CreateChannel();
Token result = client.LogIn();
On the server, I use a custom UserPasswordValidator to read out the client credentials. It looks like this:
public class CustomServiceUserNamePasswordValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
}
}
Up to this point everything works fine. As you can see in my custom ClientCredentials class, I want to send more additional information to the server.
My question is: What must I do, to read out the received custom client credentials on the server?
The theory in my head is, that I simply must tell the service endpoint on the server, that he should expect a certain type of credentials and then he can evaluate them.
Validating custom client credentials may not an easy tasks but you can following this link for validation. I would suggest also to follow this link for custom credential implementation.