Netcore how to remove endpoints/routes at runtime - asp.net-core

Is there a way to remove registered routes in net core web api project?
So I'm dynamically adding controllers in a net core web api project, the controller class code is not part of the project but dynamically loaded, compiled and add to the project at runtime
//code that compiles the c# class(controller)
var compiledAssembly= CompileHelper.Compile(csharpCode)
using (var controllerAssemblyMs = new MemoryStream(compiledAssembly))
{
var assemblyLoadContext = new SimpleAssemblyLoadContext();//inherits AssemblyLoadContext
var dynamicControllers = new MvcAssemblyPart(controllerAssemblyMs);
Services.AddControllersWithViews().ConfigureApplicationPartManager(apm =>
apm.ApplicationParts.Add(dynamicControllers));
}
so any new Endpoints/Routes are registered.
the problem is that because routes have been registered every time it compiles the code, if I change Get action to Post action, compile before and after, the endpoints end up in an erroneous state,
AmbiguousMatchException: The request matched multiple endpoints. Matches:
DynamicCodeProject.Controllers.DynamicallyAddedController.Post (string)
DynamicCodeProject.Controllers.DynamicallyAddedController.Get (string)
in which case I have to restart the application,
is it possible to remove routes/endpoints at runtime so I don't have to restart the application?

I has a same problem. My solution was remove previous controller from apm.ApplicationParts
var parts = _partManager.ApplicationParts.Where((x) => ((AssemblyPart)x).Assembly.GetName().Name == compiledAssembly.GetName().Name).ToList();
foreach(var part in parts)
{
apm.ApplicationParts.Remove(part);
}
apm.AddApplicationPart(compiledAssembly);
this work in asp.net core 5.0

Related

Get Swagger URL programmatically in ASP.NET Core

I'm using ASP.NET Core 5, and Swagger. I know how to use Swagger, and it works properly.
Swagger is served on foo:5001/swagger - but I need to determine that URL programmatically at runtime.
How can I do that?
I already tried:
Getting it by injecting IEnumerable<EndpointDataSource> into some helper/controller class, but that shows me all routes EXCEPT swagger's.
Getting it while setting up endpoint routing and inspecting IEndpointRouteBuilder, but once again it shows me all routes EXCEPT swagger's.
According to sources at https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs you can use an instance of class SwaggerUIOptions:
Register instance in DI container:
var options = new SwaggerUIOptions
{
RoutePrefix = "swagger"
};
options.SwaggerEndpoint("/swagger/v1/swagger.json", "waiting_list v1");
services.AddSingleton(options);
Use configured instance:
app.UseSwaggerUI(app.ApplicationServices.GetRequiredService<SwaggerUIOptions>());
Inject instance to any controller/class:
public WeatherForecastController(ILogger<WeatherForecastController> logger, SwaggerUIOptions swaggerOptions)
{
}
Property RoutePrefix contains swagger prefix (without leading '/')
This idea works only if options object passed to UseSwaggerUI method (available since version 6.0.0). If UseSwaggerUI invoked using callback (like a UseSwaggerUI(a => { a.RoutePrefix = string.Empty; })) it won't work.

Prevent ASP.NET Core discovering Controller in separate assembly

I've got an ASP.NET Core API that references a nuget package that contains a Controller.
By default, this controller is registered and can respond to requests. However, I only want to add this in certain circumstances - e.g. if it's in the DEV environment.
My Startup looks like this:
services.AddControllers()
.AddMvcOptions(cfg => {
cfg.Filters.Add(new CustomExceptionFilterAttribute())
});
I expected I'd need to call AddApplicationPart(typeof(ClassInPackage).Assembly) after calling AddCointrollers to register this controller?
Can someone advise a way I can enable / disable the registration of this controller?
Ok, I've found a solution - remove the ApplicationPart that contains the Controller. Any other dependencies in the assembly can still be used.
In Startup.cs / wherever you do your IoC:
if(hideControllerFromOtherAssembly)
{
var appPartManager = (ApplicationPartManager)services.FirstOrDefault(a => a.ServiceType == typeof(ApplicationPartManager)).ImplementationInstance;
var mockingPart = appPartManager.ApplicationParts.FirstOrDefault(a => a.Name == "MyMockLibrary.Namespace");
if(mockingPart != null)
{
appPartManager.ApplicationParts.Remove(mockingPart);
}
}
You can also manipulate ApplicationParts via the extension method:
AddMvc().ConfigureApplicationPartManager()
This wasn't suitable for me as I'd written an extension method in my nuget package
https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts?view=aspnetcore-5.0

Swagger/Swashbuckle doc generation only for API controllers within an MVC core app

We have an ASP.NET MVC Core application with regular MVC controllers. In addition, we have a subfolder within the Controllers folder called "API", containing a small number of API controllers.
We're using the .IncludeXmlComments method within .AddSwaggerGen to pick up the XML documentation within our project.
However, it's also picking up the XML for all our regular controllers too.
Is there a way to filter out the 'regular' controllers, or otherwise select only API controllers for inclusion in the swagger documentation?
Have a look at DocInclusionPredicate, it should solve your problem 😉
// Startup.cs
services.AddSwaggerGen(options =>
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
// Check if methodInfo is in the right assembly
// or has the right namespace or version etc.
bool isMethodIncluded = /* true or false */;
return isMethodIncluded ;
});
);

ASP.net Core web API Swagger UI version field - Is it possible to set this value in code?

I have a ASP.Net Core Web API with Swagger configured that shows the API End Points.Also API Versioning is enabled. However, the swagger UI is not populating the mandatory field Version when checking the End Point.See image below:
Is it possible to populate this field automatically by code given that the API Action already configures this value i.e. the MaptoApiVersion. In theory this field should be populated automatically??
[MapToApiVersion("2")]
[HttpGet("GetV2")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async Task<IEnumerable<TodoDto>> GetV2()
{
var query = new AllTodosQuery(_context);
var todos = await query.ExecuteAsync();
return todos;
}
The issue is at least two-fold. First, the extensions to the API Explorer from API Versioning do provide the version parameter with a default value, but many Swagger/OpenAPI generators (such as Swashbuckle) still do not yet honor it. If you want to enable this behavior, you need a custom IOperationFilter which does something to the effect of:
var parameter = operation.Parameters.First(p => p.Name == "version");
var description = context.ApiDescription.ParameterDescriptions.First(p => p.Name == "version");
if (parameter.Schema.Default == null && description.DefaultValue != null)
{
parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString());
}
You can find a complete end-to-end example in the API Versioning repo in:
SwaggerDefaultValues.cs
Since you're versioning by URL segment, if you want that inlined into the route template without a corresponding parameter, you need only configure the API Explorer extensions to do so like this:
services.AddVersionedApiExplorer(options => options.SubstituteApiVersionInUrl = true);
This option only applies to the URL segment versioning method.
A complete end-to-end Swashbuckle example with API Versioning can be found inside Startup.cs inside the repo.

What is causing the error that swagger is already in the route collection for Web API 2?

I installed Swagger in my ASP.Net MVC Core project and it is documenting my API beautifully.
My co-worker asked me to install it in a full framework 4.6.1 project so I've done the following.
In Package Console Manager run:
Install-Package Swashbuckle
To get your Test Web API controller working:
1) Comment this out in the WebApi.config:
// config.SuppressDefaultHostAuthentication();
// config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
Now this URL:
http://localhost:33515/api/Test
brings back XML:
<ArrayOfstring xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<string>value1</string>
<string>value2</string>
</ArrayOfstring>
In Global.asax Register() I call:
SwaggerConfig.Register();
In AppStart.Swagger.Config Register() I put:
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1.0", "HRSA CHAFS");
c.IncludeXmlComments(GetXmlCommentsPath());
})
.EnableSwaggerUi(c =>
{});
}
private static string GetXmlCommentsPath()
{
var path = String.Format(#"{0}bin\Services.XML", AppDomain.CurrentDomain.BaseDirectory);
return path;
}
}
Now I get this error:
"A route named 'swagger_docsswagger/docs/{apiVersion}' is already in the route collection. Route names must be unique."
I've been stuck on this for hours.
How do you get rid of this?
This can happen when you re-name your .NET assembly. A DLL with the previous assembly name will be present in your bin folder. This causes the swagger error.
Delete your bin folder and re-build your solution.
This resolves the swagger error.
Swagger config uses pre-application start hook, so you don't need to call SwaggerConfig.Register() explicitly. Otherwise Register method is called twice.
[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]
in my case i added another project as refrence and that other project has swagger too.
i remove that refrence and move needed code to new project.
I solved the problem by deleting the SwaggerConfig.cs file from the App_Start folder as I had already created it manually.
Take a look at this link, here also has more useful information:
A route named 'DefaultApi' is already in the route collection error
In my experience the error occurs when you add reference to another project and that project is a service and it occurs on the SwaggerConfig of the referenced project. Removing project reference usually solve the problem, if you need to share classes I suggest you to create a specific project as Class Library and add its reference to both your services