Prevent ASP.NET Core discovering Controller in separate assembly - asp.net-core

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

Related

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 ;
});
);

Netcore how to remove endpoints/routes at runtime

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

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.

Is there a way to add two resource folders to aspnet core localization?

I'm currently developing the SDK for one project and as a requirement I need to add two resources locations. One will be provided with the SDK lib and another to be provided by the consumer app.
Currently, according to docs, this is how to add localization:
services.AddLocalization(options => options.ResourcesPath = "Resources");
I'm calling this method from my BaseStartup class that will be inherited by the consumer app's Startup class. So I need to be able to setup the location of the SDK's resources folder and the consumer app's one as well.
Maybe something like:
services.AddLocalization(options =>
{
options.ResourcesPath = "SDKResources";
options.FromAssembly = sdkResourcesAssembly;
});
services.AddLocalization(options =>
{
options.ResourcesPath = "AppResources";
options.FromAssembly = appResourcesAssembly;
});
Is this possible? If so, how? If not, is there a workaround?
Checking online and even the source code (https://github.com/aspnet/Localization) wasn't of much help. The only thing I can think of is using IStringLocalizerFactory which accepts an assembly and the name of the file. Would it work? For instance, adding services.AddLocalization() and then just creating a wrapper class that would provide the consumer app with the strings using the factories created using IStringLocalizerFactory?
Thanks!
I found out about two ways it can be done, first by adding resources from different assemblies:
I created a base startup class to handle all source assemblies containing my resource classes and then I load them.
serviceCollection.AddLocalization();
var resourceTypes = typeof(BaseResource<>).Assembly.GetDerivedGenericTypes(typeof(BaseResource<>));
if (typeFromResourceAssembly != null)
resourceTypes.AddRange(typeFromResourceAssembly.Assembly.GetDerivedGenericTypes(typeof(BaseResource<>)));
foreach (var resourceType in resourceTypes)
{
serviceCollection.AddScoped(resourceType, resourceType);
}
return serviceCollection;
Second, by adding different resource folders:
services.Configure<ClassLibraryLocalizationOptions>(
options => options.ResourcePaths = new Dictionary<string, string>
{
{ "ResourceClass", "ResourcesFolder" },
{ "Localization.CustomResourceClass", "Folder1/Folder2" }
}
);

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