Skip to content

Same function names in multiple namespaces is unsupported #151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
lkingsford opened this issue Jun 26, 2017 · 5 comments
Closed

Same function names in multiple namespaces is unsupported #151

lkingsford opened this issue Jun 26, 2017 · 5 comments

Comments

@lkingsford
Copy link

lkingsford commented Jun 26, 2017

Per https://github.com/Microsoft/aspnet-api-versioning/wiki/How-to-Version-Your-Service, you should be able to create multiple versions of a controller by splitting them over multiple name-spaces, and using the same names. v2.1.0 is failing at this, and producing (for my case at least) the following error:

{
    "Message": "An error has occurred.",
    "ExceptionMessage": "Multiple types were found that match the controller named 'GetWebserviceVersion'. This can happen if the route that services this request ('api/v{version:apiVersion}/wsversion') found multiple controllers defined with the same name but differing namespaces, which is not supported.\r\n\r\nThe request for 'GetWebserviceVersion' has found the following matching controllers:\r\nPSConnectWS.PSConnect_5_0.GetWebserviceVersionController\r\nPSConnectWS.PSConnect_4_0.GetWebserviceVersionController",
    "ExceptionType": "System.InvalidOperationException",
    "StackTrace": "   at System.Web.Http.Dispatcher.DefaultHttpControllerSelector.SelectController(HttpRequestMessage request)\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
}

This directly contradicts the documentation.

I can provide a code example if necessary.

@commonsensesoftware
Copy link
Collaborator

commonsensesoftware commented Jun 26, 2017

Based on the stack trace, it appears that you are using Web API and also have not called configuration.AddApiVersioning. Give that a try. If it still isn't working, please share your relevant startup configuration.

@lkingsford
Copy link
Author

I've got a Global.asax.vb containing: (Apologies for the vb.net - legacy project)

Public Class Global_asax
    Inherits System.Web.HttpApplication

    Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
        ' Fires when the application is started
        LogHelper.Log("[Webservice] Application Started. Version: " & Service1.WEBSERVICE_VERSION)

        Dim configuration = New HttpConfiguration
        configuration.MapHttpAttributeRoutes()
        ' We need to consider if 4.0 is the correct version to default to - see 
        ' [https://github.com/Microsoft/aspnet-api-versioning/wiki/API-Version-Selector] for use of selector
        configuration.AddApiVersioning(Sub(options As Microsoft.Web.Http.Versioning.ApiVersioningOptions)
                                           options.AssumeDefaultVersionWhenUnspecified = True
                                           options.DefaultApiVersion =
                                               New Microsoft.Web.Http.ApiVersion(4, 0)
                                           options.ApiVersionSelector =
                                               New Microsoft.Web.Http.Versioning.LowestImplementedApiVersionSelector(options)
                                       End Sub)
... 
        RouteTable.Routes.MapHttpRoute("UploadFile",
                                       "api/v{version:apiVersion}/uploadfile",
                                       defaults:=New With {.controller = "Upload"})
...
    End Sub

The actual controllers are as:

<RoutePrefix("api/upload")>
<ApiVersion("4.0")>
Public Class UploadController
    Inherits ApiController

    ' Upload file to container
    <ActionName("uploadfile")>
    <HttpPut>
    <Route("api/v{version:apiVersion}/uploadfile")>
    Public Async Function UploadFile(ByVal request As HttpRequestMessage) As Threading.Tasks.Task(Of Boolean)
   ...

Have I just got this completely wrong?

@commonsensesoftware
Copy link
Collaborator

I don't mind VB; it's one of the many languages I know. Nor do I consider it legacy. ;)

I think the fundamental issue is that you are trying to support an implicit API version in the URL, which isn't really possible - with or without this library. Even with default parameter values, you can't make it work. For example, you cannot define api/foo/{id=42}/bar and then request api/foo/bar to implicitly match api/foo/42/bar. This is just how URLs and Web API work. This behavior is called out in the known limitations and there have been quite a few discussions here about it.

All is not lost however. You can get the results you want, but you'll have to declare duplicate routes on your controller of the default route path. This isn't a huge deal, but it can be a pain to manage. Choosing that behavior will require you remove and add routes between controllers every time the default route changes, if ever. For a small number of routes, that's workable. However, if you have a bunch of controllers, it could become untenable. If you never change your default API version, it's a non-issue.

There are a few alternatives if you really want to make this work. If you use the query string instead of a URL segment, you won't have this problem. Query string parameters are not part of the path and are therefore optional. This makes it easy to implement a default API version. Since you're using Web API, the other option is to implement a custom IDirectRouteProvider as another user did in Issue #73.

Given your current setup, here's how you can make it work with multiple routes on the same controller as also described in the known limitations:

<ApiVersion("4.0")>
<RoutePrefix("api")>
Public Class UploadController
    Inherits ApiController

    <HttpPut>
    <Route("upload")>                       ' PUT api/upload
    <Route("v{version:apiVersion}/upload")> ' PUT api/v4/upload
    Public Function Put(ByVal request As HttpRequestMessage) As Task(Of IHttpActionResult)
       Dim response As IHttpActionResult = StatusCode(HttpStatusCode.Created);
       Return Task.FromResult(response);
    End Function
End Class

Since you've configured 4.0 to be your default API version and you allow clients to not specify an API version, this means the following routes both match 4.0:

  • api/upload - implicitly matches 4.0 because no API version was specified
  • api/v4/upload - explicitly matches 4.0

Both routes map to the same controller and action.

I hope that gets you on your way. Feel free to ask more questions.

@commonsensesoftware
Copy link
Collaborator

@lkingsford Did this solution happen to work for you?

@lkingsford
Copy link
Author

Thanks for giving us options. We came to the conclusion that we had enough API calls that it was going to be untenable to maintain in its current form. We ended up porting the older parts of the API to where the long term home of the new API functions is going to be.

Thank you so much for your help. I'm sorry that I wasn't able to use it better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants