From Silverlight, generate file to save on user's computer without hitting server - silverlight

I have a Silverlight app where I want to do an export of some data. The file output format is most likely going to be PDF or Word. But let's assume I can generate the file contents appropriately. I want to be able to pop up a Save dialog for the user to save this data or open it directly in the program.
Now obviously I could just launch the user to a URL and do the export on the server, and change the MIME type of the response to be either Word or PDF. This would work just fine. However, the sticking point is that I already have the correct data on the client (including complex filters and the like) and recreating this data set on the server just to send it back to the client again seems silly if I can avoid it.
Is there any way to take an existing set of data in Silverlight and generate a Word or PDF file and get it onto the user's computer? I could also do it from JavaScript using browser interop from Silverlight. I don't want to use out-of-browser Silverlight.

You need to use the SaveFileDialog class. Note that due to Silverlight's security settings, the SaveFileDialog needs to be opened as the result of a user event (e.g., a button click).
The dialog can be configured (if you want) using properties such as DefaultExt or Filter before you display it using the ShowDialog() method.
The ShowDialog() method will return true if the user correctly specified a file and clicked OK. If this is the case, you can then call the SaveFileDialog.OpenFile() method to access this file and write your data to it.
Example:
private void Button_Click(object sender, EventArgs e)
{
SaveFileDialog saveDialog = new SaveFileDialog();
if (saveDialog.ShowDialog())
{
System.IO.Stream fileStream = textDialog.OpenFile();
System.IO.StreamWriter sw = new System.IO.StreamWriter(fileStream);
sw.Write("TODO: Generate the data you want to put in your file");
sw.Flush();
sw.Close();
}
}

Related

Silverlight: Business Application Needs Access To Files To Print and Move

I have the following requirement for a business application:
(All of this could be on local or server)
Allow user to select folder location
Show contents of folder
Print selected items from folder (*.pdf)
Display which files have been printed
Potentially move printed files to new location (sub-folder of printed)
How can I make this happen in Silverlight?
Kind regards,
ribald
First of all, all but the last item can be done (the way you expect). Due to security protocols, silverlight cannot access the user's drive and manipulate it. The closest you can get is accessing silverlight's application storage which will be of no help to you whatsoever in this case. I will highlight how to do the first 4 items.
Allow user to select folder location & Show contents of folder
public void OnSelectPDF(object sender)
{
//create the open file dialog
OpenFileDialog ofg = new OpenFileDialog();
//filter to show only pdf files
ofg.Filter = "PDF Files|*.pdf";
ofg.ShowDialog();
byte[] _import_file = new byte[0];
//once a file is selected proceed
if (!object.ReferenceEquals(ofg.File, null))
{
try
{
fs = ofg.File.OpenRead();
_import_file = new byte[fs.Length];
fs.Read(_import_file, 0, (int)fs.Length);
}
catch (Exception ex)
{
}
finally
{
if (!object.ReferenceEquals(fs, null))
fs.Close();
}
//do stuff with file - such as upload the file to the server
};
}
If you noticed, in my example, once the file is retrieved, i suggest uploading it to a webserver or somewhere with temporary public access. I would recommend doing this via a web service. E.g
//configure the system file (customn class)
TSystemFile objFile = new TNetworkFile().Initialize();
//get the file description from the Open File Dialog (ofg)
objFile.Description = ofg.File.Extension.Contains(".") ? ofg.File.Extension : "." + ofg.File.Extension;
objFile.FileData = _import_file;
objFile.FileName = ofg.File.Name;
//upload the file
MasterService.ToolingInterface.UploadTemporaryFileAsync(objFile);
Once this file is uploaded, on the async result, most likely returning the temporary file name and upload location, I would foward the call to some javascript method in the browser for it to use the generic "download.aspx?fileName=givenFileName" technique to force a download on the users system which would take care of both saving to a new location and printing. Which is what your are seeking.
Example of the javascript technique (remember to include System.Windows.Browser):
public void OnInvokeDownload(string _destination)
{
//call the browser method/jquery method
//(I use constants to centralize the names of the respective browser methods)
try
{
HtmlWindow window = HtmlPage.Window;
//where BM_INVOKE_DOWNLOAD is something like "invokeDownload"
window.Invoke(Constants.TBrowserMethods.BM_INVOKE_DOWNLOAD, new object[] { _destination});
}
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.ToString()); }
}
Ensure you have the javascript method existing either in an included javaScript file or in the same hosting page as your silverlight app. E.g:
function invokeDownload(_destination) {
//some fancy jquery or just the traditional document.location change here
//open a popup window to http://www.myurl.com/downloads/download.aspx? fileName=_destination
}
The code for download.aspx is outside the scope of my answer, as it varies per need and would just lengthen this post (A LOT MORE). But from what I've given, it will "work" for what you're looking for, but maybe not in exactly the way you expected. However, remember that this is primarily due to silverlight restrictions. What this approach does is rather than forcing you to need a pluging to view pdf files in your app, it allows the user computer to play it's part by using the existing adobe pdf reader. In silverlight, most printing, at least to my knowledge is done my using what you call and "ImageVisual" which is a UIElement. To print a pdf directly from silverlight, you need to either be viewing that PDF in a silverlight control, or ask a web service to render the PDF as an image and then place that image in a control. Only then could you print directly. I presented this approach as a lot more clean and direct approach.
One note - with the temp directory, i would recommend doing a clean up by some timespan of the files on the server side everytime a file is being added. Saves you the work of running some task periodically to check the folder and remove old files. ;)

Tombstoning the last visited page the user was on

I want to tombstone the last page that the user was on and to retrieve it when the user comes back to the app. All of the tombstoning examples on the Internet deal with saving some data or the state of the page that the user has edited somehow (i.e inputed text in the textbox). In my app I don't have anything for the user to modify/edit so I just want to save the last visited page that the user was on. I tried to use some online examples that used PhoneApplicationService.Current.State but with no success.
Thank you to anyone who would like to help me out resolving this issue!
To locally store persistent data (data that should remain even when the user closes the app), you can use Isolated Storage.
So, in your app's Deactivated event, you can write the page's name to Isolated Storage like this:
//You get the Isolated Storage for your app (other apps can't access it)
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
//if the file already exists, delete it (since we're going to write a new one)
if (isf.FileExists("lastpage.txt")) isf.DeleteFile("lastpage.txt");
using (var isoFileStream = new IsolatedStorageFileStream("lastpage.txt", FileMode.OpenOrCreate, isf))
{
//open a StreamWriter to write the file
using (var sw = new StreamWriter(isoFileStream))
{
//NavigationService.CurrentSource returns the current page
//we can write this to the file
sw.WriteLine((Application.Current.RootVisual as PhoneApplicationFrame).CurrentSource.ToString());
}
}
This will write the current page's name to the Isolated Storage. Then, in your OnNavigatedto method of your main page (the page that first opens normally) you can read the file name and navigate to it:
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication();
string lastpage = string.Empty;
if (isf.FileExists("lastpage.txt"))
{
using (var isoFileStream = new IsolatedStorageFileStream("lastpage.txt", FileMode.Open, isf))
{
//read the file using a StreamReader
using (var sr = new StreamReader(isoFileStream))
{
//get the uri we wrote and then convert it from a String to a Uri
lastpage = sr.ReadLine().Replace("file:///", "");
}
}
(Application.Current.RootVisual as PhoneApplicationFrame).Navigate(new Uri(lastpage, UriKind.Relative));
}
base.OnNavigatedTo(e);
}
This should read in the file you saved and then convert the string into an actual URI that you can pass to the NavigationService.
You can then delete the text file after it's been read so that it doesn't always keep jumping to that page.
In addition you can use this for getting the page name ,string PageName = (Application.Current.RootVisual as PhoneApplicationPage).Name; for getting the current page name
While I agree all the above options are possible, they are not really the correct way of doing something within a WP7.
It's better to construct a navigation page at the start of your app for properly controlling navigation, it also helps with managing back key events while using the app and prevents hiccups.
See Here for one example of implementing this:
Properly Exiting Silverlight-based WP7
With this done use the advice on storing values in isolated storage / application settings to then just store the "Current Page" state value (e.g. value of Page.GamePage) then the app navigation will direct you accordingly.
BUT beware when just storing the current page itself as you also need to save the correct state of any values or User entered data on that page when it tombstones as well, this the above advice should lead you in the right direction.
Hope this helps

Converting byte[] of a PDF file original source to MemoryStream for loading into PDF viewer? (component one)

I'm working with a ComponentOne (C1) silverlight PDF viewer control.
It has a "LoadDocument" method that accepts a "Stream".
I'm making an HTTP get call from my client app to get a PDF document.
This document, on the server side, has been streamed in through File.ReadAllBytes(), then converted to a base64 string using Convert.ToBase64String().
This string is sent across the wire back to my silverlight app where it's then reversely converted back into a byte array with Convert.FromBase64String(val).
Then I'm creating a MemoryStream with that byte array and passing "LoadDocument()" that memory stream.
The viewer is rendering nothing. It shows the toolbar and scrollbars, but the contents are blank and the save button is grayed out, suggesting that no document loaded.
I know for certain the file made it across because the byte array size on the client matches teh byte array pre-conversion on the server side.
Here's my code: (in the interest of time/space, i've truncated, removing validation, etc.)
SERVERSIDE
string sendingToClient = Convert.ToBase64String(File.ReadAllBytes(filePath))
CLIENTSIDE
byte[] image = null;
image = Convert.FromBase64String(stringFromServerCall);
MemoryStream stream = new MemoryStream(image);
docViewer.LoadDocument(stream);
edit As a potential workaround, I attempted to save the file into isolated storage with a ".pdf" extension. Then I use the IsolatedStorageFileStream to send to LoadDocument().
I've come to an actual error, it now says "PdfParserException was unhandled by user code: invalid file format (missing pdf header)"
Can anyone shed some light on this PDF header?
Here is an experiment I would conduct.
Add a button to your Xaml and on click use OpenFileDialog to get a FileInfo. From that FileInfo use its Open method to get a stream and pass that to docViewer.LoadDocument.
Now run it, click the button and select the same PDF document you are trying to send from the server.
If that succeeds you need to continue investigating your server streaming strategy. On the other hand if you still have the same problem, well it doesn't get more raw than that. Try other PDF files and start investigating the PDF component. Have you ever actually used it successfully, if so how does this current usage differ.
you should get the stream pointer back to 0 ,so this should do the trick
byte[] image = null;
image = Convert.FromBase64String(stringFromServerCall);
MemoryStream stream = new MemoryStream(image);
stream.Position = 0;
docViewer.LoadDocument(stream);

Parse csv or excel file in Silverlight

I've parsed these files in regular C# applications, but the IO methods for the files are different in Silverlight, and I can't seem to find the right methods. Searches haven't turned up any information I can use. For the real application I'll be receiving XML from the server, but for the prototype I just need to parse a file with some sample data in it.
You can save the Excel file as XML. An example can be found in this link
This way you can keep your import procedure the same and process the data as when you go live.
To access files from the user's machine you are required to use the OpenFileDialog and SaveFileDialog. Without elevated trust (requires out of browser apps) you will not be able to know anything more than the filename the user selected for input/saving; you will have no idea what the path is to this file. This function can only be called as the result of a user taking an action such as clicking a button; otherwise it will fail because Silverlight does not want malicious code prompting a user with annoying dialogs automatically.
To do this you would do something as follows:
var openFile = new OpenFileDialog();
if ( open.ShowDialog() == true ) // Sadly this is a nullable bool so this is necessary
{
using( Stream myStream = openFile.File.OpenRead() )
using ( var reader = new StreamReader( myStream ))
{
...
}
}

Viewing an XPS document with malformed URI's in WPF

I'm trying to use DocumentViewer (or, more specifically, DocumentViewer's DocumentPageView) to load a presentation that was saved from Powerpoint as XPS.
However, the author of the slides was being clever and entered one of his URLs as a pseudo-regex (e.g. http://[blog|www]mywebsite.com). The built in XPS Viewer is able to load the document without a problem. However, DocumentViewer throws an exception because it tries to validate the URI:
Failed to create a 'NavigateUri' from the text 'http://[blog|www]mywebsite.com'
I could of course go into the slide and fix the URI so that the document displays. However, since I can't control the documents that will be used with my application, I'd prefer to find a way to display the document in spite of invalid URI's (like XPS Viewer).
Any thoughts?
The DocumentViewer is trying to create a Uri instance from the provided URL. If the URL isn't valid, the operation will fail.
You can prevent this from happening by performing validation on the URLs provided to you by the author.
(writing this without testing, so there may be some syntax errors)
public static bool IsValidUrl(this string url)
{
if(string.IsNullOrWhitespace(url) return false;
try
{
var uri = new Url(url);
return true;
}
catch
{
// if you were implementing IDataErrorInfo rather than using a
// lousy extension method you would catch the exception
// here and display it to the user
return false;
}
}

Resources