0

I'm creating a Web Api for sending emails. There are two end points

  1. /api/SendMail this should start sending emails, working fine now.
  2. /api/StopMail this should stop sending emails, this method doesn't stop sending email. This is the actual problem. What should I do to stop email when I hit this end point.

        public bool stop = false;
    
        [HttpGet]
        public void SendMail()
        {
            var emails = db.Emails.ToList();            
    
            foreach (var email in emails)
            {
                //code for sending mail here...
    
                if (stop == true)
                    break;
            }
        }
    
        [HttpGet]
        public void StopMail()
        {
            stop = true;
        }
    
Munam Yousuf
  • 431
  • 1
  • 6
  • 17
  • you can't like this - a controller is a one time use for each request. – Daniel A. White May 04 '17 at 10:31
  • You could create a service for sending the mails and make that service singleton (with the singleton pattern or a DI framework). – Mighty Badaboom May 04 '17 at 10:33
  • @DanielA.White then what approach should I adopt – Munam Yousuf May 04 '17 at 10:34
  • 1
    Persist the value of "stop" to the database. – TheTerribleProgrammer May 04 '17 at 10:38
  • @TheTerribleProgrammer nope that doesn't work. – Munam Yousuf May 04 '17 at 11:17
  • It *does* work, but you need to read out every time. You can't read the value from something like `Session` because the record has an exclusive lock and reads per request, therefore you can't stop this mid request. If you read from the DB each time you need to check the flag, it would work – Ricky Hartmann May 04 '17 at 11:20
  • @RickyHartmann nope. reading every time from db but it's not working. if(db.SendingStatus.FirstOrDefault().stopSending == false){ send mail...} – Munam Yousuf May 04 '17 at 11:41
  • @MunamYousuf how are you saving the value to the database? Can you see the value change within the database? The the record exist within the database? – TheTerribleProgrammer May 04 '17 at 12:19
  • I have manually set first row data as **stopSending column to false** in db. Now I hit this url **/api/SendMail** which starts sending email because stopSending value in db is false. and now when calling **/api/StopMail** this actually set stopSending value to true in db I have checked in db. But the SendMail function keeps sending mails. – Munam Yousuf May 04 '17 at 12:41

5 Answers5

1

You would be better implementing another class to perform the SendMail operation which would contain the stop flag. The class can be injected into the controller as a singleton and support the start/stop operations as you've described.

Simply Ged
  • 8,250
  • 11
  • 32
  • 40
0

You really can't do that with the built-in stuff with ASP.NET there's too many considerations. A controller exists only for the lifecycle of one request. ASP.NET was not designed for background work.

There are some 3rd party libraries and one built-in facility that might help. QueueBackgroundWorkItem looks to be the most promising.

Scott Hanselman has a write up on different approaches.

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
0

Instead of the stop variable I would use HttpApplicationState: https://msdn.microsoft.com/en-us/library/system.web.httpapplicationstate.aspx

For other options please scroll down to "Server-Side State Management Options" on this page: https://msdn.microsoft.com/en-us/library/z1hkazw7.aspx

Péter Aradi
  • 370
  • 1
  • 4
  • 12
0

There are 2 solutions for your problem. My question is why you would require a web API for sending mail purpose?

1st solution

  1. Use a Windows service for this.
  2. You can stop the service anytime you want. (You can even stop the service from web api)

2nd solution

  1. Use a single API accepting boolean variable
  2. Use a timer keeping the timer variable in the class
  3. Start the timer when send email is required(while true as api input) // Add this code in timer handler var emails = db.Emails.ToList();

    foreach (var email in emails) { //code for sending mail here... if (stop == true) break; }

  4. Stop the timer when you want to stop sending email(while false as api input)

ViVi
  • 4,339
  • 8
  • 29
  • 52
-1

You need to have something that allows for a shared context. Controllers by default will have their own.

You could use something like a static wrapper around a shared context for this to use:

public static class EmailSenderContext {
    private static object _locker = new object();
    public static bool _canSend = true;

    public static bool CanSend() {
        var response = false;
        lock(_locker) {
            response = _canSend;
        }
        return response;
    }     

    public static void ChangeSendState(bool canSend) {
        lock(_locker) {
            _canSend = canSend;
        }
    }
}
public class MyController {
    [HttpGet]
    public void SendMail()
    {
        var emails = db.Emails.ToList();            

        foreach (var email in emails)
        {
            //code for sending mail here...

            if (!EmailSenderContext.CanSend())
                break;
        }
    }

    [HttpGet]
    public void StopMail()
    {
        EmailSenderContext.ChangeSendState(false);
    }

}

Mind you, this will only work on a single server. The moment you want this to be a global flag, you will need to introduce a database to store the value, and you will want to access that database value each time you check.

This is an OVERLY simplified example, and with many systems, will not work. If this is a small thing for a work project that only runs on a single server with no varying use access, it will work. Again, since this is a shared context, all users will have general access to the value.

Ricky Hartmann
  • 891
  • 1
  • 7
  • 20
  • this only allows for one sender at a time. – Daniel A. White May 04 '17 at 10:39
  • Static classes and fields are shared between requests. http://stackoverflow.com/a/195290/1251624 Maybe I misunderstand, but given that case and the `lock` in the class itself, shouldn't it allow it to be read at a different point in time with an updated value @DanielA.White – Ricky Hartmann May 04 '17 at 10:46
  • i mean if 2 things are reqeusting `sendmail` and `stopmail` at the same time the context will get confused. – Daniel A. White May 04 '17 at 10:47
  • True, but since for each email it needs to send, it checks the shared value, it will see the updated one with the next it sends, and stop: 00:01 - Send email 1 (CanSend = true) 00:03 - Send email 2 (CanSend = true) 00:04 - Stop sending (CanSend = false) 00:05 - Send email 3 (CanSend = false) - break – Ricky Hartmann May 04 '17 at 10:49
  • Thinking more about it, a lock on the read should also be necessary. Ideally it should only be accessed on lock. Correct? – Ricky Hartmann May 04 '17 at 10:50