ASP.NET WebAPI2 Attribute Routing and SubFolders - asp.net-web-api2

I'm using Attribute Routing in WebAPI. My question is more on creating sub-folders under controllers in WebAPI (not in MVC, I'm using Areas for that)
I searched what kind of impact it would cause to the existing routing pattern and mostly they referred like adding custom routing template in WebAPIConfig.cs. But since I'm using AttributeRouting, is it really required to create custom template??
I tested my code and it seems to be working fine without any custom templates and I'm also able to achieve modularization by creating sub-folders under Controllers folder but would like to know the best practice and solution.

No - as you've found you don't need to create custom templates if you're using Attribute Routing.
The underlying method (MapAttributeRoutes) calls into the Controller factory to find all classes that inherit from Controller and then checks those for a Route attribute - so where they sit in the namespace hierarchy shouldn't matter.
If you are trying to mix Attribute and Convention routing and have sub-folders for convention based routes then you will need to define a custom template.

FYI: There is one small 'catcha' that you need to be aware off when (re)organizing your controllers in subfolders using attribute routing. Make sure your controller class has a unique name! Otherwise attribute routing gets confused and will not work. To illustrate:
// File: ~/Controllers/Customers/DetailsController.cs
namespace MyProject.Controllers.Customerss
{
[RoutePrefix("~/api/customers/{id}")]
public class DetailsController: ApiController {
[HttpGet]
public IHttpMessageResult GetItem(int id) {...}
}
}
and
// File: ~/Controllers/Orders/DetailsController.cs
namespace MyProject.Controllers.Orders
{
[RoutePrefix("~/api/orders/{id}")]
public class DetailsController: ApiController {
[HttpGet]
public IHttpMessageResult GetItem(int id) {...}
}
}
Although, cleary having different routes and certainly pointing to different controller classes it will throw off the attribute routing. By changing the controller classes to CustomerDetailsControlller and OrderDetailsController the routing issue resolved itself.

Related

Why is mandatory the "Route" attribute on methods of a custom-routed controller?

Consider a fresh Asp.Net Core 2.1 MVC Web app created via the Visual Studio 2017 template. Now, consider a custom view (MyView) and also a controller (ActualController) so that the project structure looks similar to this picture:
The MyView shows nothing special, and it's off the point. However, the page should appear when the user enters an URL like http://(domain)/desired/myview or also via a hyperlink in the home page:
<a asp-area="" asp-controller="Desired" asp-action="MyView">MyView</a>
Now let's focus on the controller, which is a class named differently from what the routing expects:
[Route("desired")]
public class ActualController : Controller
{
[Route("MyView")] //without this the method won't be called
public IActionResult MyView()
{
return this.View();
}
}
From what I know, by decorating the controller with a Route attribute tells the URL resolver to match this class. However, the mapping works only if I explicitly add a (redundant) Route attribute on the target method/action. If I remove it, the path won't be found, and the server returns a 404-error.
The question is: why should be mandatory to decorate with Route the method, even the action is implicitly defined by the method name (as usual)?
NOTE: is rather simple for me to rename the controller class, but I'd like to know what are the reasons behind this behavior.
You are overriding the default route of [controller]/[action] with [Route("desired")]. Since you don't define an action parameter on controller level, all other routes have to be done explicitly.
Changing the top route parameter to [Route("desired/[action]")] should solve it and the method name will be used as parameter. You can still override single actions if you want to name them differently by adding the [Route("")] attribute to them.
Also see the docs (Token replacement in route templates) for further description on the route parameters

How do you use tag helpers to create links to controllers/actions that use slashes in route attributes?

Before I start, I am aware there are similar questions to this, but they all pertain to using slashes with parameters in the routes which is not what I am trying to achieve (at least not yet).
I am creating an ASP.NET Core 2 MVC web application and am having problems with slashes in my routes being escaped when creating links to my actions. I have the following controller:
[Route("Manage/Account")]
public class AccountsController : Controller
{
[Route("[Action]")]
public IActionResult Index() => View();
[Route("[Action]")]
public IActionResult Other() => View();
}
And the following links in a view:
<a asp-controller="Manage/Accounts" asp-action="Index">Accounts</a>
<a asp-controller="Manage/Account" asp-action="Other">Accounts (Other)</a>
This generates the following HTML:
Accounts
Accounts (Other)
Notice that the slash has been escaped to %5C which to be fair does work and the links actually take me to the correct actions. Additionally manually putting the slash back in via the browsers address bar also takes me to the correct action. However this does give me some ugly URLs that I would rather not have.
Can anyone suggest a way to stop it from escaping my slashes?
Edit: More details
In my efforts to distil my problem down to the bare minimum I fear I may have left out some important context regarding my ultimate goal.
I am trying to implement a a feature folders setup that I have successfully used with ASP.NET MVC5 that makes use of attribute routing and a custom view engine to allow nested features. To make this work I used the [RoutePrefix(...)]. The controller below would be in the following directory ~/Features/Manage/Accounts:
[RoutePrefix("Manage/Accounts")]
public class Accounts : Controller
{
[Route("Index")]
public ActionResult Index() => View();
[Route("Other")]
public ActionResult Other() => View();
}
Links were then added in views like this:
#Html.ActionLink("Accounts", "Index", "Manage/Accounts")
#Html.ActionLink("Accounts (Other)", "Other", "Manage/Accounts")
Which then renders as:
Accounts
Accounts (Other)
Sadly the [RoutePrefix(...)] attribute is not available in ASP.NET Core MVC, and it would appear that using the standard [Route(...)] attribute is not able to emulate the behaviour found in MVC5.
It appears that you're confusing controllers and routes. When using asp-controller, you're expected to provide the name of a controller class (without the Controller suffix). In your case, you need:
<a asp-controller="Accounts" asp-action="Index">Accounts</a>
<a asp-controller="Accounts" asp-action="Other">Accounts (Other)</a>
Using the above, you should end up with the expected routes:
Accounts
Accounts (Other)
As an aside, you can simplify your attributes, like so:
[Route("Manage/Account/[action]")]
public class AccountsController : Controller
{
public IActionResult Index() => View();
public IActionResult Other() => View();
}
The [action] token can be applied at the controller level, as shown, avoiding the need to add it to each method.
What #Kirk said is exactly correct. I just want to give you an idea of Area in MVC as well, because by looking at the links in your example, it looks like you want to have those links start with /manage.
I previously shared how to set up areas in MVC. You can take a look here.

Create a custom route in ASP.Net Core that can pass a subdomain as a parameter

Been wrapped around the axle on this one, while there are some questions that are close, they either are different enough (addressing multi-tenancy, URL rewriting, etc), or are not answered. I would like to create a custom route in ASP.Net Core that takes the subdomain (if any) and passes it to the controller as a parameter.
The closest thing I found was (here), but I couldn't make it work, not sure what needs to be passed into a constructor that isn't described, but seems to be needed by the base MvcRouteHandler class.
For example, since a normal route might look like this:
http://www.example.com/home/index/3
Goes to the controller as
public class HomeController : Controller
{
public ActionResult Index(string id)
{
...
}
}
I would like to create a custom route that takes a URL like
http://somesubdomain.example.com/home/index/3
and turn it into
public class HomeController : Controller
{
public ActionResult Index(string subdomain, string id)
{
// "somesubdomain" is passed into the subdomain parameter.
...
}
}
Thanks, I've dug through more routing stuff than I know what to do with and custom routing is still pretty opaque to me. Thanks!
EDIT: I know that in each call I can probably look at the request, but that doesn't seem like the right thing to do. That's my fallback, though...
ANOTHER EDIT: Here's another example of what I want to do, only not in Core. I'm trying to figure out how to make this 'Core'.

Umbraco with MVC Controller

I am working on MVC and i started learning Umbraco, I didn't get how to bind the umbraco page with mvc controller get method to show the database values. can anyone suggest any url or video?
Thansk...
What you're looking for is Umbraco route hijacking.
You can read about it here.
https://our.umbraco.org/documentation/reference/routing/custom-controllers
It's easiest to demonstrate with an example : let's say you have a Document Type called 'Home'. You can create a custom locally declared controller in your MVC web project called 'HomeController' and ensure that it inherits from Umbraco.Web.Mvc.RenderMvcController and now all pages that are of document type 'Home' will be routed through your custom controller! Pretty easy right :-) OK so let's see how we can extend this concept. In order for you to run some code in your controller you'll need to override the Index Action.
So, basically, you "simply" need to create a controller named after your document type, so for example, a document type with the name "TextPage" would need a controller called "TextPageController". Now, if you read through the documentation, you'll find that your "TextPageController" will need to inherit from the RenderMvcController. Here's an example how to achieve this.
public class TextPageController : RenderMvcController
{
public ActionResult Index()
{
return View("~/Views/TextPage.cshtml");
}
}
This forum link may help you:
https://our.umbraco.org/forum/developers/razor/38242-Umbraco-MVC4111-Surface-controller-using-an-AJAX-form

PartialView() does not return a View with a underscore

TemplateController:
this works:
return PartialView("_Create");
but this does not work:
return PartialView();
The asp.net mvc convention should actually check a View folder with the name of the controller => "Template" and check for a View the same name as the action => "Create".
This is valid for a return View(). Why does a return PartialView() not just consider the underscore?
This answer is specifically for ASP.NET MVC5. It may require slight modification to work with other version of MVC but it should generally be applicable.
To have return Partial(model) respect underscores on partial names, you need a custom view engine. Fortunately this is an exceedingly trivial custom view engine.
public class CustomRazorViewEngine : RazorViewEngine
{
public CustomRazorViewEngine()
{
var underScored = new[] { "~/Views/{1}/_{0}.cshtml", "~/Views/{1}/_{0}.vbhtml" }
PartialViewLocationFormats = underScored.Union(PartialViewLocationFormats).ToArray();
}
}
The following format is the default patterns for Shared views:
~/Views/Shared/{0}.cshtml
~/Views/Shared/{0}.vbhtml
You could include alternates for these too if you wish. If you specifically want to only serve files with the underscore, remove the union and merely use: PartialViewLocationFormats = underScored;
This is with the razor view engine, I assume it would be comparable with the webforms view engine if that's your engine of choice.
Lastly you need to register this to be the view engine:
public class Startup
{
public void Configuration(IAppBuilder app)
{
//View Engines
ViewEngines.Engines.Remove(ViewEngines.Engines.Single(x => x is RazorViewEngine));
ViewEngines.Engines.Add(new CustomRazorViewEngine());
The Startup class is specific to MVC5, this would vary slightly between versions. You could use App_Start files with webactivator or the global.asax in other versions.
It is only a naming convention that partial views should start with underscore.
but strangely /mvc engine doesn't search for partial views with underscore.
so you have to explicitly say return PartialView("_Create").
or
Break the naming convention so that u don't have strings lying in your code.
After reading up on this across the web, opinions are very divided as to why the _ should be there / if it should be there at all.
I would argue that this is not a asp.net mvc naming convention, and the proof is that the framework itself doesn't adhere to this. (as you pointed out in your question)
The origin of the _ comes from webmatrix/asp.net where _ is used for resources that aren't directly servable to the user.
The only thing that could be prefixed are views that are reusable components such as _layout, and maybe a _nav or _datepicker
Razor kind of removed the concept of a partial views anyway (as there is no difference similar to the one between .aspx and .ascx, in razor it's all .cshtml anyway).