WCF WebInvoke with query string parameters AND a post body - wcf

I'm fairly new to web services and especially WCF so bear with me.
I'm writing an API that takes a couple of parameters like username, apikey and some options, but I also need to send it a string which can be a few thousands words, which gets manipulated and passed back as a stream. It didn't make sense to just put it in the query string, so I thought I would just have the message body POSTed to the service.
There doesn't seem to be an easy way to do this...
My operation contract looks like this
[OperationContract]
[WebInvoke(Method = "POST", BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate="Method1?email={email}&apikey={apikey}"+
"&text={text}&quality={qual}", BodyStyle = WebMessageBodyStyle.Bare)]
Stream Method1(string email, string apikey, string text, string qual);
And this works. But it is the 'text' parameter I want to pull out and have in the post body. One thing I read said to have a stream as another parameter, like this:
Stream Method1(string email, string apikey, string qual, Stream text);
which I could then read in. But that throws an error saying that if I want to have a stream parameter, that it has to be the only parameter.
So how can I achieve what I am trying to do here, or is it no big deal to send up a few thousand words in the query string?

https://social.msdn.microsoft.com/Forums/vstudio/en-US/e2d074aa-c3a6-4e78-bd88-0b9d24b561d1/how-to-declare-post-parameters-in-wcf-rest-contract?forum=wcf
Best answer I could find that tackles this issue and worked for me so I could adhere to the RESTful standards correctly

A workaround is to not declare the query parameters within the method signature and just manually extract them from the raw uri.
Dictionary<string, string> queryParameters = WcfUtils.QueryParameters();
queryParameters.TryGetValue("email", out string email);
// (Inside WcfUtils):
public static Dictionary<string, string> QueryParameters()
{
// raw url including the query parameters
string uri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch;
return uri.Split('?')
.Skip(1)
.SelectMany(s => s.Split('&'))
.Select(pv => pv.Split('='))
.Where(pv => pv.Length == 2)
.ToDictionary(pv => pv[0], pv => pv[1].TrimSingleQuotes());
}
// (Inside string extension methods)
public static string TrimSingleQuotes(this string s)
{
return (s != null && s.Length >= 2 && s[0] == '\'' && s[s.Length - 1] == '\'')
? s.Substring(1, s.Length - 2).Replace("''", "'")
: s;
}

Ended up solving simply by using WebServiceHostFactory

Related

Can't rewrite a URL including the query string components

I'm trying to do some URL rewriting in asp.net core 2.2 but it doesn't seem to work with the query string part. I want to change any path like "finditem?txn=3" into something like "find/item?transactionid=3". As a simpler example, without a smart replacement of the transactionID, look at this code:
private static RewriteOptions GetRewriteOptions() => new RewriteOptions()
.AddRewrite(#"^bananatxn=\d$", "Download", true) // Works with bananatxn=1
.AddRewrite(#"^banana\?txn=\d$", "Download", true); // Does NOT work with banana?txn=1
Why can't the rewriter match on the question mark character? I've tested my patterns in http://regexstorm.net/tester and although the pattern seems to be correct it doesn't work. Can the rewriter in asp.net core rewrite the entire URL, including the query string, or only the part before the question mark?
I've investigate and think (but am not sure) this functionality is not available in the built-in rules provided by asp.net core. This worked for me. Definitely NOT tested thoroughly, probably gives too much importance to upper and lower case, and I'm not super familiar with all the URL components and formats.
public class RewritePathAndQuery : IRule
{
private Regex _regex;
private readonly string _replacement;
private readonly RuleResult _resultIfRewrite;
/// <param name="regex">Pattern for the path and query, excluding the initial forward slash.</param>
public RewritePathAndQuery(string regex, string replacement, bool skipRemainingRules)
{
_regex = new Regex(regex);
_replacement = replacement;
_resultIfRewrite = skipRemainingRules ? RuleResult.SkipRemainingRules : RuleResult.ContinueRules;
}
public void ApplyRule(RewriteContext context)
{
HttpRequest request = context.HttpContext.Request;
string pathExcludingInitialForwardSlash = request.Path.Value.Substring(1);
string queryStringWithLeadingQuestionCharacter = request.QueryString.Value;
string original = $"{pathExcludingInitialForwardSlash}{queryStringWithLeadingQuestionCharacter}";
string replaced = _regex.Replace(original, _replacement);
if (replaced.StartsWith('/')) { // Replacement pattern may include this character
replaced = replaced.Substring(1);
}
if (original != replaced) { // Case comparison?
string[] parts = replaced.Split('?');
request.Path = $"/{parts[0]}";
request.QueryString = new QueryString(parts.Length == 2 ? $"?{parts[1]}" : "");
context.Result = _resultIfRewrite;
}
else {
context.Result = RuleResult.ContinueRules;
}
}
}

WCF REST Svc GET Returning HTML

I put together a simple REST service in WCF as such:
....
[OperationContract]
[WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Xml, UriTemplate = "{uid}/{pwd}/{exrcsPrgmId}/{exchEnum}")]
string GetLiftDataExchange(string uid, string pwd, string exrcsPrgmId, string exchEnum);
....
When calling it however I do not get back XML exactly. I get HTXML (my own made up acronym)
Instead of what I expect:
<Exercise>
<AccountName>Joe Muscle</AccountName>
<UserID>8008008</UserID>
I get the XML with html encoding:
<Exercise>
<AccountName>John Bonner</AccountName>
<UserID>8008008</UserID>
In other words I have no need to see this data in the browser instead it will be accessed and parsed in an application so straight up XML will work just fine.
What am I doing wrong with the service decorations to return this encoded xml?
When you return a string, and the result type is XML, you'll get the string encoded to be able to represent all characters in the string - which causes the XML characters to be escaped.
You have two options for your scenario. If you want to return "pure" XML (i.e., XHTML, or HTML which happens to be well-formed XML), you can use the return type as either XmlElement or XElement. That is telling WCF that you do want to return arbitrary XML. If you do like the code below, you'll get the "pure" XML which you need.
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Xml, UriTemplate = "...")]
public XElement GetLiftDataExchange(string uid, string pwd, string exrcsPrgmId, string exchEnum)
{
return XElement.Parse(#"<Exercise>
<AccountName>Joe Muscle</AccountName>
<UserID>8008008</UserID>
</Exercise>");
}
Another alternative is to return a Stream - which means that you control the output (see this blog post for more details), and your code would look something like the one below. The advantage of this method is that your HTML doesn't need to be well-formed XML (i.e., you can have things like <br> or <hr> which are valid HTML but not valid XML).
[OperationContract]
[WebGet(UriTemplate = "...")]
public Stream GetLiftDataExchange(string uid, string pwd, string exrcsPrgmId, string exchEnum)
{
var str = #"<html><head><title>This is my page</title></head>
<body><h1>Exercise</h1><ul>
<li><b>AccountName</b>: Joe Muscle</li>
<li><b>UserID</b>: 8008008</li></body></html>";
WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
return new MemoryStream(Encoding.UTF8.GetBytes(str));
}
On a related node, please don't use [WebInvoke(Method="GET")], use [WebGet] instead.

WCF Service JSON Data

I've created a WCF Web Service which returns data in JSON format. The code for the service is as follows:
[OperationContract]
[WebGet(ResponseFormat = WebMessageFormat.Json)]
List<MyCustomer> GetCustomerJSON();
And
public List<MyCustomer> GetCustomerJSON()
{
var nm = (from n in _ent.Customers
select new MyCustomer() { CustomerID = n.CustomerID, AccountNumber = n.AccountNumber }).Take(10);
return nm.ToList();
}
However, the output isn't well formed. It included square brackets at start and end.
Because of which I can't use Json Parser tool.
Please help.
If you return List<T> it will be encoded like array of T in the JSON and array will be encoded with respect of square brackets:
[{"strProprety":"bla","intProperty":123,"booleanProperty":true}]
In your case it will be probably
[{"CustomerID":1,"AccountNumber":123},{"CustomerID":2,"AccountNumber":456}]
It is valid JSON. You can use http://www.jsonlint.com/ to verify this. So WCF produce correct output and you have problem only with the "Json Parser tool".

How can I return bare result back to WCF client [duplicate]

This question already has answers here:
WCF ResponseFormat For WebGet
(4 answers)
Closed 8 years ago.
I have code something like the following:
<OperationContract()>
<Description("")>
<WebGet(Bodystyle:=WebMessageBodyStyle.Bare, UriTemplate:="TestConnection")>
Function TestConnection() As String
Public Function TestConnection() As String Implements ITestSvc.TestConnection
WebOperationContext.Current.OutgoingResponse.ContentType = "text/plain"
Return "Connection Success"
End Function
But it returns is <string xmlns='...'>Connection Success</string>
How can I have only "Connection Success" to be returned without XML wrapper. I know that we can do something with MessageEncoder. But, I want to have it available at operation level (certain operations need XML/JSON wrappers and certain operations don't).
Can anyone help me on this?
here is the simplest solution to return plain text. Set response format to xml and set outgoingresponse to text/html. Should do the trick.
[WebGet(ResponseFormat = WebMessageFormat.Xml)]
public string DoWork()
{
WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
return "THIS IS PLAIN TEXT";
}
There is one way how to achieve this if you're dealing with HTTP, it's not exactly nice, but I thought I could mention it.
You can set the return type of your method to void and just output your raw string directly into the response.
[OperationContract]
[WebGet(UriTemplate = "foo")]
void Foo()
{
HttpContext.Current.Response.Write("bar");
}
The answer is here WCF ResponseFormat For WebGet (and it worked for me)

WCF + REST: Where is the request data?

I'm currently developing a WCF RESTful service. Within the validation of the POST data, I am throwing exceptions if the request XML does not conform to our business rules.
The goal is to send an e-mail to the appropriate staff if a request comes in that considered invalid. But, along with the incoming request headers, method and URI, I'd like to also send the XML that was posted.
I have not been able to find a way to access this data. Is WCF actually destroying the request body/data before I have a chance to access it or am I missing something?
Your help is appreciated as I'm confused as to why I can't access the request data.
This unfortunately isn't supported- we had a similar need, and did it by calling internal members with reflection. We just use it in an error handler (so we can dump the raw request), but it works OK. I wouldn't recommend it for a system you don't own and operate though (eg, don't ship this code to a customer), since it can change at any time with a service pack or whatever.
public static string GetRequestBody()
{
OperationContext oc = OperationContext.Current;
if (oc == null)
throw new Exception("No ambient OperationContext.");
MessageEncoder encoder = oc.IncomingMessageProperties.Encoder;
string contentType = encoder.ContentType;
Match match = re.Match(contentType);
if (!match.Success)
throw new Exception("Failed to extract character set from request content type: " + contentType);
string characterSet = match.Groups[1].Value;
object bufferedMessage = operationContextType.InvokeMember("request",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField,
null, oc, null);
//TypeUtility.AssertType(bufferedMessageType, bufferedMessage);
object messageData = bufferedMessageType.InvokeMember("MessageData",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty,
null, bufferedMessage, null);
//TypeUtility.AssertType(jsonBufferedMessageDataType, messageData);
object buffer = jsonBufferedMessageDataType.InvokeMember("Buffer",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty,
null, messageData, null);
ArraySegment<byte> arrayBuffer = (ArraySegment<byte>)buffer;
Encoding encoding = Encoding.GetEncoding(characterSet);
string requestMessage = encoding.GetString(arrayBuffer.Array, arrayBuffer.Offset, arrayBuffer.Count);
return requestMessage;
}
So, if you declare your contract something like:
[WebInvoke(Method = "POST", UriTemplate = "create", ResponseFormat=WebMessageFormat.Json)]
int CreateItem(Stream streamOfData);
(you can use XML instead)
The streamOfData should be the body of an HTTP POST. You can deserialize it using something like:
StreamReader reader = new StreamReader(streamId);
String res = reader.ReadToEnd();
NameValueCollection coll = HttpUtility.ParseQueryString(res);
It's working like that for us, at least. You may want to use a different approach to get the string into an XMLDocument or something. This works for our JSON posts. Might not be the most elegant solution, but it is working.
I hope this helps.
Glenn
Try this,
OperationContext.Current.RequestContext.RequestMessage
Here's how you do it without reflection:
using (var reader = OperationContext.Current.RequestContext.RequestMessage.GetReaderAtBodyContents ()) {
if (reader.Read ())
return new string (Encoding.ASCII.GetChars (reader.ReadContentAsBase64 ()));
return result;
}
}
If the reader is a HttpStreamXmlDictionaryReader (as it was in my case), the class's implementation of the method ReadContentAsBase64(byte[] buffer, int index, int count) simply passes these parameters to the Stream.Read method.
Once I have the byte[] I convert the bytes to a string via ASCII encoding. For a proper implementation, you could use the content type & encoding from the message's headers to do per HTTP spec.
You could arrest the HttpApplication.Request.InputStream in a custom HttpModule of the WCF Service, read the stream and again set its position to 0 in the custom HttpModule's event handler. Then store it in session and access it further in the actual OperationContract.
For example:
public class CustomModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.AcquireRequestState +=context_AcquireRequestState;
}
void context_AcquireRequestState(object sender, EventArgs e)
{
HttpApplication application = sender as HttpApplication;
Stream str = application.Request.InputStream;
StreamReader sr = new StreamReader(str);
string req = sr.ReadToEnd();
str.Position = 0;
application.Session["CurrentRequest"] = req;
}
}