0

As a follow-up to this question, I wanted to understand how my invoking of a Service manually can be improved. This became longer than I wanted, but I feel the background info is needed.

When doing a pub/sub (broadcast), the normal sequence and flow in the Messaging API isn't used, and I instead get a callback when a pub/sub message is received, using IRedisClient, IRedisSubscription:

_subscription.OnMessage = (channel, msg) =>
{
    onMessageReceived(ParseJsonMsgToPoco(msg));
};

The Action onMessageReceived will then, in turn, invoke a normal .NET/C# Event, like so:

protected override void OnMessageReceived(MyRequest request)
{
    OnMyEvent?.Invoke(this, new RequestEventArgs(request));
}

This works, I get my request and all that, however, I would like it to be streamlined into the other flow, the flow in the Messaging API, meaning, the request finds its way into a Service class implementation, and that all normal boilerplate and dependency injection takes place as it would have using Messaging API.

So, in my Event handler, I manually invoke the Service:

private void Instance_OnMyEvent(object sender, RequestEventArgs e)
{
    using (var myRequestService = HostContext.ResolveService<MyRequestService>(new BasicRequest()))
    {
        myRequestService.Any(e.Request);
    }
}

and the MyRequestService is indeed found and Any called, and dependency injection works for the Service.

Question 1:

  • Methods such as OnBeforeExecute, OnAfterExecute etc, are not called, unless I manually call them, like: myRequestService.OnBeforeExecute(e) etc. What parts of the pipeline is lost? Can it be reinstated in some easy way, so I don't have to call each of them, in order, manually?

Question 2:

I think I am messing up the DI system when I do this:

using (var myRequestService = HostContext.ResolveService<MyRequestService>(new BasicRequest()))
{
    myRequestService.OnBeforeExecute(e.Request);
    myRequestService.Any(e.Request);
    myRequestService.OnAfterExecute(e.Request);
}

The effect I see is that the injected dependencies that I have registered with container.AddScoped, isn't scoped, but seems static. I see this because I have a Guid inside the injected class, and that Guid is always the same in this case, when it should be different for each request.

container.AddScoped<IRedisCache, RedisCache>();

and the OnBeforeExecute (in a descendant to Service) is like:

public override void OnBeforeExecute(object requestDto)
{
    base.OnBeforeExecute(requestDto);
    IRedisCache cache = TryResolve<IRedisCache>();
    cache?.SetGuid(Guid.NewGuid());
}

So, the IRedisCache Guid should be different each time, but it isn't. This however works fine when I use the Messaging API "from start to finish". It seems that if I call the TryResolve in the AppHostBase descendant, the AddScoped is ignored, and an instance is placed in the container, and then never removed.

JoshMc
  • 10,239
  • 2
  • 19
  • 38
Ted
  • 19,727
  • 35
  • 96
  • 154

1 Answers1

0

What parts of the pipeline is lost?

None of the request pipeline is executed:

myRequestService.Any(e.Request);

Is physically only invoking the Any C# method of your MyRequestService class, it doesn't (nor cannot) do anything else.

The recommended way for invoking other Services during a Service Request is to use the Service Gateway.

But if you want to invoke a Service outside of a HTTP Request you can use the RPC Gateway for executing non-trusted services as it invokes the full Request Pipeline & converts HTTP Error responses into Typed Error Responses:

HostContext.AppHost.RpcGateway.ExecuteAsync()

For executing internal/trusted Services outside of a Service Request you can use HostContext.AppHost.ExecuteMessage as used by ServiceStack MQ which applies Message Request Request/Response Filters, Service Action Filters & Events.

I have registered with container.AddScoped

Do not use Request Scoped dependencies outside of a HTTP Request, use Singleton if the dependencies are ThreadSafe, otherwise register them as Transient. If you need to pass per-request storage pass them in IRequest.Items.

mythz
  • 141,670
  • 29
  • 246
  • 390
  • Thanks for the answer, I will see if I can workaround this using your guidance. I also realized that the `Any` method won't do anything, but I thought that maybe the `HostContext.ResolveService` would do some magic. – Ted Oct 28 '20 at 07:03
  • "Do not use Request Scoped dependencies outside of a HTTP Request". In my case, the dependencies are registered in the AppHostBase descendant, and is used by Services (using Messaging API). So, as I understand it, I can't really choose to do a Singleton, because sometimes the requests come in via Messaging API, and sometimes via the pub/sub, as described above. So it was built and done with Messaging API in mind, and I was hoping to streamline the sub/pub approach into the existing and otherwise correct implementation. In any case, I will use your tips and see how it works. Thanks. – Ted Oct 28 '20 at 07:05
  • @Ted All `ResolveService` does is resolve a new Service instance from the IOC and inject the IRequest context. You can only register dependencies on Startup in your AppHost or ConfigureServices() Startup class, but you should not be using Request Scoped dependencies if you need to execute your Services outside of a HTTP Request. – mythz Oct 28 '20 at 08:37
  • Alright, we need one instance per request in the normal Messaging API, since we have "trackers" (to track what is going on during a request), and this needs to be created for each request, and thus, AddScoped seemed like the natural approach. But as you have explained, this works if you use Messaging API exclusivly, but not if we go outside of the Request (its not a regular HTTP API, its through/via Redis). Transient seems wrong though, that would create new instances every time the Interface property is accessed? That would mean loosing the "state" then, I assume. Hmm. – Ted Oct 28 '20 at 08:54
  • @Ted Use `IRequest.Items` dictionary for per-request storage of any instances you want to passed and accessible through the entire Request Pipeline. – mythz Oct 28 '20 at 08:56
  • Yep, I read that you recommend that approach in other posts. I use that in my HTTP REST API, but I was aiming at using the DI framework to handle such things for me. But, I just might have to do the dependency injection manually, if I want these to approaches to streamline... I appreciate the quick answers and good support! – Ted Oct 28 '20 at 08:58
  • 1
    Follow-up: Using `HostContext.AppHost.ExecuteMessage(theMessage);` worked very well for me in this situation; that "hooked" it into the request pipeline and got all DI to work as expected. Even in the AppHostBase descendant, I get the correct instance from `TryResolve` when doing it in method beloning to the pipeline, like in `OnAfterExecute`, which is enough for me! – Ted Oct 28 '20 at 11:09