邮件发送程序&请求超时

时间:2010-02-14 11:39:48

标签: http timeout request

我被要求在asp.net上开发一个自动发送邮件程序。它应该发送5000封电子邮件,从数据库中读取地址。它肯定会陷入请求超时的困难。所以我似乎必须将其转换为Windows应用程序。但是我想知道这个网络应用程序是否会有所帮助。如果我写一个Web服务,我的Web应用程序一次发送50个列表的邮件地址。完成后,发送下一个50,依此类推。这有助于解决http请求超时的问题吗?

2 个答案:

答案 0 :(得分:1)

使用webservice端点发送电子邮件是个好主意,无论是从aspx类调用还是从客户端使用javascript调用它。

只需使用webservice调用即可生成一个线程来发送电子邮件并立即返回。

如果你想要视觉进度提示,那么写另一个ajax端点或aspx页面,它将显示电子邮件线程进度的状态。

有很多方法可以实现这一目标,你应该能够提供一个包含所提供信息的方法。

从ajax批处理可能比你想做的工作更多,并增加了不必要的复杂性(这绝不是一件好事)。

这很有意思。我可能会加入这个并发布一些代码。

好的,我回来了。你走吧。 webform ui和ajax ui。

这些都不是成品 - 是支持故事的高峰。弯曲/折叠/主轴随意。

<强> 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" />
        &nbsp;<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>

答案 1 :(得分:0)

因此,在发送所有电子邮件之前,用户必须打开浏览器窗口吗?听起来不太好。我会使用由cron运行的守护进程或简单脚本来解决这个问题(并检查数据库是否有要发送的东西),在Windows上我希望你能做类似的事情(编写Windows服务等)。这是一个纯粹的服务器端任务,我认为它显示它表明Web应用程序的作者无法以更好的方式制作它,它甚至可能使您的Web应用程序在thedailywtf.com上被提及:)