Intercepting/extending NativeTypesService

Is there a way that I can hook into the “Add ServiceStack Reference”/NativeTypes functionality to intercept the processing of requests? The main thing that I’m looking to do is allow extra parameter(s) in the header of that file that can then be processed, it would only be to add some props to request DTO : NativeTypesBase and act on them to filter down the MetadataTypes used to generate the code.

I have a few questions around this:

  1. Is it possible to hijack the various NativeTypes routes (e.g. /types/csharp) to do some processing? I’m keen not to touch the generation as it’s all there. I’ve had a read of the RouteResolution but seems like the FIFO will mean that NativeTypesService will always consume the route which is why I looked at implementing a new service (details below) which leads to a lot of duplication of what you already have.
  2. If it’s not possible to do this, is there a reasonably straight forward way to use all of the code gen/NativeTypes metadata processing that’s already there? The generators internally use NativeTypesFeature but this always registers the NativeTypesService, is there any way to extend this or customise how it processes requests?

I’ve managed to get a very crude implementation working where I allow a service reference to be added but only for a subset of DTOs. I’ve added a new //OnlyTypes: parameter to the header of the Services.dtos.cs file that is generated. I can then intercept the request in a new service and use the predicate parameter of INativeTypesMetadata.GetMetadataTypes to filter to the required types.

The following code is a stripped out sample of what I’ve done…

public class MyPlugin : IPlugin {
  public MyPlugin {
    // Remove this to avoid NativeTypeServices being registered
    HostContext.AppHost.Plugins.RemoveAll(x => x is NativeTypesFeature);
  }

  public void Register(AppHost appHost) {
    // new'ing feature only to get MetadataTypesConfig
    appHost.Register<INativeTypesMetadata>(new NativeTypesMetadata(appHost.Metadata, new NativeTypesFeature().MetadataTypesConfig));
    appHost.RegisterService<MyNativeTypesService>();
  }
}

public class MyNativeTypesService : Service {
  public INativeTypesMetadata NativeTypesMetadata {get;set;}
	
  [Route("/types/csharp")]
  public class MyTypesCSharp : NativeTypesBase {
      public string OnlyTypes {get;set;} // new param
  }
	
  [AddHeader(ContentType = MimeTypes.PlainText)]
  public object Any(TypesCSharp request) {
      // Generator internally calls GetPlugin<NativeTypesFeature>(); so need to add despite removing
      HostContext.AppHost.Plugins.Add(new NativeTypesFeature());
      // ..snip...
      var csharp = new CSharpGenerator(typesConfig).GetCode(metadataTypes, Request);
		
      // ..string manip to add parameter into csharp..
      return csharp
  }
}

As you can see what I have is pretty rough and by not registering the NativeTypesFeature I’m sure to run into issues I’ve not yet forseen.

Any suggestions/pointers would be great.

Add ServiceStack Reference isn’t customizable beyond what’s available in the NativeTypesFeature.

You should definitely not be adding any plugins in a Service at runtime, plugins should only be registered in AppHost.Configure() that gets executed once on Startup. I’m also not sure why you’re just not modifying the existing plugin instead of registering a new NativeTypesFeature plugin.

If you just want to modify the text response sent back to the server you can just override OnPostExecuteServiceFilter() in your AppHost and modify the C# DTO’s returned, e.g:

public override object OnPostExecuteServiceFilter(IService service, 
    object response, IRequest req, IResponse res)
{
    return httpReq.Dto is TypesCSharp
        ? ((string)response).Replace("...", "...")
        : response;
}

Similarly you can also register a Response Converter to do something similar.

Otherwise you can just create your own customized version of the C# Service hosted on a different route, e.g:

[Exclude(Feature.Soap)]
[Route("/types/csharp2")]
public class MyTypesCSharp : NativeTypesBase { }
//...

[AddHeader(ContentType = MimeTypes.PlainText)]
public object Any(MyTypesCSharp request)
{
    if (request.BaseUrl == null)
        request.BaseUrl = Request.GetBaseUrl();

    var typesConfig = NativeTypesMetadata.GetConfig(request);
    var metadataTypes = NativeTypesMetadata.GetMetadataTypes(Request, typesConfig);
    var csharp = new CSharpGenerator(typesConfig).GetCode(metadataTypes, base.Request);
    return csharp;
}

At the risk of muddying the waters here, the essence of the issue is this.

If we use the following option in the header when adding a service reference

//IncludeTypes: MyDto

It will generate a file with that DTO but not include any complex sub types or response types. In the case of a DTO with complex sub types it will produce a file that doesn’t compile.

We want a central registry of services exposed via a single service. This service will behave exactly like the current NativeTypesFeature with one key difference. It will not itself contain the types but aggregate the metadata from the /types endpoint for each service’s DTO it is to include and include the reference types to produce a compilable file.

The code gen will be handled by the single service but currently the /types endpoint cannot filter types, nor optionally include reference types from any filters.

This makes operating on the full set of metadata without the benefits and convenience of reflection more work or requires that every service take a dependence on a customised version of NativeTypesFeature.

We’ll go that route if need be but could also see about submitting a PR if this is both non-breaking and useful for others as it allows the service reference functionality in all IDE’s and languages to work across distributed services using discovery because when you don’t know where your services are, you can’t take a reference on them.

Hope that didn’t confuse things even more!!!

Still not clear on what the feature is as not sure what you mean by not being able to filter types as we have both a whitelist and blacklist of types to include. But sure if you can put together a PR I can take a look, otherwise it sounds like you could benefit from a Custom C# Service where you can modify typesConfig to have the configuration you want.

Opened PR https://github.com/ServiceStack/ServiceStack/pull/1034

Excellent, thanks for merging, we can now create a single self-hosted service ex. http://api.acme.com and make calls to generate references in any language, any IDE all across multiple services.

So http://api.acme.com/types/fsharp?includeTypes=Svc1DTO.*,Svc2DTO.* can use service discovery to find the service for each DTO

http://svc1/types/metadata?includeTypes=Svc1DTO.*
and
http://svc2/types/metadata?includeTypes=Svc2DTO.*

We can now point our ‘Add Service Reference’ url to this api service and make a single call to return everything we need.

Our api service will have a customised NativeTypesFeature which will stitch these metadata responses together and generate the file for the requested language. :smile:

We will likely include in this api service a developer-friendly UI using a plugin @fermin is currently working on for providing aggregated global services documentation and possibly a cart-esque feature to.‘pick and mix’ service DTOs

1 Like

How wold someone do this in appHost?

Override OnPostExecuteServiceFilter in your AppHost.