I have quite a few RESTful (GET and POST) methods implemented in WCF 4.0. All these work over SSL.
An example of some of the methods:
[OperationContract]
[WebInvoke(UriTemplate = "Login?", Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
LoginResponse Login(LoginRequest request);
[OperationContract]
[WebInvoke(UriTemplate = "UpdateDetails?", Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
UpdateUserDetailResponse UpdateDetails(UpdateUserDetailRequest request);
[OperationContract]
[WebInvoke(UriTemplate = "GetDetails?", Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
UserDetailResponse GetDetails(UserDetailRequest request);
I have looked through so many blogs and forums and I still cannot find something that meets
my requirements. I need to implement basic authentication on some of the methods but not all. If you look at the examples above I require a username and password to be sent through for the UpdateDetails and GetDetails method, but not for the Login method. The username and password is then authenticated against a database. Is it possible to do something like this?
As a side note: these REST methods are called by many different mobile devices.
I have looked at the following sites and they all implement basic authentication over REST but they cover all the methods mentioned above.
http://msdn.microsoft.com/en-us/library/aa702565.aspx
Adding basic HTTP auth to a WCF REST service
http://custombasicauth.codeplex.com/ (links at the bottom don't work
anymore)
Is it possible to do what I want to do?
I created a BasicAuthenticationInvoker class which you attribute on the methods you would like to authenticate as follows:
[OperationContract]
[BasicAuthenticationInvoker] // this is the auth attribute!
[WebInvoke(UriTemplate = "QuickQuote?", Method = "POST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Bare)]
QuickQuoteResponse QuickQuote(QuickQuoteRequest request);
The actual class looks as follows:
public class BasicAuthenticationInvoker : Attribute, IOperationBehavior, IOperationInvoker
{
#region Private Fields
private IOperationInvoker _invoker;
#endregion
#region IOperationBehavior Members
public void ApplyDispatchBehavior(OperationDescription operationDescription,
DispatchOperation dispatchOperation)
{
_invoker = dispatchOperation.Invoker;
dispatchOperation.Invoker = this;
}
public void ApplyClientBehavior(OperationDescription operationDescription,
ClientOperation clientOperation)
{
}
public void AddBindingParameters(OperationDescription operationDescription,
BindingParameterCollection bindingParameters)
{
}
public void Validate(OperationDescription operationDescription)
{
}
#endregion
#region IOperationInvoker Members
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
if (Authenticate("Client Name here"))
return _invoker.Invoke(instance, inputs, out outputs);
else
{
outputs = null;
return null;
}
}
public object[] AllocateInputs()
{
return _invoker.AllocateInputs();
}
public IAsyncResult InvokeBegin(object instance, object[] inputs,
AsyncCallback callback, object state)
{
throw new NotSupportedException();
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
throw new NotSupportedException();
}
public bool IsSynchronous
{
get
{
return true;
}
}
#endregion
private bool Authenticate(string realm)
{
string[] credentials = GetCredentials(WebOperationContext.Current.IncomingRequest.Headers);
if (credentials != null && credentials.Length == 2)
{
// do auth here
var username = credentials[0];
var password = credentials[1];
// validate the username and password against whatever auth logic you have
return true; // if successful
}
WebOperationContext.Current.OutgoingResponse.Headers["WWW-Authenticate"] = string.Format("Basic realm=\"{0}\"", realm);
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized;
return false;
}
private string[] GetCredentials(WebHeaderCollection headers)
{
string credentials = WebOperationContext.Current.IncomingRequest.Headers["Authorization"];
if (credentials != null)
credentials = credentials.Trim();
if (!string.IsNullOrEmpty(credentials))
{
try
{
string[] credentialParts = credentials.Split(new[] {' '});
if (credentialParts.Length == 2 && credentialParts[0].Equals("basic", StringComparison.OrdinalIgnoreCase))
{
credentials = Encoding.ASCII.GetString(Convert.FromBase64String(credentialParts[1]));
credentialParts = credentials.Split(new[] {':'});
if (credentialParts.Length == 2)
return credentialParts;
}
}
catch
{
}
}
return null;
}
}
Related
The contract
[OperationContract]
[WebGet(UriTemplate = "Filter/{paramName:paramValue}/"),
RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
string[] Filter(string paramNameAndparamValue);
The implementation
public string Filter(string paramNameAndparamValue)
{
string[] tmp = paramNameAndparamValue.split(':');
// do something ...
}
Is there any why to pass this restful method a parameter that will be use as json object and avoid of using the string.split?
You can try like below
Iservice.cs
[OperationContract]
[WebGet(UriTemplate = "Filter/{paramName}/{paramValue}"),
RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
string[] Filter(string paramName,string paramValue);
service .cs
public string[] Filter(string paramName,string paramValue);
{
//your code;
}
I had a WCF service using CORS hosted on a console application that was working fine in .Net 4.5. the application is now required to work in .Net 3.5 and the preflight OPTIONS request is now failing for any POST request
Here is the interface for the services:
[OperationContract]
[WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
BiometricCaptureResponse Start(BiometricCaptureRequest biometricRequest);
[OperationContract]
[WebInvoke(Method = "OPTIONS", UriTemplate = "*")]
void Options();
The actual service:
public BiometricCaptureResponse Start(BiometricCaptureRequest biometricRequest)
{
//Stuff and things
}
public void Options()
{
return;
}
The Behaviour for CORS that gets applied to the requests and response
public string clientOrigin;
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
HttpRequestMessageProperty requestProperty = request.Properties[HttpRequestMessageProperty.Name]
as HttpRequestMessageProperty;
if (requestProperty != null && requestProperty.Headers.AllKeys.Contains("Origin") && !string.IsNullOrEmpty(requestProperty.Headers["Origin"]))
{
clientOrigin = requestProperty.Headers["Origin"];
}
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
HttpResponseMessageProperty httpHeader = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
if (httpHeader != null)
{
httpHeader.ApplyCrossOriginSupport(clientOrigin);
}
}
public static void ApplyCrossOriginSupport(this HttpResponseMessageProperty message, string origin)
{
message.Headers.Add("Access-Control-Allow-Origin", origin);
message.Headers.Add("Access-Control-Allow-Method", ConfigurationManager.AppSettings["Access-Control-Allow-Method"]);
message.Headers.Add("Access-Control-Allow-Headers", ConfigurationManager.AppSettings["Access-Control-Allow-Headers"]);
}
this all worked fin in 4.5 this is the process that would happen:
AfterReceiveRequest()
Options() service hit
BeforeSendReply()
AfterReceiveRequest()
Start()
BeforeSendReply()
Now in 3.5 the Ooptions Service will not get hit which will cause the whole request to fail, Is there anything I am missing to get this working for 3.5?
The Error I am getting from the console of Chrome is:
OPTIONS http://localhost:8502/testservice.svc/rest/Start net::ERR_CONNECTION_RESET
could this have anything to do with the WebInvoke attribute? It's MSDN page describes it as a 4.5/3.5 Client tech while I am only using .Net 3.5.
UPDATE:
Changing the following atrribute of the options service to use all methods
[WebInvoke(Method = "OPTIONS", UriTemplate = "*")]
[WebInvoke(Method = "*", UriTemplate = "*")]
has allowed option requests to hit the service, the application can work functionally like this but this is still not the optimal solution as it is not specific to an OPTIONS request, any reason behind this?
I’m afraid there is a little bug in the .Net 3.5 Version of the WebHttpDispatchOperationSelector.
The Method OPTIONS in combination with a * UriTemplate is not recognized by the selector.
As a workaround you could replace the default WebHttpBehavior with a custom version.
public class CorsWebHttpDispatchOperationSelector : WebHttpDispatchOperationSelector
{
private WebHttpDispatchOperationSelector target;
private ServiceEndpoint endpoint;
OperationDescription optionOperation;
public CorsWebHttpDispatchOperationSelector(ServiceEndpoint endpoint, WebHttpDispatchOperationSelector target)
{
this.target = target;
this.endpoint = endpoint;
foreach (var item in this.endpoint.Contract.Operations) {
var webInvoke = item.Behaviors.OfType<WebInvokeAttribute>().FirstOrDefault();
if (webInvoke != null && webInvoke.Method.Equals("options",StringComparison.OrdinalIgnoreCase) && webInvoke.UriTemplate == "*") {
optionOperation = item;
break;
}
}
}
#region IDispatchOperationSelector Members
protected override string SelectOperation(ref Message message, out bool uriMatched)
{
var result = target.SelectOperation(ref message);
var matched = message.Properties["UriMatched"] as bool?;
message.Properties.Remove("UriMatched");
message.Properties.Remove("HttpOperationName");
uriMatched = matched.HasValue && matched.Value;
var httpRequest = message.Properties["httpRequest"] as HttpRequestMessageProperty;
var cond = string.IsNullOrEmpty(result) &&
httpRequest != null &&
httpRequest.Method.Equals("options",StringComparison.OrdinalIgnoreCase);
if (cond && optionOperation != null) {
result = optionOperation.Name;
uriMatched = true;
}
return result;
}
#endregion
}
public class CorsWebHttpBehavior : WebHttpBehavior {
protected override WebHttpDispatchOperationSelector GetOperationSelector(ServiceEndpoint endpoint)
{
return new CorsWebHttpDispatchOperationSelector(endpoint, base.GetOperationSelector(endpoint));
}
}
I have an Apex Class (a class in SalesForce) that calls out to a REST web service.
public class WebServiceCallout
{
#future (callout=true)
public static void sendNotification(String aStr)
{
HttpRequest req = new HttpRequest();
HttpResponse res = new HttpResponse();
Http http = new Http();
req.setEndpoint('http://xx.xxx.xxx.xx:41000/TestService/web/test');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(aStr); // I want to read this in the web service
try
{
res = http.send(req);
}
catch(System.CalloutException e)
{
System.debug('Callout error: '+ e);
System.debug(res.toString());
}
}
}
The REST web service (C#, WCF) looks like so:
public interface ITestService
{
[OperationContract]
[WebInvoke(Method = "POST",
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate = "/test")]
string Test(string aStr);
}
The Test() method does primitive operations.
When I run
WebServiceCallout.sendNotification("a test message")
the POST gets to the web service but how can I read what was set in the body of the HttpRequest req that was set back in the sendNotification() method - req.setBody(aStr);?
That is, what should the parameter of string Test(string aStr); be?
Do I need to specify anything else such as any configs/attributes in my WebInvoke or the App.config (e.g. the binding)?
If you want to read the raw body of the incoming request, you should define the type of the parameter as a Stream, not string. The code below shows one way to implement your scenario, and the post at http://blogs.msdn.com/b/carlosfigueira/archive/2008/04/17/wcf-raw-programming-model-receiving-arbitrary-data.aspx has more information on this "raw" mode.
public class StackOverflow_25377059
{
[ServiceContract]
public interface ITestService
{
[OperationContract]
[WebInvoke(Method = "POST",
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate = "/test")]
string Test(Stream body);
}
public class Service : ITestService
{
public string Test(Stream body)
{
return new StreamReader(body).ReadToEnd();
}
}
class RawMapper : WebContentTypeMapper
{
public override WebContentFormat GetMessageFormatForContentType(string contentType)
{
return WebContentFormat.Raw;
}
}
public static void Test()
{
var baseAddress = "http://" + Environment.MachineName + ":8000/Service";
var host = new ServiceHost(typeof(Service), new Uri(baseAddress));
var binding = new WebHttpBinding { ContentTypeMapper = new RawMapper() };
host.AddServiceEndpoint(typeof(ITestService), binding, "").Behaviors.Add(new WebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
var req = (HttpWebRequest)HttpWebRequest.Create(baseAddress + "/test");
req.Method = "POST";
req.ContentType = "application/json";
var reqStream = req.GetRequestStream();
var body = "a test message";
var bodyBytes = new UTF8Encoding(false).GetBytes(body);
reqStream.Write(bodyBytes, 0, bodyBytes.Length);
reqStream.Close();
var resp = (HttpWebResponse)req.GetResponse();
Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
foreach (var header in resp.Headers.AllKeys)
{
Console.WriteLine("{0}: {1}", header, resp.Headers[header]);
}
Console.WriteLine();
Console.WriteLine(new StreamReader(resp.GetResponseStream()).ReadToEnd());
Console.WriteLine();
}
}
By the way, your incoming request is not technically correct - you say (via Content-Type) that you're sending JSON, but the request body (a test message) is not a valid JSON string (it should be wrapped in quotes - "a test message" to be a JSON string instead).
I have a RESTful WCF Service function, however it is not serializing properly via the RestSharp Client.
[ServiceContract]
public interface IRestDragon
{
[OperationContract(Name = "getconfig")]
[WebInvoke(Method = "GET", UriTemplate = "getconfig/{id}", BodyStyle = WebMessageBodyStyle.Wrapped,
ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
DragonConfig GetConfig(string id);
}
public class RestDragon : IRestDragon
{
public DragonConfig GetConfig(string id)
{
var config = new DragonConfig {Name = "Test", Data = "DAtaaaaaa"};
return config;
}
}
And here is how I consume the service:
static void Main(string[] args)
{
Console.ReadLine();
var client = new RestClient("http://localhost:5463/RESTDragon.svc");
client.AddDefaultHeader("ContentType", "application/json");
var request = new RestRequest("/getconfig/11123") {Method = Method.GET, RequestFormat = DataFormat.Json};
var response = client.Execute<DragonConfig>(request);
Console.WriteLine("Response: " + response.Content);
Console.ReadLine();
}
However it is returning:
Response: {"getconfigResult":{"Data":"DAtaaaaaa","Name":"Test"}}
I am unable to access the De-Serialized Data via response.Data.*. It comes back as null, and the data is shown in Content however, in the JSON Format, with the strange getconfigResult identifier.
Setting BodyStyle = WebMessageBodyStyle.Bare corrected the issue.
You need to access response.Data instead of response.Content to get the deserialized object.
I can't pass over parameters to wcf web service. My web method:
[OperationContract]
[WebInvoke(Method = "POST",
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "playersJson2")]
List<Person> GetPlayers(string name1, string name2);
When I make http post request, I got 200 OK response with correct json form but web service seems to fail to get paramters (name1, name2). Wireshark shows following:
Do you see anything wrong?
Update: Not sure it matters but my service is using "webHttpBinding" and post request is coming from Android.
WCF doesn't support forms/encoded data out-of-the box. The other answers mentioned some alternatives (receive the input as a Stream, change the request to JSON). Another alternative, which doesn't force you to change the request or the operation is to use a custom formatter which understands form-urlencoded requests. The code below shows one which does that.
public class MyWebHttpBehavior : WebHttpBehavior
{
protected override IDispatchMessageFormatter GetRequestDispatchFormatter(OperationDescription operationDescription, ServiceEndpoint endpoint)
{
bool isRequestWrapped = this.IsRequestWrapped(operationDescription.Behaviors.Find<WebInvokeAttribute>());
IDispatchMessageFormatter originalFormatter = base.GetRequestDispatchFormatter(operationDescription, endpoint);
if (isRequestWrapped)
{
return new MyFormUrlEncodedAwareFormatter(
operationDescription,
originalFormatter,
this.GetQueryStringConverter(operationDescription));
}
else
{
return originalFormatter;
}
}
private bool IsRequestWrapped(WebInvokeAttribute wia)
{
WebMessageBodyStyle bodyStyle;
if (wia.IsBodyStyleSetExplicitly)
{
bodyStyle = wia.BodyStyle;
}
else
{
bodyStyle = this.DefaultBodyStyle;
}
return bodyStyle == WebMessageBodyStyle.Wrapped || bodyStyle == WebMessageBodyStyle.WrappedRequest;
}
class MyFormUrlEncodedAwareFormatter : IDispatchMessageFormatter
{
const string FormUrlEncodedContentType = "application/x-www-form-urlencoded";
OperationDescription operation;
IDispatchMessageFormatter originalFormatter;
QueryStringConverter queryStringConverter;
public MyFormUrlEncodedAwareFormatter(OperationDescription operation, IDispatchMessageFormatter originalFormatter, QueryStringConverter queryStringConverter)
{
this.operation = operation;
this.originalFormatter = originalFormatter;
this.queryStringConverter = queryStringConverter;
}
public void DeserializeRequest(Message message, object[] parameters)
{
if (IsFormUrlEncodedMessage(message))
{
XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
bodyReader.ReadStartElement("Binary");
byte[] bodyBytes = bodyReader.ReadContentAsBase64();
string body = Encoding.UTF8.GetString(bodyBytes);
NameValueCollection pairs = HttpUtility.ParseQueryString(body);
Dictionary<string, string> values = new Dictionary<string, string>();
foreach (var key in pairs.AllKeys)
{
values.Add(key, pairs[key]);
}
foreach (var part in this.operation.Messages[0].Body.Parts)
{
if (values.ContainsKey(part.Name))
{
string value = values[part.Name];
parameters[part.Index] = this.queryStringConverter.ConvertStringToValue(value, part.Type);
}
else
{
parameters[part.Index] = GetDefaultValue(part.Type);
}
}
}
else
{
this.originalFormatter.DeserializeRequest(message, parameters);
}
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
throw new NotSupportedException("This is a request-only formatter");
}
private static bool IsFormUrlEncodedMessage(Message message)
{
object prop;
if (message.Properties.TryGetValue(WebBodyFormatMessageProperty.Name, out prop))
{
if (((WebBodyFormatMessageProperty)prop).Format == WebContentFormat.Raw)
{
if (message.Properties.TryGetValue(HttpRequestMessageProperty.Name, out prop))
{
if (((HttpRequestMessageProperty)prop).Headers[HttpRequestHeader.ContentType].StartsWith(FormUrlEncodedContentType))
{
return true;
}
}
}
}
return false;
}
private static object GetDefaultValue(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
else
{
return null;
}
}
}
}
[ServiceContract]
public class Service
{
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
public string Concat(string text1, string text2)
{
return text1 + text2;
}
[WebInvoke(BodyStyle = WebMessageBodyStyle.WrappedRequest)]
public int Add(int x, int y)
{
return x + y;
}
}
class Program
{
public static void SendRequest(string uri, string method, string contentType, string body)
{
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
req.Method = method;
if (!String.IsNullOrEmpty(contentType))
{
req.ContentType = contentType;
}
if (body != null)
{
byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
req.GetRequestStream().Write(bodyBytes, 0, bodyBytes.Length);
req.GetRequestStream().Close();
}
HttpWebResponse resp;
try
{
resp = (HttpWebResponse)req.GetResponse();
}
catch (WebException e)
{
resp = (HttpWebResponse)e.Response;
}
Console.WriteLine("HTTP/{0} {1} {2}", resp.ProtocolVersion, (int)resp.StatusCode, resp.StatusDescription);
foreach (string headerName in resp.Headers.AllKeys)
{
Console.WriteLine("{0}: {1}", headerName, resp.Headers[headerName]);
}
Console.WriteLine();
Console.WriteLine(new StreamReader(resp.GetResponseStream()).ReadToEnd());
Console.WriteLine();
Console.WriteLine(" *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* ");
Console.WriteLine();
}
static void Main(string[] args)
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(Service), new WebHttpBinding(), "").Behaviors.Add(new MyWebHttpBehavior());
host.Open();
Console.WriteLine("Host opened");
SendRequest(baseAddress + "/Add", "POST", "application/json", "{\"x\":22,\"y\":33}");
SendRequest(baseAddress + "/Add", "POST", "application/x-www-form-urlencoded", "x=22&y=33");
SendRequest(baseAddress + "/Add", "POST", "application/json", "{\"x\":22,\"z\":33}");
SendRequest(baseAddress + "/Add", "POST", "application/x-www-form-urlencoded", "x=22&z=33");
SendRequest(baseAddress + "/Concat", "POST", "application/json", "{\"text1\":\"hello\",\"text2\":\" world\"}");
SendRequest(baseAddress + "/Concat", "POST", "application/x-www-form-urlencoded", "text1=hello&text2=%20world");
SendRequest(baseAddress + "/Concat", "POST", "application/json", "{\"text1\":\"hello\",\"text9\":\" world\"}");
SendRequest(baseAddress + "/Concat", "POST", "application/x-www-form-urlencoded", "text1=hello&text9=%20world");
}
}
It looks like you need to parse your post data manual...
You can look here for example
The only thing that looks out of place is the playerJson2 but that is just because I have never used the UriTemplate before. Can you get it to work without the UriTemplate and just post to /WcfService1/Service1.svc/GetPlayers? Are there any other WCF services that you have working in the project?
You need to set the correct Content-Type which is application/json in this case.
Sorry for misreading your question. Update your UriTemplate as follows:
[WebInvoke(Method = "POST",
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "playersJson2?name1={name1}&name2={name2}")]