uploading a file with WCF REST Services - c#

I am new to WCF and Rest services, and tried to do some implementation from posts I found on the web, but I am still getting some problems.
So let me explain my scenario.
I have a WPF application, and in it I have a feedback form, which the client can fill up, attach some screenshots, and send it. Now my idea was to gather all this info inside an XML file, which I am already doing successfully, and then uploading this XML file on my server in a particular folder.
Now as I understand it, the client app has to have a POST method to post the stream to the server, and then I should have an aspx page on the server to decode back the stream I get from the POST, and formulate my XML file, and then save it inside the folder, correct me if I'm wrong.
At the moment I have implemented the code on the client as follows :-
public static void UploadFile()
{
serverPath = "http://localhost:3402/GetResponse.aspx";
filePath = "C:\\Testing\\UploadFile\\UploadFile\\asd_asd_Feedback.xml";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(serverPath);
//request.MediaType = "text/xml";
request.ContentType = "text/xml";
//request.Method = "POST";
request.Method = "POST";
//request.ContentLength = contentLength;
//request.ContentType = "application/x-www-form-urlencoded";
using (FileStream fileStream = File.OpenRead(filePath))
using (Stream requestStream = request.GetRequestStream())
{
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int byteCount = 0;
while ((byteCount = fileStream.Read(buffer, 0, bufferSize)) > 0)
{
requestStream.Write(buffer, 0, byteCount);
}
}
string result = String.Empty;
try
{
using (WebResponse response = request.GetResponse())
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
result = reader.ReadToEnd();
}
}
catch (Exception exc)
{
}
if (result == "OK")
{
}
else
{
// error...
}
}
Now how can I pass the requestStream to the GetResponse.aspx page? And is this the correct way to go?
Thanks for your help and time

I don't understand what your code is trying to do. Have you considered actually using a WCF client and a WCF service for doing the actual upload itself?
There is a sample that does this! This blog post details how to use the programming model on the service side, and this follow-up blog post details how to use it on the client side. I've seen it used quite a bit for file upload and image transfer scenarios, so it might help your situation as well! The example present in those blog posts is a file upload one.
Hope this helps!

Related

Use C# to Save an Excel File Contained in a Tableau API Response

I'm trying to download an Excel file from a Tableau view, using a snippet based on this article: Downloading Files with the WebRequest and WebResponse Classes.
WebResponse response = null;
Stream webStream = null;
Stream localStream = null;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(endPoint);
request.Method = "GET";
request.Headers.Add("X-Tableau-Auth", token);
response = request.GetResponse();
webStream = response.GetResponseStream();
localStream = File.Create("testing.xlsx");
byte[] buffer = new Byte[1024];
int bytesRead;
int bytesProcessed = 0;
do
{
bytesRead = webStream.Read(buffer, 0, buffer.Length);
localStream.Write(buffer, 0, bytesRead);
bytesProcessed += bytesRead;
} while (bytesRead > 0);
response.Close();
webStream.Close();
localStream.Close();
But when I try to open the Excel file it says "Nope, maybe it is corrupt?". The response is a complete Excel file "ready to be saved", which means that it's encoded using UTF-8. Indeed, if I use Postman to do the call, and then save the response, it's saved and opens without any problem.
Before finding the page I said above, I thought the problem was because the response is UTF-8 and the class String is UTF-16. So, I made some testing getting the data from the file generated by Postman and then writing to a new file. The result of the testings: indeed, if the data passes through a String, it's not well saved. Then I tried with that code, and got the same result: fail. I'm pretty sure this code is not using any UTF-16 encoding variable, but maybe I'm wrong.
Anyway, do anyone knows what is my problem with that code, or point me to the right way to accomplish my task? That is, to download a Tableau report to Excel, using the Tableau API.
Thanks in advance.
Unless you're stuck on a particularly old version of .NET, that referenced link is from 2004. The following code will work in .NET 5 / C# 9, and in earlier versions with just some minor tweaking of the using statements. It's showing for Tableau Online, but should work fine for recent versions of Server, if I had to guess. This is sample-grade code, so I would recommend following best practices for the HttpClient if you intend to make this call frequently.
//using System.IO;
//using System.Net.Http;
var token = "your-session-token";
var uri = "https://your-pod.online.tableau.com/api/.../sites/.../views/.../crosstab/excel";
var yourFile = "D:/file/test.xlsx";
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Add("X-Tableau-Auth", token);
using var response = await client.SendAsync(request);
using FileStream outputFileStream = new FileStream(yourFile, FileMode.Create);
await response.Content.CopyToAsync(outputFileStream);
UPDATE: If you're constrained to WebRequest and non-async methods, you can try the following:
var token = "your-session-token";
var uri = "https://your-pod.online.tableau.com/api/.../sites/.../views/.../crosstab/excel";
var yourFile = "D:/file/test.xlsx";
WebRequest request = WebRequest.Create(uri);
request.Method = "GET";
request.Headers.Add("X-Tableau-Auth", token);
var response = request.GetResponse();
if(((HttpWebResponse)response).StatusCode == HttpStatusCode.OK)
{
using (Stream dataStream = response.GetResponseStream())
using (FileStream fileStream = new FileStream(yourFile, FileMode.CreateNew))
{
dataStream.CopyTo(fileStream);
}
}
response.Close();

Issues passing an Xml file to a method in console application

I am working on a c# console application where I am making a Http Post request to a web api by using xml file and I'm kind of new to XML and web services but I figured out the following code for the request but failed to pass xml data to the method
static void Main(string[] args)
{
string desturl=#"https://xyz.abcgroup.com/abcapi/";
Program p = new Program();
System.Console.WriteLine(p.WebRequestPostData(desturl, #"C:\Applications\TestService\FixmlSub.xml"));
}
public string WebRequestPostData(string url, string postData)
{
System.Net.WebRequest req = System.Net.WebRequest.Create(url);
req.ContentType = "text/xml";
req.Method = "POST";
byte[] bytes = System.Text.Encoding.ASCII.GetBytes(postData);
req.ContentLength = bytes.Length;
using (Stream os = req.GetRequestStream())
{
os.Write(bytes, 0, bytes.Length);
}
using (System.Net.WebResponse resp = req.GetResponse())
{
if (resp == null) return null;
using (System.IO.StreamReader sr = new System.IO.StreamReader(resp.GetResponseStream()))
{
return sr.ReadToEnd().Trim();
}
}
}
For obvious reasons the above code throws 404 error as I think I am not passing the xml data properly
May I know how I can fix this?
You're not posting xml, your posting the string C:\Applications\TestService\FixmlSub.xml
Change your method call from:
System.Console.WriteLine(p.WebRequestPostData(desturl, #"C:\Applications\TestService\FixmlSub.xml"));
to
var xml = XElement.Load(#"C:\Applications\TestService\FixmlSub.xml");
System.Console.WriteLine(p.WebRequestPostData(desturl, xml.ToString(SaveOptions.DisableFormatting));
If you are trying to learn post / receive, go for it. But there are open source libraries that are already well tested to use if you want to use them.
The non-free version of Servicestack.
And their older free-version. I think the older free version is great. I've never tried the newer one. You deal with objects, like say an Employee and pass that to the library and it does the translation to xml or whatever the web-service wants.
You can post whole strings if you want. They have great extension methods to help you with that too.

Is HttpClient flawed when sending large files/content?

After reading/googling about HttpClient, I have the impression that this component is not suitable for uploading large files or contents to REST services.
It seems that if the upload takes more than the established timeout, the transmission will fail. Does it make sense? What does this timeout means?
Getting progress information seems hard or requires add-ons.
So my questions are: Is it possible to sove these two issues without too much hassle? Otherwise, what's the best approach when working with large contents and REST services?
Yes, if the upload takes longer that the TimeOut, the upload will fail. This is a limitation of HttpClient. The most robust solution to this problem is the one that Thomas Levesque has written an article about, and linked in his comments to your question. You have to use HttpWebRequest instead of HttpClient.
If you want to get progress messages, open the file as a FileStream and manually iterate through it, copying bytes in increments onto the (upload) request stream. As you go, you can calculate your progress relative to the file size.
TL's code example. Be sure to read the article though!:
long UploadFile(string path, string url, string contentType)
{
// Build request
var request = (HttpWebRequest)WebRequest.Create(url);
request.Method = WebRequestMethods.Http.Post;
request.AllowWriteStreamBuffering = false;
request.ContentType = contentType;
string fileName = Path.GetFileName(path);
request.Headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", fileName);
try
{
// Open source file
using (var fileStream = File.OpenRead(path))
{
// Set content length based on source file length
request.ContentLength = fileStream.Length;
// Get the request stream with the default timeout
using (var requestStream = request.GetRequestStreamWithTimeout())
{
// Upload the file with no timeout
fileStream.CopyTo(requestStream);
}
}
// Get response with the default timeout, and parse the response body
using (var response = request.GetResponseWithTimeout())
using (var responseStream = response.GetResponseStream())
using (var reader = new StreamReader(responseStream))
{
string json = reader.ReadToEnd();
var j = JObject.Parse(json);
return j.Value<long>("Id");
}
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.Timeout)
{
LogError(ex, "Timeout while uploading '{0}'", fileName);
}
else
{
LogError(ex, "Error while uploading '{0}'", fileName);
}
throw;
}
}

Error while reading response from HttpWebRequest

I am trying to send contents of 1GB text file over the network. I modified the suggested code for basic authentication and kept it as follows :
WRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
WRequest.Credentials = Credentials;
WRequest.PreAuthenticate = true;
WRequest.ContentType = "text/plain";
WRequest.Method = "POST";
WRequest.AllowWriteStreamBuffering = false;
WRequest.Timeout = 10000;
FileStream ReadIn = new FileStream(filename, FileMode.Open, FileAccess.Read);
ReadIn.Seek(0, SeekOrigin.Begin);
WRequest.ContentLength = ReadIn.Length;
Byte[] FileData = new Byte[ReadIn.Length];
int DataRead = 0;
Stream tempStream = WRequest.GetRequestStream();
do
{
DataRead = ReadIn.Read(FileData, 0, 2048);
if (DataRead > 0)
{
tempStream.Write(FileData, 0, DataRead);
Array.Clear(FileData, 0, 2048);
}
} while (DataRead > 0);
// The response
WResponse = (HttpWebResponse)WRequest.GetResponse();
However, now it gives me System.Net.ProtocolViolationException error : "You must write ContentLength bytes to the request stream before calling [Begin]GetResponse". I checked HttpWebRequest.BeginGetRequestResponse ... and found from debugging that the contentlength for WRequest is not -1. What else could be going wrong ? How should I get the response ?
Update :
The code which worked for small files is as followed :
WebRequest request = WebRequest.Create(url);
request.Method = "POST";
request.Credentials = Credentials;
using (StreamReader reader = new StreamReader(filename))
{
postData = reader.ReadToEnd();
}
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
request.ContentType = "text/plain";
request.ContentLength = byteArray.Length;
Stream dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
// The response
WebResponse response = request.GetResponse();
Console.WriteLine(((HttpWebResponse)response).StatusDescription);
dataStream = response.GetResponseStream();
using (StreamReader reader = new StreamReader(dataStream))
{
responseFromServer = reader.ReadToEnd();
}
dataStream.Close();
response.Close();
The article you referenced says
If the Microsoft Internet Information Services (IIS) Web server is configured to use Basic authentication, and you must set the HttpWebRequest.AllowWriteStreamBuffering property to false, you must send a HEAD request to pre-authenticate the connection before you send the POST or PUT request.
EDIT - now with more clarification!
To restate the article, if you want to send a large file to a destination which requires basic authentication, you'll need to issue two separate requests. The key here is that you are setting PreAuthenticate = true. Read the statement literally -- by setting the property to true, you are saying that you will authenticate any requests that you make before you actually attempt them! The framework doesn't know how you want to accomplish this pre-authentication, so you need to perform that action yourself, by sending a HEAD request to the destination. Think of the HEAD HTTP method as being a prologue to the actual request - it describes (or requests information about) a particular resource.
So the process goes like this:
Make a HEAD request to http://someurl/aresource containing the credentials you want to use when making future requests from this client to that server for the listed resource
The server will respond (ideally) with "OK - you may proceed. You're authenticated"
The server immediately regrets its' decision to allow the operation as it finds itself saving a very large file :-)
I don't see you making that HEAD request anywhere in the code you posted - if it's not already there, add this at the beginning of your code (snipped from the sample article ref in OP):
//preAuth the request
// You can add logic so that you only pre-authenticate the very first request.
// You should not have to pre-authenticate each request.
WRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
// Set the username and the password.
WRequest.Credentials = new NetworkCredential(user, password);
WRequest.PreAuthenticate = true;
WRequest.UserAgent = "Upload Test";
WRequest.Method = "HEAD";
WRequest.Timeout = 10000;
WResponse = (HttpWebResponse)WRequest.GetResponse();
WResponse.Close();
// Make the real request.

How do you copy a file into SharePoint using a WebService?

I am writting a winforms c# 2.0 application that needs to put an XML file into a document library on SharePoint.
I want to use a WebService instead of using the object model (no sharepoint.dll to reference here)
I am currently using the http://webserver/site/_vti_bin/copy.asmx webservice.
Here is some code:
byte[] xmlByteArray;
using (MemoryStream memoryStream = new MemoryStream())
{
xmlDocument.Save(memoryStream);
xmlBytes = memoryStream.ToArray();
}
string[] destinationUrlArray = new string[] {"http://webserver/site/Doclib/UploadedDocument.xml"};
FieldInformation fieldInfo = new FieldInformation();
FieldInformation[] fields = { fieldInfo };
CopyResult[] resultsArray;
using (Copy copyService = new Copy())
{
copyService.Credentials = CredentialCache.DefaultCredentials;
copyService.Url = "http://webserver/site/_vti_bin/copy.asmx";
copyService.Timeout = 600000;
uint documentId = copyService.CopyIntoItems("", destinationUrlArray, fields, xmlByteArray, out resultsArray);
}
When this code runs, I get a single result in the resultsArray out parameter:
DestinationURL: "http://webserver/site/Doclib/UploadedDocument.xml"
ErrorCode: UnKnown
ErrorMessage: "Object reference not set to an instance of an object."
From my searching, I have found a couple of possible helps.
Microsoft TechNet -- "The copy.asmx copyintoitems will only work if the source and destination urls are in the same SPWebApplication (Site Collection)."
Microsoft Social -- "Object reference not set to an instance of an object
error occurs because of SharePoint not able to identified that particular property."
This leads me to believe my source url should be set to something, but what? This is originating from a client workstation and does not have a source URL.
Any help would be appricated.
hank you,
Keith
I know this is an old thread but it kept coming up as I was searching for a solution to the same problem.
Check Steve Curran's answer on this thread http://social.msdn.microsoft.com/Forums/en-SG/sharepointdevelopment/thread/833e38a8-f13c-490d-8ba7-b889b6b25e38. Looks like Basically the request fails because the destination url can't be resolved.
(Limitations of a new stackflow user - can't post more than one link. See my comment for the rest)
pat
SharePoint responds to a plain old HTTP PUT
Here is what is currently working:
WebRequest request = WebRequest.Create(“http://webserver/site/Doclib/UploadedDocument.xml”);
request.Credentials = CredentialCache.DefaultCredentials;
request.Method = "PUT";
byte[] buffer = new byte[1024];
using (Stream stream = request.GetRequestStream())
{
using (MemoryStream memoryStream = new MemoryStream())
{
dataFile.MMRXmlData.Save(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
for (int i = memoryStream.Read(buffer, 0, buffer.Length); i > 0;
i = memoryStream.Read(buffer, 0, buffer.Length))
{
stream.Write(buffer, 0, i);
}
}
}
WebResponse response = request.GetResponse();
response.Close();
So... Does anyone have an opinion as to if this "PUT" method is better in the SharePoint environment than using a built-in webservice?
Right now I would have to say the "PUT" method is better since it works and I could not get the WebService to work.
Keith
your code is fine, just use the destination url instead of an empty string. See below:
byte[] xmlByteArray;
using (MemoryStream memoryStream = new MemoryStream())
{
xmlDocument.Save(memoryStream);
xmlBytes = memoryStream.ToArray();
}
string destinationUrl = “http://webserver/site/Doclib/UploadedDocument.xml”
string[] destinationUrlArray = new string[] { destinationUrl };
FieldInformation fieldInfo = new FieldInformation();
FieldInformation[] fields = { fieldInfo };
CopyResult[] resultsArray;
using (Copy copyService = new Copy())
{
copyService.Credentials = CredentialCache.DefaultCredentials;
copyService.Url = "http://webserver/site/_vti_bin/copy.asmx";
copyService.Timeout = 600000;
uint documentId = copyService.CopyIntoItems(destinationUrl , destinationUrlArray, fields, xmlByteArray, out resultsArray);
}
I get the same message when I use the default credentials.
Try replacing them with this:
copyWebService.Credentials
= new NetworkCredential("Administrator", "pass", "MyDomain");
Here's some code I wrote awhile (i apologize, i've had to piece meal it together, but hopefully you get the point of it)
// Create a request using a URL that can receive a post.
WebRequest request = WebRequest.Create("http://sharepointsite/somefile.txt");
// Set the Method property of the request to POST.
request.Method = "PUT"
Stream dataStream;
// Set the ContentType property of the WebRequest.
request.ContentType = "multipart/form-data; charset=ISO-8859-1";
byte[] byteArray = File.ReadAllBytes(#"c:\somefile.txt");
// Set the ContentLength property of the WebRequest.
request.ContentLength = byteArray.Length;
// Get the request stream.
dataStream = request.GetRequestStream();
// Write the data to the request stream.
dataStream.Write(byteArray, 0, byteArray.Length);
// Close the Stream object.
dataStream.Close();
// Get the response.
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
HttpStatusCode statCode = response.StatusCode;
// Get the stream containing content returned by the server.
dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
StreamReader reader = new StreamReader(dataStream);
// Read the content.
string responseFromServer = reader.ReadToEnd();
// Clean up the streams.
reader.Close();
dataStream.Close();
response.Close();
I'm not sure if it will solve your problem but, when you reference the webservice, don't use the [site] part of the URL.
Try instead: http://[server]/_vti_bin/[webservice].
I'm not an expert in SP but I'm pretty sure the webservices belongs to the main server, not to an especific site.
Hope it helps.
I had a similiar problem, it turned out that the the client was configured to use NTLM security, but no NTLM header was attached.
I my case, becuase of the fact that I was using this code on the server-side of an ASP.NET applicaton, was to enable Windows authentication and set
identity impersonate="true"
in the server.web section.
if your sharepoint server is built on a farm,
Check your "Alternate Access Mapping" see if there is an entry:
yourwebserverurl intranet yourwebserverurl
if not, add it.
for my case, after adding this, the Copy service start working.
It probably due to farm load balance address resolve related.
I don't get it, why are you using Copy rather then UpdateListItems. Perhaps UpdateListItems will be a better match?

Categories

Resources