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
Related
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:
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.
Hi I am working with IdentityServer4 and EFCore 3.1.0 recently and have a very weird issue with the following two pieces of code. The result is that in ApiResourcesGetByIdController, apiResource in GetById function includes the value of scope and secret. However in ApiResourcesUpdateController, result in Update function does not include the value of scope and secret.
The usage of these two endpoints is that, the frontend calls the GetById endpoint to get ApiResource object and makes some modification in frontend. Then the frontend calls Update endpoint to update the object in database.
Can anyone help to see what could be wrong? Thank you
[Authorize]
[Route("ApiResources")]
[ApiController]
public class ApiResourcesGetByIdController : ControllerBase
{
private readonly ConfigurationDbContext _configurationDbContext;
public ApiResourcesGetByIdController(
ConfigurationDbContext configurationDbContext)
{
_configurationDbContext = configurationDbContext;
}
[HttpGet]
[Route("")]
public IActionResult GetById([FromQuery]int id)
{
ApiResource apiResource = _configurationDbContext.ApiResources
.Include(apiResource => apiResource.Scopes)
.Include(apiResource => apiResource.Secrets)
.FirstOrDefault(apiResource => apiResource.Id == id);
}
}
[Authorize]
[Route("ApiResources")]
[ApiController]
public class ApiResourcesUpdateController : ControllerBase
{
private readonly ConfigurationDbContext _configurationDbContext;
public ApiResourcesUpdateController(
ConfigurationDbContext configurationDbContext)
{
_configurationDbContext = configurationDbContext;
}
[HttpPut]
[Route("")]
public IActionResult Update([FromBody] ApiResource resource )
{
ApiResource result = _configurationDbContext.ApiResources
.Include(apiResource => apiResource.Scopes)
.Include(apiResource => apiResource.Secrets)
.FirstOrDefault(ar => ar.Id == resource.Id);
return Ok()
}
}
Not sure why but I deleted the file and recreated it. Then it works by itself.
Well known situation. I need two endpoints
GetAll -> api/brands
GetById -> api/brands/1
[ApiController]
[Route("api/[controller]")]
public class BrandsController : ControllerBase
{
private readonly BrandRepository repository;
public BrandsController(BrandRepository repository)
{
this.repository = repository;
}
[HttpGet("{id:int}")]
public async Task<ActionResult> GetById(int id)
{
var brand = await repository.FindAsync(id);
if (brand == null)
{
return NotFound();
}
return Ok(brand);
}
[HttpGet("")]
public ActionResult<IEnumerable<Brand>> GetAll()
{
var brands = repository.GetAll().ToList();
return Ok(brands);
}}
So, I always get into GetAll()
Any ideas? Help, please :)
Is it a correct namespace?
using Microsoft.AspNetCore.Mvc;
for
[HttpGet]
Startup.cs
namespace BackOffice
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<ApplicationDbContext>(
options =>
options.UseMySql(Configuration.GetConnectionString("local")));
services.AddTransient<BrandRepository, BrandRepository>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(
endpoints =>
{
endpoints.MapControllers();
});
app.UseCors();
}
}
}
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
Change your the attribute on your GetAll action to simply [HttpGet] and then change the attribute on your GetById action to [HttpGet("{id}")] .
You can use a constraint to id if need be but in your case I don't see any need for it. Generally you can use constraints when you have multiple actions on the same route but with different parameter types. For example, "api/brands/1" to get by integer ID and then maybe you have another action that is mapped to "api/brands/gucci" that will search for the brand by string name. Then you can use the {id:int} and {id:string} constraints in your route template to define which action to invoke.
Also make sure you use IActionResult when declaring the action return types. You don't want to use the concrete ActionResult type. Code samples below.
For the GetById action :
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
var brand = await repository.FindAsync(id);
if (brand == null)
{
return NotFound();
}
return Ok(brand);
}
For your GetAll action :
[HttpGet]
public IActionResult<IEnumerable<Brand>> GetAll()
{
var brands = repository.GetAll().ToList();
return Ok(brands);
}
This will tell the routing middleware which action to invoke. For actions that you want mapped to the base controller route (i.e. "api/brands"), just use the attribute without an overload. Such as [HttpGet], [HttpPost], [HttpDelete]. For the actions that have a route parameter then you can use [HttpGet("{id}")] and so forth depending on the HTTP method. Don't worry about defining the type of the parameter in the attribute route template. You define the parameter in your action's parameters. For instance:
[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
// Code here
return Ok();
}
If you want to map a route to something like "api/brands/designers/2" then you would use a template like [HttpGet("designers/{id}")] to do so. Don't put a "/" before the designers.
Edit : Forgot to mention, make sure your Startup.cs is properly configured for Web API routing. You can read the specifics on the ASP.NET Core 3.1 docs for what all the different options do. If you used the Web API template then it's probably fine but it's worth double checking as improperly configured endpoint routing can cause issues. Make sure you have the following in your Configure method in Startup.cs.
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
Make sure that app.UseRouting(); is called before app.UseEndpoints();
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.