HTML5 audio seek is not working properly. Throws Response Content-Length mismatch Exception - html

I'm trying to stream audio file to Angular application where is html5 audio element and src set to my api end point (example. /audio/234). My backend is implemented with .NET Core 2.0. I have implemented already this kind of streaming: .NET Core| MVC pass audio file to html5 player. Enable seeking
Seek works if I don't seek to end of file immediately when audio starts playing. I use audio element's autoplay attribute to start playing immediately audio element has enough data. So in my situation audio element has not yet all the data when I seek so it make new GET to my API. In that situation in my backend log there is this Exception:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
[1] Connection id "0HL9V370HAF39", Request id "0HL9V370HAF39:00000001": An unhandled exception was thrown by the application.
[1] System.InvalidOperationException: Response Content-Length mismatch: too few bytes written (0 of 6126919).
Here is my audio controller GET method.
byte[] audioArray = new byte[0];
//Here I load audio file from cloud
long fSize = audioArray.Length;
long startbyte = 0;
long endbyte = fSize - 1;
int statusCode = 200;
var rangeRequest = Request.Headers["Range"].ToString();
_logger.LogWarning(rangeRequest);
if (rangeRequest != "")
{
string[] range = Request.Headers["Range"].ToString().Split(new char[] { '=', '-' });
startbyte = Convert.ToInt64(range[1]);
if (range.Length > 2 && range[2] != "") endbyte = Convert.ToInt64(range[2]);
if (startbyte != 0 || endbyte != fSize - 1 || range.Length > 2 && range[2] == "")
{ statusCode = 206; }
}
_logger.LogWarning(startbyte.ToString());
long desSize = endbyte - startbyte + 1;
_logger.LogWarning(desSize.ToString());
_logger.LogWarning(fSize.ToString());
Response.StatusCode = statusCode;
Response.ContentType = "audio/mp3";
Response.Headers.Add("Content-Accept", Response.ContentType);
Response.Headers.Add("Content-Length", desSize.ToString());
Response.Headers.Add("Content-Range", string.Format("bytes {0}-{1}/{2}", startbyte, endbyte, fSize));
Response.Headers.Add("Accept-Ranges", "bytes");
Response.Headers.Remove("Cache-Control");
var stream = new MemoryStream(audioArray, (int)startbyte, (int)desSize);
return new FileStreamResult(stream, Response.ContentType)
{
FileDownloadName = track.Name
};
Am I missing some Header or what?
I didn't get this exception with .NET Core 1.1 but I'm not sure is it just coincident and/or bad testing. But if anybody has information is there something changed in .NET Core related to streaming I will appreciate that info.

Now when I research more I found this: https://learn.microsoft.com/en-us/aspnet/core/aspnetcore-2.0 look Enhanced HTTP header support- heading. It says this
If an application visitor requests content with a Range Request header, ASP.NET will recognize that and handle that header. If the requested content can be partially delivered, ASP.NET will appropriately skip and return just the requested set of bytes. You do not need to write any special handlers into your methods to adapt or handle this feature; it is automatically handled for you.
So all I need is some clean up when I move to .NET Core 1.1 to 2.0 because there is already handler for those headers.
byte[] audioArray = new byte[0];
//Here I get my MP3 file from cloud
var stream = new MemoryStream(audioArray);
return new FileStreamResult(stream, "audio/mp3")
{
FileDownloadName = track.Name
};

Problem was in Headers. I don't know exactly which header was incorrect or was my stream initialization incorrect but now It's working. I used this https://stackoverflow.com/a/35920244/8081009 . Only change I make this was renamed it as AudioStreamResult. And then I used it like this:
Response.ContentType = "audio/mp3";
Response.Headers.Add("Content-Accept", Response.ContentType);
Response.Headers.Remove("Cache-Control");
var stream = new MemoryStream(audioArray);
return new AudioStreamResult(stream, Response.ContentType)
{
FileDownloadName = track.Name
};
Notice that I pass full stream to AudioStreamResult.
var stream = new MemoryStream(audioArray);

Related

search embeded webpage source in vb.net

I wrote a program that includes an embedded web browser that loads a website which have a changing part (the part changes about 2 times a week and it have no regular timing pattern) that I want to search for a particular part in the opened webpage source code after refreshing the webpage in a specified time interval.
I found many things similar to my question but this is what I want and those questions doesn't have:
search embedded webpage source (they searching the webpage without embedding, and I had to embed it because I had to login before I see the particular page)
so this is the procedure I'm trying to do:
1- open a website in embedded web browser
2- after user logged in, with a press of button in program, it hides the embedded
web browser and start to refresh the page in a time interval (like
every minute) and search if the particular code changed in the source of
that opened webpage
any other/better Ideas appreciated
thanks
Many years ago I wrote an app to reintegrate forum posts from several pages into one and I struggled with the login issue too and thought it was only possible using an embedded browser. As it turns out, it's possible to use System.Net in .NET to handle web pages that need a login as you can pull the cookies out and keep them on hand. I would suggest you do that and move away from the embedded browser.
Unfortunately I wrote the code in C# originally, but as it's .NET and is mostly classes-based, it shouldn't be too difficult to port over.
The Basic Principle
Find out what information is included in the POST when you login, which you can do in Chrome with developer mode on (F12). Convert that to a byteArray, POST it to the page, store the cookies and make another call with the cookie data later on. You will need a class variable to hold the cookies.
Code:
private void Login()
{
byte[] byteArray = Encoding.UTF8.GetBytes("username=" + username + "&password=" + password + "&autologin=on&login=Log+in"); // Found by investigation
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("yourURL");
request.AllowAutoRedirect = false;
request.CookieContainer = new CookieContainer();
request.Method = "POST";
request.ContentLength = byteArray.Length;
request.ContentType = "application/x-www-form-urlencoded";
Stream dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
WebResponse response = request.GetResponse();
if (((HttpWebResponse)response).StatusCode == HttpStatusCode.Found)
{
// Well done, your login has been accepted
loginDone = true;
cookies = request.CookieContainer;
}
else
{
// If at first you don't succeed...
}
response.Close();
}
private string GetResponseHTML(string url)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.AllowAutoRedirect = false;
// Add cookies from Login()
request.CookieContainer = cookies;
request.ContentType = "application/x-www-form-urlencoded";
WebResponse response = request.GetResponse();
string sResponse = "";
StreamReader reader = null;
if (((HttpWebResponse)response).StatusCode == HttpStatusCode.OK)
{
reader = new StreamReader(response.GetResponseStream());
sResponse = reader.ReadToEnd();
reader.Close();
}
response.Close();
return sResponse;
}
Hope that helps.
I had to change to C# and I found what I was looking for:
string webPageSource = webBrowser1.DocumentText;
That gave me the source of web page opened in webBrowser1 control.

Reading a project text file windows phone 8

I'm having trouble accessing a text file that is packaged with my Windows Phone 8 app.
Here is the code:
var ResrouceStream = Application.GetResourceStream(new Uri("Data-Test.docx", UriKind.Relative));
if (ResrouceStream != null)
{
Stream myFileStream = ResrouceStream.Stream;
if (myFileStream.CanRead)
{
// logiic here
retrun "Hi";
}
}
else
{
return "hello";
}
Seems simple but the app always returns "hello". i have placed the file in root and also in assets, changed it to content - copy and do not copy, resource copy and do not copy but always it returns "hello".
Spent several hours on this and all solutions I can find show the solution or very similar above!
What am I doing wrong?
EDIT: Returns "hello" when I deploy to phone or emulator.
also tried "/Data-Test...", #"\Data-Text..., #/"Data-Test...!
UPDATE 1:
string aReturn = "";
var asm = Assembly.GetExecutingAssembly();
//Use this to verify the namespacing of the "Embedded Resource".
//asm.GetManifestResourceNames()
// .ToList()
// .ForEach(name => Debug.WriteLine(name));
var ResourceStream = asm.GetManifestResourceStream("ContosoSocial.Assets.QuizQuestions.QuizQuestions-Test1.docx");
if (ResourceStream != null) // <--CHECKED AND DOES NOT EQUAL NULL
{
Stream myFileStream = ResourceStream;
if (myFileStream.CanRead) // <-- CHEACKED AND CAN READ
{
StreamReader myStreamReader = new StreamReader(myFileStream);
LOGIC & EXCEPTION HERE...?
string myLine = myStreamReader.ReadLine();
}
else
{
aReturn = "myFileStream.CanRead = true";
}
}
else
{
aReturn = "stream equals null";
}
Debug.WriteLine(aReturn);
}
The assignment of myFileStream to a StreamReader object is throwing the exception null pointer. I thought I would wrap myFileStream to a StreamReader so I can read a line at a time..? This is my first c# project and I'm unfamiliar with it's syntax and classes.
UPDATE 2:
OK I added...
Debug.WriteLine(aReturn);
...following...
string myLine = myStreamReader.ReadLine();
...and noticed it was retrieving only the 2 characters 'PK' !
So saved the .docx file as .txt and reinserted adn changed build copy to embedded - do not copy...Happy days it now pulls off the first line in the file.
Thanks to OmegaMan for your help with this one :-)
Change file type in the project to Embedded Resource
Extract the resource by working the namespace to its location. Here is an example code where I pull in an XSD:
Code:
var asm = Assembly.GetExecutingAssembly();
// Use this to verify the namespacing of the "Embedded Resource".
// asm.GetManifestResourceNames()
// .ToList()
// .ForEach(name => Debug.WriteLine(name));
var f1 = asm.GetManifestResourceStream("UnitTests.Resources.NexusResponse.xsd");
Note this is not tested on WP8, but GetExecutingAssembly is stated to work within .Net. If you get the namespace wrong, uncomment out the code and display or debug to determine the resources and their namespace.

How to get MP4 using JavaCV for live video streaming

I am trying to make an application that gets the streaming video data using JavaCV and send it to Web socket server. Then, the Web socket server distributes the video data to connected client(s).
1. Application gets live streaming data(MP4) from my PC's webcam using JavaCV.
2. Application keeps sending it to web socket server.
3. The server gets it as binary and send it to connected client.
4. Web browser connects to the server and Javascript running on browser shows the live video after receiving it from server.
I am new to JavaCV and OpenCV. The snippet below works for drawing video using com.googlecode.javacv.CanvasFrame with no problem. However, I am not sure how to grab MP4 data as live streaming data.
try {
FrameGrabber grabber = FrameGrabber.createDefault(0);
grabber.setFormat("mp4");;
grabber.setFrameRate(30);
grabber.setImageWidth(640);
grabber.setImageHeight(480);
grabber.start();
double frameRate = grabber.getFrameRate();
long wait = (long) (1000 / (frameRate == 0 ? 10 : frameRate));
ByteBuffer buf = null;
while (true) {
Thread.sleep(wait);
IplImage image = grabber.grab();
if(image != null) {
buf = image.getByteBuffer();
send(buf); // Send video data using web socket.
}
}
} catch (FrameGrabber.Exception ex) {
Logger.getLogger(WSockClient.class.getName()).log(Level.SEVERE, null, ex);
}
The below are the code for web socket server and Javascript/HTML5 client.
The both code works fine if the application reads MP4 file from local disk, and send it to the server. But the data using JavaCV like above seems to be invalid for showing on browser.
The server just receives the data and distribute it to the clients. I believe there seems to have no problem. Here is the code.
#OnMessage
public void binaryMessage(ByteBuffer buf, Session client) throws IOException, EncodeException {
for (Session otherSession : peers) {
if (!otherSession.equals(client)) {
otherSession.getAsyncRemote().sendBinary(buf, new StreamHandler());
}
}
}
Here is the Javascript/HTML5.
<body>
<video controls width="640" height="480" autoplay></video><br>
<canvas id="canvas1" width="640" height="480"></canvas><br>
</body>
<script>
var ws;
var protocol = 'ws';
var host = "localhost:8080";
var url = protocol + "://" + host + "/live/stream";
ws = new WebSocket(url);
ws.binaryType = 'arraybuffer';
ws.addEventListener("open",onOpenWebSocket,false);
ws.addEventListener("close",onCloseWebSocket,false);
ws.addEventListener("message",onMessageWebSocket,false);
window.addEventListener("unload",onUnload,false);
...
function onMessageWebSocket(event){
var blob = new Blob([event.data], {type: 'video/mp4'});
var rb = blob.slice(0, blob.size, 'video/mp4');
video.src = window.URL.createObjectURL(rb);
}
...
</script>
I think I need to obtain MP4 data as stream using JavaCV, but don't know how to do that. Please help.
Any comment, suggestions would be appreciated. Thank you.

Writing a full website to socket with microncontroller

I'm using a web server to control devices in the house with a microcontroller running .netMF (netduino plus 2). The code below writes a simple html page to a device that connects to the microcontroller over the internet.
while (true)
{
Socket clientSocket = listenerSocket.Accept();
bool dataReady = clientSocket.Poll(5000000, SelectMode.SelectRead);
if (dataReady && clientSocket.Available > 0)
{
byte[] buffer = new byte[clientSocket.Available];
int bytesRead = clientSocket.Receive(buffer);
string request =
new string(System.Text.Encoding.UTF8.GetChars(buffer));
if (request.IndexOf("ON") >= 0)
{
outD7.Write(true);
}
else if (request.IndexOf("OFF") >= 0)
{
outD7.Write(false);
}
string statusText = "Light is " + (outD7.Read() ? "ON" : "OFF") + ".";
string response = WebPage.startHTML(statusText, ip);
clientSocket.Send(System.Text.Encoding.UTF8.GetBytes(response));
}
clientSocket.Close();
}
public static string startHTML(string ledStatus, string ip)
{
string code = "<html><head><title>Netduino Home Automation</title></head><body> <div class=\"status\"><p>" + ledStatus + " </p></div> <div class=\"switch\"><p>On</p><p>Off</p></div></body></html>";
return code;
}
This works great, so I wrote a full jquery mobile website to use instead of the simple html. This website is stored on the SD card of the device and using the code below, should write the full website in place of the simple html above.
However, my problem is the netduino only writes the single HTML page to the browser, with none of the JS/CSS style files that are referenced in the HTML. How can I make sure the browser reads all of these files, as a full website?
The code I wrote to read the website from the SD is:
private static string getWebsite()
{
try
{
using (StreamReader reader = new StreamReader(#"\SD\index.html"))
{
text = reader.ReadToEnd();
}
}
catch (Exception e)
{
throw new Exception("Failed to read " + e.Message);
}
return text;
}
I replaced string code = " etc bit with
string code = getWebsite();
How can I make sure the browser reads all of these files, as a full website?
Isn't it already? Use an HTTP debugging tool like Fiddler. As I read from your code, your listenerSocket is supposed to listen on port 80. Your browser will first retrieve the results of the getWebsite call and parse the HTML.
Then it'll fire more requests, as it finds CSS and JS references in your HTML (none shown). These requests will, as far as we can see from your code, again receive the results of the getWebsite call.
You'll need to parse the incoming HTTP request to see what resource is being requested. It'll become a lot easier if the .NET implementation you run supports the HttpListener class (and it seems to).

Calling wkhtmltopdf to generate PDF from HTML

I'm attempting to create a PDF file from an HTML file. After looking around a little I've found: wkhtmltopdf to be perfect. I need to call this .exe from the ASP.NET server. I've attempted:
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.FileName = HttpContext.Current.Server.MapPath("wkhtmltopdf.exe");
p.StartInfo.Arguments = "TestPDF.htm TestPDF.pdf";
p.Start();
p.WaitForExit();
With no success of any files being created on the server. Can anyone give me a pointer in the right direction? I put the wkhtmltopdf.exe file at the top level directory of the site. Is there anywhere else it should be held?
Edit: If anyone has better solutions to dynamically create pdf files from html, please let me know.
Update:
My answer below, creates the pdf file on the disk. I then streamed that file to the users browser as a download. Consider using something like Hath's answer below to get wkhtml2pdf to output to a stream instead and then send that directly to the user - that will bypass lots of issues with file permissions etc.
My original answer:
Make sure you've specified an output path for the PDF that is writeable by the ASP.NET process of IIS running on your server (usually NETWORK_SERVICE I think).
Mine looks like this (and it works):
/// <summary>
/// Convert Html page at a given URL to a PDF file using open-source tool wkhtml2pdf
/// </summary>
/// <param name="Url"></param>
/// <param name="outputFilename"></param>
/// <returns></returns>
public static bool HtmlToPdf(string Url, string outputFilename)
{
// assemble destination PDF file name
string filename = ConfigurationManager.AppSettings["ExportFilePath"] + "\\" + outputFilename + ".pdf";
// get proj no for header
Project project = new Project(int.Parse(outputFilename));
var p = new System.Diagnostics.Process();
p.StartInfo.FileName = ConfigurationManager.AppSettings["HtmlToPdfExePath"];
string switches = "--print-media-type ";
switches += "--margin-top 4mm --margin-bottom 4mm --margin-right 0mm --margin-left 0mm ";
switches += "--page-size A4 ";
switches += "--no-background ";
switches += "--redirect-delay 100";
p.StartInfo.Arguments = switches + " " + Url + " " + filename;
p.StartInfo.UseShellExecute = false; // needs to be false in order to redirect output
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardInput = true; // redirect all 3, as it should be all 3 or none
p.StartInfo.WorkingDirectory = StripFilenameFromFullPath(p.StartInfo.FileName);
p.Start();
// read the output here...
string output = p.StandardOutput.ReadToEnd();
// ...then wait n milliseconds for exit (as after exit, it can't read the output)
p.WaitForExit(60000);
// read the exit code, close process
int returnCode = p.ExitCode;
p.Close();
// if 0 or 2, it worked (not sure about other values, I want a better way to confirm this)
return (returnCode == 0 || returnCode == 2);
}
I had the same problem when i tried using msmq with a windows service but it was very slow for some reason. (the process part).
This is what finally worked:
private void DoDownload()
{
var url = Request.Url.GetLeftPart(UriPartial.Authority) + "/CPCDownload.aspx?IsPDF=False?UserID=" + this.CurrentUser.UserID.ToString();
var file = WKHtmlToPdf(url);
if (file != null)
{
Response.ContentType = "Application/pdf";
Response.BinaryWrite(file);
Response.End();
}
}
public byte[] WKHtmlToPdf(string url)
{
var fileName = " - ";
var wkhtmlDir = "C:\\Program Files\\wkhtmltopdf\\";
var wkhtml = "C:\\Program Files\\wkhtmltopdf\\wkhtmltopdf.exe";
var p = new Process();
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.FileName = wkhtml;
p.StartInfo.WorkingDirectory = wkhtmlDir;
string switches = "";
switches += "--print-media-type ";
switches += "--margin-top 10mm --margin-bottom 10mm --margin-right 10mm --margin-left 10mm ";
switches += "--page-size Letter ";
p.StartInfo.Arguments = switches + " " + url + " " + fileName;
p.Start();
//read output
byte[] buffer = new byte[32768];
byte[] file;
using(var ms = new MemoryStream())
{
while(true)
{
int read = p.StandardOutput.BaseStream.Read(buffer, 0,buffer.Length);
if(read <=0)
{
break;
}
ms.Write(buffer, 0, read);
}
file = ms.ToArray();
}
// wait or exit
p.WaitForExit(60000);
// read the exit code, close process
int returnCode = p.ExitCode;
p.Close();
return returnCode == 0 ? file : null;
}
Thanks Graham Ambrose and everyone else.
OK, so this is an old question, but an excellent one. And since I did not find a good answer, I made my own :) Also, I've posted this super simple project to GitHub.
Here is some sample code:
var pdfData = HtmlToXConverter.ConvertToPdf("<h1>SOO COOL!</h1>");
Here are some key points:
No P/Invoke
No creating of a new process
No file-system (all in RAM)
Native .NET DLL with intellisense, etc.
Ability to generate a PDF or PNG (HtmlToXConverter.ConvertToPng)
Check out the C# wrapper library (using P/Invoke) for the wkhtmltopdf library: https://github.com/pruiz/WkHtmlToXSharp
There are many reason why this is generally a bad idea. How are you going to control the executables that get spawned off but end up living on in memory if there is a crash? What about denial-of-service attacks, or if something malicious gets into TestPDF.htm?
My understanding is that the ASP.NET user account will not have the rights to logon locally. It also needs to have the correct file permissions to access the executable and to write to the file system. You need to edit the local security policy and let the ASP.NET user account (maybe ASPNET) logon locally (it may be in the deny list by default). Then you need to edit the permissions on the NTFS filesystem for the other files. If you are in a shared hosting environment it may be impossible to apply the configuration you need.
The best way to use an external executable like this is to queue jobs from the ASP.NET code and have some sort of service monitor the queue. If you do this you will protect yourself from all sorts of bad things happening. The maintenance issues with changing the user account are not worth the effort in my opinion, and whilst setting up a service or scheduled job is a pain, its just a better design. The ASP.NET page should poll a result queue for the output and you can present the user with a wait page. This is acceptable in most cases.
You can tell wkhtmltopdf to send it's output to sout by specifying "-" as the output file.
You can then read the output from the process into the response stream and avoid the permissions issues with writing to the file system.
My take on this with 2018 stuff.
I am using async. I am streaming to and from wkhtmltopdf. I created a new StreamWriter because wkhtmltopdf is expecting utf-8 by default but it is set to something else when the process starts.
I didn't include a lot of arguments since those varies from user to user. You can add what you need using additionalArgs.
I removed p.WaitForExit(...) since I wasn't handling if it fails and it would hang anyway on await tStandardOutput. If timeout is needed, then you would have to call Wait(...) on the different tasks with a cancellationtoken or timeout and handle accordingly.
public async Task<byte[]> GeneratePdf(string html, string additionalArgs)
{
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = #"C:\Program Files\wkhtmltopdf\wkhtmltopdf.exe",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
Arguments = "-q -n " + additionalArgs + " - -";
};
using (var p = Process.Start(psi))
using (var pdfSream = new MemoryStream())
using (var utf8Writer = new StreamWriter(p.StandardInput.BaseStream,
Encoding.UTF8))
{
await utf8Writer.WriteAsync(html);
utf8Writer.Close();
var tStdOut = p.StandardOutput.BaseStream.CopyToAsync(pdfSream);
var tStdError = p.StandardError.ReadToEndAsync();
await tStandardOutput;
string errors = await tStandardError;
if (!string.IsNullOrEmpty(errors)) { /* deal/log with errors */ }
return pdfSream.ToArray();
}
}
Things I haven't included in there but could be useful if you have images, css or other stuff that wkhtmltopdf will have to load when rendering the html page:
you can pass the authentication cookie using --cookie
in the header of the html page, you can set the base tag with href pointing to the server and wkhtmltopdf will use that if need be
Thanks for the question / answer / all the comments above. I came upon this when I was writing my own C# wrapper for WKHTMLtoPDF and it answered a couple of the problems I had. I ended up writing about this in a blog post - which also contains my wrapper (you'll no doubt see the "inspiration" from the entries above seeping into my code...)
Making PDFs from HTML in C# using WKHTMLtoPDF
Thanks again guys!
The ASP .Net process probably doesn't have write access to the directory.
Try telling it to write to %TEMP%, and see if it works.
Also, make your ASP .Net page echo the process's stdout and stderr, and check for error messages.
Generally return code =0 is coming if the pdf file is created properly and correctly.If it's not created then the value is in -ve range.
using System;
using System.Diagnostics;
using System.Web;
public partial class pdftest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
private void fn_test()
{
try
{
string url = HttpContext.Current.Request.Url.AbsoluteUri;
Response.Write(url);
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName =
#"C:\PROGRA~1\WKHTML~1\wkhtmltopdf.exe";//"wkhtmltopdf.exe";
startInfo.Arguments = url + #" C:\test"
+ Guid.NewGuid().ToString() + ".pdf";
Process.Start(startInfo);
}
catch (Exception ex)
{
string xx = ex.Message.ToString();
Response.Write("<br>" + xx);
}
}
protected void btn_test_Click(object sender, EventArgs e)
{
fn_test();
}
}