6

I have now something like this:

public class Service1 : System.Web.Services.WebService
{
    [WebMethod]
    public string Method1()
    {
        SomeObj so = SomeClass.GetSomeObj(); //this executes very long time, 50s and more
        return so.Method1(); //this exetus in a moment 
    }

    [WebMethod]
    public string Method2()
    {
        SomeObj so = SomeClass.GetSomeObj(); //this executes very long time, 50s and more
        return so.Method2(); //this exetus in a moment 
    }

 ...
}

Is it possible to make stateful web service so that I can reuse SomeObj so and just call methods on the same object?

So the client which will use this service would first call web method which would create so object and return some ID. And then in subsequent calls the web service would reuse the same so object based on ID.

EDIT


Here is my actual code:

[WebMethod]
public List<ProcInfo> GetProcessList(string domain, string machineName)
{
    string userName = "...";
    string password = "...";
    TaskManager tm = new TaskManager(userName, password, domain, machineName);

    return tm.GetRunningProcesses();
}

[WebMethod]
public bool KillProcess(string domain, string machineName, string processName)
{
    string userName = "...";
    string password = "...";
    (new TaskManager(userName, password, domain, machineName);).KillProcess(processName);               
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Primoz
  • 4,079
  • 17
  • 56
  • 67

3 Answers3

6

Stateful web services are not scalable and I wouldn't recommend them. Instead you could store the results of expensive operations in the cache. This cache could be distributed through custom providers for better scalability:

[WebMethod]
public string Method1()
{
    SomeObj so = TryGetFromCacheOrStore<SomeObj>(() => SomeClass.GetSomeObj(), "so");
    return so.Method1(); //this exetus in a moment 
}

[WebMethod]
public string Method2()
{
    SomeObj so = TryGetFromCacheOrStore<SomeObj>(() => SomeClass.GetSomeObj(), "so");
    return so.Method2(); //this exetus in a moment 
}

private T TryGetFromCacheOrStore<T>(Func<T> action, string id)
{
    var cache = Context.Cache;
    T result = (T)cache[id];
    if (result == null)
    {
        result = action();
        cache[id] = result;
    }
    return result;
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Can you store all object in the cache or there are any limitations ? My objects includes open WMI connections to various computers. – Primoz Nov 07 '10 at 13:10
  • By default the cache is stored in memory and if you start running on low memory it will automatically be evicted. So yes, you could store as much as you wish but you should always check that the object is in the cache and never rely on it being there because you stored it. – Darin Dimitrov Nov 07 '10 at 13:13
  • @Darin Dimitrov, you should make your solution type-safe by using a lock. While one request is executing the "result = action();" line, which takes time, any other request will also see the cache as being null and repeat the same "result = action();" line. See http://en.wikipedia.org/wiki/Double-checked_locking – Sklivvz Nov 07 '10 at 13:22
  • I really don't understand what you are saying. You must be mistaken. Write a test. :-) – Sklivvz Nov 07 '10 at 13:41
  • In other words: cache[id] will be null until action() is executed. any other thread testing it will also execute action(). – Sklivvz Nov 07 '10 at 13:45
  • @Sklivvz, yes you are correct but as reads will be more frequent than writes locking on each read could be a bottleneck. Once the action is executed it should be OK. Also you are saying that the code is not thread safe: this is not true. The code is thread safe. The problem is that the action could indeed be executed twice but you won't get corrupt or partially assigned results. – Darin Dimitrov Nov 07 '10 at 13:46
  • Yep there will be no corrupt results, only double execution (each time the cache is cleared). – Sklivvz Nov 07 '10 at 13:57
  • @Darin Dimitrov Thank you for your answer. New objects will be rerly created and there is almost impossible that clients would requiers two same objects. Also can you plese explain your code a bit, because I'm new to C# and I relly don't get it. – Primoz Nov 07 '10 at 13:58
1

Option 1

You can use your HttpSession.

//this executes very long time, 50s and more, but only once.
private SomeObj SessionSomeObj { 
  get 
  { 
    var ret = (SomeObj)Session["SomeObjStore"] ?? SomeClass.GetSomeObj();
    SessionSomeObj = ret;
    return ret; 
  }
  set { Session["SomeObjStore"] = value; }
}

[WebMethod(EnableSession = true)]
public string Method1()
{
    return SessionSomeObj.Method1(); //this exetus in a moment 
}

[WebMethod(EnableSession = true)]
public string Method2()
{
    return SessionSomeObj.Method2(); //this exetus in a moment 
}

Note that this will only work if one call per client is made at a time.

Option 2

You can leave the class as is but use the WebMethod differently. If you are calling from a .Net generated class, async methods are provided for these occurrences. Basically you invoke the Method1 begin request method and get a call back when the execution is finished. You might need to tweak the timeout parameter of the web service client class for this to work though.

Option 3

You can use the caching features of the SixPack library to do this effortlessly! ;-)


[Edited after comment] There are now two static fields in option 1 to allow two different instances, one per method, as requested.


[Edited after further explanation] Using Session to make the calls stateful.

See: http://msdn.microsoft.com/en-us/library/aa480509.aspx

Also added Option 3.

Sklivvz
  • 30,601
  • 24
  • 116
  • 172
  • Option 1 is no-go because different client need different "so" object to call method on. – Primoz Nov 07 '10 at 14:07
  • I think we have some missunderstanding. Client1 creates instance of "so" Object and then calls method1 to methodN on this instance. Client2 creates another instance of "so" oject and again calls same or different methods on this instance. So there needs to be different "so" instance for every client – Primoz Nov 07 '10 at 14:13
  • Then you could use a Dictionary instead... The main problem with this approach is that the objects don't persist beyond the lifetime of the AppDomain. – CodesInChaos Nov 07 '10 at 14:18
  • When it ends this "lifetime of the AppDomain" ? – Primoz Nov 07 '10 at 14:24
  • For a normal application when the process terminated. For ASP.net it can happen whenever IIS decides to restart it, in particular after configuration changes. http://weblogs.asp.net/owscott/archive/2006/02/21/ASP.NET-v2.0-_2D00_-AppDomain-recycles_2C00_-more-common-than-before.aspx – CodesInChaos Nov 07 '10 at 14:51
  • Is option 1 applicable to my real code and how ? Options 2 and 3 are not suitable for me. – Primoz Nov 07 '10 at 16:04
  • You need to use a method with parameters instead of a property, but yes, it should work. – Sklivvz Nov 07 '10 at 16:14
  • Ok! And what is the difference using Session or using Cache ? – Primoz Nov 07 '10 at 16:25
  • Session is a different instance per user. Cache is a single instance for all users. – Sklivvz Nov 07 '10 at 16:34
0

Change the ServiceContract of your interface into:

[ServiceContract(SessionMode = SessionMode.Required)]

And put the following attribute on your class:

[ServiceBehaviorAttribute(InstanceContextMode = InstanceContextMode.PerSession)]

See http://msdn.microsoft.com/en-us/library/system.servicemodel.sessionmode.aspx for more information and an example.

Pieter van Ginkel
  • 29,160
  • 8
  • 71
  • 111