Using Application.Controller MVC - c#

Ok basically I am building a nuget package, and one of the things i would like the class to do is get the current applications controllers
So at the moment in the main project I do the following
using SolutionName.Website.MVC5.Controllers;
What I want to be able to do is something like
using this.Application.Controllers;
So it dynamicaly fills in the namespace for whatever solution the package is installed to.
I have sat here for an hour going through the possible permutations and have also googled a fair bit but not sure exactly what to google for
This is including API Controllers and I am Using MVC 5
Cheers
Martyn

The following little piece of reflection will give you all the namespaces for files that end in "Controller".
var namespaces = this.GetType().Assembly.GetTypes()
.Where(t => t.Name.EndsWith("Controller"))
.Select(x => x.Namespace).Distinct().ToList();
You would need to call this from within your code, preferably in the Global.asax.
Keep in mind that your controllers may be scattered across a number of namespaces, so additional logic will have to account for that. The number of namespaces is solely determined by how rigidly you structure your applications.
Alternatively, you can also pull types that inherit directly from 'System.Web.Mvc.Controller', as pointed out by Andrew Whitaker.
var namespaces = this.GetType().Assembly.GetTypes()
.Where(t => t => t.IsSubclassOf(typeof(Controller)))
.Select(x => x.Namespace).Distinct().ToList();

After using the information that #DavidL and #AndrewWhitaker gave me I thought I would post a little code snippet of how this can work in an MVC application as it maybe useful to go hand in hand with the question
I created the class below
public class GetControllerNameSpace
{
public static string NamespaceTag;
public void GetControllerNameSpaceMethod()
{
var NamespaceTagList = this.GetType().Assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Controller))).Select(x => x.Namespace).Distinct().ToList();
NamespaceTag = NamespaceTagList.FirstOrDefault();
}
}
and then in the RouteConfig.cs
using Controllers = GetControllerNameSpace;
This then lets me pass the information to my method here
private static IEnumerable<Type> GetTypesWithFixedControllerRouteAttribute(RouteCollection routes)
{
//This is where i am passing the variables
foreach (Type type in Assembly.GetAssembly(typeof(Controllers.HomeController)).GetTypes())
{
// Register any fixed actions
RegisterFixedActionRoutes(type, routes);
if (type.GetCustomAttributes(typeof(FixedControllerRouteAttribute), true).Any())
{
yield return type;
}
}
}
Please note that this works in my use case and may require more work when using multiple controller namespaces as mentioned in #DavidL's post

Related

Call a versioned webapi controller path ending with a number

Why does adding versioning to a webApi project removes number from the controller path name?
Replication steps :
Create a fresh .net6 project. And rename WeatherForecastController to WeatherForecast2Controller.
Run app and call https://localhost:x/WeatherForecast2 (where x is your port)
Observe valid/expected results
Add the below code in program.cs/startup
services.AddApiVersioning(config =>
{
config.AssumeDefaultVersionWhenUnspecified = true;
config.DefaultApiVersion = new ApiVersion(1, 0);
config.ReportApiVersions = true;
config.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("version"),
new HeaderApiVersionReader("x-version"));
config.UseApiBehavior = false;
});
Run app and call https://localhost:x/WeatherForecast2 (where x is your port).
Step 5 will return a 404 not found error.
However if you call https://localhost:x/WeatherForecast. It will work.
So why does adding versioning, change the url path?
It's not entirely clear if you are using <= 5.0 or 6.0+.
History
The reason this behavior happens is because the only logical way to group controllers together is by their names. A controller name, therefore, becomes very important. This is problematic in code because two or more controllers in the same namespace cannot have the same type name. The assumption and long-defined convention has been to allow ASP.NET to remove the Controller suffix and then remove any remaining numbers. This allows ValuesController, Values2Controller and Values3Controller to all map to the logical controller name Values by default. In most cases, that's probably want someone wants. If API Versioning doesn't do this, then there is no way to collate all API versions together for an API.
Contrary to popular belief, route templates are not considered for grouping controllers (e.g. APIs). There is too much ambiguity as to how a template can map to code. Take the simplest example of two different versions of the same API with different route templates: V1 = values/{id}, V2 = values/{id:int}. These are semantically equivalent, but not the same. API Versioning does not try to understand what the route template means nor compare their equivalence. It can easily get a lot more complicated; especially, for overlapping route templates. For example, should order/{oid}/customer/{cid} be part of the Orders API or the Customer API? Only the service author knows for sure.
Regression
In the 5.0 release, a regression was accidentally introduced due to an over-optimization. The controller name is used in two places: the actual name of the controller and the name used to group controllers. It seems reasonable they'd be the same and why normalize (e.g. trim suffixes) more than necessary? It seemed like a good idea, but it caused unexpected behavior - such as this one. There are also legitimate reasons to have a number in the name of a controller; for example, S3Controller.
Fix
In library versions <= 5.0, developers had no control over the behavior of how names were normalized. In 5.1 and 6.0+, this is now exposed via the IControllerNameConvention service, which has two methods: one for normalizing the controller name and one for normalizing the group name. The following implementations are provided out of the box as properties on ControllerNameConvention:
Default: The default, out-of-the-box conventions
Original: The original names without any normalization (could result in the wrong behavior)
Grouped: The group name is normalized, but the controller name is unmodified
If none of those work for you, then you can create your own custom convention. In 5.1 this is wired up via ApiVersioningOptions.ControllerNameConvention, while in 6.0+ IControllerNameConvention is a transient service in the DI container.
Workaround
There are two ways you can workaround the problem using the current version you are leveraging:
Explicit Route Template
If you omit using the [controller] token, the routing problem will be resolved; for example, api/weatherforecast. You appear to have already discovered this.
Explicit Controller Name
The controller name is derived from a convention, even without API Versioning. It was understood this behavior could be a problem so API Versioning provides a way to explicit set it with the ControllerNameAttribute.
[ControllerName("WeatherForecast")]
[Route("api/[controller]")] // ← expands to 'api/WeatherForecast'
public class WeatherForecast2Controller : ControllerBase { }
Edge Case
This will solve the routing issues, but it will not fix the controller name issue. That should only matter if you are planning on documenting your API with OpenAPI (formerly Swagger). For example, S3Controller will simply show up as S, even though the route might be api/s3.
I think that your controller may have issues, as I used your AddApiVersioning code and it works for me. To avoid the conflicting action names, you can two different controllers.
ApiController
namespace WebApiVersioningApp.Controllers;
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[ApiController]
[Route("api/Version2")]
public class Version2Controller : ControllerBase
{
[MapToApiVersion("1.0")]
[HttpGet(Name = "GetWeatherForecastV1")]
public string GetV1()
{
return "Version 1";
}
[MapToApiVersion("2.0")]
[HttpGet(Name = "GetWeatherForecastV2")]
public string GetV2()
{
return "Version 2";
}
}
Program:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First())
);
// Versioning setup
builder.Services.AddApiVersioning(o =>
{
o.ReportApiVersions = true;
o.AssumeDefaultVersionWhenUnspecified = true;
o.DefaultApiVersion = new ApiVersion(1, 0);
o.ApiVersionReader = ApiVersionReader.Combine(
new QueryStringApiVersionReader("version"),
new HeaderApiVersionReader("x-version"));
o.UseApiBehavior = false;
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Hope this works for you.

.net core replacement for MvcCodeRouting. Namespace based routing

We have ASP.NET MVC 5 project, that we have plans to migrate to ASP.NET Core 3. Currently I gather dependency list and their equivalent replacements on the new new platform.
We are using MvcCodeRouting package to separate different workflows between different C# namespaces as was described in https://maxtoroq.github.io/2013/02/aspnet-mvc-workflow-per-controller.html.
Now with new platform in place, we need something similar. As last resort we could just specify all our namespaces in routing table, but I'd rather not to do so.
Any suggestions on how accomplish similar behavior?
EDIT:
I think with example it would be more understandable what I'm trying to accomplish.
We have following structure of controllers:
- Namespace1
-- Workflow1Controller.Index/Edit/Action
-- Workflow2Controller.Index/Edit/Action
-- Workflow3Controller.Index/Edit/Action
- Namespace2
-- Workflow4Controller.Index/Edit/Action
Workflow1Controller code:
namespace RootProjectNamespace.Controllers.Namespace1
{
class Workflow1Controller : Controller
{
public ActionResult Index() {}
// and so on
}
}
Appropriate Views are placed in similar manner.
And using MvcCodeRouting we able to create Action urls by following:
Url.Action("Namespace1.Workflow1Controller", "Index") // Creates -> ~/Namespace1/Workflow1Controller/Index
Url.Action("Namespace2.Workflow4Controller", "Action") // Creates -> ~/Namespace2/Workflow4Controller/Index
Is there possibility to achieve similar in .net core without explicit hardcoding routes in route table?
As an start point for your solution, you can create a custom IApplicationModelConvention and change the routing to use namespace in the routing.
There's an example in docs showing how you can do this. To learn more you can take a look at this great docs article: Work with the application model in ASP.NET Core.
To do so, first create the following NamespaceRoutingConvention class:
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using System.Linq;
public class NamespaceRoutingConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach (var controller in application.Controllers)
{
var hasAttributeRouteModels = controller.Selectors
.Any(selector => selector.AttributeRouteModel != null);
if (!hasAttributeRouteModels)
{
controller.Selectors[0].AttributeRouteModel = new AttributeRouteModel()
{
Template = controller.ControllerType.Namespace.Replace('.', '/')
+ "/[controller]/[action]/{id?}"
};
}
}
}
}
Then in startup, register the convention like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.Conventions.Add(new NamespaceRoutingConvention());
});
}
Then you can browse:
http://localhost:xxxxx/SampleProject/Controllers/Home/Index
I have recently come across this issue.
An MVC .NET Framework project I had been working on needed migrating to .NET Core, however it was made up from lots of smaller assemblies (DLLs), each containing different parts of the overall codebase.
MvcCodeRouting was used to implement this, and provided a way for the assemblies to be utilised, routed, and also provided the ability for the central (core) project to utilise the relevant views, stored within the assemblies.
I have found that the .NET Core "Application Parts" feature seems to fulfill most of these points, with it having the ability for projects to be seperated between different assemblies, imported into a main project, and utilised.
The only real functional difference in using this method is that each "Application Part" generates two assemblies instead of one, with one containing the controllers and module logic, whilst the other contains the (Razor) views (if the specific module has any).
I'm still experimenting with this, however I thought I'd add this here for the sake of completeness.
Below are some useful links to documentation regarding this feature:
https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/app-parts?view=aspnetcore-1.0#sample-generic-controller-feature
This is the main documentation for the feature within the ASP.NET Core documentation.
https://learn.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-1.0&tabs=visual-studio
This tutorial demonstrated creating an assembly containing only Razor Views, if some of the modules require no logic.

Is it possible to include the method name for an endpoint through Swashbuckle?

So I'm trying to add API documentation for a set of old REST endpoints created through .NET Web API. Someone suggested that I try using Swashbuckle to generate documentation off the existing endpoints, which sort of works.
My issue is that these endpoints have names that provide some context and Swashbuckle only seems to pick up the controller and not the actual method name. For example, I have the following endpoint:
public class CatalogAvailabilityController
{
public List<string> GetSupportedCatalogsForCountry([FromUri] string countryCode)
{
//--return supported catalogs
}
}
In this case, the generated Swagger outputs something like the following:
Basically, it only includes the controller name (CatalogAvailability) in the URL, but I want it to have the "GetSupportedCatalogsForCountry" in the URL as well. Is there a way to have Swashbuckle generate it like this or do I need to resort to going back and creating the Swagger myself?
And yes, ideally, it probably shouldn't be setup like this and it should be more RESTful, but it's an older legacy system which would take a substantial effort to refactor, so I thought I'd ask first. Thank you in advance.
Yes! Add Attributes to your action and Route attributes to your controller.
public class CatalogAvailabilityController
{
[HttpGet]
public List<string> GetSupportedCatalogsForCountry([FromUri] string countryCode)
{
//--return supported catalogs
}
}
Following crossminder answer:
public class CatalogAvailabilityController
{
[HttpGet("GetSupportedCatalogsForCountry")]
public List<string> GetSupportedCatalogsForCountry([FromUri] string countryCode)
{
//--return supported catalogs
}
}

Dynamic route prefix for controllers in separate library

I'm developing an MVC API in a separate class library. The API methods use attribute routing. The API will be used by other MVC applications (not built by me).
The main MVC application will reference my library assembly and call AddMvc() / UseMvc() in it's own startup class. It will be able to set the root API url's for my API library dynamically (from configuration or options setup delegate), so that it can make sure there are no conflicts with it's own routes, which can use either attribute routing or centralized routing.
So let's say my API library has a product/{id} route. The main application should be able to choose any route prefix, like api/product/{id} or some/other/prefix/product/{id}.
At startup, MVC will discover all controllers/routes in all referenced assemblies, and it will also discover and register my API library routes, but only on the hardcoded product/{id} route without any prefix.
I've been trying to get MVC to register the routes with a prefix, but so far no success. The main application will call custom AddMyApi() / UseMyApi() config methods, so I can do configuration / setup for my library. Some of the things I tried:
Mapping
app.Map("/custom-prefix", api =>
{
api.UseMvc();
});
This will result in duplicate routes for both custom-prefix/product/{id} and product/{id}.
Route Convention
Based on http://www.strathweb.com/2016/06/global-route-prefix-with-asp-net-core-mvc-revisited/
services.AddMvc(options =>
{
options.Conventions.Insert(0, new RouteConvention(new RouteAttribute("custom-prefix")));
});
It looks like this will not work because the options will be overwritten by the main application's call to AddMvc(), or the other way around, depending which gets called first.
Custom route attribute
A custom route attribute based on IRouteTemplateProvider on the Controller classes will not work because I need the prefix injected from an options class, and attributes do not support constructor injection.
Postpone discovery of routes
Based on http://www.strathweb.com/2015/04/asp-net-mvc-6-discovers-controllers/
I've added [NonController] to the library controllers to prevent them being discovered at the main application's startup. However I've not been able to add them later, and also I suppose I will run into the same problem of the main application overwriting the MVC options again.
Areas
I can't use areas, because the main application may decide to run the API from the root (without prefix).
So I'm stuck as to how to solve this problem. Any help is appreciated.
I believe a convention is the right approach here and the bit you are missing is just providing the proper extension method for your library to be registered within MVC.
Start by creating a convention that will add a prefix to all controllers that pass a certain selector.
It is based on one I wrote for adding culture prefixes, but the idea is very similar to the article you linked.
Basically it will either update any existing AttributeRouteModel or add a new one if none is found.
This would be an example of such a convention:
public class ApiPrefixConvention: IApplicationModelConvention
{
private readonly string prefix;
private readonly Func<ControllerModel, bool> controllerSelector;
private readonly AttributeRouteModel onlyPrefixRoute;
private readonly AttributeRouteModel fullRoute;
public ApiPrefixConvention(string prefix, Func<ControllerModel, bool> controllerSelector)
{
this.prefix = prefix;
this.controllerSelector = controllerSelector;
// Prepare AttributeRouteModel local instances, ready to be added to the controllers
// This one is meant to be combined with existing route attributes
onlyPrefixRoute = new AttributeRouteModel(new RouteAttribute(prefix));
// This one is meant to be added as the route for api controllers that do not specify any route attribute
fullRoute = new AttributeRouteModel(
new RouteAttribute("api/[controller]"));
}
public void Apply(ApplicationModel application)
{
// Loop through any controller matching our selector
foreach (var controller in application.Controllers.Where(controllerSelector))
{
// Either update existing route attributes or add a new one
if (controller.Selectors.Any(x => x.AttributeRouteModel != null))
{
AddPrefixesToExistingRoutes(controller);
}
else
{
AddNewRoute(controller);
}
}
}
private void AddPrefixesToExistingRoutes(ControllerModel controller)
{
foreach (var selectorModel in controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList())
{
// Merge existing route models with the api prefix
var originalAttributeRoute = selectorModel.AttributeRouteModel;
selectorModel.AttributeRouteModel =
AttributeRouteModel.CombineAttributeRouteModel(onlyPrefixRoute, originalAttributeRoute);
}
}
private void AddNewRoute(ControllerModel controller)
{
// The controller has no route attributes, lets add a default api convention
var defaultSelector = controller.Selectors.First(s => s.AttributeRouteModel == null);
defaultSelector.AttributeRouteModel = fullRoute;
}
}
Now, if this was all part of an app you are writing instead of a library, you would just register it as:
services.AddMvc(opts =>
{
var prefixConvention = new ApiPrefixConvention("api/", (c) => c.ControllerType.Namespace == "WebApplication2.Controllers.Api");
opts.Conventions.Insert(0, prefixConvention);
});
However since you are providing a library, what you want is to provide an extension method like AddMyLibrary("some/prefix") that will take care of adding this convention and any other setup like registering required services.
So you can write an extension method for IMvcBuilder and update the MvcOptions inside that method. The nice thing is that since is an extension of IMvcBuilder, it will always be called after the default AddMvc():
public static IMvcBuilder AddMyLibrary(this IMvcBuilder builder, string prefix = "api/")
{
// instantiate the convention with the right selector for your library.
// Check for namespace, marker attribute, name pattern, whatever your prefer
var prefixConvention = new ApiPrefixConvention(prefix, (c) => c.ControllerType.Namespace == "WebApplication2.Controllers.Api");
// Insert the convention within the MVC options
builder.Services.Configure<MvcOptions>(opts => opts.Conventions.Insert(0, prefixConvention));
// perform any extra setup required by your library, like registering services
// return builder so it can be chained
return builder;
}
Then you would ask users of your library to include it within their app as in:
services.AddMvc().AddMyLibrary("my/api/prefix/");
//Try this Reference enter link description here
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UsePathBase("/Api/v/00");
app.Map("/api/v/0", api =>
{
api.UseMvc();
});
}

Custom attributes in C#

I have a custom attribute for my page like this:
[PageDefinition("My page", "~/Parts/MyPage.aspx")]
My PageDefinition looks like this, where AttributeItemDefinitions is get set for Title, Url, IsPage and IsUserControl
public class PageDefinition : AttributeItemDefinitions
{
public PageDefinition(string title, string url)
: this()
{
Title = title;
Url = Url;
}
public PageDefinition()
{
IsPage = true;
IsUserControl = false;
}
}
But i can't find any good way to add all page with that attribute to a placeholder where all links should be list with the title and url. Do you have any good idea? Thanks for your help.
When I've created such custom attributes that define some metadata on a class I've often built a small routine that scans all classes of an assembly using reflection.
In my current project I'm using a IoC framework (other story) and instead of configuring it in a custom config file I've built myself a ComponentAttribute that defines what interface a class belongs to. (From a bird's eye view: I ask the IoC framework for a interface later on and it knows how to instantiate classes that implement that and how they fit together)
To configure that IoC framework I need to call a member of a certain class and tell it which class to interface mappingts exist.
ioc.ConfigureMapping(classType, interfaceType)
To find all those mappings I use the following two methods in one of my helper classes
internal static void Configure(IoCContainer ioc, Assembly assembly)
{
foreach (var type in assembly.GetTypes())
AddToIoCIfHasComponentAttribute(type, ioc);
}
internal static void AddToIoCIfHasComponentAttribute(Type type, IoC ioc)
{
foreach (ComponentAttribute att in type.GetCustomAttributes(typeof(ComponentAttribute), false))
{
ioc.ConfigureMapping(attribute.InterfaceType, type);
}
}
What I'm doing here is enumerating all of an assemblies' types in the first method, and than evaluting the attribute in the second one.
Back to your problem:
Using a similar approach you could find all the marked classes and record them in a container (ArrayList or similar) along with all the data you have defined in the attribute (Page path, etc.).
Update (Answer to comment)
When you build your program in Visual Studio you normally have one or more projects. For each project you will get a distinct assembly (.dll or .exe file). The code above will examine all the classes within one assembly. Seen that way an assembly is a collection of collected .cs files. So you want to search an assembly, not a directory of .cs files (which are source code and not part of the running application.)
So what's probably is missing: How can you access an assembly from your code when you want to search for classes? You just take ANY class you know (that is in the assembly/project where your other classes are) and obtain the assembly it is in by calling
var assembly = typeof(MyDummyClass).Assembly;
and afterwards you'd call something you derived from the code above
AnalyzeClasses(assembly)
and AnalyzeClasses would look like
internal static void AnalyzeClasses(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
AnalzyeSingleClass(type);
}
internal static void AnalzyeSingleClass(Type type)
{
foreach (MyCustomAttribute att in type.GetCustomAttributes(typeof(MyCustomAttribute), false))
{
Console.WriteLine("Found MyCustomAttribute with property {0} on class {1}",
att.MyCustomAttributeProperty,
type);
}
}
And you'd just call all that before you run your application code, for example right
at the top in main() (for applications) or if it's difficult in advanced you can also
call this on demand when you need the collected data. (For example from an ASP.NET page)
It might be more than you need but...
I run into this pattern all of the time in my projects so I implemented a type loader that can be supplied with user defined delegates for a type search matching.
http://www.codeproject.com/KB/architecture/RuntimeTypeLoader.aspx

Categories