I am doing a Xamarin Forms project that requires connection to a WCF service. I must use Rest to access it, so I opted to use a PCL-compatible build of RestSharp. I have done many SOAP based web services, but this is my first dive into Rest, and I feel like I am missing something very basic. I have confirmed that my web service functions correctly when I make SOAP calls, so I believe I have set something up incorrectly.
Here is sample code for my web service:
Imports System.IO
Imports System.Net
Imports System.ServiceModel
Imports System.ServiceModel.Description
Imports System.ServiceModel.Web
<ServiceContract()>
Public Interface Iapi
<WebInvoke(Method:="PUT",
UriTemplate:="Login/Email/{Email}/Password/{Password}",
RequestFormat:=WebMessageFormat.Json,
ResponseFormat:=WebMessageFormat.Json)>
<OperationContract(AsyncPattern:=True)>
Function Login(email As String, password As String) As String
End Interface
Here is sample code for my attempt to call the service:
public void Login(string email, string password)
{
RestClient client = new RestClient("http://www.example.com/service.svc/");
RestRequest request = new RestRequest
{
Method = Method.PUT,
Resource = "Login/Email/{Email}/Password/{Password}",
RequestFormat = DataFormat.Json
};
request.AddParameter("Email", email, ParameterType.UrlSegment);
request.AddParameter("Password", password,ParameterType.UrlSegment);
client.ExecuteAsync(request, response => {
session = response.Content;
ActionCompleted(this, new System.EventArgs());
});
}
When I make the call above, I get no exceptions, just an empty string return value. The same thing happens in the browser. I suspect my service definition. I have several questions that are probably a bit basic, but I hope will also help other WCF/Rest beginners in the future.
1. What, if anything, is wrong with my UriTemplate in the definition of my service? What would a proper UriTemplate look like?
2. Should I be using the PUT method for this kind of service call, or is GET or POST more appropriate?
3. Is anything else obviously missing from my web service definition?
4. Am I correct in passing the full service uri (http://www.example.com/service.svc/) to the Rest client?
5. Any additional suggestions for a Rest beginner, specifically in relation to the WCF-Rest combination?
A proper URI-template could look something like this if you're using a GET:
C#
[OperationContract]
[WebGet(UriTemplate = "Book/{id}")]
Book GetBookById(string id);
VB:
<OperationContract()> _
<WebGet(UriTemplate:="Book/{id}")> _
Function GetBookById(ByVal id As String) As Book
Then you can call it using http://example.com/Book/1 for the book with ID==1.
In the Microsoft-world PUT is often used for creating or updating data, such as a new task, order etc. However, you CAN use it for login even if I personally think a POST or GET would be the more accurate approach. But that's just my oppinion.
See this question for more information:
PUT vs POST in REST
There's nothing that seems to be missing in you declaration.
If you cannot reach it with a browser, it's probably not the usage of RestSharp that's wrong. However, here's some notes. When using async methods you will often want to try using .NET's async/await-pattern. Then the request doesn't lock the main thread.
Example:
http://www.dosomethinghere.com/2014/08/23/vb-net-simpler-async-await-example/
Here's a little piece of code that I use in my Xamarin-projects for invoking services:
protected static async Task<T> ExecuteRequestAsync<T>(string resource,
HttpMethod method,
object body = null,
IEnumerable<Parameter> parameters = null) where T : new()
{
var client = new RestClient("http://example.com/rest/service.svc/");
var req = new RestRequest(resource, method);
AddRequestKeys(req);
if (body != null)
req.AddBody(body);
if (parameters != null)
{
foreach (var p in parameters)
{
req.AddParameter(p);
}
}
Func<Task<T>> result = async () =>
{
var response = await client.Execute<T>(req);
if (response.StatusCode == HttpStatusCode.Unauthorized)
throw new Exception(response.Data.ToString());
if (response.StatusCode != HttpStatusCode.OK)
throw new Exception("Error");
return response.Data;
};
return await result();
}
Yes, that's correct.
How do you host your WCF? If using IIS, what does your web.config look like? Here's an example:
As a side note, I noticed you mentioned that you require access to a WCF-service. Have you considered using .NET Web API instead? It provides a more straight-forward approach for creating RESTful endpoints, without the need for configs. It's more simple to implement and to use, however it does not provide the same flexibility as a WCF-service does.
For debugging a WCF-service I strongly recommend "WCF Test client":
https://msdn.microsoft.com/en-us/library/bb552364(v=vs.110).aspx
Where can I find WcfTestClient.exe (part of Visual Studio)
With metadata-enabled in your web.config, you will be able to see all available methods. Example-configuration below:
<configuration>
<system.serviceModel>
<services>
<service name="Metadata.Example.SimpleService">
<endpoint address=""
binding="basicHttpBinding"
contract="Metadata.Example.ISimpleService" />
</service>
</services>
<behaviors>
</behaviors>
</system.serviceModel>
</configuration>
Source:
https://msdn.microsoft.com/en-us/library/ms734765(v=vs.110).aspx
If it doesn't help, could you provide your web.config and implementation of your service as well?
Related
I have one solution in which I have 2 projects with:
ASP.NET MVC Application to consume wcf services.
5 WCF services.
I have added one web service reference in the project 1. Now, I need to use different services based on the user e.g.
User Type 1: Only allow to consume Service 1.
User Type 2: Only allow to consume Service 2.
etc.
I have Service URL's like localhost:6227/Service1.svc, localhost:6227/Service2.svc etc.
I have stored all service URL's in the db and I need to change URL for each user type to consume his allowed service only without adding more end points and only change URL from the backend based on user type. I need relevant link or code to solve this problem.
Edit
In Web Config
I have added just this endpoint in the mvc application and I don't want to use web config to change address in here but I want to change address in the code for each user type while application is running.
<client>
<endpoint address="http://localhost:6227/Service1.svc"
binding="customBinding" bindingConfiguration="CustomBinding_IService1"
contract="Service1.IService1" name="CustomBinding_IService1" />
</client>
if i completely realize your question you need dynamic soap service calling. maybe something like this:
private void CallService()
{
var myBinding = new BasicHttpBinding();
myBinding.Security.Mode = BasicHttpSecurityMode.None;
var myEndpointAddress = new EndpointAddress("your url depend on user type");
var client = new ClientService1(myBinding, myEndpointAddress);
var outpiut = client.someCall();
client.close();
}
Not sure if I understand you correctly but you could use below snippet, if it suits.
//assuming you get string URL. Change type as per need.
string reqdURL = GetServiceURL(typeof(userObject));
private string GetServiceURL(Type userType)
{
if (userType == typeof(UserType1))
{
// currently hardcoded but you can replace your own fetch logic
return "localhost:6227/Service1.svc";
}
if (userType == typeof(UserType2))
{
return "localhost:6227/Service2.svc";
}
//and so on
}
You can modify directly your EndPoint Address doing this:
ClientService1 ws = new ClientService1();
ws.Endpoint.Address = new EndpointAddress("Your new URL svc service");
I have a normal 3rd party SOAP service with WSDL and stuff. The problem is - it only accepts GET requests. How can I access it in c#?
When I add that service to VS via Add Service Reference and try to use it as usual:
var service = new BaseSvcClient(
new BasicHttpContextBinding(),
new EndpointAddress("http://some.internal.ip/WebServices/Base.svc"));
var ver = service.Version();
I see (via fiddler) that it actually sends POST requests and web-service responds with Endpoint not found error message.
If I simply hit http://some.internal.ip/WebServices/Base.svc/Version in a browser the proper xml is returned.
I can use WebClient, but then I have to construct all the GET requests manually, which doesn't look good.
Are there other solutions?
I have found an answer that helped me a lot.
Basically if I take an autogenerated interface for the client, decorate methods with [WebGet] and use
var cf = new WebChannelFactory<IBaseSvc2>(new Uri("..."));
var service = cf.CreateChannel();
var result = service.Version();
it all works well. That's not a perfect solution, since changes won't be picked up automatically, so may be there are other solutions?
P.S. an interface for a web service is now like:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName = "BaseService.IBaseSvc")]
public interface IBaseSvc2
{
[System.ServiceModel.OperationContractAttribute(Action = "http://tempuri.org/IBaseSvc/Version", ReplyAction = "http://tempuri.org/IBaseSvc/VersionResponse")]
[WebGet]
VersionInformation Version();
}
You can achieve it by adding the protocols in config file
<webServices>
<protocols>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>
Need a way for one service on a well-known Endpoint to return strings which are relative addresses. The client can then connect to Endpoints using these relative addresses.
Clearly this resembles REST in some ways, but in this case running a Windows Service using NetNamedPipeBinding for IPC, so no need for HTTP.
Don't want to create the Endpoint ahead of time since there will be a potentially large number of relative addresses, only some of which the client would be interested in.
All Contracts are known in advance.
Tried to find a solution with AddressFilterMode but wasn't sure how to provision new Binding so that client connected to it, UriTemplate but don't want to use the HTTP framework. Haven't looked into RoutingService because constrained to .Net 3.5.
Pseudocode for client would be something like that below...
namespace Testing
{
class RunTest
{
static void Test()
{
NetNamedPipeBinding namedpipe = new NetNamedPipeBinding();
ChannelFactory<Contracts.IRoot> factoryRoot =
new ChannelFactory<Contracts.IRoot>(
namedpipe
, new EndpointAddress("net.pipe://localhost/root");
);
Contracts.IRoot root = factoryRoot.CreateChannel();
ICommunicationObject commsRoot = root as ICommunicationObject;
commsRoot.Open();
// Service examines address and creates Endpoint dynamically.
string address = root.SomeFunctionWhichGetsARelativeAddress();
// IBar service routes endpoint requests internally based on
// "address" variable.
ChannelFactory<Contracts.IBar> factoryBar =
new ChannelFactory<Contracts.IBar>(
namedpipe
, new EndpointAddress("net.pipe://localhost/root/IBar/" +
address)
);
Contracts.IBar bar = factoryBar.CreateChannel();
bar.DoSomething();
}
} // Ends class RunTest
} // Ends namespace Testing
Message Filters are the way to go. You can use “Prefix” or create a custom.
WCF Addressing In Depth
From the Message Filters section of the article:
...it uses message filters to determine the matching endpoint, if one
exists. You can choose which message filter to use or you can provide
your own. This flexibility allows you to break free from the
traditional dispatching model when using Windows Communication
Foundation to implement things other than traditional SOAP—for
instance, the techniques described here enable you to implement
REST/POX-style services on the Windows Communication Foundation
messaging foundation.
Nice question, by the way. I learned something trying to figure this out.
AddressFilterMode.Prefix might suffice. The actual Endpoint used can be inspected in Service methods via
OperationContext.Current.IncomingMessageHeaders.To
Helper code can parse the endpoint and do any necessary internal processing from there.
Hopefully there's some extensibility on the server side which can simplify that code.
Pseudocode for host:
namespace Services
{
[System.ServiceModel.ServiceBehavior(AddressFilterMode =
System.ServiceModel.AddressFilterMode.Prefix)]
class BarService : Contracts.IBar
{
#region IBar Members
public void DoSomething()
{
System.Uri endpoint = System.ServiceModel.OperationContext.Current.IncomingMessageHeaders.To;
Console.WriteLine("DoSomething endpoint: {0}", endpoint);
}
} // Ends class BarService
} // Ends namespace Services
class RunHost
{
static void HostIBar()
{
System.Uri uriBase = new System.Uri("net.pipe://localhost");
System.ServiceModel.ServiceHost hostBar =
new System.ServiceModel.ServiceHost(
typeof(Services.BarService),
uriBase);
hostBar.AddServiceEndpoint(
typeof(Contracts.IBar) // Type implementedContract
, namedpipeBinding // System.ServiceModel.Channels.Binding binding
, "root/IBar" //string address
);
hostBar.Open();
Console.WriteLine("Press <ENTER> to stop...");
Console.ReadLine();
}
}
Correction: I'd originally said that this wouldn't treat "net.pipe://localhost/root/IBar/1" and "net.pipe://localhost/root/IBar/2" as distinct endpoints, but it does. Each causes its own WCF Service instance to be created and called.
An additional change was to encode the data in URL style query parameters and not embed it in the path. E.g.: "net.pipe://localhost/root/IBar?something=1&somethingelse=11" and "net.pipe://localhost/root/IBar?something=2&somethingelse=22" using HttpUtility.ParseQueryString
HttpContext.Current.Request.ServerVariables["SERVER_PORT"]
HttpContext.Current.Request.ServerVariables["SERVER_PORT_SECURE"]
HttpContext.Current.Request.ServerVariables["SERVER_NAME"]
HttpContext.Current.Request.ApplicationPath
I want to access these value via a webservice -C#, whenever I call these values in webservice I get null for all of the above, where as above works for web pages (aspx).
As others have mentioned, you need to enable ASP.NET compatibility. You can also enable this via configuration if you don't want to limit your code via attributes like so:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
Here's a great resource that helped explain to me the underlying functionality and trade-offs made by enabling compatibility mode.
What sort of web service are you using? asmx or wcf? They should work fine with asmx services but if you're using WCF, you'll need to add the following attribute to the method:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
have you tried to define your method with EnableSession at true?
[WebMethod(EnableSession = true)]
public string your_public_method(your_params)
{ [...] }
If it is a WCF web service you can do the following:
[AspNetCompatibilityRequirementsAttribute(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class FooBar : IFooBar
{
public void DoSomething()
{
HttpContext context = HttpContext.Current;
if (context != null)
{
// Should get here now
}
}
}
The key is to add [AspNetCompatibilityRequirementsAttribute(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)].
Say I have an ASMX web service, MyService. The service has a method, MyMethod. I could execute MyMethod on the server side as follows:
MyService service = new MyService();
service.MyMethod();
I need to do similar, with service and method not known until runtime.
I'm assuming that reflection is the way to go about that. Unfortunately, I'm having a hard time making it work. When I execute this code:
Type.GetType("MyService", true);
It throws this error:
Could not load type 'MyService' from assembly 'App_Web__ktsp_r0, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
Any guidance would be appreciated.
I'm not sure if this would be the best way to go about it. The most obvious way to me, would be to make an HTTP Request, and call the webservice using an actual HTTP GET or POST. Using your method, I'm not entirely sure how you'd set up the data you are sending to the web service. I've added some sample code in VB.Net
Dim HTTPRequest As HttpWebRequest
Dim HTTPResponse As HttpWebResponse
Dim ResponseReader As StreamReader
Dim URL AS String
Dim ResponseText As String
URL = "http://www.example.com/MyWebSerivce/MyMethod?arg1=A&arg2=B"
HTTPRequest = HttpWebRequest.Create(URL)
HTTPRequest.Method = "GET"
HTTPResponse = HTTPRequest.GetResponse()
ResponseReader = New StreamReader(HTTPResponse.GetResponseStream())
ResponseText = ResponseReader.ReadToEnd()
// Try this ->
Type t = System.Web.Compilation.BuildManager.GetType("MyServiceClass", true);
object act = Activator.CreateInstance(t);
object o = t.GetMethod("hello").Invoke(act, null);
Although I don't know why Reflection is not working for you there (I assume the compiler might be creating a new class from your [WebService] annotations), here is some advice that might solve your problem:
Keep your WebService simple, shallow, in short: An implementation of the Facade Pattern.
Make your service delegate computation to an implementation class, which should easily be callable through Reflection. This way, your WebService class is just a front for your system - you can even add an email handler, XML-RPC frontend etc., since your logic is not coupled to the WebService, but to an actual business layer object.
Think of WebService classes as UI layer objects in your Architecture.
Here's a quick answer someone can probably expand on.
When you use the WSDL templating app (WSDL.exe) to genereate service wrappers, it builds a class of type SoapHttpClientProtocol. You can do it manually, too:
public class MyService : SoapHttpClientProtocol
{
public MyService(string url)
{
this.Url = url;
// plus set credentials, etc.
}
[SoapDocumentMethod("{service url}", RequestNamespace="{namespace}", ResponseNamespace="{namespace}", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public int MyMethod(string arg1)
{
object[] results = this.Invoke("MyMethod", new object[] { arg1 });
return ((int)(results[0]));
}
}
I haven't tested this code but I imagine it should work stand-alone without having to run the WSDL tool.
The code I've provided is the caller code which hooks up to the web service via a remote call (even if for whatever reason, you don't actually want it to be remote.) The Invoke method takes care of packaging it as a Soap call. #Dave Ward's code is correct if you want to bypass the web service call via HTTP - as long as you are actually able to reference the class. Perhaps the internal type is not "MyService" - you'd have to inspect the control's code to know for sure.
#Kibbee: I need to avoid the HTTP performance hit. It won't be a remote call, so all of that added overhead should be unnecessary.
#Daren: I definitely agree with that design philosophy. The issue here is that I'm not going to be in control of the service or its underlying business logic.
This is for a server control that will need to execute against an arbitrary service/method, orthogonally to how the web service itself is implemented.
Although I cannot tell from your post:
One thing to keep in mind is that if you use reflection, you need to create an instance of the autogenerated webservice class(the one created from your webservice's WSDL). Do not create the class that is responsbile for the server-side of the service.
So if you have a webservice
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
public class WebService1 : System.Web.Services.WebService
{
...
}
you cannot reference that assembly in your client and do something like:
WebService1 ws = new WebService1 ();
ws.SomeMethod();
#Radu: I'm able to create an instance and call the method exactly like that. For example, if I have this ASMX:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class MyService : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}
}
I'm able to call it from an ASPX page's codebehind like this:
MyService service = new MyService();
Response.Write(service.HelloWorld());
Are you saying that shouldn't work?
I looked back at this question and I think what you're facing is that the ASMX code will be built into a DLL with a random name as part of the dynamic compilation of your site. Your code to look up the type will, by default, only search its own assembly (another App_Code DLL, by the looks of the error you received) and core libraries. You could provide a specific assembly reference "TypeName, AssemblyName" to GetType() but that's not possible in the case of the automatically generated assemblies, which have new names after each recompile.
Solution.... I haven't done this myself before but I believe that you should be able to use something like this:
System.Web.Compilation.BuildManager.GetType("MyService", true)
as the BuildManager is aware of the DLLs it has created and knows where to look.
I guess this really doesn't have to do with Web Services but if it were your own code, Daren's right about Facade patterns.