domaindrivendev / Swashbuckle.WebApi

Seamlessly adds a swagger to WebApi projects!
BSD 3-Clause "New" or "Revised" License
3.06k stars 679 forks source link

Wrong discovered Web API paths, multiple duplication of Class that implements ApiController #626

Open radrad opened 8 years ago

radrad commented 8 years ago

Hi, I am trying to build a Swashbuckle module for Dotnetnuke (DNN) CMS open source project. I customized the Generated Swagger Docs. The problem I am facing is that I get many duplication when I ran this code (inside IDocumentFilter.Apply method: foreach (PathItem path in swaggerDoc.paths.Values) { //output each path }

/DesktopModules/CoreMessaging/API/dnn.authservices.jwt.services.mobilehelpercontroller/ModuleDetails /DesktopModules/InternalServices/API/dnn.authservices.jwt.services.mobilehelpercontroller/ModuleDetails /DesktopModules/Journal/API/dnn.authservices.jwt.services.mobilehelpercontroller/ModuleDetails OK:/DesktopModules/JwtAuth/API/dnn.authservices.jwt.services.mobilehelpercontroller/ModuleDetails /DesktopModules/MemberDirectory/API/dnn.authservices.jwt.services.mobilehelpercontroller/ModuleDetails

Only one entry is correct (that has OK:

DNN has a wrapper interface for mapping routers because each custom module can bring their own Web API implementation that inherits from DnnApiController which in turns inherits from ApiController

This is the code that is used to register a route for a module: using DotNetNuke.Web.Api; namespace Dnn.AuthServices.Jwt.Services { public class ServiceRouteMapper : IServiceRouteMapper { public void RegisterRoutes(IMapRoute mapRouteManager) { mapRouteManager.MapHttpRoute( moduleFolderName:"JwtAuth", routeName: "default", url: "{controller}/{action}", namespaces: new[] { "Dnn.AuthServices.Jwt.Services" }); } } }

Noticed that "Dnn.AuthServices.Jwt.Services" is one of the namespaces within a module (which is basically a .dll assembly), where DNN will scan to find Web API implementation.

This path: /DesktopModules/JwtAuth/API/dnn.authservices.jwt.services.mobilehelpercontroller/ModuleDetails is kind of OK. It should be: /DesktopModules/JwtAuth/API/mobilehelpercontroller/ModuleDetails

I am not sure why I get Class with the whole namespace in the path. This same Class incorrectly appears in other paths.

radrad commented 8 years ago

Just a small correction It should not be: /DesktopModules/JwtAuth/API/mobilehelpercontroller/ModuleDetails but: It should be: /DesktopModules/JwtAuth/API/mobilehelper/ModuleDetails (so without controller)

radrad commented 8 years ago

Anybody??

domaindrivendev commented 8 years ago

I don't think this is a Swashbuckle issue. I'm not that familiar with DNN but I would hazard a guess that it's related to the routing trickery being applied by DNN and the affect that has on ApiExplorer, the WebApi metadata layer that Swashbuckle depends on.

You can confirm this theory by creating the following test controller and inspecting the paths returned by ApiExplorer:

public class TestController : ApiController
{
    [HttpGet]
    [Route("api-paths")]
    public IEnumerable<string> GetApiPaths()
    {
        var apiExplorer = Configuration.Services.GetApiExplorer();

        return apiExplorer.ApiDescriptions.Select(apiDesc => apiDesc.RelativePath);
    }
}

I think you'll still see the same duplication

radrad commented 8 years ago

@domaindrivendev,

I pointed at the very bottom this: I don't see anything wrong here. I provided you with the detailed analysis of what is returned from GetApiPaths() mehtod call that I embeded within my DNN module's ApiController's implemenation: https://drive.google.com/file/d/0B1I1nQTGClv3eEMwbzhwLUsxZ1k/view?usp=sharing

Is there a way we can work offline on this since I really wanted to contribute DNN comunity a module that will expose (conditionally) Web API's from modules that want to register with DNN's Services Framework. We could debug this issue together by digging deeper into DNN source. If we manage to do it, it will allow many thousands of DNN developer to utilize automatic code generation based on Swagger document.

DNN modules can provide Web Api interface by inheriting DnnApiController : ApiController and registering a route using IServiceRouteMapper inherited class.

Instead of standard way of registering Web Api Controller like this: config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } );

... DNN registers it like this (there is a special class that is used for registering routers. DNN on start-up iterates over each class that implements IServiceRouteMapper and calls its RegisterRoutes method thus registering all modules' routes. Notice "namespaces" argument used to tell System.Web.Routing.Route in what namespace to search for ApiController implementations.

public class ServiceRouteMapper : IServiceRouteMapper { //RegisterRoutes is used to register the module's routes public void RegisterRoutes(IMapRoute mapRouteManager) { mapRouteManager.MapHttpRoute( moduleFolderName:"Swagger", routeName: "default", url: "{controller}/{itemId}", defaults: new { itemId = RouteParameter.Optional }, namespaces: new[] { "Ravco.Modules.Swagger.Services" }); } }

This is the final code called in DNN to register a Route. I don't see anything wrong here. private Route MapHttpRouteWithNamespace(string name, string url, object defaults, object constraints, string[] namespaces) { Route route = _routes.MapHttpRoute(name, url, defaults, constraints); if(route.DataTokens == null) { route.DataTokens = new RouteValueDictionary(); } route.SetNameSpaces(namespaces); route.SetName(name); return route; }

Example of parameters passed in: name "Dnn/ContactList-default-0" url "DesktopModules/Dnn/ContactList/API/{controller}/{action}" defaults null constraints null namespaces {string[1]} [0] "Dnn.ContactList.Spa.Services" string

Note: There can be more than one ApiController per namespace included in namespaces collection like above:Dnn.ContactList.Spa.Services

Why do we have duplication!?

Can you please read this article and maybe you will notice something and can be a good preparation for DNN debugging?

DNN service framework is explained in this article. http://www.dnnsoftware.com/community-blog/cid/145351/digging-deep-into-services-framework

Thanks, Rad

mavjt commented 7 years ago

Radrad, did you ever solve this issue? I'm struggling just to get to see the Swagger UI on a DNN module. I've created a module with some API calls and can get them working, eg http://site.me/desktopmodules/HMS_WebAPI/API/Quote/Notify But when I add /swagger onto the end of that URL I get a 404. I presume its because of DNNs urlrewriting? But not sure how to get it working

radrad commented 7 years ago

@mavjt I uploaded my DNN module I worked on a while back at: https://drive.google.com/open?id=0B1I1nQTGClv3azEyT3NNU21VS0E I believe the hook is in IServiceRouteMapper's RegisterRoute method (your own implementation within your module - You can extract the zip and see my implementation and I have a line in there with a call to: new SwaggerProvider().Initialize(). So I am not sure about GUI, but this is about creating an endpoint for GUI and docs and in Initialize I tried to find what modules want their API endpoints exposed. You will see what endpoints I set in there and I don't recall seeing the GUI because I saw above problem of duplication. If you want to work with me on this I can probably have a session with you over skype. You can email me your info at radoslav AT everestkc DOT net so I can open my solution and show you where I stopped.

parasdaryanani commented 3 years ago

@radrad any chance you can place the source code of your DNN Module for swagger on github? The google drive link is broken, and I'm willing to actively contribute and get this working.