1
votes

I have a validator that must check for the existence of another entity. I would like to be able to call the ServiceGateway, using a HEAD method, to check for the status of 404/200. .

For now, I am doing a nasty trick. I'm emitting a regular GET request, surrounded by a try/catch. But this is polluting my logs big time with so many 404s. Also, sometimes, I have to check for the NON-existence of some entity. So my logs show 404s errors, but that are expected.

I could implement another DTO just to check for that, but I would prefer to use existing HTTP conventions

I tried to use the ServiceGateway / custom BasicRequest

But I have two problems

I cannot access the ServiceGateway IResponse (Gateway.Send().Response.StatusCode).

I cannot set the verb to HEAD (InProcess only supports GET,POST,DELETE,PUT,OPTIONS,PATCH)

Also, there is no IHead interface / HEAD support in general


My question is: How can I use the service gateway internally to emit HEAD requests, to check for the existence (or lack) of other entities? - be it through InProcess, Grpc, Json, ...

Also, this would be useful for accessing the already built-in versioning (Etags, ...)


[Route("/api/other-entity/{Id}", "GET,HEAD")]
public class GetOtherEntity : IReturn<OtherEntityDto>, IGet
{
  public Guid Id {get; set;}
}


public class OtherEntityService : Service {

  public async Task<object> Get(GetOtherEntity request){
    return (await _repository.Get(request.Id)).ToDto();
  }

  // This doesn't get called
  public async Task Head(GetOtherEntity request){
    var exists = await _repository.Exists(request.Id);
    Response.StatusCode = exists ? (int)HttpStatusCode.OK : (int)HttpStatusCode.NotFound;
  }

  // This either
  public async Task Any(GetOtherEntity request){
    var exists = await _repository.Exists(request.Id);
    Response.StatusCode = exists ? (int)HttpStatusCode.OK : (int)HttpStatusCode.NotFound;
  }


}

public class CreateMyEntityValidator: AbstractValidator<CreateMyEntity>{

  public CreateMyEntityValidator(){


    // This rule ensures that the OtherId references an existing OtherEntity

    RuleFor(e => e.OtherId).MustAsync(async (entity, id, cancellationToken) => {

      var query = new GetOtherEntity(){ Id = id };
      var request = new BasicRequest(query , RequestAttributes.HttpHead);

      // This doesn't call the OtherService.Head nor the OtherService.Any
      // Actually my logs show that this registers a a POST request ?
      var response = await HostContext.AppHost.GetServiceGateway(Request).SendAsync(request);

      // And how could I get the response.StatusCode from here ? 
      return response.StatusCode == (int)HttpStatusCode.OK;

    })


  }

}

1

1 Answers

3
votes

You can't implement HEAD requests in ServiceStack Services.

You can handle them before ServiceStack by intercepting and short-circuiting them in Pre Request Filters, e.g:

RawHttpHandlers.Add(httpReq =>
  httpReq.HttpMethod == HttpMethods.Head
    ? new CustomActionHandler(
    (httpReq, httpRes) =>
    {
        // handle request and return desired response
        httpRes.EndRequest(); //short-circuit request
    });
    : null);

But very few HTTP Clients are going to have native support for HEAD requests, normally you'd just attempt to fetch the resource which would throw a 404 Exception if the target resource doesn't exist.

If you need to commonly check if a resource exists without returning it you'll have more utility by implementing a single batch Service that accepts a batch of Ids or URNs and returns a dictionary or list of ids which exist.