简体   繁体   中英

Implementing a Session Timeout for a Console App in C#

I am implementing a Timeout for my console application in C#. I have a class called MySession which has to be bound by a Session timeout. This means that in the constructor of the class, I configure a timeout for the objects created. If any method in the class is not called within that specified timeout, then on subsequent access of any methods, a Timeout exception has to be thrown.

This is the dummy implementation of that class.

public class MySession {
private TimeSpan timeout;
private DateTime lastAccessedTime;

public MySession(TimeSpan timeout) {
    this.timeout = timeout;
    lastAccessedTime = DateTime.Now;
}

public void StoreName(string name) {
    TimeSpan diff = DateTime.Now - lastAccessedTime;
    if(diff.Ticks - timeout.Ticks > 0) {
        throw new TimeoutException("session timed out");
    }
    //store logic
    lastAccessedTime = DateTime.Now;
}

public void StoreAddress(string address) {
    TimeSpan diff = DateTime.Now - lastAccessedTime;
    if(diff.Ticks - timeout.Ticks > 0) {
        throw new TimeoutException("session timed out");
    }
    //store logic
    lastAccessedTime = DateTime.Now;
}

public void Commit() {
    TimeSpan diff = DateTime.Now - lastAccessedTime;
    if(diff.Ticks - timeout.Ticks > 0) {
        throw new TimeoutException("session timed out");
    }
    //commit logic
    lastAccessedTime = DateTime.Now;
}}

I have a two questions.

  1. Is there a better way to do this that avoids the checking that comes at the beginning of every method? I can obviously refactor a IsAlive property out of that code, but nonetheless I still need to check for IsAlive at the beginning of every method. Is there any Session class in .NET that I could inherit my class from?
  2. Hypothetically, let me say that one of my methods in the session class returns another object. Any calls on this object also needs to be considered as updating the lastaccessed time from a timeout perspective. This could soon get pretty complicated with multiple nested objects. But this is only a hypothetical question and maybe YAGNI; but I would like to know if there is any way this can be accomplished.

EDIT: (since these points were not mentioned in the original question)

  1. The Session class does indeed have a rollback method. I just did not add it to keep the code small!
  2. The Stores actually go to a backend database within the scope of a database transaction. So all Store calls in this class, would actually write the data to the database, but commit on the DB needs to happen only after commit on the session is called.

Before anything else, you should consider rethinking what your session object should be responsible for. Usually, when I see a Commit method, I look for a Rollback method also, to make sure that your updates are in some way consistent if an operation fails (although I am still not sure what the class is supposed to do).

Also, if Commit commits transient data added by your StoreSomething methods, then I don't see why a "Session" (again, whatever that is) should be opened at all until you decide to actually commit.

Adding a better description and some background to the problem might allow us to provide a better solution in the long term.

Having said that, a slightly more refactored version might be:

  1. Start by defining an interface first ( Liskov Substitution Principle ):

     public interface IMySession { void StoreName(string name); void StoreAddress(string address); void Commit(); } 
  2. Implement a simplest "plain old" session with basic functionality ( Single Responsibility Principle ):

     public class BasicSession : IMySession { #region IMySession members public void StoreName(string name) { // plain store } public void StoreAddress(string address) { // plain store } public void Commit() { // plain commit } #endregion } 
  3. Finally, create a proxy class, which checks for timeouts, and forwards method calls to the base class:

     public class TimeLimitedSessionProxy : IMySession { private readonly IMySession _baseSession; private readonly TimeSpan _timeout; private DateTime _lastAccessedTime = DateTime.Now; public TimeLimitedSessionProxy(IMySession baseSession, TimeSpan timeout) { _baseSession = baseSession; _timeout = timeout; } #region IMySession members public void StoreName(string name) { IfNotTimedOut(() => _baseSession.StoreName(name)); } public void StoreAddress(string address) { IfNotTimedOut(() => _baseSession.StoreAddress(address)); } public void Commit() { IfNotTimedOut(() => _baseSession.Commit()); } #endregion private void IfNotTimedOut(Action action) { if (DateTime.Now - _lastAccessedTime > _timeout) { throw new TimeoutException("session timed out"); } action(); _lastAccessedTime = DateTime.Now; } } 

Overall result:

  • Other parts of your code should accept an IMySession object, not caring how it's actually implemented under the hood.

  • Even the TimeLimitedSessionProxy accepts an IMySession and is ignorant about the actual implementation; all it cares about is timing.

  • If you decide to add other functionality, consider leaving these classes intact and proxying or decorating them as needed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM