Download a file through the WebBrowser control - winforms

I have a WebBrowser control on a form, but for the most part it remains hidden from the user. It is there to handle a series of login and other tasks. I have to use this control because there is a ton of Javascript that handles the login. (i.e., I can't just switch to a WebClient object.)
After hopping around a bit, we end up wanting to download a PDF file. But instead of downloading, the file is displayed within the webBrowser control, which the user can not see.
How can I download the PDF instead of having it load in the browser control?

Add a SaveFileDialog control to your form, then add the following code on your WebBrowser's Navigating event:
private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
if (e.Url.Segments[e.Url.Segments.Length - 1].EndsWith(".pdf"))
{
e.Cancel = true;
string filepath = null;
saveFileDialog1.FileName = e.Url.Segments[e.Url.Segments.Length - 1];
if (saveFileDialog1.ShowDialog() == DialogResult.OK)
{
filepath = saveFileDialog1.FileName;
WebClient client = new WebClient();
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
client.DownloadFileAsync(e.Url, filepath);
}
}
}
//Callback function
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
MessageBox.Show("File downloaded");
}
Source: http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/d338a2c8-96df-4cb0-b8be-c5fbdd7c9202

The solution I ended up using:
I did everything else as-needed to get the URL where it needed to go. Knowing that all of the login information, required settings, viewstates, etc. were stored in the cookies, I was finally able to grab the file using a hybrid of the web control to navigate then the WebClient object to actually snag the file bytes.
public byte[] GetPDF(string keyValue)
{
DoLogin();
// Ask the source to generate the PDF. The PDF doesn't
// exist on the server until you have visited this page
// at least ONCE. The PDF exists for five minutes after
// the visit, so you have to snag it pretty quick.
LoadUrl(string.Format(
"https://www.theMagicSource.com/getimage.do?&key={0}&imageoutputformat=PDF",
keyValue));
// Now that we're logged in (not shown here), and
// (hopefully) at the right location, snag the cookies.
// We can use them to download the PDF directly.
string cookies = GetCookies();
byte[] fileBytes = null;
try
{
// We are fully logged in, and by now, the PDF should
// be generated. GO GET IT!
WebClient wc = new WebClient();
wc.Headers.Add("Cookie: " + cookies);
string tmpFile = Path.GetTempFileName();
wc.DownloadFile(string.Format(
"https://www.theMagicSource.com/document?id={0}_final.PDF",
keyValue), tmpFile);
fileBytes = File.ReadAllBytes(tmpFile);
File.Delete(tmpFile);
}
catch (Exception ex)
{
// If we can't get the PDF here, then just ignore the error and return null.
throw new WebScrapePDFException(
"Could not find the specified file.", ex);
}
return fileBytes;
}
private void LoadUrl(string url)
{
InternalBrowser.Navigate(url);
// Let the browser control do what it needs to do to start
// processing the page.
Thread.Sleep(100);
// If EITHER we can't continue OR
// the web browser has not been idle for 10 consecutive seconds yet,
// then wait some more.
// ...
// ... Some stuff here to make sure the page is fully loaded and ready.
// ... Removed to reduce complexity, but you get the idea.
// ...
}
private string GetCookies()
{
if (InternalBrowser.InvokeRequired)
{
return (string)InternalBrowser.Invoke(new Func<string>(() => GetCookies()));
}
else
{
return InternalBrowser.Document.Cookie;
}
}

bool documentCompleted = false;
string getInnerText(string url)
{
documentCompleted = false;
web.Navigate(url);
while (!documentCompleted)
Application.DoEvents();
return web.Document.Body.InnerText;
}
private void web_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
documentCompleted = true;
}

Related

WPF How to create, save and load multiple setting files

In my WPF application I have settings accessed by Properties.Settings.Default.Something.
In these user settings I'm saving different textbox, radiobutton, checkbox values.
I need to have sets of these settings depending on one combobox choice, and saved it. For example, user picks "1" in combobox, sets text in textbox, picks 2, sets text in textbox again. After reopening application I want those textbox values saved. Content of combobox options is generated dynamically.
I know those settings are saved in config file located in Users/appdata/... but I have no clue how and if its even possible to make multiple files like this to be manually saved and loaded on runtime.
Serialize them as xml-file. Here is an generic example how to do this.
Please check out DataContract here
C#
private static T ReadXmlFile<T>(string path) where T : class
{
T result = null;
if (File.Exists(path))
{
try
{
using (XmlReader reader = XmlReader.Create(path))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
result = (T)serializer.ReadObject(reader);
}
}
catch (Exception ex)
{
throw ex; // or what ever
}
}
return result;
}
private static void WriteXmlFile<T>(string path, T content2write) where T : class
{
if (!Directory.Exists(Path.GetDirectoryName(path)))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
using (XmlWriter writer = XmlWriter.Create(path,
new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
Encoding = Encoding.UTF8,
CloseOutput = true
}))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(writer, content2write);
}
}
Maybe save them in your own AppData-folder with Environment.SpecialFolder.LocalApplicationData ;-) and go like this
private static readonly string MyPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), #"MyApp\AppDescription");

ShowInputAsync is not showed sometimes

I realized an interface in WPF Page and I want to call ShowInputAsync in the MainWindow, which is where the Page is. In order to do that, I raise event in Page and block function with AutoResetEvent:
public AutoResetEvent OnMessageReceived;
public void MessageReceived(object sender, PageReturnMessageEventArgs e)
{
try
{
if (e.ToString() == "Cancel" || string.IsNullOrEmpty(e.ToString()))
{
throw new Exception("Exception of parsing of hours");
}
}
catch (Exception ex)
{
// log it
}
this.OnMessageReceived.Set();
}
private void Page_OnNextPageClick(object sender, EventArgs e)
{
// ...
MessageShow("Additional information", "How much time will this SuperProcess take?", "HOURS");
OnMessageReceived.WaitOne();
OnMessageReceived.Reset();
// ...
}
If I try to show ShowInputAsync from any function in MainWindow it works:
MetroDialogSettings s = new MetroDialogSettings();
s.AffirmativeButtonText = #"Create";
s.NegativeButtonText = #"Cancel";
s.AnimateShow = true;
var result = await this.ShowInputAsync("Test", "TestMessage", s);
if (result == null)
{ return; }
If I try to show this dialog from event handler, it doesn't work. After line
var result = await this.ShowInputAsync("Test", "TestMessage", s);
code returns to Page without any exception, executes line
OnMessageReceived.WaitOne();
and shows the window without any dialog, all Controls are showing and enabled, but I can't press them.
I also tried to put the dialog in a separate function, and call it with/without await, nothing changes.
To use AutoResetEvent after calling the Dialog to block further execution of code, also didn't help.
private AutoResetEvent OnMessageReturning;
var result = await this.ShowInputAsync(e.Title, e.Message, s);
OnMessageReturning.WaitOne();
Also tried to call the separate function in this way without result:
CancellationToken token;
TaskScheduler uiSched = TaskScheduler.FromCurrentSynchronizationContext();
await Task.Factory.StartNew(SeparateFunction, token, TaskCreationOptions.None, uiSched);
How can I call ShowInputAsync correctly in event handler? Or how can I call ShowInputAsync from Page in MainWindow?
P.S. There is TabControl in MainWindow, Page is placed in Frame of one TabItem
Using:
MahApps.Metro v1.4.1 (NuGet package)
Windows OS 7
Visual Studio Express 2015
.NET Framework 4.5
UPD: Easy sample is https://github.com/awg21/MahAppsShowInputAsyncFromPage
I found a solution here I'm using:
TryFindParent<> is an extension method defined in MahApps.Metro.Controls.TreeHelper, and ShowMessageAsync<> is defined in MahApps.Metro.Controls.Dialogs.DialogManager

Windows Phone - MediaElement not working anymore after navigation

I have a problem with the MediaElement in my windows phone (8) application. It's working fine until I navigate to some other page and come back on the page containing the MediaElement.
I have the following code to play a stream coming from Bing Translator APIs:
private void TranslationService_SpeakComplete(object sender, SpeakCompleteEventArgs e)
{
var stream = e.Stream;
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
try
{
MediaElement.Stop();
MediaElement.Source = null;
string filename = "FlipNLearnItAudio";
using (var isf = IsolatedStorageFile.GetUserStoreForApplication())
{
bool fileExists = isf.FileExists(filename);
if (fileExists)
{
isf.DeleteFile(filename);
}
var isfs = isf.CreateFile(filename);
using (isfs)
{
Helpers.SaveFile(stream, isfs);
isfs.Position = 0;
MediaElement.AutoPlay = true;
MediaElement.SetSource(isfs);
}
}
}
catch (Exception ex)
{
//TODO: log exception
}
});
}
The biggest problem is that there's no exception.... the "MediaElement.SetSource(isfs);" is called normally without any exception!
Do you have any idea of what can happen?
Thanks for any help!
Bastien
Ok, I find a solution to my problem. I put the MediaElement in the App.xaml resources and now it's working fine...
But it doesn't explain why it was not working with the MediaElement in the MainPage.xaml.
If somebody has the answer, I would be very interested to hear it ;-).

ChannelFactory method call increse memory

I have an winform application which consumes windows service, i user ChannelFactory
to connect to service, problem is when i call service method using channel the memory usage increase and after
method execute memory not go down(even after form close), i call GC.Collect but no change
channel Create class
public class Channel1
{
List<ChannelFactory> chanelList = new List<ChannelFactory>();
ISales salesObj;
public ISales Sales
{
get
{
if (salesObj == null)
{
ChannelFactory<ISales> saleschannel = new ChannelFactory<ISales>("SalesEndPoint");
chanelList.Add(saleschannel);
salesObj = saleschannel.CreateChannel();
}
return salesObj;
}
}
public void CloseAllChannels()
{
foreach (ChannelFactory chFac in chanelList)
{
chFac.Abort();
((IDisposable)chFac).Dispose();
}
salesObj = null;
}
}
base class
public class Base:Form
{
public Channel1 channelService = new Channel1();
public Channel1 CHANNEL
{
get
{
return channelService;
}
}
}
winform class
Form1:Base
private void btnView_Click(object sender, EventArgs e)
{
DataTable _dt = new DataTable();
try
{
gvAccounts.AutoGenerateColumns = false;
_dt = CHANNEL.Sales.GetDatatable();
gvAccounts.DataSource = _dt;
}
catch (Exception ex)
{
MessageBox.Show("Error Occurred while processing...\n" + ex.Message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
finally
{
CHANNEL.CloseAllChannels();
_dt.Dispose();
//GC.Collect();
}
}
You're on the right track in terms of using ChannelFactory<T>, but your implementation is a bit off.
ChannelFactory<T> creates a factory for generating channels of type T. This is a relatively expensive operation (as compared to just creating a channel from the existing factory), and is generally done once per life of the application (usually at start). You can then use that factory instance to create as many channels as your application needs.
Generally, once I've created the factory and cached it, when I need to make a call to the service I get a channel from the factory, make the call, and then close/abort the channel.
Using your posted code as a starting point, I would do something like this:
public class Channel1
{
ChannelFactory<ISales> salesChannel;
public ISales Sales
{
get
{
if (salesChannel == null)
{
salesChannel = new ChannelFactory<ISales>("SalesEndPoint");
}
return salesChannel.CreateChannel();
}
}
}
Note that I've replaced the salesObj with salesChannel (the factory). This will create the factory the first time it's called, and create a new channel from the factory every time.
Unless you have a particular requirement to do so, I wouldn't keep track of the different channels, especially if follow the open/do method/close approach.
In your form, it'd look something like this:
private void btnView_Click(object sender, EventArgs e)
{
DataTable _dt = new DataTable();
try
{
gvAccounts.AutoGenerateColumns = false;
ISales client = CHANNEL.Sales
_dt = client.GetDatatable();
gvAccounts.DataSource = _dt;
((ICommunicationObject)client).Close();
}
catch (Exception ex)
{
((ICommunicationObject)client).Abort();
MessageBox.Show("Error Occurred while processing...\n" + ex.Message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
The code above gets a new ISales channel from the factory in CHANNEL, executes the call, and then closes the channel. If an exception happens, the channel is aborted in the catch block.
I would avoid using Dispose() out of the box on the channels, as the implementation in the framework is flawed and will throw an error if the channel is in a faulted state. If you really want to use Dispose() and force the garbage collection, you can - but you'll have to work around the WCF dispose issue. Google will give you a number of workarounds (google WCF Using for a start).

download file from absolute uri to stream to SaveFileDialog

I've gotten as far as putting a file into a stream from a url.
However puttin savefiledialog inside the event OpenReadCompleted gives an exception because the savefiledialog needs to be fired from an user iniated event.
Putting the savefiledialog NOT inside OpenReadCompleted gives an error because the bytes array is empty, not yet processed.
Is there another way to save a file to stream from a uri without using an event?
public void SaveAs()
{
WebClient webClient = new WebClient(); //Provides common methods for sending data to and receiving data from a resource identified by a URI.
webClient.OpenReadCompleted += (s, e) =>
{
Stream stream = e.Result; //put the data in a stream
MemoryStream ms = new MemoryStream();
stream.CopyTo(ms);
bytes = ms.ToArray();
}; //Occurs when an asynchronous resource-read operation is completed.
webClient.OpenReadAsync(new Uri("http://testurl/test.docx"), UriKind.Absolute); //Returns the data from a resource asynchronously, without blocking the calling thread.
try
{
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter = "All Files|*.*";
//Show the dialog
bool? dialogResult = dialog.ShowDialog();
if (dialogResult != true) return;
//Get the file stream
using (Stream fs = (Stream)dialog.OpenFile())
{
fs.Write(bytes, 0, bytes.Length);
fs.Close();
//File successfully saved
}
}
catch (Exception ex)
{
//inspect ex.Message
MessageBox.Show(ex.ToString());
}
}
The approach to take is to first open the SaveFileDialog as a result of some user interaction like a Button click. Having had the user determine where to save the download and the SaveDialog method has returned you keep that instance of SaveFileDialog on hand.
You then invoke the download and in the OpenReadCompleted you can use the the SaveFileDialog OpenFile method to get a stream to which you can pump the result.
public void SaveAs()
{
SaveFileDialog dialog = new SaveFileDialog();
dialog.Filter = "All Files|*.*";
bool? dialogResult = dialog.ShowDialog();
if (dialogResult != true) return;
WebClient webClient = new WebClient();
webClient.OpenReadCompleted += (s, e) =>
{
try
{
using (Stream fs = (Stream)dialog.OpenFile())
{
e.Result.CopyTo(fs);
fs.Flush();
fs.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
};
webClient.OpenReadAsync(new Uri("http://testurl/test.docx"), UriKind.Absolute);
}
You'll note that not only is the code cleaner and simpler but if the user ends up cancelling the SaveFileDialog you haven't wasted their time or bandwidth downloading a file.
i found simple way to download file from silverlight application.
use HyperLinkButton control.
you can specify target also using "TargetName" propery.

Resources