I am trying to create an iOS app using PhoneGap that will allow a user to upload photos to a web server. Here is my code.
<!DOCTYPE html>
<html>
<head>
<title>Capture Photo</title>
<script type="text/javascript" charset="utf-8" src="cordova-1.9.0.js"></script>
<script type="text/javascript" charset="utf-8">
// Wait for PhoneGap to load
document.addEventListener("deviceready", onDeviceReady, false);
// PhoneGap is ready
function onDeviceReady() {
// Do cool things here...
}
function getImage() {
// Retrieve image file location from specified source
navigator.camera.getPicture(uploadPhoto, function(message) {
alert('get picture failed');
},{
quality: 50,
destinationType: navigator.camera.DestinationType.FILE_URI,
sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY
}
);
}
function uploadPhoto(imageURI) {
var options = new FileUploadOptions();
options.fileKey="recFile";
options.fileName=imageURI.substr(imageURI.lastIndexOf('/')+1);
options.mimeType="image/jpeg";
var params = new Object();
params.value1 = "test";
params.value2 = "param";
options.params = params;
var ft = new FileTransfer();
ft.upload(imageURI, "http://someWebSite.com/Testing/SaveImage.asmx/SaveImage", win, fail, options, true);
}
function win(r) {
console.log("Code = " + r.responseCode);
console.log("Response = " + r.response);
console.log("Sent = " + r.bytesSent);
}
function fail(error) {
alert("An error has occurred: Code = " + error.code);
alert("source = " + error.source);
alert("http_status = " + error.http_status);
console.log("upload error source " + error.source);
console.log("upload error target " + error.target);
}
</script>
</head>
<body>
<button onclick="getImage();">Upload a Photo</button>
</body>
Is anything wrong with my index.html file, or is the problem with the ASMX file?
Whenever I try to test this out on a 4th generation iPod Touch, I get the following error message:
2012-07-09 16:24:03.257 Test1[916:707] File Transfer Finished with response code 404
2012-07-09 16:24:03.260 Test1[916:707] FileTransferError {
code = 3;
"http_status" = 404;
source = "http://someWebSite.com/Testing/SaveImage.asmx/SaveImage";
target = "file://localhost/var/mobile/Applications/5DD01E68-02F7-410B-996A- 2D70BF1A61D3/tmp/cdv_photo_046.jpg";}
2012-07-09 16:24:07.137 Test1[916:707] ERROR: Plugin 'Debug Console' not found, or is not a CDVPlugin. Check your plugin mapping in Cordova.plist.
You can follow this example Upload image from android phonegap to a server using asmx
Or
A simple example
the js
<!DOCTYPE HTML>
<html>
<head>
<title>Cordova</title>
<link rel="stylesheet" href="style.css" media="screen" />
<script type="text/javascript" charset="utf-8" src="cordova-2.0.0.js"></script>
<script type="text/javascript" charset="utf-8" src="jquery-1.7.2.min.js"></script>
<script type="text/javascript" charset="utf-8">
var pictureSource; // picture source
var destinationType; // sets the format of returned value
document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
pictureSource = navigator.camera.PictureSourceType;
destinationType = navigator.camera.DestinationType;
}
</script>
</head>
<body>
<script type="text/javascript" charset="utf-8">
var myData = "";
$(document).ready(function() {
$("#getDataFromServer").click(function() {
var imageData = myData;
$.ajax({
type : "POST",
url : 'http://my.domain.name/saveImage.ashx',
data : {
image : imageData
},
beforeSend : function() {
$("#comment2").text("Start ajax " + imageData.length);
},
success : function(data) {
$("#comment2").text("Uploaded! " + data);
},
error : function(request, error) {
$("#comment2").text("Error! " + error);
}
});
});
})
function capturePhotoEdit(source) {
navigator.camera.getPicture(onPhotoDataSuccess, onFail, {
quality : 50,
destinationType : destinationType.DATA_URL,
sourceType : source
});
}
function onFail(message) {
alert('Failed because: ' + message);
}
function onPhotoDataSuccess(imageData) {
console.log(imageData);
var smallImage = document.getElementById('smallImage');
smallImage.style.display = 'block';
smallImage.src = "data:image/jpeg;base64," + imageData;
myData = imageData;
$("#comment").text(imageData.length);
}
</script>
<h1>Hello World</h1>
<p>
<a href="#" onclick="capturePhotoEdit(pictureSource.PHOTOLIBRARY);">get
image</a>
</p>
<p>
send image
</p>
<span id="comment2"></span>
<img style="display: none; width: 100px; height: 100px;"
id="smallImage" src="" />
<span id="imagename"></span>
<span id="comment"></span>
the asp.net handler saveImage.ashx
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Text;
namespace Recepies
{
/// <summary>
/// Summary description for saveImage
/// </summary>
public class saveImage : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
try
{
string filePath = "";
filePath = context.Server.MapPath(".");
string fileName = RandomString(10);
string myImage = context.Request.Form["image"];
if (myImage.Length > 0)
{
File.WriteAllBytes(filePath + "/upload/" + fileName + ".jpg", Convert.FromBase64String(myImage));
context.Response.ContentType = "text/plain";
context.Response.Write("File was saved - " + fileName + ".jpg");
}
else
{
context.Response.ContentType = "text/plain";
context.Response.Write("File was not saved");
}
}
catch (Exception ex)
{
context.Response.ContentType = "text/plain";
context.Response.Write(ex.Message);
}
}
private static Random random = new Random((int)DateTime.Now.Ticks);//thanks to McAden
private string RandomString(int size)
{
StringBuilder builder = new StringBuilder();
char ch;
for (int i = 0; i < size; i++)
{
ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
builder.Append(ch);
}
return builder.ToString();
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
I've been having the same issue (on iOS7). The solution that worked for me was to add the "saveToPhotoAlbum" parameter.
navigator.camera.getPicture(success, fail, {
quality: 80,
destinationType: Camera.DestinationType.FILE_URI,
sourceType: Camera.PictureSourceType.CAMERA,
saveToPhotoAlbum: true
});
Related
enter image description here
<!DOCTYPE html>
<html>
<body>
<h1>HTML DOM Events</h1>
<h2>The onclick Event</h2>
<button onclick="myFunction()">Confirm</button>
<script>
function myFunction() {
var message = "You have selected:";
message += "Stmt1 \r\n stmt2 \r\n stmt2";
message += "\r\n";
message += "stmt2stmt2stmt2stmt2stmt2stmt2stmt2stmt2stmt2stmt2stmt2\n";
message += "stmt2stmt2stmt2stmt2stmt2stmt2stmt2stmt2stmt2stmt2stmt2stmt2";
status = confirm(message);
}
</script>
</body>
</html>
Here after 3rd line of the message , it is hidden.Also window size is not increasing to contain full text. Same is working fine with IE.
using System.Windows;
namespace CefSharp.Wpf.Example.Handlers
{
public class JsDialogHandler : CefSharp.Handler.JsDialogHandler
{
protected override bool OnJSDialog(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage)
{
var b = (ChromiumWebBrowser)chromiumWebBrowser;
b.Dispatcher.InvokeAsync(() =>
{
if (dialogType == CefJsDialogType.Confirm)
{
var messageBoxResult = MessageBox.Show(messageText, $"A page at {originUrl} says:", MessageBoxButton.YesNo);
callback.Continue(messageBoxResult == MessageBoxResult.Yes);
}
else if(dialogType == CefJsDialogType.Alert)
{
var messageBoxResult = MessageBox.Show(messageText, $"A page at {originUrl} says:", MessageBoxButton.OK);
callback.Continue(messageBoxResult == MessageBoxResult.OK);
}
else if (dialogType == CefJsDialogType.Prompt)
{
var messageBoxResult = PromptDialog.Prompt(messageText, $"A page at {originUrl} says:", defaultPromptText);
callback.Continue(messageBoxResult.Item1, userInput: messageBoxResult.Item2);
}
});
return true;
}
}
}
//Assign your handler to the ChromiumWebBrowser instance
browser.JsDialogHandler = new CefSharp.Wpf.Example.Handlers.JsDialogHandler();
Full example added in commit https://github.com/cefsharp/CefSharp/commit/df8a6086a6bbb79280916c955015a1b58a421ddc
Im trying to catch an error when uploading a file that is too large.
At the server, the multipart section reader throws which I catch and return as an BadRequest (also tried InternalError) :
try
{
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
...
}
}
catch (Exception ex)
{
return StatusCode((int)HttpStatusCode.BadRequest, ProblemFactory.Shared.BadRequestProblem("Could not upload file", ex.Message));
}
To upload, I have the following (using RestSharp currently, but same result with HttpClient via HttpClientFactory):
var request = new RestRequest(REQ_UPLOADFILE, Method.POST, DataFormat.Json);
var token = await _agentTokenService.GetToken();
AddTokenHeader(request, token.AccessToken);
request.AddFile("file", path);
request.AddParameter("externalFileType", fileType, ParameterType.GetOrPost);
request.AddParameter("subType", subType, ParameterType.GetOrPost);
var resp = await _client.ExecuteTaskAsync(request);
if (resp.IsSuccessful)
{
return JsonConvert.DeserializeObject<ExternalFileResponse>(resp.Content);
}
else
{
string reason = "unknown error";
//switch(resp.StatusCode)
//{
// case HttpStatusCode.???
//}
throw new Exception($"Could not upload file: {reason}");
}
The response from the post is status code 0 with a message:
The stream does not support concurrent IO read or write operations
The upload is running in a task so I guess it's something to do with that, but there is only a single download running and if the file is smaller it works without a problem.
I can only think that something in the response handling is fracking with this somehow.
Does anyone have a clue??
Thanks and Merry Christmas :)
PS: Im using Kestrel only - no IIS or nginx - with options thus:
.UseKestrel(options =>
{
options.AddServerHeader = false;
options.Limits.MaxRequestBodySize = 100 * 1024 * 1024; // 100 MB
})
UPDATE
I think I understand this better now.
The server is terminating the connection when reading the bytes form the stream.
The client, writing asyncly, continues for a little bit, but I then try to read the response - hence the error message.
UPDATE
HttpClient does actually respond a little differently - I get my badrequest and the message is that the stream was closed.
here is a simple way to loading large file use ASP.NET.
(1)downloand webupload from https://github.com/fex-team/webuploader
(2) webupload can upload large file to chunk file,crate a webform
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script src="../Javascript/jquery-1.9.1.min.js"></script>
<link href="../javascript/webuploader/webuploader.css" rel="stylesheet" />
<script src="../javascript/webuploader/webuploader.js"></script>
</head>
<body >
<form>
<br /><br />
<div id="uploader" class="wu-example">
<div class="btns">
<div id="picker" style="display:inline-block">Select a File</div>
<input id="ctlBtn" type="button" value="Upload" class="btn btn-primary" style="border-radius:0px; position:relative; top:-13px"/>
</div>
<div id="thelist" class="uploader-list"></div>
</div>
<script>
_extensions = '3gp,mp4,rmvb,mov,avi,m4v';
_mimeTypes = 'video/*,audio/*,application/*';
var GUID = WebUploader.Base.guid();//一个GUID
// alert(GUID);
uploader = WebUploader.create({
auto: false,
// swf path
swf: '../javascript/webuploader/Uploader.swf',
//server receive data
server: '_uploadVideo.aspx',
pick: {
id: '#picker',
label: 'Select a File',
innerHTML: 'Select a File',
multiple: false
},
fileNumLimit: 1,
fileSingleSizeLimit: 1024 * 1024 * 120,
accept: {
title: 'large file',
extensions: _extensions,
mimeTypes: _mimeTypes, // eg. image/*,
},
chunked: true,//split large into small file
chunkSize: 1024 * 1024 * 2, //every small file size,this is 2M
formData: {
guid: GUID,
types:"upload"
}
});
uploader.on('fileQueued', function (file) {
$("#thelist").append('<div id="' + file.id + '" class="item">' +
'<b class="info">' + file.name + '</b> ' +
'<p class="state">wait...</p>' + '</div>');
});
uploader.on('uploadSuccess', function (file, response) {
//merge small into a large file
$('#' + file.id).find('p.state').html('<font color=green>upload success</font>');
$.post('_uploadVideo.aspx', { guid: GUID, fileName: file.name, types: "merge" ,r:Math.random(),itemid:<%=Request.QueryString["itemid"]%> },
function (data) {
if (data == 1) {
alert("success");
}
else {
alert("fail");
}
});
});
uploader.on("error", function (type, handler) {
if (type == "Q_TYPE_DENIED") {
alert("format error");
} else if (type == "F_EXCEED_SIZE") {
alert("size too large");
}
});
uploader.on('uploadProgress', function (file, percentage) {
var $li = $('#' + file.id),
$percent = $li.find('.progress .progress-bar');
// stop re-upload
if (!$percent.length) {
$percent = $('<div class="progress progress-striped active">' +
'<div class="progress-bar" role="progressbar" style="width: 0%">' +
'</div>' +
'</div>').appendTo($li).find('.progress-bar');
}
$li.find('p.state').text('uploading');
$percent.css('width', percentage * 100 + '%');
});
$("#ctlBtn").click(
function () {
uploader.upload();
}
);
</script>
</form>
</body>
</html>
(3)create a asp.net webform page _uploadVideo.aspx
delete all content,the aspx should only has 1 line
<%# Page Language="C#" AutoEventWireup="true" CodeFile="_uploadVideo.aspx.cs" Inherits="Gallery.Gallery._uploadVideo" %>
(4)in code _uploadVideo.aspx.cs wirte code below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
using System.Data.SqlClient;
namespace Gallery.Gallery
{
public partial class _uploadVideo : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request["types"] == "upload")
{
if (Request.Form.AllKeys.Any(m => m == "chunk"))
{
int year = DateTime.Now.Year;
//取得chunk和chunks
int chunk = Convert.ToInt32(Request["chunk"]);
int chunks = Convert.ToInt32(Request["chunks"]);
string folder = Server.MapPath("../uploads/video/"+year+"/" + Request["guid"] + "/");
string path = folder + chunk;
//建立临时传输文件夹 create teamplate folder
if (!Directory.Exists(Path.GetDirectoryName(folder)))
{
Directory.CreateDirectory(folder);
}
FileStream addFile = new FileStream(path, FileMode.Append, FileAccess.Write);
BinaryWriter AddWriter = new BinaryWriter(addFile);
//获得上传的分片数据流 get chunk
var file = Request.Files[0];
Stream stream = file.InputStream;
BinaryReader TempReader = new BinaryReader(stream);
//将上传的分片追加到临时文件末尾
AddWriter.Write(TempReader.ReadBytes((int)stream.Length));
//关闭BinaryReader文件阅读器
TempReader.Close();
stream.Close();
AddWriter.Close();
addFile.Close();
TempReader.Dispose();
stream.Dispose();
AddWriter.Dispose();
addFile.Dispose();
string f_ext = Path.GetExtension(file.FileName);
string _result = "{\"chunked\" :\"true\",\"hasError\" :\"false\",\"f_ext\" :\"" + f_ext + "\" }";
System.Web.HttpContext.Current.Response.Write(_result);
}
}
if (Request["types"] == "merge")
{
try
{
int year = DateTime.Now.Year;
var guid = Request["guid"];//GUID
var uploadDir = Server.MapPath("../uploads/video/" + year+"/");//Upload 文件夹
//建立临时传输文件夹
if (!Directory.Exists(Path.GetDirectoryName(uploadDir)))
{
Directory.CreateDirectory(uploadDir);
}
var dir = Path.Combine(uploadDir, guid);//临时文件夹
var ext = Path.GetExtension(Request["fileName"]);
var files = Directory.GetFiles(dir);//获得下面的所有文件
var name = Guid.NewGuid().ToString("N") + ext;
var finalPath = Path.Combine(uploadDir, name);//最终的文件名
var fs = new FileStream(finalPath, FileMode.Create);
foreach (var part in files.OrderBy(x => x.Length).ThenBy(x => x))//排一下序,保证从0-N Write
{
var bytes = System.IO.File.ReadAllBytes(part);
fs.Write(bytes, 0, bytes.Length);
bytes = null;
System.IO.File.Delete(part);//删除分块
}
fs.Flush();
fs.Close();
Directory.Delete(dir);//删除文件夹
//INSERT INTO DB finalPath
SqlParameter[] p = {
new SqlParameter("#videopath","../uploads/video/" + year+"/"+name)
};
string sql = #"update portal_photoes set videopath=#videopath where id=" + int.Parse(Request["itemid"]);
//you can exe SQL to into database
System.Web.HttpContext.Current.Response.Write("1");
}
catch (Exception ex)
{
System.Web.HttpContext.Current.Response.Write("0");
}
}
}
}
}
I'm a serious newbie to ASP.NET SignalR and have been trying to develop a simple application using it that shows changes made to the database in real time.
I'm getting the following error when I'm trying to run the application:
Unhandled exception at line 52, column 13 in http://localhost:57702/
0x800a138f - JavaScript runtime error: Unable to get property
'PaymentHub' of undefined or null reference
This is my javascript:
#{
ViewBag.Title = "Status";
}
<h2>Status</h2>
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>PaymentStatus</title>
#Styles.Render("~/Content/css")
#Scripts.Render("~/bundles/modernizr")
#Scripts.Render("~/bundles/jquery")
<script src="~/Scripts/jquery.signalR-2.0.0.min.js"></script>
<script src="~/signalr/hubs" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
// Proxy created on the fly
var job = $.connection.PaymentHub;
// Declare a function on the job hub so the server can invoke it
job.client.displayStatus = function () {
getData();
};
// Start the connection
$.connection.hub.start();
getData();
});
function getData() {
var $tbl = $('#tblPaymentInfo');
$.ajax({
url: '../api/values',
type: 'GET',
datatype: 'json',
success: function (data) {
if (data.length > 0) {
$tbl.empty();
$tbl.append(' <tr><th>ID</th><th>Payment_ID</th><th>Payment_Received</th><th>Payment_Pending</th></tr>');
var rows = [];
for (var i = 0; i < data.length; i++) {
rows.push(' <tr><td>' + data[i].Payment_ID + '</td><td>' + data[i].Payment_Received + '</td><td>' + data[i].Payment_Pending + '</td><td>');
}
$tbl.append(rows.join(''));
}
}
});
}
</script>
</head>
<body>
<div>
<table id="tblPaymentInfo" style="text-align:center;margin-left:10px"></table>
</div>
</body>
</html>
My hub looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace WebDatabaseUpdateSignalR
{
public class PaymentHub : Hub
{
public static void Show()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<PaymentHub>();
context.Clients.All.displayStatus();
}
}
}
var job = $.connection.PaymentHub; should be var job = $.connection.paymentHub; without capital. By default, on the JS part of SignalR things will get camel cased.
Hope this helps. Best of luck!
the following code for upload image
<a id="addImage" href="javascript:;">Add Image</a>
Javascript:
$().ready(function () {
var counter = 0;
$(function () {
var btnUpload = $('#addImage');
new AjaxUpload(btnUpload, {
action: 'saveupload.aspx',
name: 'uploadimage',
dataType: 'json',
onSubmit: function (file, ext) {
$("#loading").show();
},
onComplete: function (file, response) {
alert(response);
var uploadedfile = "UserData/" + file;
$("#uploadImageWrapper").append("
<div class='imageContainer offset' id='current" + counter + "'>
<img height='65px' width='65px' src='" + uploadedfile + "' alt='" + uploadedfile + "'/></div>");
$('#current' + counter).fadeIn('slow', function () {
$("#loading").hide();
$("#message").show();
$("#message").html("Added successfully!");
$("#message").fadeOut(3000);
counter++;
});
}
});
});
});
Server code: (saveupload.aspx.cs)
protected void Page_Load(object sender, EventArgs e)
{
HttpFileCollection uploadedFiles = Request.Files;
int i = 0;
string width = "0";
string height = "0";
if (uploadedFiles.Count > 0)
{
while (!(i == uploadedFiles.Count))
{
HttpPostedFile userPostedFile = uploadedFiles[i];
if (userPostedFile.ContentLength > 0)
{
string filename = userPostedFile.FileName.Substring(userPostedFile.FileName.LastIndexOf("\\") + 1);
userPostedFile.SaveAs(Path.Combine(Server.MapPath("UserData"), filename));
Bitmap img = new Bitmap(Path.Combine(Server.MapPath("UserData"), filename));
width = img.Width.ToString();
height = img.Height.ToString();
}
i += 1;
}
}
//I would like to return Uploaded image Height and Width
Response.Write(#"{Width:" + width + ", Height:" + height + "}");
}
and the return JsonResult is i have display in Alert message.
Problem:
I am not able to get response.Width and response.Height.
first of all I would suggest to clean the HTML of your saveupload.aspx. You do not need it and it pollutes your response.
You just need:
<%# Page Language="C#" AutoEventWireup="true" CodeBehind="saveupload.aspx.cs" Inherits="WebApplication1.saveupload" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
Another thing; when you get the response back in your script you can use parseJSON, like this:
var obj = jQuery.parseJSON(response);
now you should be able to access Width and Height:
obj.Width
Last thing. Valums' Ajax Uploader has been replaced by the author with a new component.
You can find it here
It's very much similar but he's still updating this project so you might consider to switch.
UPDATE:
Another thing I would suggest is to use the jSon serializer (System.Web.Script.Serialization) to serialize the stream you want to return:
var jSon = new JavaScriptSerializer();
var OutPut = jSon.Serialize(myClass);
Response.Write(OutPut);
I was asked to develop an auto-mail sending program on asp.net. It is supposed to send, say 5000 e-mails reading the addresses from a database. It will sure fall into request timeout tough. So it seems I have to convert it into a windows app. But I'd like to know if ajaxifying this web-app would help. If I write a web service, and my web app sends the mail addresses as lists of 50 at a time. when done, send the next 50 and so on. Would this help to solve the problem of http request timeout?
Using a webservice endpoint to send your emails is a good idea, whether you call it from an aspx class or from the client with javascript.
Simply use the webservice call to spawn a thread to send the emails and return immediately.
If you wanted visual progress cues then write another ajax endpoint or aspx page that will display the status of the email thread's progress.
There are many ways to accomplish this, you should be able to come up with one with the information given.
Batching from ajax is probably going to be more work than you want to do and adds unnecessary complexity (which is never a good thing).
this is interesting. I may spike this and post some code.
Ok, im back. here ya go. both a webform ui and an ajax ui.
None of this is meant to be finished product - is a spike to support a story. bend/fold/spindle at will.
EmailService.asmx
using System;
using System.ComponentModel;
using System.Threading;
using System.Web.Script.Services;
using System.Web.Services;
namespace EmailSendingWebApplication
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[ScriptService]
public class EmailService : WebService
{
private static EmailSendingProgress _currentProgress;
private static Thread _emailThread;
/// <summary>
///
/// </summary>
/// <param name="criteria">just an example</param>
/// <param name="anotherCriteria">just an example</param>
/// <returns></returns>
[WebMethod]
public EmailSendingProgress SendEmails(string criteria, int anotherCriteria)
{
try
{
if (_currentProgress != null && _emailThread.IsAlive)
{
throw new InvalidOperationException(
"Email batch is already in progress. Wait for completion or cancel");
}
// use your criteria to cue up the emails to be sent.
// .....
// and derive a way for a thread to identify the emails
// i am using a batchid
int batchId = 1000; // contrived
// create a thread
_emailThread = new Thread(ProcessEmails);
_currentProgress = new EmailSendingProgress
{
Status = ProcessState.Starting,
BatchId = batchId
};
// you could use a 'state' object but this process/thread
// is single use/single instance just access _currentProgress
// by the static member
_emailThread.Start();
return _currentProgress;
}
catch (Exception ex)
{
_currentProgress = new EmailSendingProgress
{
Status = ProcessState.Error,
Message = "Error starting process:" + ex.Message
};
}
return _currentProgress;
}
[WebMethod]
public EmailSendingProgress CancelEmailProcess()
{
if (_currentProgress != null && _emailThread.IsAlive)
{
_currentProgress.Cancel = true;
_currentProgress.Message = "Cancelling";
}
return _currentProgress;
}
[WebMethod]
public EmailSendingProgress GetProgress()
{
return _currentProgress;
}
private static void ProcessEmails()
{
// process your emails using the criteria, in this case,
// a batchId
int totalEmails = 100;
int currentEmail = 0;
lock (_currentProgress)
{
_currentProgress.Total = totalEmails;
_currentProgress.Status = ProcessState.Processing;
}
for (; currentEmail < totalEmails; currentEmail++)
{
lock (_currentProgress)
{
if (_currentProgress.Cancel)
{
_currentProgress.Status = ProcessState.Cancelled;
_currentProgress.Message = "User cancelled process.";
break;
}
_currentProgress.Current = currentEmail + 1;
}
try
{
// send your email
Thread.Sleep(100); // lallalala sending email
}
catch (Exception ex)
{
// log the failure in your db
// then check to see if we should exit on error
// or just keep going.
lock (_currentProgress)
{
if (_currentProgress.CancelBatchOnSendError)
{
_currentProgress.Status = ProcessState.Error;
_currentProgress.Message = ex.Message;
break;
}
}
}
}
{
// don't want to obscure state/message from abnormal
// termination..
if (_currentProgress.Status == ProcessState.Processing)
{
_currentProgress.Status = ProcessState.Idle;
_currentProgress.Message = "Processing complete.";
}
}
}
}
public enum ProcessState
{
Idle,
Starting,
Processing,
Cancelled,
Error
}
[Serializable]
public class EmailSendingProgress
{
public int BatchId;
public bool Cancel;
public bool CancelBatchOnSendError;
public int Current;
public string Message;
public ProcessState Status;
public int Total;
}
}
WebFormUI.aspx
<%# Page Language="C#" %>
<%# Import Namespace="EmailSendingWebApplication" %>
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
var svc = new EmailService();
UpdateProgress(svc.GetProgress());
}
protected void SendEmailsButton_Click(object sender, EventArgs e)
{
// arbitrary params - modify to suit
string criteria = string.Empty;
int anotherCriteria = 0;
var svc = new EmailService();
UpdateProgress(svc.SendEmails(criteria, anotherCriteria));
}
protected void CancelEmailProcessButton_Click(object sender, EventArgs e)
{
var svc = new EmailService();
UpdateProgress(svc.CancelEmailProcess());
}
private void UpdateProgress(EmailSendingProgress progress)
{
SetButtonState(progress);
DisplayProgress(progress);
}
private void DisplayProgress(EmailSendingProgress progress)
{
if (progress != null)
{
EmailProcessProgressLabel.Text = string.Format("Sending {0} of {1}", progress.Current, progress.Total);
EmailProcessStatusLabel.Text = progress.Status.ToString();
EmailProcessMessageLabel.Text = progress.Message;
}
else
{
EmailProcessProgressLabel.Text = string.Empty;
EmailProcessStatusLabel.Text = string.Empty;
EmailProcessMessageLabel.Text = string.Empty;
}
}
private void SetButtonState(EmailSendingProgress progress)
{
if (progress != null &&
(progress.Status == ProcessState.Starting || progress.Status == ProcessState.Processing))
{
CancelEmailProcessButton.Visible = true;
SendEmailsButton.Visible = false;
}
else
{
CancelEmailProcessButton.Visible = false;
SendEmailsButton.Visible = true;
}
}
protected void RefreshButton_Click(object sender, EventArgs e)
{
// noop just to get postback. you could also use meta headers to refresh the page automatically
// but why?
}
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<br />
EmailProcessStatus:
<asp:Label ID="EmailProcessStatusLabel" runat="server" Text="EmailProcessStatus"></asp:Label>
<br />
EmailProcessProgress:
<asp:Label ID="EmailProcessProgressLabel" runat="server" Text="EmailProcessProgress"></asp:Label>
<br />
EmailProcessMessage:<asp:Label ID="EmailProcessMessageLabel" runat="server" Text="EmailProcessMessage"></asp:Label>
<br />
<br />
<asp:Button ID="SendEmailsButton" runat="server" OnClick="SendEmailsButton_Click"
Text="Send Emails" />
<asp:Button ID="CancelEmailProcessButton" runat="server" OnClick="CancelEmailProcessButton_Click"
Text="Cancel Email Process" />
<br />
<br />
<asp:Button ID="RefreshButton" runat="server" OnClick="RefreshButton_Click" Text="Refresh" />
</div>
</form>
</body>
</html>
AjaxUI.htm
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
//http://www.codeproject.com/Articles/38999/Consuming-ASP-net-WebServices-WCF-Services-and-sta.aspx
var ProcessState = ["Idle", "Starting", "Processing", "Cancelled", "Error"];
function createXHR() {
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
}
else if (window.ActiveXObject) {
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
else {
throw new Error("Could not create XMLHttpRequest object.");
}
return xhr;
}
function emailAjax(operation, postData, callback) {
var xhr = createXHR();
xhr.open("POST", "EmailService.asmx/" + operation, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
callback(xhr.responseText);
}
};
xhr.setRequestHeader("content-type", "application/json");
xhr.send(postData);
}
function $(id) {
var e = document.getElementById(id);
return e;
}
function startProcess() {
var postData = '{"criteria" : "something", "anotherCriteria" : "1"}';
emailAjax("SendEmails", postData, displayProgress);
}
function cancelProcess() {
emailAjax("CancelEmailProcess", null, displayProgress);
}
function getProgress() {
emailAjax("GetProgress", null, displayProgress);
}
function displayProgress(json) {
eval('var result=' + json + '; var prg=result.d;');
$("EmailProcessMessage").innerHTML = "";
$("EmailProcessStatus").innerHTML = "";
$("EmailProcessProgress").innerHTML = "";
$("CancelEmailProcessButton").style.display = "none";
$("SendEmailsButton").style.display = "none";
if (prg) {
$("EmailProcessMessage").innerHTML = prg.Message;
$("EmailProcessStatus").innerHTML = ProcessState[prg.Status];
$("EmailProcessProgress").innerHTML = "Sending " + prg.Current + " of " + prg.Total;
}
if (prg && (prg.Status == 1 || prg.Status == 2)) {
$("SendEmailsButton").style.display = "none";
$("CancelEmailProcessButton").style.display = "inline";
}
else {
$("CancelEmailProcessButton").style.display = "none";
$("SendEmailsButton").style.display = "inline";
}
}
function init() {
$("SendEmailsButton").onclick = startProcess;
$("CancelEmailProcessButton").onclick = cancelProcess;
// kinda quick but we are only proccing 100 emails for demo
window.setInterval(getProgress, 1000);
}
</script>
</head>
<body onload="init()">
EmailProcessStatus:<span id="EmailProcessStatus"></span><br />
EmailProcessProgress:<span id="EmailProcessProgress"></span><br />
EmailProcessMessage:<span id="EmailProcessMessage"></span><br />
<input type="button" id="SendEmailsButton" value="SendEmails" style="display: none" />
<input type="button" id="CancelEmailProcessButton" value="CancelEmailProcess" style="display: none" />
</body>
</html>
So user will have to leave the browser window open until all the e-mails are sent? Does not sound very good. I would solve this using a daemon or simple script that is run by cron (and checks db if there is something to send), on Windows I hope you can do something similar (write Windows service etc.). This is a purely server-side task, I think ajaxifying it shows that author of the web app wasn't able to make it in a better way, it may even make your web app to be mentioned on thedailywtf.com :)