How to separate controllers by areas in Swagger 3? - c#

I'm implementing Swagger in an integrations api and in that case it was necessary to separate these services by area for project organization issues, but I have the following problem, some controllers have common names in different areas and when swagger generates the documentation, even separated by area, if it has the same controller name together, I would like to know if there is any way to separate the controllers by area as well? Below are two integrations: Mercado Livre and Skyhub, both are in different areas, but if they notice the categories they joined because they have the same controller name.
! https://tribofoxcombr-my.sharepoint.com/personal/leonardo_silva_tribofox_com_br/_layouts/15/onedrive.aspx?id=%2Fpersonal%2Fleonardo_silva_tribofox_com_br%2FDocuments%2FCompartilhado%2FCapturar%2EPNG&parent=%2Fpersonal%2Fleonardo_silva_tribofox_com_br%2FDocuments%2FCompartilhado&slrid=3e37b79e-f020-7000-b380-a8a0e7785d97
Thanks.

You can use:
[ApiExplorerSettings(GroupName = "Group")]
public class SomethingController : Controller
{
And in declaration
services.AddSwaggerGen(options =>
{
options.SwaggerDoc(version,
new Info
{
Title = name,
Version = version
}
);
options.DocInclusionPredicate((_, api) => !string.IsNullOrWhiteSpace(api.GroupName));
options.TagActionsBy(api => api.GroupName);
});

Related

C# .NET Enable Swagger UI only for specific api controller or for api controllers within module (project)

Is there any way to configure Swagger so that it generates UI & documentation only for a certain API controller within solution, or for a group of API controllers that belong to specific module (project withing solution)?
My solution consist of 50+ projects, several of them contains many API controllers, but I need to enable Swagger only for one of them, located in specific project.
I know about [ApiExplorerSettings(IgnoreApi = true)] attribute, but this way I would need to set this attribute to all API controllers which I don't need, and I would like just to mark the specific API controller which I want to use swagger on.
Is there any way to do that?
You can use conventions for this when registering your controllers.
If you create a new IActionModelConvention, something like this:
public class WhitelistControllersConvention : IActionModelConvention
{
public void Apply(ActionModel action)
{
if (action.Controller.ControllerName == "Item")
{
action.ApiExplorer.IsVisible = true;
}
else
{
action.ApiExplorer.IsVisible = false;
}
}
}
Then use it when configuring swagger in Startup:
services.AddControllers(c =>
{
c.Conventions.Add(new WhitelistControllersConvention());
});
Then you can control which controllers get included. In my example I'm just doing it off the name (only including ItemController), but you can change that to identify the controllers that you want however you want to do it.

Route to multiple projects

I'm trying to create a webapi solution consisting of the main project with other projects as additional endpoints. If the additional projects could not be dependency of the main, that would be great too.
Structure would be something like:
MainWebApiProject accessed as example.com/api/Values
SubProject1 accessed as example.com/sub1/Values
SubProject2 accessed as example.com/sub2/Values
I've created a solution with the 3 projects. I tried setting up the routing in the startup.cs of the MainWebApiProject using Areas and without.
example with the Areas
app.UseMvc(routes =>
{
routes.MapAreaRoute("subProject1_route", "subProject1", "sub1/{controller}/{action}/{id?}");
routes.MapAreaRoute("subProject2_route", "subProject2", "sub2/{controller}/{action}/{id?}");
routes.MapRoute("default_route", "{controller}/{action}/{id?}");
});
then the controller inside SubProject1
[Area("subProject1")]
[Route("sub1/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "sub 1 value1", "sub 1 value2" };
}
}
Just not quite sure what I'm missing or doing wrong, or if this can really be done.
I don't see how it is possible if the sub-projects aren't configured as dependency for the main project.
Areas in the new ASP.NET Core, is a feature to logically separate your namespace and views (where views are defined in the area). You would still need to have controllers in the main project to listen for that route.
I wonder, how do you run the sub projects? separately and totally independent of the main one? In that case you will definitely need a different protocol/port for them to be executed and routes will simply not work for them (within the main project).

ASP.NET WebApi versioning not working

I'm trying to implement api versioning following this tutorial. So in my startup I have:
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
configuration.MapHttpAttributeRoutes(constraintResolver);
configuration.AddApiVersioning()
and my controllers:
[Route("api/v{version:apiVersion}/my")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class MyV1Controller
[Route("api/v{version:apiVersion}/my")]
[ApiVersion("3.0")]
public class MyV3Controller
When I request for http://localhost/api/v1.0/my I get an error
Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.\r\n\r\nThe request has found the following matching controller types: \r\nMyV1Controller\r\nMyV2Controller
Could you please advice how to make controller versioning to work?
I took a break and I remembered that in my project I have a custom IHttpControllerSelector implementation which extends DefaultHttpControllerSelector.
configuration.Services.Replace(typeof(IHttpControllerSelector), new ApiControllerSelector(config));
After I removed it versioning started to work.
Executing configuration.AddApiVersioning sets ApiVersionControllerSelector in ServicesContainer. It was accidently replaced with my custom implementation.

String literal in resource path

I'm writing a WebApi OData service (using Microsoft.AspNet.OData v6 package) for an ERP system and I'm running into a bit of issues.
The system has multiple modules (Sales, Purchase, Management...) and I'm trying to reflect this in my service.
For example, I have an entity "SalesOrders" and an entity "PurchaseOrders". These are different from each other, and I would like to expose these via different OData routes.
For SalesOrders, I'd like my route to be ~/odata/Sales/Orders while PurchaseOrders should be under ~/odata/Purchase/Orders.
Unfortunately I've been unable to get this to work, because OData keeps telling me that "Sales" and "Purchase" are not valid OData path segments (which is correct, as these are not my entity): Resource not found for segment 'Sales'
I could use different models, linked to different routes (using a workaround for a ModelBoundODataRoute found here), but then I won't have a unified $metadata document. I could probably get away with writing a custom ControllerSelector (possibly a NamespaceAwareControllerSelector, or maybe using some kind of marker attributes), but that's a whole different can of worms I'd rather avoid if possible. Last resort would be to just not try to divide this up and use entities called PurchaseOrder, SalesOrder and so on.
WebApiConfig.Register:
public static void Register(HttpConfiguration config)
{
//OData routes
var salesModel = ModelBuilder.GetSalesModel(); // this just builds an edm model with the required entities
config.MapModelBoundODataServiceRoute(routeName: "salesRoute", routePrefix: "odata/Sales", configureAction: action =>
{
action.AddDefaultODataServices()
.AddService<IEdmModel>(ServiceLifetime.Singleton, s => salesModel)
.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp =>
{
return ODataRoutingConventions.CreateDefault()
.Concat(new[] { new ModelBoundODataAttributeRoutingConvention(salesModel, "salesRoute", config) });
})
});
var purchaseModel = ModelBuilder.GetPurchaseModel();
config.MapModelBoundODataServiceRoute(routeName: "purchaseRoute", routePrefix: "odata/Purchase", configureAction: action =>
{
action.AddDefaultODataServices()
.AddService<IEdmModel>(ServiceLifetime.Singleton, s => purchaseModel)
.AddService<IEnumerable<IODataRoutingConvention>>(ServiceLifetime.Singleton, sp =>
{
return ODataRoutingConventions.CreateDefault()
.Concat(new[] { new ModelBoundODataAttributeRoutingConvention(purchaseModel, "purchaseRoute", config) });
})
});
}
Is there any way to add a string literal (not mapping to a resource) to the OData route prefix, while preserving a single unified $metadata document?
An alternative option may be to use Containment to do this
This would involve having a singleton named Sales with a contained navigation property called Orders and a singleton named Purchase with a contained navigation property named Orders. This does raise the interesting question of what should be returned when you ask for the singleton Sales using the URL ~/odata/Sales but it would give you the URLs that you are looking for. For more details, see the link below:
https://learn.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/odata-containment-in-web-api-22

ASP.Net MVC 4 w/ AttributeRouting and multiple RoutePrefix attributes

TL;DR
I need a way to programtically choose which RoutePrefix is chosen when generating URLs based on the properties of a user in my MVC app
Not TL;DR
I have an MVC 4 app (with the AttributeRouting NuGet package)
Due to the requirements of the hosting environment I have to have two routes for a lot of my actions so that the hosting environment can have different permissions for access.
I am solving this by decorating my controller with with [RoutePrefix("full")] [RoutePrefix("lite)]. which allows each action method to be accessed via /full/{action} and /lite/{action}.
This works perfectly.
[RoutePrefix("full")]
[RoutePrefix("lite")]
public class ResultsController : BaseController
{
// Can be accessed via /full/results/your-results and /lite/results/your-results and
[Route("results/your-results")]
public async Task<ActionResult> All()
{
}
}
However, each user should only use either full or lite in their urls, which is determined by some properties of that user.
Obviously when I use RedirectToAction() or #Html.ActionLink() it will just choose the first available route and won't keep the "correct" prefix.
I figured I can override the RedirectToAction() method as well as add my own version of #Html.ActionLink() methods.
This will work, but it will involve some nasty code for me to generate the URLs because all I get is a string representing the action and controllers, but not the reflected types. Also there might be route attributes such as in my example, so I am going to have to replicated a lot of MVCs built in code.
Is there a better solution to the problem I am trying to solve?
How about something like:
[RoutePrefix("{version:regex(^full|lite$)}")]
Then, when you create your links:
#Url.RouteUrl("SomeRoute", new { version = "full" })
Or
#Url.RouteUrl("SomeRoute", new { version = "lite" })
You could even do the following to just keep whatever was already set:
#Url.RouteUrl("SomeRoute", new { version = Request["version"] })
I ended up finding a solution to this
I just overrided the default routes to include this. ASP.Net automatically keeps the usertype value and puts it back in when it regenerates the routes
const string userTypeRegex = "^(full|lite)$";
routes.Add("Default", new Route("{usertype}/{controller}/{action}/{id}",
new { controller = "Sessions", action = "Login", id = UrlParameter.Optional }, new { usertype = userTypeRegex }));
I found that this didn't work with the Route or RoutePrefix attributes, and so I had to remove all of them. Forcing me to add specific routes in these cases
routes.Add("Profile-Simple", new Route("{usertype}/profile/simple",
new { controller = "ProfileSimple", action = "Index" }, new { usertype = userTypeRegex }));
I thought that a half-dozen hard coded routes in my RouteConfig file was a better solution than having to manually add values to each place I generated a URL (as in Chris's solution).

Categories

Resources