How to bind WCF methods to arbitrary URIs - c#

I use WCF (by means of using the WebChannelFactory) to invoke some services that are outside of my control, implemented in a variety of technologies. From the WCF perspective, my interface only has one method, let's call it "get-stuff". So, the same method can be implemented by these services as http://www.service-a.com/get-stuff, or as http://www.service-b.com/my-goodies/, or as http://www.service-c.com/retrieve-thing.php
In all examples I've seen the method binding to a particular URI is accomplished via the UriTemplate member of the WebGet/WebInvoke attribute. But this means, all the URIs for the "get-stuff" method must follow a fixed template. For, example, I can create a UriTemplate = "/get-stuff", so that my method will always be bound to /get-stuff.
However, I want my method to bind to any arbitrary URI. BTW, the parameters are passed as a POST data, so I do not need to worry about binding URI to parameters of the method.

why don't you do something like this
EndpointAddress endpointAddress = new EndpointAddress("any service url");
ChannelFactory<IMyService> channelFactory = new ChannelFactory<IMyService>(binding, endpointAddress);
IMyServiceclient = channelFactory.CreateChannel();
client.GetStuff();

OK, I have found a solution to the problem, by patching the UriTemplate of the WebInvokeAttribute at run-time. My single-method WCF interface is:
[ServiceContract]
interface IGetStuff
{
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
ResponseData GetStuff(RequestData request);
}
Here is how I get the handle to the interface:
//Find the last portion of the URI path
var afterLastPathSepPos = uri.LastIndexOf('/', uri.Length - 2) + 1;
var contractDesc = ContractDescription.GetContract(typeof(IGetStuff));
foreach (var b in contractDesc.Operations[0].Behaviors)
{
var webInvokeAttr = b as WebInvokeAttribute;
if (webInvokeAttr != null)
{
//Patch the URI template to use the last portion of the path
webInvokeAttr.UriTemplate = uri.Substring(afterLastPathSepPos, uri.Length - afterLastPathSepPos);
break;
}
}
var endPoint = new ServiceEndpoint(contractDesc, new WebHttpBinding(), new EndpointAddress(uri.Substring(0, afterLastPathSepPos)));
using (var wcf = new WebChannelFactory<I>(endPoint))
{
var intf = wcf.CreateChannel();
var result = intf.GetStuff(new RequestData(/*Fill the request data here*/)); //Voila!
}

Related

Consume WCF Rest Service with multiple parameters and allow nullable parameter

I'm writing a web service as code below:
[OperationContract]
[WebInvoke(Method = "GET",
ResponseFormat = WebMessageFormat.Json,
RequestFormat = WebMessageFormat.Json,
// BodyStyle = WebMessageBodyStyle.Wrapped,
UriTemplate = "/BudgetBalance/{userlogin}/{prnumber=null}")]
Budget BudgetBalance(string userlogin, string prnumber);
As you can see at the code above, the second parameter can be filled or keeped null. By doing this, I can access the service by two different urls.
http://localhost:44880/Service1.svc/BudgetBalance/nurul.widiyanti
http://localhost:44880/Service1.svc/BudgetBalance/nurul.widiyanti/PRM-000114
If the second parameter is filled with some data, the service will return a different value. The problem is when I want to consume this webservice. Here's the code I've tried to call this service.
WebClient proxy = new WebClient();
string serviceUrl = string.Format("http://localhost:1089/Service1.svc/BudgetBalance/Nurul.Widiyanti/PRM-000114");
byte[] data = proxy.DownloadData(serviceUrl);
Stream _mem = new MemoryStream(data);
var reader = new StreamReader(_mem);
var result = reader.ReadToEnd();
var model = JsonConvert.DeserializeObject<Budget>(result);
This doesn't work and make an error. I realize that this code will work if I change the UriTemplate within OperationContract to something like this:
UriTemplate = "/BudgetBalance/{userlogin}/{prnumber}"
But if I do this, it doesn't suit my requirement. I need to create a webservice which allow one of the parameters remains empty (null). Is this possible accomplish this requirement? If so, please guide me to find the answer.
I think you want something like this ...
...
UriTemplate = "/BudgetBalance/{userlogin}")]
UriTemplate = "/BudgetBalance/{userlogin}/{prnumber}")]
Budget BudgetBalance(string userlogin, string prnumber = null) { .. }

Passing parameter into WCF metadata

I have a WCF service with a class that implements IContractBehavior and IWsdlExportExtension that generates a WSDL with a user's allowed operations and excludes operations and types they do not have access to.
The limitation of this is however is that for each user, I have to manually change which user I am generating the WSDL for.
I'd like to fix this limitation by passing in the user as part of the request for the metadata for example.
localhost/service.svc?user=me
or
localhost:9766/service.svc?singleWsdl&user=me
Alternatively I could use svcutil would also work as long as the resulting WSDL is flattened.
I was able to get this to work by doing the following
I host the service in console application.
I added an endpoint with the IContractBehavior added to it, passing in the desired parameter into the IContractBehavior class.
After the service is open, I use WsdlExporter to export the Metatadaset
Finally I use the WsdlHelper to generate the Wsdl file as
described here
http://www.shulerent.com/2013/03/14/generating-a-single-flattened-wsdl-from-an-existing-wcf-service/
The code
const string BASE_ADDRESS =
"http://localhost:8731/Design_Time_Addresses/CalcService";
var uri = new Uri(BASE_ADDRESS);
var user = "userName";
using (var serviceHost = new ServiceHost(typeof(Calc), uri))
{
var exporter = new WsdlExporter();
var endpoint = serviceHost.AddServiceEndpoint(typeof(ICalc),
new BasicHttpBinding(), "");
endpoint.Contract.Behaviors.Add(new
RestrictedOperationsWsdlExportExtensionAttribute(user));
serviceHost.Open();
Console.WriteLine("The service is ready: " + user);
exporter.ExportEndpoint(endpoint);
if (exporter.Errors.Count == 0)
{
var metadataSet = exporter.GetGeneratedMetadata();
var asy= Assembly.GetAssembly(typeof(WsdlExporter));
var t = asy.GetType("System.ServiceModel.Description.WsdlHelper",
true);
var method = t.GetMethod("GetSingleWsdl",
System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.Static);
var serviceDescription =
method.Invoke(null, new object[] {metadataSet})
as System.Web.Services.Description.ServiceDescription;
if (serviceDescription != null)
{
serviceDescription.Name = "Calc";
serviceDescription.Write(user + ".wsdl");
}
}
}

Self Hosted REST Web Service not accepting POST with parameters

Hopefully a simple question, but I have a simple app that is self-hosted (which works fine), and I can hit the requested method using a POST without parameters from the body (which hits the method but defaults the parameter to null), but when I try to pass the named parameter in the BODY of the POST request in raw JSON format, it receives a 400 response, and never hits the method for some reason...
Any advice is appreciated.
[Environment: Visual Studio 2015, C#, self hosted REST application]
[Code Details]
(Web service hosting code for Self Hosted app)
WebServiceHost host = new WebServiceHost(typeof(CalculatorServiceREST), new Uri("http://localhost:8000"));
ServiceEndpoint ep = host.AddServiceEndpoint(typeof(ICalculator), new WebHttpBinding(), "");
ServiceDebugBehavior stp = host.Description.Behaviors.Find<ServiceDebugBehavior>();
stp.HttpHelpPageEnabled = false;
host.Open();
Console.WriteLine("Service is up and running");
Console.WriteLine("Press enter to quit ");
Console.ReadLine();
host.Close();
(The contracts implementation class: CalculatorServiceRest.cs)
public class CalculatorServiceREST : ICalculator
{
[WebInvoke(Method = "POST")] // POST to: /RoundUp (with BODY item of 'number' in the request)
public int RoundUp(double number)
{
return Convert.ToInt32(Math.Round(number));
}
[HttpPost] // POST to: /GetName (with BODY item of 'number' in the request)
public string GetName(string name)
{
return name;
}
}
It appears that I have to add the following attribute values to the POST method so that I can pass JSON data in the BODY for it to pick it up (which is different from how I am use to being able to just put [HttpPost] attribute for MVC and it just knows how to pick it up. Can anyone provide some insight on why it is different?
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.WrappedRequest, ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]

How to call many remote WCF service from one central WCF service using Async call?

Simple Scenario:
Client makes a AJAX Sync call to a central WCF server ==> url: "svc/About.svc/GetAboutInfo";
The WCF "GetAboutInfo()" will call "GetSiteInfo()" in 80 remote servers;
I get the results but it takes awhile since these are NOT Async calls;
With that in mind I have 2 things to fix (but I don't know how):
1 - make GetSiteInfo() a Async call;
2 - only return GetAboutInfo() to the client after ALL Async calls from GetSiteInfo() are returned;
Note: I cannot use "Tasks" since we are still on .Net 3.5.
Currently I am researching about IAsyncResult (with Begin/End methods) but could not find anything that would allow me to adapt to my current code below.
(I am calling remote servers from a central WCF in a loop).
Bear in mind the WCF below is identical in all remote servers except that the loop which only exists in the "CENTRAL WCF server"(the WCF that calls the remote servers). Here is the partial WCF code:
[ServiceContract]
public interface IAbout
{
[OperationContract(Name = "About_GetAboutInfo")]
[WebGet(UriTemplate = "/GetAboutInfo", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
About.AboutInfo GetAboutInfo();
[OperationContract(Name = "About_GetSiteInfo")]
[WebGet(UriTemplate = "/GetSiteInfo", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
About.SiteInfo GetSiteInfo();
}
public SiteInfo GetSiteInfo()
{
SiteInfo siteInfo = new SiteInfo();
//...code stripped out...
return (siteInfo);
}
public AboutInfo GetAboutInfo()
{
AboutInfo aboutInfo = new AboutInfo();
SiteInfo siteInfo = new SiteInfo()
{
ID = site.ID
,Name = site.Name
,DatabaseVersion = "Unavailable"
};
foreach (Site site in sites)
{
try
{
string uri = Utilities.CombineUri(site.Uri, "svc/About.svc/ws");
AboutServiceClient wcfClient = new AboutServiceClient("About");
wcfClient.Endpoint.Address = new EndpointAddress(uri);
SiteInfo childSiteInfo = wcfClient.GetSiteInfo(); <== call is blocked here until I get a response from remote server
siteInfo.DatabaseVersion = childSiteInfo.DatabaseVersion;
}
catch (Exception e)
{ //...code stripped out... }
aboutInfo.Sites.Add(siteInfo); <== this should only be returned after we receive response from all calls
}
...
public class AboutServiceClient : ClientBase<IAbout>
{
public AboutServiceClient(Binding Binding, EndpointAddress Address) : base(Binding, Address)
{
if (Binding is WebHttpBinding)
{
this.Endpoint.Behaviors.Add(new WebHttpBehavior());
}
}
public AboutServiceClient(string endpointConfigurationName) : base(endpointConfigurationName)
{ }
public About.SiteInfo GetSiteInfo()
{ return base.Channel.GetSiteInfo(); }
public About.AboutInfo GetAboutInfo()
{ return base.Channel.GetAboutInfo(); }
}
Thank you
Using .NET 3.5 is a major restrain. You won't be able to keep your linear code flow. Here's the new workflow:
You'd need to implement BeginGetAboutInfo/EndGetAboutInfo as described in "How to: Implement an Asynchronous Service Operation".
In BeginGetAboutInfo you'd do start 80 asynchronous requests to the remote WCF service with wcfClient.GetSiteInfoBegin (in parallel) and keep track of IAsyncResult of each.
As these asynchronous operation are completing (your callback will be called for each), use wcfClient.EndSiteInfoBegin to retriever and store the result of each operation.
As soon as all of them have completed, invoke the callback, provided by the client when your BeginGetAboutInfo was called.
Now expect the client to call your EndGetAboutInfo, where you'd provide the combined result of all 80 operations.
You can install the "Task Parallel Library for .NET 3.5" from NuGEt and use tasks that way. Then you can wrap you wcfClient.GetSiteInfoBegin and wcfClient.EndSiteInfoBegin methods with Task.Factory.FromAsync.
This is untested, but maybe something like this:
public AboutInfo GetAboutInfo()
{
AboutInfo aboutInfo = new AboutInfo();
SiteInfo siteInfo = new SiteInfo()
{
ID = site.ID
,Name = site.Name
,DatabaseVersion = "Unavailable"
};
var tasks = new List<Task<SiteInfo>>();
foreach (Site site in sites)
{
try
{
string uri = Utilities.CombineUri(site.Uri, "svc/About.svc/ws");
AboutServiceClient wcfClient = new AboutServiceClient("About");
wcfClient.Endpoint.Address = new EndpointAddress(uri);
tasks.Add(Task<SiteInfo>.Factory.FromAsync(wcfClient.GetSiteInfoBegin, wcfClient.EndSiteInfoBegin, null)
.ContinueWith(t =>
{
siteInfo.DatabaseVersion = t.Result.DatabaseVersion.DatabaseVersion;
}, TaskScheduler.FromCurrentSynchronizationContext()));
}
catch (Exception e)
{ //...code stripped out...
}
}
Task.WhenAll(tasks.ToArray()).ContinueWith
( ts =>
{
ts.ForEach( t => aboutInfo.Sites.Add(t.Rrsult);
});
return aboutInfo;
}

Programmatically determine endpoint for Web Service in C#

I'm building a plugin to a windows service I've made (it's C#, uses plugins to do certain functionality). This new plugin will be using Web Services to make calls to a web-service and get some information.
Unfortunately, the web-service URL is different for my DEV, QC, and PRODUCTION environments. I'd like to make that end-point URL be configurable (the plugin will look into the database and get the URL).
How, exactly, do I set up a web-service caller in my code so that it can use a dynamic endpoint?
I can add a service and point to the existing one in DEV - and that builds up my proxy class - but how can I make it so it's not "hard locked" with the URL - so the plugin works in any environment (based on the URL in the database it pulls out)? I'd like to be able to change that on the fly in the code, so to speak.
Basically you can call this to create your WCF Service client:
MyClient = new MyWCFClient(new BasicHttpBinding("CustomBinding"), new EndpointAddress(GetEndpointFromDatabase()));
Where GetEndpointFromDatabase() returns a string - the endpoint.
I made an end to end sample which runs in LINQPad. This is for a completely self-hosted scenario, and enables exploring various bindings, etc (for both the client and the server). Hope it's not over the top and posted the entire sample in case you find any of the other aspects helpful later on.
void Main()
{
MainService();
}
// Client
void MainClient()
{
ChannelFactory cf = new ChannelFactory(new WebHttpBinding(), "http://localhost:8000");
cf.Endpoint.Behaviors.Add(new WebHttpBehavior());
IService channel = cf.CreateChannel();
Console.WriteLine(channel.GetMessage("Get"));
Console.WriteLine(channel.PostMessage("Post"));
Console.Read();
}
// Service
void MainService()
{
WebServiceHost host = new WebServiceHost(typeof(Service), new Uri("http://localhost:8080"));
ServiceEndpoint ep = host.AddServiceEndpoint(typeof(IService),new WebHttpBinding(), "");
ServiceDebugBehavior stp = host.Description.Behaviors.Find();
stp.HttpHelpPageEnabled = false;
stp.IncludeExceptionDetailInFaults = true;
host.Open();
Console.WriteLine("Service is up and running");
Console.ReadLine();
host.Close();
}
// IService.cs
[ServiceContract]
public interface IService
{
[OperationContract]
[WebInvoke(Method="GET", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
string GetMessage(string inputMessage);
[OperationContract]
[WebInvoke(Method="POST", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
string PostMessage(string inputMessage);
[OperationContract]
[WebInvoke(Method="POST", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
System.IO.Stream PostJson(System.IO.Stream json);
}
// Service.cs
public class Service : IService
{
public string GetMessage(string inputMessage){
Console.WriteLine(inputMessage);
return "Calling Get for you " + inputMessage;
}
public string PostMessage(string inputMessage){
Console.WriteLine(inputMessage);
return "Calling Post for you " + inputMessage;
}
public System.IO.Stream PostJson (System.IO.Stream json) {
Console.WriteLine(new System.IO.StreamReader(json).ReadToEnd());
return json;
}
}
Can't you just put the URI inside the .config file? You can just change the URI when it's debug or release by having different URI inside .debug.config and .release.config.
Just set the url point
TheWebservice.Url = TheUrlFromTheDatabase;

Categories

Resources