Missing using statement when generating C# code

Hello,

When I generate C# code using the Visual Studio plugin I get code that cannot be compiled because many using statement are missing from the code.

I reproduced the issue in a smaller project here: https://www.dropbox.com/s/8pvribn7g8v4xf5/CodeGen.zip?dl=0

You can reproduce the issue by executing the Server project to start the application and open the DTO file in the test project and update it. You will see that the code cannot be compiled because of a missing using statement.

I am using version 4.5.4 of ServiceStack.

Please include as much relevent info like the using statement that’s missing and the DTO that causes the issue in the issue description. Hiding all details about the issue in a zip file hurts discoverability and makes it harder for anyone else to be able to help.

My project had a structure similar to this:

  • ApplicationService
    • Contains every service
  • ServiceModel
    • This folder contains every requests and models of the project. This is far from optimal and having a few dozen files in the same folder is a PITA.

I changed the structure to something like this:

  • ApplicationService
    • Contains every service (kept as is since it does not cause any issues right now)
  • ServiceModel
    • Service1
      • Requests of service1
    • Service2
      • Requests of service2
    • Models
      • Service1
        • Models used by service1
      • Service2
        • Models used by service2

The namespaces of the files under ServiceModel were changed to reflect the new structure and the application is working as expected. However if I try to generate the C# DTOs (http://localhost:33333/types/csharp) the generated code cannot be compiled because it cannot find the models used by the requests:

I verified and the models are present in the generated code. Which prompted me to create this post.

This is the code I used to reproduce the issue in a smaller project:

I have these requests:

using System;
using System.Net;
using CodeGen.Service.ApplicationServices;
using ServiceStack;

namespace CodeGen.Service.ServiceModel.Requests.WebHooks
{
    [Api(ApiDocConstants.WebHookApi)]
    [ApiResponse(HttpStatusCode.Unauthorized, ApiDocConstants.MustBeAuthenticated)]
    [ApiResponse(HttpStatusCode.BadRequest, ApiDocConstants.RequestFormatIsInvalid)]
    [Route("/webhooks/{Id}",
           Verbs = "PUT",
           Summary = "PREVIEW - Creates or updates a web hook registrations - {CreateOrUpdateWebHookRequest}",
           Notes = "PREVIEW - Creates or updates a web hook registrations.")]
    public class CreateOrUpdateWebHookRequest : IReturnVoid
    {
        public Guid Id { get; set; }

        public string Trigger { get; set; }

        public string Url { get; set; }
    }
}
using System;
using System.Net;
using CodeGen.Service.ApplicationServices;
using ServiceStack;

namespace CodeGen.Service.ServiceModel.Requests.WebHooks
{
    [Api(ApiDocConstants.WebHookApi)]
    [ApiResponse(HttpStatusCode.Unauthorized, ApiDocConstants.MustBeAuthenticated)]
    [ApiResponse(HttpStatusCode.BadRequest, ApiDocConstants.RequestFormatIsInvalid)]
    [ApiResponse(HttpStatusCode.NotFound, ApiDocConstants.RegistrationDoesNotExist)]
    [Route("/webhooks/{Id}",
           Verbs = "DELETE",
           Summary = "PREVIEW - Deletes a web hook registration - {DeleteWebHookRequest}",
           Notes = "PREVIEW - Deletes a web hook registration.")]
    public class DeleteWebHookRequest : IReturnVoid
    {
        public Guid Id { get; set; }
    }
}
using System.Net;
using CodeGen.Service.ApplicationServices;
using CodeGen.Service.ServiceModel.WebHooks;
using ServiceStack;

namespace CodeGen.Service.ServiceModel.Requests.WebHooks
{
    [Api(ApiDocConstants.WebHookApi)]
    [ApiResponse(HttpStatusCode.Unauthorized, ApiDocConstants.MustBeAuthenticated)]
    [Route("/webhooks",
           Verbs = "GET",
           Summary = "PREVIEW - Returns all web hook registrations - {GetAllWebHooksRequest}",
           Notes = "PREVIEW - Returns a specific web hook registration.")]
    public class GetAllWebHooksRequest : IReturn<GetAllWebHooksResponse>
    {
    }
}
using System;
using System.Net;
using CodeGen.Service.ApplicationServices;
using ServiceStack;

namespace CodeGen.Service.ServiceModel.Requests.WebHooks
{
    [Api(ApiDocConstants.WebHookApi)]
    [ApiResponse(HttpStatusCode.Unauthorized, ApiDocConstants.MustBeAuthenticated)]
    [ApiResponse(HttpStatusCode.BadRequest, ApiDocConstants.RequestFormatIsInvalid)]
    [ApiResponse(HttpStatusCode.NotFound, ApiDocConstants.RegistrationDoesNotExist)]
    [Route("/webhooks/{Id}", 
           Verbs = "GET", 
           Summary = "PREVIEW - Returns a specific web hook registration - {GetWebHookRequest}", 
           Notes = "PREVIEW - Returns a specific web hook registration.")]
    public class GetWebHookRequest : IReturn<WebHook>
    {
        public Guid Id { get; set; }
    }
}

This is the DTO used by the requests:

using System.Collections.Generic;

namespace CodeGen.Service.ServiceModel.WebHooks
{
    public class GetAllWebHooksResponse
    {
        public IReadOnlyList<WebHook> WebHooks { get; set; }
    }
}

I also have an “empty” service:

   class WebHookRestService : ServiceStack.Service
    {
        private static readonly ILog Logger = LogProvider.GetCurrentClassLogger();


        public WebHookRestService()
        {
        }

        public Task<object> Any(GetWebHookRequest request)
        {
            return Task.FromResult(new object());
        }

        public Task<GetAllWebHooksResponse> Any(GetAllWebHooksRequest request)
        {
            return Task.FromResult((GetAllWebHooksResponse)null);
        }

        public Task Any(CreateOrUpdateWebHookRequest request)
        {
            return Task.FromResult(3);
        }

        public Task Any(DeleteWebHookRequest request)
        {
            return Task.FromResult(new object());
        }
    }

When I use the VS plugin to generate C# code I obtain this generated code:

/* Options:
Date: 2016-11-21 11:48:55
Version: 4.54
Tip: To override a DTO option, remove "//" prefix before updating
BaseUrl: http://localhost:33333

//GlobalNamespace: 
//MakePartial: True
//MakeVirtual: True
//MakeInternal: False
//MakeDataContractsExtensible: False
//AddReturnMarker: True
//AddDescriptionAsComments: True
//AddDataContractAttributes: False
//AddIndexesToDataMembers: False
//AddGeneratedCodeAttributes: False
//AddResponseStatus: False
//AddImplicitVersion: 
//InitializeCollections: True
//IncludeTypes: 
//ExcludeTypes: 
//AddNamespaces: 
//AddDefaultXmlNamespace: http://schemas.servicestack.net/types
*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using ServiceStack;
using ServiceStack.DataAnnotations;
using CodeGen.Service.ServiceModel.Requests.WebHooks;


namespace CodeGen.Service.ServiceModel
{

    public partial class WebHook
    {
        public virtual Guid Id { get; set; }
        public virtual string Trigger { get; set; }
        public virtual string Url { get; set; }
    }
}

namespace CodeGen.Service.ServiceModel.Requests.WebHooks
{

    ///<summary>
    ///Web Hooks Registration Management
    ///</summary>
    [Route("/webhooks/{Id}", "PUT")]
    [Api("Web Hooks Registration Management")]
    [ApiResponse(401, "You must be authenticated to use this request")]
    [ApiResponse(400, "Request format is invalid")]
    public partial class CreateOrUpdateWebHookRequest
        : IReturnVoid
    {
        public virtual Guid Id { get; set; }
        public virtual string Trigger { get; set; }
        public virtual string Url { get; set; }
    }

    ///<summary>
    ///Web Hooks Registration Management
    ///</summary>
    [Route("/webhooks/{Id}", "DELETE")]
    [Api("Web Hooks Registration Management")]
    [ApiResponse(401, "You must be authenticated to use this request")]
    [ApiResponse(400, "Request format is invalid")]
    [ApiResponse(404, "Specified web hook registration does not exist")]
    public partial class DeleteWebHookRequest
        : IReturnVoid
    {
        public virtual Guid Id { get; set; }
    }

    ///<summary>
    ///Web Hooks Registration Management
    ///</summary>
    [Route("/webhooks", "GET")]
    [Api("Web Hooks Registration Management")]
    [ApiResponse(401, "You must be authenticated to use this request")]
    public partial class GetAllWebHooksRequest
        : IReturn<GetAllWebHooksResponse>           // *** GetAllWebHooksResponse is not found because of a missing using 
    {
    }

    ///<summary>
    ///Web Hooks Registration Management
    ///</summary>
    [Route("/webhooks/{Id}", "GET")]
    [Api("Web Hooks Registration Management")]
    [ApiResponse(401, "You must be authenticated to use this request")]
    [ApiResponse(400, "Request format is invalid")]
    [ApiResponse(404, "Specified web hook registration does not exist")]
    public partial class GetWebHookRequest
        : IReturn<WebHook>
    {
        public virtual Guid Id { get; set; }
    }
}

namespace CodeGen.Service.ServiceModel.WebHooks
{

    public partial class GetAllWebHooksResponse
    {
        public virtual IReadOnlyList<WebHook> WebHooks { get; set; }
    }
}

As you can see on line 85 of the generated code, the request GetAllWebHooksRequest implements IReturn<GetAllWebHooksResponse> however if I try to compile I get the error “The type or namespace ‘GetAllWebHooksResponse’ could not be found” :

The missing using statement is this:

using CodeGen.Service.ServiceModel.WebHooks;

The Request and Response DTOs should be in the same namespace - as they capture the surface area of each Service Contract, see the recommended Physical Project structure for the recoomended way DTOs should ideally be structured.

Note creating a new namespace per Service is going to cause a lot of unnecessary friction for clients. So the first recommendation would be to move Request + Response DTOs in the same namespace.

The preferred solution however would be to specify a single namespace, i.e:

GlobalNamespace: CodeGen.Service.ServiceModel

Which cause the least friction for clients as they don’t need to be concerned with your internal complex structure of how you’re structuring your models on the server - which also matches how all other languages (which don’t have .NET’s namespace semantics) will see your Application DTOs.

Otherwise you can add the missing namespace, i.e:

AddNamespaces: CodeGen.Service.ServiceModel.WebHooks

Which can also be configured in your AppHost:

var nativeTypes = this.GetPlugin<NativeTypesFeature>();
nativeTypes.MetadataTypesConfig.AddNamespaces = new List<string> {
    "CodeGen.Service.ServiceModel.WebHooks"
};

Also please don’t use interfaces in DTOs they are horrible for interoperability and are a source of frequent runtime issues:

public class GetAllWebHooksResponse
{
    public IReadOnlyList<WebHook> WebHooks { get; set; }
}

Any chances of fixing this on your side? Moving all requests/DTO under the same namespace will take a few weeks including recertification of the application.

Using the “AddNamespaces” feature does not work for me for the following reasons:

  • This project use a plugin architecture where I do not know in advance every namespaces I need to use
  • As I said above I have quite a few of these requests/DTO and if I concatenate them all I exceed IIS’ querystring limit of 2k characters

Would you be open to a PR for this? I was thinking of modifying this line https://github.com/ServiceStack/ServiceStack/blob/7ada8d013fe934b6bae8a3a507a80d4e14096bc4/src/ServiceStack/NativeTypes/CSharp/CSharpGenerator.cs#L54 to this:

            types.Operations.Each(x => {
                                      namespaces.Add(x.Request.Namespace);
                                      if (x.Response != null)
                                      {
                                          namespaces.Add(x.Response.Namespace);
                                      }
                                  });

As far as I can tell this should not introduce unwanted behavior.
I guess that it should also be applied to the other code generators.


Good catch about the interface I will remove it. Thanks

Ok I’ve added Response DTO Namespace to C#, F# + VB.NET Providers in this commit.

This change is available from v4.5.5 that’s now available on MyGet.

I tested the changes and everything looks OK.

Thanks for the quick fix as always!