Default Route does not work after upgrading to MVC5 with Attribute Routing - attributerouting

After upgrading from MVC4 with AttributeRouting.net to MVC5 with MVC5's attribute routing, I can't seem to get the default route working so that http://server defaults to http://server/home/index . Browsing directly to /home/ or /home/index works fine.
For route config, I have this:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The HomeController declaration looks like this:
// Controller
[RoutePrefix("home")]
public class HomeController : MvcControllerBase
....
// Action
[HttpGet, Route, Route("index")]
public ActionResult Index()
{
.....
I'm not sure where else to check. I've commented out everything in Global and disabled all WebActivator-activated items.
And ideas? The response is 404 with no exception being thrown.

Ah.. got it!
Based on Kiran's answer to : Specify default controller/action route in WebAPI using AttributeRouting
I changed HomeController to:
// Controller
public class HomeController : MvcControllerBase
....
// Action
[HttpGet, Route, Route("home"), Route("home/index")]
public ActionResult Index()
{
....
And I got rid of the MVC Config default.

Related

ASP.NET MVC API controller not hitting

I have added an ASP.NET Web API to an ASP.NET MVC 5 web application project developed in Visual Studio 2019.
WebApiConfig:
public class WebApiConfig {
public static void Register(HttpConfiguration config) {
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
Global.asax.cs:
public class MvcApplication : HttpApplication {
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configure(WebApiConfig.Register);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
Api Controller Method:
// GET: api/Web
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
When I hit https://localhost:44324/api/web the browser gives this error:
The resource cannot be found.
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
Requested URL: /api/web
In MVC 5 routing you can try using attributing routing to overcome this error.
The solution is shown below.
API Controller Method:
//write this above the class and below namespace
[ApiController]
[Route("[controller]")]
// GET: api/Web
[HttpGet]
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
When you hit https://localhost:44324/api/web but here "Web" is controller id it's not then you have to mention that first and then the method the browser will not give you an error:

Is there any way to register tenant specific controller in asp.net core using autofac?

I am working on an application built on asp.net core 3.1 + Autofac as DI. Now i want to implement multi tenant feature. I am able to register tenant specific services and it is working as expected.
What i want to achieve is to register tenant specific controller to override default controller registered in main/application level container.
I have registered custom IApplicationFeatureProvider to register only main controllers.
here is the code.
//Startup.cs
public class StartUp
{
void ConfigureServices(IServiceCollection services)
{
services.AddControllers().ConfigureApplicationPartManager(apm =>
{
apm.FeatureProviders.RemoveAt(0); //remove default controller feature provider
apm.FeatureProviders.Add(new MyControllerFeatureProvider()); //register custom provider
}).AddControllersAsServices();
services.AddAutofacMultitenantRequestServices();
//rest of the configuration
}
public void ConfigureContainer(ContainerBuilder builder)
{
//registration of global or main services
builder.RegisterType<MyTenantIdentificationStrategy>()
.As<ITenantIdentificationStrategy>().SingleInstance(); //Get Tenant from request header
builder.Register(container =>
{
ITenantIdentificationStrategy strategy =
container.Resolve<ITenantIdentificationStrategy>();
// tenant resolution code
return new Tenant();
}).InstancePerLifetimeScope();
}
public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
{
var strategy <-- resolved from container
MultitenantContainer mtc = new MultitenantContainer(strategy, container);
mtc.ConfigureTenant(1, cb => {
cb.RegisterType<IP.Controllers.Extended.HomeController>).InstancePerLifetimeScope();
});
return mtc;
}
}
//HomeController.cs
namespace IP.Controllers
{
[Route("[controller]")]
public class HomeController : ControllerBase
{
}
[Route("Get")
public IActionResult Get()
{
return new JsonResult(new {Main = true});
}
}
//HomeController1.cs
namespace IP.Controllers.Extended <-- different namespace
{
[Route("[controller]")]
public class HomeController : ControllerBase
{
}
[Route("Get")
public IActionResult Get()
{
return new JsonResult(new {Main = false});
}
}
//program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(Startup.ConfigureMultitenantContainer))
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
http://localhost:8082/Home/Get [Without tenantid in header]
http://localhost:8082/Home/Get [Without tenantid=1 in header]
In both case main home controller is resolved not tenant specific..
Any help well be appreciated.
Answer to my own question,
Routing and controller selection is handled Asp.Net Core not by Autofac.
I am registering tenant specific controllers in ConfigureTenant method. Asp.Net Core Framework is not aware about these controllers. So for given route framework always selects registered controller not extended one registered in ConfigureTenant method.
So first i have removed custom application feature provider. Now all controllers in current assembly are registered. Then removed separate registration of controllers from ConfigureTenant method. Now framework throws ambiguity exception because there are two controllers with same route.
From stack trace, i found that EndPointResolver implementation is throwing ambiguity exception. Since we can provide our own implementation for EndPointResolver, i create custom EndPointResolver and register it.
Now during route selection, if there is ambiguity while selecting controller, i just retrieve tenant information from request header and based on Tanent i am handling ambiguity exception.
I am not sure if it is the correct approach but now i am able to register controller with same name [same Route] in different namespace/assembly.

How to change Swagger-Net root url from "/swagger" to root?

I"m using swagger-net. By default, the swagger UI will be "/swagger", how do I change it to root?
Feel like what I'm doing now is a hack
public class HomeController : Controller
{
public ActionResult Index()
{
return Redirect("/swagger");
//return View();
}
}
Found a solution. Added the below route mapping to WebApiConfig.cs
config.Routes.MapHttpRoute(
name: "Swagger UI",
routeTemplate: "",
defaults: null,
constraints: null,
handler: new RedirectHandler(SwaggerDocsConfig.DefaultRootUrlResolver, "swagger/ui/index"));

Routing error with default url in ASP.NET MVC 6

I have a routing problem in an MVC 6 web application : when I set route parameter in the controller used by default, application send a 404 error.
My routing configuration :
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Dashboard}/{action=Index}/{id?}");
});
My dashboard controller (application works) :
[Authorize]
public class DashboardController : Controller
{
public DashboardController()
{ }
[HttpGet]
public IActionResult Index() => View(new IndexViewModel());
}
Same dashboard controller (application responds a 404 error) :
[Authorize]
[Route("[controller]")]
public class DashboardController : Controller
{
public DashboardController()
{ }
[HttpGet]
[Route("[action]")]
public IActionResult Index() => View(new IndexViewModel());
}
The reason that this is occurring is that routes specified via routes.MapRoute only apply to controllers that are not using attribute based routing. Since your second example is using attribute based routing that controller can only be reached via the route specified in the attribute. So it can only be reached at /Dashboard/Index

Attribute routing in MVC 5 and optional defaults

Traditional routing defaults meant we were able to access these URLs and always end up on the same action:
/
/Home
/Home/Index
But today we would be writing something in these lines:
[RoutePrefix("Home")]
[Route("{action=Index}")]
public class HomeController
{
public ActionResult Index() {}
public ActionResult ...
}
But this routing definition is by no means the same.
/ (fails)
/Home (works)
/Home/Index (works)
So if we then change upper code to
[RoutePrefix("Home")]
[Route("{action=Index}")]
public class HomeController
{
[Route("~/")]
public ActionResult Index() {}
public ActionResult ...
}
But then we turn the processing upside down:
/ (works)
/Home (fails)
/Home/Index (fails)
We could make declarative code more verbose and make it work as the old-fashioned routing mechanism by:
[RoutePrefix("Home")]
[Route("{action=Index}")]
public class HomeController
{
[Route("~/")]
[Route("~/Home")]
[Route("~/Home/Index")]
public ActionResult Index() {}
public ActionResult ...
}
This works with all three different routes.
Question
This issue is of course bound to the very application default action that defaults controller and action. It's just that I wonder whether this is the only way of doing it? Is there any less verbose code way of getting it to work as expected?
Yeah, right..what you have is the way to do here...
I modified the code a bit here:
[RoutePrefix("Home")]
[Route("{action}")]
public class HomeController
{
[Route("~/")] // GET /
[Route] // GET /Home
[Route("Index")] // GET /Home/Index
public ActionResult Index() {}
public ActionResult ...
}
Some details:
1. Your first case is not exactly the same as conventional routing as in this case you have a literal segment Home which is not similar to the conventional routing optional of {controller}/{action}/{id} and controller = Home, action=Index,id=optional.
2. Your second case is expected as by design if a Route attribute is used on action the attributes on Controller do not take effect.
right now for SEO you should use canonical url meaning single url something like this
public class HomeController
{
[Route("~/")] // GET /
public ActionResult Index() {}
public ActionResult ...
}
so home controller is accessible at root only