0

Building on a previous question, I'm Intercepting incoming requests inside a middleware of Configure method.

public class Startup: IStartup
{
    public Startup(IHostingEnvironment environment)
    {
        // starting apparatus
    }

    public void Configure(IApplicationBuilder app)
    {
        var self = this;
        app.Run(async (context) =>
        {
             await self.Delegate.Service(context);
        });
    }

    public void output(HttpContext context string output) 
    {
          context.Response.WriteAsync(output);
    }

    // other methods

    public IRouter Delegate { get; set; }
}

Handler

public class Handler: IRouter
{

    public void Service(HttpContext context)
    {
        // shuttles the context to the other parts of the system
        // for processing data
    }

    public void ServiceResult(HttpContext context, object result) 
    {
        // system calls this method, which is then passed back to Startup object.
        Startup.output(context, result);
    }
}
  1. Startup should hand over the context to the Handler for processing the request.

  2. Handler then dispatches to another part of the system where request is processed based on the URI. that other part will invoke the ServerResult once processing is done.

  3. Basically the workflow is Startup delegates context to the apparatus and apparatus returns the context along with result for output. there's a disconnect, output will happen in another function/closure/lambda but not the same middleware.

I'm blocked at this middleware where it's forcing me to await on this and define it as async, any further advice on how can I implement the above in asp.net core world.

P.S. Request interception can happen anywhere, it doesn't have to me in the middleware as in my example.

P.P.S An idea could be Startup can pass a lambda to the apparatus which apparatus can then call once request is finished processing.


My Attempt:

Startup

public void Configure(IApplicationBuilder app)
{
    var self = this;
    app.Run(async (context) =>
    {
        await self.Handler.Handle(new Request(context, new Task(() => { })));
    });
}

public void Result(Request request)
{
    request.Context.Response.WriteAsync(request.Result); // write the result
    request.Task.Start(); // end waiting for the middleware async
}

Handler

public Task Handle(Request request)
{
    // delegates to helper objects to process the request
    return request.Task;
}

public void Result(Request request)
{
    // helper objects called this function after populating results
    startup.Result(request);
}
Community
  • 1
  • 1
user2727195
  • 7,122
  • 17
  • 70
  • 118
  • Assuming the middleware would *not* await the content, how would that even work? The “handler” could return at any random moment, writing output at any possible moment and place inside the generated HTML… or even worse could attempt to write when the response is already sent. – If you have an asynchronous mechanism that isn’t task-based (using `async` & `await`)—for whatever reason—then you need to convert it to one in order to consume it correctly. – poke Feb 06 '17 at 08:00
  • ok, I see the middleware has to await, so it possible to pass a lambda from Startup to the library, where library process and outputs the data and then eventually calls this lambda to finish the sequence. Please see Configure method under "Using Routing Middleware" for perhaps any ideas. https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing – user2727195 Feb 06 '17 at 08:10
  • You seem to misunderstand what lambdas are. They are just anonymous delegates. Note that this is *not* JavaScript, and there is not an asynchronous event loop. – Yes, you could pass a lambda that is called once everything is done but that will not make the process magically asynchronous. If you want async, you have to explicitly make it so. – poke Feb 06 '17 at 08:26
  • ok, I'm working, refactoring handler to return Task, will edit my question as I make progress. – user2727195 Feb 06 '17 at 08:30
  • So your problem is that your Handler is synchronous but app.Run requires asynchronous delegate? – Evk Feb 06 '17 at 08:32
  • @Evk yes that seems to be the problem. I can have the handler return a Task but then the rest of the apparatus is synchronous. – user2727195 Feb 06 '17 at 08:36
  • If you are going asynchronous - you better make everything asynchronous, including your apparatus. Otherwise it makes little sense. I suppose you can change the code of apparatus, or it's already existing and quite hard to change? – Evk Feb 06 '17 at 08:41
  • The apparatus is frozen, not able to change, that's why I'm looking for something in the middle that can help both pieces work together, in my limited knowledge I can only think of passing lambdas which seems doesn't work here. I believe there has to be something similar to callbacks or delegates in .net world – user2727195 Feb 06 '17 at 08:44
  • And why do you need callback if apparatus is syncrhonous? Why it cannot just return output directly without callback? – Evk Feb 06 '17 at 09:40
  • @Evk I may not be good with explanation, however, I've edited my question, please see my attempt, I'm creating a task on the fly for incoming request for which the middlware will wait on, then once results are returned, then task.start is called, it's not working but perhaps we can build on top of this concept, yes please? – user2727195 Feb 06 '17 at 09:50
  • Eureka, it worked, creating Task on the fly and returning to the middleware, and later once result is processed, I called Task.start() to end the await. – user2727195 Feb 06 '17 at 10:06

1 Answers1

1

I don't say that is how it's best to do it, but due to your lack of experience and to get the job done - you can use TaskCompletionSource:

public class Handler {        
    private TaskCompletionSource<string> _tcs;
    public Task<string> Start(HttpContext context) {
        _tcs = new TaskCompletionSource<string>();
        // shuttles the context to the other parts of the system
        // for processing data
        return _tcs.Task;
    }

    private void Complete(string output) {
        if (_tcs == null)
            throw new Exception("Task has not been started yet");
        // system calls this method, which is then passed back to Startup object.
        _tcs.SetResult(output);
    }
}


public void Configure(IApplicationBuilder app) {
    app.Run(async (context) => {
        var output = await new Handler().Start(context);
        await context.Response.WriteAsync(output);
    });
}
Evk
  • 98,527
  • 8
  • 141
  • 191
  • Thanks @Evk, the Handler is not returning any output/result, handler accepts the request and then delegates the request and then forgets about it. Only later The helper objects call the handler with result and the Task. Basically it's the Request object that wraps the `Context` and `Task` which gets shuttled around but then finally comes back to Startup for outputting the result. Startup object then outputs the result and calls Thread.start to end the await of the middleware. however, with your experience I'd like to review the approach for any fine tunings. – user2727195 Feb 06 '17 at 10:10
  • So that's what I do in my example. In Handler.Start you start your processing and forget. When result is available, you call Complete method which will complete the task. You can also pass TaskCompletionSource to your apparatus and it can call SetResult on it itself. – Evk Feb 06 '17 at 10:12
  • ok, let me absorb your answer, any docs for TaskCompletionSource please – user2727195 Feb 06 '17 at 10:13
  • Google "C# TaskCompletionSource" :) – Evk Feb 06 '17 at 10:13
  • thanks, I'm going to. Appreciate your patience and help on this to finally crack this down. – user2727195 Feb 06 '17 at 10:23
  • I think it's the best way to do it, `to represent an external asynchronous operation. TaskCompletionSource is provided for this purpose.` I also like the fact that it has error handling `SetException` if apparatus run into problem. – user2727195 Feb 06 '17 at 12:06
  • I noticed you are handling concurrency via `new Handler` instantiating for each request, I won't be able to since there's only one handler reference, that's why I have been shuttling the `Task` as well besides `context` – user2727195 Feb 06 '17 at 12:12
  • Well you can just pass reference to TaskCompletionSource to your apparatus directly, then no need to store it in a field and class can be static. I'd suggest to learn more about C# in general and .Net platform, because you might do some bad things if you will develop production level applications with your current knowledge :) – Evk Feb 06 '17 at 18:43
  • yes I'm wrapping the `TaskCompletionSource` in my request object, passing to apparatus, and have defined `Complete` function within the request that apparatus can call to mark the request complete so `App.Run` can let go of the task. Your concern is right, It's just have done a lot in similar manner in other environments node.js and Java but I feel confident after your two answers, this is what's needed, I've a third and last one regarding chaining async Tasks while shuttling the request between them, like promises/monoids in nodejs, I'll post in a few and appreciate your answer. – user2727195 Feb 07 '17 at 03:47
  • when you say pass reference of `TaskCompletionSource` to the apparatus, the point is that apparatus will be receiving several concurrent requests via `app.Run` so it has no way of knowing which `tcs` belongs to which `context`, `Handler` itself is asynchronous in the sense that it shuttles the requests and forgets, and result come from another helper object. so apparatus is more of a functional stateless implementation rather than imperative stateful style. request object's result field gets populated as it shuttles around, at end uses context to send the result and marks the task complete. – user2727195 Feb 07 '17 at 03:58
  • Sure, but then just pass both tcs AND context (combined in some object). – Evk Feb 07 '17 at 05:52
  • Thanks, here's the last of it. http://stackoverflow.com/questions/42091348/chaining-tasks-in-csharp-with-success-and-fault-handler – user2727195 Feb 07 '17 at 13:41
  • Hi Evk, just last bit of it and I'll be done, I'm 95% there, and trying to crack this `ContinueWith` piece down with `Result` and `Fault` method, please have a look at this question and without looking into all clutter, just see a section within my question at the end called "Problem". http://stackoverflow.com/questions/42091348/chaining-tasks-in-csharp-with-success-and-fault-handler I know `async` and `await` are there but let's crack this `ContinueWith` please. – user2727195 Feb 08 '17 at 07:21