When I create a self hosted wcf application, I create ServiceHost objects for each service I want to expose. It then looks in the app.config (matching up the server name), and then pulls the associated endpoint address and contract.
Is there a way to automatically create ServiceHosts for every service that is listed in the app.config. I would like to add new services to the app.config and have them loaded automatically without recompilng my program and using my manually coded process to create ServiceHost objects.
Is there a factory or a tutorial someone could link me that shows me how to do this?
Thanks
I'm not sure what do you mean by pulling associated addresses and contracts from config - this is done automatically. Service section in configuration file is automatically paired with type of service hosted in ServiceHost:
Service hosting:
using (var host = new ServiceHost(typeof(MyNamespace.Service))
{
// no endpoint setting needed if configuration is correctly paired by the type name
host.Open()
}
Service configuration:
<services>
<service name="MyNamespace.Service">
...
</service>
</service>
Now the only thing you need is to handle ServiceHost creation automatically. Here is my sample code to do it:
class Program
{
static void Main(string[] args)
{
List<ServiceHost> hosts = new List<ServiceHost>();
try
{
var section = ConfigurationManager.GetSection("system.serviceModel/services") as ServicesSection;
if (section != null)
{
foreach (ServiceElement element in section.Services)
{
var serviceType = Type.GetType(element.Name);
var host = new ServiceHost(serviceType);
hosts.Add(host);
host.Open();
}
}
Console.ReadLine();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.ReadLine();
}
finally
{
foreach (ServiceHost host in hosts)
{
if (host.State == CommunicationState.Opened)
{
host.Close();
}
else
{
host.Abort();
}
}
}
}
}
Related
When connecting a SOAP service in .NET Core the Connected Service is shown as expected in the solution explorer
The ConnectedService.json does contain the definitions as supposed. I.e.
{
"ProviderId": "Microsoft.VisualStudio.ConnectedService.Wcf",
...
"ExtendedData": {
"Uri": "https://test.example.net/Service.svc",
"Namespace": "UserWebService",
"SelectedAccessLevelForGeneratedClass": "Public",
...
}
The Uri from ExtendedData ends up in the Reference.cs file
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.WSHttpBinding_IAnvandareService))
{
return new System.ServiceModel.EndpointAddress("https://test.example.net/Service.svc");
}
throw new System.InvalidOperationException(string.Format("Could not find endpoint with name \'{0}\'.", endpointConfiguration));
}
If a deployment process looks like TEST > STAGING > PRODUCTION one might like to have corresponding endpoints. I.e. https://production.example.net/Service.svc.
We use Azure Devops for build and Azure Devops/Octopus Deploy for deployments
The solution (as I figured) was to change the endpoint address when you register the dependency i.e.
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
services.AddTransient<IAnvandareService, AnvandareServiceClient>((ctx) => new AnvandareServiceClient()
{
Endpoint =
{
Address = new EndpointAddress($"https://{environment}.example.net/Service.svc")
}
});
This is just an expansion of the answer provided by Eric Herlitz. Primarily meant to show how to use your appsettings.json file to hold the value for the endpoint url.
You will need to add the different endpoints to your appsettings.{enviroment}.json files.
{
...
"ServiceEndpoint": "http://someservice/service1.asmx",
...
}
Then you will need to make sure your environment variable is updated when you publish to different environments. How to transform appsettings.json
In your startup class find the method ConfigureServices() and register your service for dependency injection
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ADSoap, ADSoapClient>(fac =>
{
var endpoint = Configuration.GetValue<string>("ServiceEndpoint");
return new ADSoapClient(ADSoapClient.EndpointConfiguration.ADSoap12)
{
Endpoint =
{
Address = new EndpointAddress(new Uri(endpoint))
}
};
});
}
Then to consume the service in some class you can inject the service into the constructor:
public class ADProvider : BaseProvider, IADProvider
{
public ADSoap ADService { get; set; }
public ADProvider(IAPQ2WebApiHttpClient httpClient, IMemoryCache cache, ADSoap adClient) : base(httpClient, cache)
{
ADService = adClient;
}
}
I'm hosting a duplex wcf service using windows service with castle windsor wcffacility using TCP binding.
There is no problem with hosting, I think, when I add a service reference to a console application.I'm able to access the duplex service without any issues.
Problem arises when I use castle windsor at the client side while resolving. Below is the code am using for adding the wcf services through code based on config file.
public static IWindsorContainer RegisterWcfClients(IocBuildSettings iocBuildSettings,
IWindsorContainer container)
{
//Register callback methods for duplex service first.
container.Register(Component.For<INotificationCallback>()
.ImplementedBy<NotificationCallbackCastle>()
.LifestyleTransient());
// get dictionary with key = service class, value = service interface
var servicesWithWcfInterfaces = Assembly.GetAssembly(typeof (IApplicationService))
.GetTypes()
.Where(x => (x.IsInterface || x.IsClass) && HasServiceContract(x))
.ToList();
var registrations = new List<IRegistration>();
//get the client section in System.ServiceModel from web.config file
var clientSection = ConfigurationManager.GetSection("system.serviceModel/client") as ClientSection;
//get the endpointsCollection from childSection
var endpointCollection =
clientSection.ElementInformation.Properties[string.Empty].Value as ChannelEndpointElementCollection;
foreach (var serviceInterface in servicesWithWcfInterfaces)
{
//get the childEndpoint name from web.config file
var endpointName = GetClientEndpointName(endpointCollection, serviceInterface);
//register services which are declared in web.config file only.
if (string.IsNullOrEmpty(endpointName)) continue;
// attribute is either on the service class or the interface
var attribute =
(ServiceContractAttribute)
(Attribute.GetCustomAttribute(serviceInterface, typeof (ServiceContractAttribute)));
if (attribute != null)
{
WcfClientModelBase model = null;
// handle duplex differently
if (attribute.CallbackContract != null)
{
model = new DuplexClientModel
{
Endpoint =
WcfEndpoint.ForContract(serviceInterface).FromConfiguration(endpointName)
}.Callback(container.Resolve(attribute.CallbackContract));
registrations.Add(WcfClient.ForChannels(model).Configure(c => c.LifestyleSingleton()));
}
else
{
//regular attributes
model = new DefaultClientModel
{
Endpoint = WcfEndpoint.ForContract(serviceInterface).FromConfiguration(endpointName)
};
registrations.Add(WcfClient.ForChannels(model).Configure(c => c.LifestyleTransient()));
}
}
}
return container.Register(registrations.ToArray());
}
Am hosting only one duplex service and the below are the servicecontracts -
[ServiceContract(CallbackContract = typeof(INotificationCallback))]
public interface INotificationService
{
[OperationContract(IsOneWay = false)]
void Subscribe(Guid subscriptionId, string userName, string[] eventNames);
[OperationContract(IsOneWay = true)]
void EndSubscribe(Guid subscriptionId);
}
[ServiceContract]
public interface INotificationCallback
{
[OperationContract(IsOneWay = true)]
void ReceiveNotification(NotificationResultDto notificationResult);
}
[DataContract]
public class NotificationResultDto
{
[DataMember]
public string UserName { get; set; }
[DataMember]
public string NotificationMessage { get; set; }
}
When I try to resolve the duplex service using the below statement.
var temp = _container.Resolve();
I get error -
WcfClientActivator: could not proxy component c2a216c2-af61-4cb2-83ba-e4d9a5cc4e68
with inner exception - The Address property on ChannelFactory.Endpoint was null. The ChannelFactory's Endpoint must have a valid Address specified.
in the web.config file under client section -
<endpoint address="net.tcp://localhost:9877/NotificationService" binding="netTcpBinding"
bindingConfiguration="netTcpBindingConfiguration" contract="ServiceContracts.INotificationService"
name="INotificationService_Endpoint" />
After few hours of struggling, I found a work around for this problem.
I think this could a bug in Castle Windsor, while creating DuplexClientModel, endpoint cannot be created using "FromConfiguration". It fails while resolving during runtime. However samething works fine with "DefaultClientModel".
My workaround was to read the config file and get the address, binding and contract details and use them to create Endpoint in code.
model = new DuplexClientModel
{
//Endpoint = WcfEndpoint.ForContract(serviceInterface).FromConfiguration(endpointName)
//FromConfiguration method is failing for some reason,could be b.u.g in castle,
//so had to do this workaround by reading the web.config file and creating the Endpoint
//from there manually.
Endpoint = WcfEndpoint.ForContract(serviceInterface)
.BoundTo(CreateBindings(clientEndpoint.Binding))
.At(clientEndpoint.Address)
}.Callback(container.Resolve(attribute.CallbackContract));
I have created WCF service project.
It has following content in SVC file.
<%# ServiceHost Service="Deepak.BusinessServices.Implementation.ApiImplementation"
Factory="Deepak.BusinessServices.Implementation.CustomServiceHostFactory"%>
SVC reference
http://localhost/DeepakGateway/Service.svc
Service is UP and WSDL generated. Now I want to host this service as Windows Service.
How can I do it?
I have created "Windows Service" Project ans have following code.
protected override void OnStart(string[] args)
{
if (m_Host != null)
{
m_Host.Close();
}
Uri httpUrl = new Uri("http://localhost/DeepakGateway/Service.svc");
m_Host = new ServiceHost
(typeof(?????? WHAT TO FILL HERE?), httpUrl);
//Add a service endpoint
m_Host.AddServiceEndpoint
(typeof(?????? WHAT TO FILL HERE?), ), new WSHttpBinding(), "");
//Enable metadata exchange
ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
m_Host.Description.Behaviors.Add(smb);
//Start the Service
m_Host.Open();
}
You need to add the type of the class that implements your service contract in the ServiceHost constructor, and type of the service contract in your AddServiceEndpoint
Assuming your service implementation class looks something like this:
namespace Deepak.BusinessServices.Implementation
{
public class ApiImplementation : IApiImplementation
{
....
}
}
then you need:
m_Host = new ServiceHost(typeof(ApiImplementation), httpUrl);
m_Host.AddServiceEndpoint(typeof(IApiImplementation), new WSHttpBinding(), "");
the service host needs to know what (concrete) type of service class to host
the endpoint needs to know what service contract (interface) it exposes
i am new in WCF. i know how to host wcf service in windows form. now i develop a small wcf service which has .svc file. i want to host this svc file in win form. so just want to know process will be same or different?
here is my svc file markup
<%# ServiceHost Language="C#" Debug="true"
Service="Services.ChatService" CodeBehind="ChatService.svc.cs" %>
here is small code inside svc file code behind file
namespace Services
{
/// <summary>
/// Implements the chat service interface.
/// </summary>
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class ChatService : IChatService
{
private readonly Dictionary<Guid, IChatServiceCallback> clients =
new Dictionary<Guid, IChatServiceCallback>();
#region IChatService
Guid IChatService.Subscribe()
{
IChatServiceCallback callback =
OperationContext.Current.GetCallbackChannel<IChatServiceCallback>();
Guid clientId = Guid.NewGuid();
if (callback != null)
{
lock (clients)
{
clients.Add(clientId, callback);
}
}
return clientId;
}
void IChatService.Unsubscribe(Guid clientId)
{
lock (clients)
{
if (clients.ContainsKey(clientId))
{
clients.Remove(clientId);
}
}
}
void IChatService.KeepConnection()
{
// Do nothing.
}
void IChatService.SendMessage(Guid clientId, string message)
{
BroadcastMessage(clientId, message);
}
#endregion
/// <summary>
/// Notifies the clients of messages.
/// </summary>
/// <param name="clientId">Identifies the client that sent the message.</param>
/// <param name="message">The message to be sent to all connected clients.</param>
private void BroadcastMessage(Guid clientId, string message)
{
// Call each client's callback method
ThreadPool.QueueUserWorkItem
(
delegate
{
lock (clients)
{
List<Guid> disconnectedClientGuids = new List<Guid>();
foreach (KeyValuePair<Guid, IChatServiceCallback> client in clients)
{
try
{
client.Value.HandleMessage(message);
}
catch (Exception)
{
// TODO: Better to catch specific exception types.
// If a timeout exception occurred, it means that the server
// can't connect to the client. It might be because of a network
// error, or the client was closed prematurely due to an exception or
// and was unable to unregister from the server. In any case, we
// must remove the client from the list of clients.
// Another type of exception that might occur is that the communication
// object is aborted, or is closed.
// Mark the key for deletion. We will delete the client after the
// for-loop because using foreach construct makes the clients collection
// non-modifiable while in the loop.
disconnectedClientGuids.Add(client.Key);
}
}
foreach (Guid clientGuid in disconnectedClientGuids)
{
clients.Remove(clientGuid);
}
}
}
);
}
}
}
here is binding info
<service behaviorConfiguration="Services.ChatServiceBehavior" name="Services.ChatService">
<endpoint address="" binding="wsDualHttpBinding" bindingConfiguration="WSDualHttpBinding_IChatService" contract="Services.IChatService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
here is two endpoint one for wsDualHttpBinding and another one for mex
so now my mex endpoint is
http://localhost:49722/ChatService.svc?wsdl
now i want to add another tcp endpoint and expose this service with two endpoint. so just tell me what i need to write for tcp endpoint and when i add tcp endpoint then what will mex endpoint for tcp because i want that user can create proxy with any of two url
one would be http url and another would be tcp url. so do i need to add mex for tcp here?
please guide me. thanks
you must start host manually
follow to msdn link http://msdn.microsoft.com/en-us/library/system.servicemodel.servicehost.aspx
edited
ServiceHost _serviceHost;
public void Start(Type type)
{
_serviceHost = new ServiceHost(type);
_serviceHost.Open();
}
I have a WCF Service application project , a class library project(acts as a proxy betwen the service and the client) and a Asp.net web project.
Now in the WCF Service application project, I have the method GetData(int) [the default one]
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}", value);
}
}
I complied the WCF service project and found it working and henceforth added that as service reference to the Class library project. And written a method as under to fetch the value from the service
public string GetResult(int number)
{
string result = "";
try
{
Service1Client sc = new Service1Client();
result = sc.GetData(number);
}
catch (Exception ex)
{
var message = ex.Message;
}
return result;
}
Now this method is being invoked from the Web application. At runtime I am getting an exception
Could not find default endpoint element that references contract 'ServiceReference1.IService1' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element.
The error is happening at Service1Client sc = new Service1Client();
All the config files are in place....Should I have to create the proxy using SVC util?
What am I missing?
Are you sure you have apropriate configuration for wcf placed in web.config? It seems you don't.