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));
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;
}
}
Lets say I have a simple service contract:
[ServiceContract(Namespace = Constants.MyNamespace)]
public interface IAccountService
{
[OperationContract]
Account GetByAccountNumber(string accountNumber);
}
Here is the service:
[ServiceBehavior(Namespace = Constants.MyNamespace)]
public class AccountService : IAccountService
{
private readonly IUnitOfWorkAsync _uow;
private readonly IRepositoryAsync<Account> _repository;
public AccountService(IDataContextAsync dataContext)
{
_uow = new UnitOfWork(dataContext);
_repository = new Repository<Account>(dataContext, _uow);
}
public Account GetByAccountNumber(string accountNumber)
{
return _repository.GetByAccountNumber(accountNumber);
}
}
Here is the CustomServiceHostFactory:
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
var builder = new ContainerBuilder();
builder.RegisterType<MyDbContext>().As<IDataContextAsync>();
builder.Register(c => new AccountService(c.Resolve<IDataContextAsync>())).As<IAccountService>();
using (var container = builder.Build())
{
var host = new CustomServiceHost(serviceType, baseAddresses);
host.AddDependencyInjectionBehavior<IAccountService>(container);
return host;
}
}
}
..where CustomServiceHost creates all of the bindings/behaviors programmatically. I am using file-less activation so my .config file just has section like this:
<serviceHostingEnvironment>
<serviceActivations>
<add service="Company.Project.Business.Services.AccountService"
relativeAddress="Account/AccountService.svc"
factory="Company.Project.WebHost.CustomServiceHostFactory"/>
</serviceActivations>
</serviceHostingEnvironment>
I publish to IIS and can view the site in a browser. It says "you have created a service". However, any call I try to make to the service from my client application gives the following error:
Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.
How do you use Autofac with WCF and a CustomServiceHostFactory?
I am able to use poor man's DI as a workaround for now but was hoping to get this working. I can't seem to find any good examples on the web. Thanks.
Don't dispose of the container. Instead of a using statement, keep the container alive. It needs to live as long as the host.
You'll notice in the default Autofac WCF stuff the container is a global static that lives for the app lifetime - that's why.
I have created a very simple server and client console app demonstrating the issue I have in that I am trying to bring an instance of a serializable object across to the client but it fails on the server.
What am I missing?? I am NOT concerned right now having it Service orientated using DataContracts - I am simply trying to understand why the code as it stands doesn't bring the EJob accross to the client (it DOES however calls the 'Hello from the server' message)
Many thanks.
EDIT
Even if I decorate the EJob class with a DataContract attribute (like below) it STILL doesn't work - the object I receive on the client has LastName set to null?????
[DataContract]
public class EJob
{
[DataMember]
public string LastName = "Smith";
}
SERVER
namespace testServer
{
[ServiceContract()]
public interface IRemoteClient
{
[OperationContract]
void SayHi(string msg);
[OperationContract]
void ProcessJob(EJob job);
}
[Serializable()]
public class EJob
{
public string LastName = "Smith";
}
class Program
{
static void Main(string[] args)
{
MngrServer.SendJob();
}
}
public class MngrServer
{
public static void SendJob()
{
try
{
// send this off to the correct exe
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, true);
string address = string.Format("net.tcp://localhost:33888/BatchMananger/client");
EndpointAddress epa = new EndpointAddress(address);
// create the proxy pointing to the correct exe
IRemoteClient clientProxy = ChannelFactory<IRemoteClient>.CreateChannel(binding, epa);
clientProxy.SayHi("Hello from server"); <-- THIS WORKS FINE
EJob job = new EJob { LastName = "Janssen" };
clientProxy.ProcessJob(job); <-- THIS RAISES AN EXCEPTION see below...
}
catch (Exception ex)
{
string msg = ex.Message;
//The formatter threw an exception while trying to deserialize the message: There was an error while
//trying to deserialize parameter http://tempuri.org/:job. The InnerException message was ''EndElement' 'job'
//from namespace 'http://tempuri.org/' is not expected. Expecting element 'LastName'.'.
}
}
}
}
CLIENT
namespace testClient
{
[ServiceContract()]
public interface IRemoteClient
{
[OperationContract]
void SayHi(string msg);
[OperationContract]
void ProcessJob(EJob job);
}
[Serializable()]
public class EJob
{
public string LastName = "Smith";
}
class Program
{
static void Main(string[] args)
{
MngrClient.Prepare();
Console.ReadLine();
}
}
/// <summary>
/// STATIC / INSTANCE
/// </summary>
public class MngrClient : IRemoteClient
{
public void SayHi(string msg)
{
Console.WriteLine(msg);
}
public void ProcessJob(EJob job)
{
Console.WriteLine(job.LastName);
}
public static void Prepare()
{
// allow this class to be used! - so instances are created and info directly passed on to its static members.
ServiceHost sh = new ServiceHost(typeof(MngrClient));
// create the net binding
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, true);
// define the tcpaddress
string address = string.Format("net.tcp://localhost:33888/BatchMananger/client");
// add a service point so my server can reach me
sh.AddServiceEndpoint(typeof(IRemoteClient), binding, address);
// now open the service for business
sh.Open();
}
}
}
Your EJob datacontract is in a different namespace on the server vs. the client. You need to either declare both classes in the same namespace, or use attributes to set the namespace on the client to match the namespace on the server
(Either the Datacontract attribute has a namespace value that you can pass, or there is a separate namespace attribute that you can use to tell WCF to use an alternate namespace for the contract, can't remember off the top of my head)
EDIT
Just verified -- it's the Namespace property of the DataContractAttribute that you want, so in your client-side declaration:
[DataContract(Namespace="EJobNamespaceAsItIsDeclaredOnTheServer")]
public class EJob ...
Now, it is very common to put all of your DataContracts in a separate assembly (called a contract assembly) that is referenced by both the client and the server. You would want just the contract class definitions in that assembly, nothing else.
You somehow have it all a bit backwards...
given your service contract of IRemoteClient, you should then have an implementation class on the server-side that implements that interface:
public class ServiceImplementation : IRemoteClient
{
public void SayHi(string msg)
{
.....
}
public void ProcessJob(EJob job)
{
.....
}
}
Also: the service methods should be returning something to the caller! Without a return type, you're kinda creating a black-hole of a service - you can call its methods, but nothing gets returned.... Plus: the service implementation class should NOT be hosting itself! Make that a separate class
you should then have a host class on the server side that hosts this service:
public class HostForYourService
{
public HostForYourService()
{
// send this off to the correct exe
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, true);
string address = string.Format("net.tcp://localhost:33888/BatchMananger/client");
EndpointAddress epa = new EndpointAddress(address);
ServiceHost sh = new ServiceHost(typeof(ServiceImplementation));
// define the tcpaddress
sh.AddServiceEndpoint(typeof(IRemoteClient), binding, address);
// now open the service for business
sh.Open();
}
and then your client should build the client-side proxy for this service and call it
public class YourServiceClient
{
public void CallService()
{
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None, true);
string address = string.Format("net.tcp://servername:33888/BatchMananger/client");
EndpointAddress epa = new EndpointAddress(address);
// create the proxy pointing to the correct exe
IRemoteClient clientProxy = ChannelFactory<IRemoteClient>.CreateChannel(binding, epa);
clientProxy.SayHi("Hello from server"); <-- THIS WORKS FINE
EJob job = new EJob { LastName = "Janssen" };
clientProxy.ProcessJob(job);
}
}
But again: typically, your service methods should be returning something that the client can then operate on - after all, you typically don't want to do a Console.WriteLine on the server - you want to compute something, look up something etc. and return a response to the client which then in turns can e.g. output the result to the console or something....
I have a WCF service that works when accessed by a simple MVC application.
When I try to make call on the same endpoint from a different MVC app that's wired up with Autofac I get a binding/contract mismatch exception like this:
Content Type application/soap+xml;
charset=utf-8 was not supported by service http://localhost:6985/ProductService.svc.
The client and service bindings may be mismatched.
System.Net.WebException: The remote server returned an error: (415) Unsupported Media Type.
I'm reasonably confident I do not have a mismatch in the configuration settings on either end, I base this confidence on testing the exact same settings on a WCF + MVC combination where Autofac is not present. The config settings are on pastebin.com/t7wfR77h.
I therefore would like some help analysing if the way I have registered the dependency/endpoint with Autofac is the issue...
*Application_Start* code in MVC app for Autofac setup:
var builder = new ContainerBuilder();
//other registrations...
builder.Register(c =>
new ChannelFactory<IProductService>(
new WSHttpBinding("ProductService_wsHttpBinding"),
new EndpointAddress("http://localhost:6985/ProductService.svc")
)
).SingleInstance();
builder.Register(c =>
{
var factory = c.Resolve<ChannelFactory<IProductService>>();
return factory.CreateChannel();
}
).InstancePerHttpRequest();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
(For completeness) where I make use of this is in a ProductController that has only 1 dependency to be injected, very simple:
public class ProductController : AsyncController
{
private IProductService _service;
public ProductController(IProductService ps)
{
_service = ps;
}
//...
//later simply call
_service.SomeMethod();
}
As mentioned in the comment to #Nick Josevski, I was able to get something similar to work.
In my MVC3 application's Application_Start method, I have the following code:
protected void Application_Start()
{
var builder = new ContainerBuilder();
builder.Register(c => new ChannelFactory<ICartService>("CartService")).SingleInstance();
builder.Register(c => c.Resolve<ChannelFactory<ICartService>>().CreateChannel()).InstancePerHttpRequest();
var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
// other MVC startup activities, like registering areas and routes
}
These registrations gather the WCF configuration data from Web.config. I've also gotten registrations to work with endpoints defined in code. For completeness, here's some of the pertinent client-side Web.config entries:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding" ... />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:50930/Purchasing/CartService.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding"
contract="CartService.ICartService" name="CartService" />
</client>
</system.serviceModel>
Then, in my controller, I have code like the following:
using Autofac.Features.OwnedInstances;
public class BulkCartController : Controller
{
private readonly Owned<ICartService> cartService_;
public BulkCartController(Owned<ICartService> cartService)
{
cartService_ = cartService;
}
protected override void Dispose(bool disposing) // defined in Controller
{
cartService_.Dispose();
base.Dispose(disposing);
}
//
// GET: /BulkCart/Get/1
public ActionResult Get(int id)
{
var model = new ShoppingCart { ShoppingCartId = id };
using (var cartService = cartService_)
{
model.Items = cartService.Value.GetCartProductItems(id);
}
return View("Get", model);
}
}
Unit testing looks like this:
using Autofac.Features.OwnedInstances;
using Autofac.Util;
using Moq;
[TestMethod]
public void Get_ReturnsItemsInTheGivenCart()
{
var mock = new Mock<ICartService>(MockBehavior.Strict);
mock.Setup(x => x.GetCartProductItems(2)).Returns(new CartProductItemViewObject[0]);
var controller = new BulkCartController(new Owned<ICartService>(mock.Object, new Autofac.Util.Disposable()));
var result = controller.Get(2);
Assert.IsInstanceOfType(result, typeof(ViewResult));
var view = (ViewResult)result;
Assert.AreEqual("Get", view.ViewName);
Assert.IsInstanceOfType(view.ViewData.Model, typeof(ShoppingCart));
var model = (ShoppingCart)view.ViewData.Model;
Assert.AreEqual(2, model.ShoppingCartId);
Assert.AreEqual(0, model.Items.Length);
}
I validate disposal with a unit test defined in an abstract controller test base class:
[TestClass]
public abstract class ControllerWithServiceTestBase<TController, TService>
where TController : Controller
where TService : class
{
[TestMethod]
public virtual void Dispose_DisposesTheService()
{
var disposable = new Mock<IDisposable>(MockBehavior.Strict);
disposable.Setup(x => x.Dispose()).Verifiable();
var controller = (TController) Activator.CreateInstance(typeof(TController), new Owned<TService>(null, disposable.Object));
controller.Dispose();
disposable.Verify();
}
}
One thing I don't know yet is whether this use of Owned<T> and Dispose() gives me adequate disposal, or whether I'll need to use a LifetimeScope as per An Autofac Lifetime Primer.
is it possible to call a service operation at a wcf endpoint uri with a self hosted service?
I want to call some default service operation when the client enters the endpoint uri of the service.
In the following sample these uris correctly call the declared operations (SayHello, SayHi):
- http://localhost:4711/clerk/hello
- http://localhost:4711/clerk/hi
But the uri
- http://localhost:4711/clerk
does not call the declared SayWelcome operation. Instead it leads to the well known 'Metadata publishing disabled' page. Enabling mex does not help, in this case the mex page is shown at the endpoint uri.
private void StartSampleServiceHost()
{
ServiceHost serviceHost = new ServiceHost(typeof(Clerk), new Uri( "http://localhost:4711/clerk/"));
ServiceEndpoint endpoint = serviceHost.AddServiceEndpoint(typeof(IClerk), new WebHttpBinding(), "");
endpoint.Behaviors.Add(new WebHttpBehavior());
serviceHost.Open();
}
[ServiceContract]
public interface IClerk
{
[OperationContract, WebGet(UriTemplate = "")]
Stream SayWelcome();
[OperationContract, WebGet(UriTemplate = "/hello/")]
Stream SayHello();
[OperationContract, WebGet(UriTemplate = "/hi/")]
Stream SayHi();
}
public class Clerk : IClerk
{
public Stream SayWelcome() { return Say("welcome"); }
public Stream SayHello() { return Say("hello"); }
public Stream SayHi() { return Say("hi"); }
private Stream Say(string what)
{
string page = #"<html><body>" + what + "</body></html>";
return new MemoryStream(Encoding.UTF8.GetBytes(page));
}
}
Is there any way to disable the mex handling and to enable a declared operation instead?
Thanks in advance, Dieter
Did you try?
[OperationContract, WebGet(UriTemplate = "/")]
Stream SayWelcome();
UPDATE:
Not sure why it is not working for you, I have a self hosted WCF service with the following service contract:
[ServiceContract]
public interface IDiscoveryService {
[OperationContract]
[WebGet(BodyStyle=WebMessageBodyStyle.Bare, UriTemplate="")]
Stream GetDatasets();
The only difference I can see is that I use WebServiceHost instead of ServiceHost.