Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions Swagger.Net.WebAPI/App_Start/SwaggerNet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,17 @@ public static class SwaggerNet
{
public static void PreStart()
{
RouteTable.Routes.MapHttpRoute(
name: "SwaggerResourceList",
routeTemplate: "apidocs/swagger",
defaults: new { swagger = true, controller = "Swagger", action = "GetResourceList" }
);
RouteTable.Routes.MapHttpRoute(
name: "SwaggerApiDeclaration",
routeTemplate: "apidocs/{controllerName}",
defaults: new { swagger = true, controller = "Swagger", action = "GetApiDeclaration" }
);
//RouteTable.Routes.MapHttpRoute(
// name: "SwaggerResourceList",
// routeTemplate: "api/swagger",
// defaults: new { swagger = true, controller = "Swagger", action = "GetResourceList" }
//);
//RouteTable.Routes.MapHttpRoute(
// name: "SwaggerApiDeclaration",
// routeTemplate: "apidocs/{controllerName}",
// defaults: new { swagger = true, controller = "Swagger", action = "GetApiDeclaration" }
//);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think value is lost by doing this only by convention.

Here's my main use case now: My root (/) app is a legacy webforms app. On top of that, I have a single-page javascript application (just html+js) in a sub-directory, and in /restapi I have a MVC (eventually WebAPI) app that is purely my API, called by the single-page javascript app. Using the default forced convention of ~/api means my urls become /restapi/api/ .. which is a bit redundant.

I could live with the default being /api/swagger but it has to be configurable. I think it also slightly confuses things - the developer's actual application API is /api, swagger is a separate concern and therefore it makes sense -- to me, anyway -- that it lives in a different place.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see documentControllerAlias for configuration


SwaggerConfiguration.DefaultConfiguration = SwaggerConfiguration.CreateDefaultConfig(GlobalConfiguration.Configuration);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is essential for configuration -- this is where the developer user of the library would configure it.

}
}
6 changes: 6 additions & 0 deletions Swagger.Net.WebAPI/Controllers/BlogPostsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@

namespace Swagger.Net.WebApi.Controllers
{
/// <summary>
/// This is blog posts controller summary
/// </summary>
/// <remarks>
/// This is blog posts controller remarks
/// </remarks>
public class BlogPostsController : ApiController
{
// GET api/blogposts
Expand Down
7 changes: 7 additions & 0 deletions Swagger.Net.WebAPI/Global.asax.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web;
using System.Web.Http;
using System.Web.Http.Description;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
Expand All @@ -22,6 +24,11 @@ protected void Application_Start()
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

var baseType = HttpContext.Current.ApplicationInstance.GetType().BaseType;
var assemblyname = Assembly.GetAssembly(baseType).GetName().Name;
var path = HttpContext.Current.Server.MapPath("~/bin/" + assemblyname + ".xml");
GlobalConfiguration.Configuration.Services.Replace(typeof(IDocumentationProvider),new XmlCommentDocumentationProvider(path));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in the library, or at least in the App_start\swaggerNet.cs file, not left up to App global.asax. Eg there should never have to be instructions like "install via nuget, then go add these lines to global.asax.."

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine. I just tossed it in the first place I thought of.

}
}
}
2 changes: 1 addition & 1 deletion Swagger.Net.WebAPI/Views/Home/Index.cshtml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h1>Welcome to ASP.NET Web API!</h1>
Navigate to <a href="/docs">/docs</a> for the Swagger UI docs. These are not included in the NuGet package.<br />
Navigate to <a href="/apidocs/swagger">/api/swagger</a> to see the JSON specification emitted.<br />
Navigate to <a href="/api/swagger">/api/swagger</a> to see the JSON specification emitted.<br />
You may then navigate to each api path to see the JSON documentation for each ApiController individually.
1 change: 1 addition & 0 deletions Swagger.Net.WebAPI/Web.config
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-Swagger.Net.WebApi-20120826143632;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-Swagger.Net.WebApi-20120826143632.mdf" />
</connectionStrings>
<appSettings>
<add key="document_controller_alias" value="api" />
<add key="webpages:Version" value="2.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="PreserveLoginUrl" value="true" />
Expand Down
2 changes: 1 addition & 1 deletion Swagger.Net.WebAPI/docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<script type="text/javascript">
$(function () {
window.swaggerUi = new SwaggerUi({
discoveryUrl: "/apidocs/swagger",
discoveryUrl: "/api/swagger",
apiKey: "",
dom_id:"swagger-ui-container",
supportHeaderParams: false,
Expand Down
57 changes: 57 additions & 0 deletions Swagger.Net/Factories/ResourceListingFactory.cs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Swagger.Net/Swagger.Net.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Entity" />
<Reference Include="System.Net.Http" />
Expand All @@ -53,6 +54,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Factories\ResourceListingFactory.cs" />
<Compile Include="Models\Api.cs" />
<Compile Include="Models\ApiDeclaration.cs" />
<Compile Include="Models\ApiErrorResponse.cs" />
Expand Down
53 changes: 36 additions & 17 deletions Swagger.Net/SwaggerConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,60 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Http;
using System.Web.Http.Description;

namespace Swagger.Net
{

public class SwaggerConfiguration
{
/// <summary>
/// Static instance used by default
/// </summary>
public static SwaggerConfiguration DefaultConfiguration { get; set; }

/// <summary>
/// The API version of your application
/// </summary>
public virtual string ApiVersion { get; set; }
private static SwaggerConfiguration _instance;

/// <summary>
/// Swagger-spec version
/// </summary>
private HttpContext _context;
private Collection<ApiDescription> _apiDescriptions;

public virtual string ApiVersion { get; set; }
public virtual string SwaggerVersion { get; set; }
public virtual string DocumentControllerAlias { get; set; }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to configure a route if you don't want to use default /api/docs



public virtual System.Web.Http.Description.IApiExplorer ApiExplorer { get; set; }
public Collection<ApiDescription> ApiDescriptions
{
get { return _apiDescriptions ?? GlobalConfiguration.Configuration.Services.GetApiExplorer().ApiDescriptions; }
set { _apiDescriptions = value; }
}

public static SwaggerConfiguration CreateDefaultConfig(HttpConfiguration httpConfig) {
public HttpContext HttpContext
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think HttpContext makes sense in Configuration. Context is created per-request. Configuration is meant to be initialized once at app start time. Perhaps our use of context this will not make a difference, but it would be easy to make a mistake in the future and end up using the wrong HttpContext object (eg: using the HttpContext.Current.Request object from app_start in a future request would be wrong).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm... hadn't thought of configuration having implied lifecycle. My intent here was to consolidate nonspecific inputs for use by swagger.

{
get { return _context ?? HttpContext.Current; }
set { _context = value; }
}

// Singleton
public static SwaggerConfiguration Instance
{
get { return _instance ?? (_instance = CreateDefaultInstance()); }
set { _instance = value; }
}


public static SwaggerConfiguration CreateDefaultInstance()
{
// todo: fetch from web.config
var alias = ConfigurationManager.AppSettings["document_controller_alias"] ?? "api";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My strong personal preference is not to use web.config variables for this.

Web.config is good for when the end-user of an application is configuring things, but not a development user who is consuming a library. I'd rather have config via the WebActivator App_Start\SwaggerNet.cs file, and if the developer implementing the library wants to use web.config values, it's up to them to wire that up (and more importantly, make sure that their web application uses it throughout).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either way works. I prefer settings in code as well

return new SwaggerConfiguration()
{
SwaggerVersion = "1.1",
ApiVersion = System.Reflection.Assembly.GetCallingAssembly().GetType().Assembly.GetName().Version.ToString(),
ApiExplorer = httpConfig.Services.GetApiExplorer()
ApiVersion = System.Reflection.Assembly.GetCallingAssembly().GetName().Version.ToString(),
DocumentControllerAlias = alias,
};
}


}
}
40 changes: 16 additions & 24 deletions Swagger.Net/SwaggerController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Web.Http;
using System.Linq;
using System.Text.RegularExpressions;
using Swagger.Net.Factories;

namespace Swagger.Net
{
Expand All @@ -22,44 +23,35 @@ public class SwaggerController : ApiController
/// <returns>JSON document representing structure of API</returns>
public HttpResponseMessage GetResourceList()
{
var config = SwaggerConfig ?? SwaggerConfiguration.DefaultConfiguration;
var httpContext = new HttpContextWrapper(HttpContext.Current); // TODO: testable way to pass context

var resourceListing = new Models.ResourceListing()
{
apiVersion = config.ApiVersion,
basePath = ResolveServerUrl("~", httpContext),
swaggerVersion = config.SwaggerVersion,
apis = from d in config.ApiExplorer.ApiDescriptions
where d.ActionDescriptor.ControllerDescriptor.ControllerType != this.GetType()
group d by d.ActionDescriptor.ControllerDescriptor.ControllerName into g
select new Models.ResourceListing.ResourceListingApi()
{
path = "/apidocs/" + g.Key
}
};
// Arrange
var config = SwaggerConfig ?? SwaggerConfiguration.Instance;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I used DefaultConfiguration instead of a singleton is because SwaggerConfig can be passed to the controller, and in fact, if you use an IoS container like autofac or unity etc, you can have it automatically inject a SwaggerConfig. Also makes unit tests easier; singletons are hard to test.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats why I'm not using the singleton unless the this.SwaggerConfig property in null. Controller property can be marked as dependency property for IoC. (as can swaggerConfiguration.Instance)

var factory = new ResourceListingFactory();

// Act
var resourceListing = factory.Create(config);

// Answer
return new HttpResponseMessage()
{
Content = new ObjectContent<Models.ResourceListing>(resourceListing, ControllerContext.Configuration.Formatters.JsonFormatter)
};
}

/// <summary>
/// Get the API Declaration for a particular controller
/// </summary>
public Models.ApiDeclaration GetApiDeclaration(string controllerName)
/// Get the API Declaration for a particular controller
/// </summary>
public Models.ApiDeclaration GetApiDeclaration(string id)
{
var config = SwaggerConfig ?? SwaggerConfiguration.DefaultConfiguration;
var config = SwaggerConfig ?? SwaggerConfiguration.Instance;
var httpContext = new HttpContextWrapper(HttpContext.Current); // TODO: testable way to pass context

return new Models.ApiDeclaration()
{
apiVersion = config.ApiVersion,
swaggerVersion = config.SwaggerVersion,
basePath = ResolveServerUrl("~", httpContext),
apis = from d in config.ApiExplorer.ApiDescriptions
where d.ActionDescriptor.ControllerDescriptor.ControllerName == controllerName
apis = from d in config.ApiDescriptions
where d.ActionDescriptor.ControllerDescriptor.ControllerName == id
group d by d.RelativePath into paths
select new Models.Api()
{
Expand All @@ -76,8 +68,8 @@ group d by d.RelativePath into paths
}

private static string ResolveServerUrl(string relativeUrl, HttpContextWrapper httpContext)
{
{
return httpContext.Request.Url.GetLeftPart(UriPartial.Authority) + VirtualPathUtility.ToAbsolute(relativeUrl);
}
}
}
}
Loading