Multiple controller types were found that match the URL MVC 5.2 - asp.net

I get this error when I hit the url Shop/Checkout
The request has found the following matching controller types:
shopmvc.Controllers.HomeController
shopmvc.Controllers.ProductsController
My HomeController.cs:
[Route("{action=index}")]
public class HomeController : Controller
{
[Route("Shop/Checkout")]
public ActionResult Checkout()
{
}
}
My ProductsController.cs:
[RoutePrefix("Shop")]
[Route("{action=index}")]
public class ProductsController : Controller
{
[HttpGet]
[Route("{brand}/{category}/{subcategory?}/{page:int?}")]
public ActionResult Index(string brand, string category, string subcategory, int? page, SortOptions currentSort = SortOptions.SinceDesc)
{
}
[HttpGet]
[ActionName("Details")]
[Route("{brand}/{category}/{productid}")]
public ActionResult Details(int productid)
{
}
}
I get it that both routes have Shop in it, but I have no clue how to resolve this. This is the razor code in my shared layout:
<a href="#Url.Action("checkout", "Home" )">

The issue is that "Checkout" is valid as a parameter for brand in your ProductController routes. There's no intrinsic order to routes with attribute routing, so you have to be more careful to make sure only one route can truly match the URL. In your case here, you can simply do something like:
[Route("{brand:regex((?!Checkout))}/...")]

Related

.Net: Does controller have to have file name corresponding to URL?

I'm following this tutorial on .Net controllers, and it says "imagine that you enter the following URL into the address bar of your browser: http://localhost/Product/Index/3. In this case, a controller named ProductController is invoked."
What I Want To Know:
In order to successfully hit http://localhost/Product/Index/3, do you need a controller called ProductController specifically?
No, it is not necessary. You can use Route Attribute.
[Route("new-name-for-product")]
public class ProductController{
}
now you have to use http://localhost/new-name-for-product/Index/ this URL to invoke ProductController.
If you want to use one or more parameters with this URL you have to use different route templates for ActionMethod. Example below.
[Route("new-name-for-product")]
public class ProductController
{
// http://localhost/new-name-for-product/3/ will show product details based on id
// http://localhost/new-name-for-product/Index/3/ will show product details based on id
[HttpGet]
[Route("/{id}")]
public IActionResult Index(int id)
{
// your code
}
// you can use a different action method name.
// http://localhost/details/3/ will show product details based on id
// but parameter name (Ex: id) and the id inside route template the spelling must be the same.
[HttpGet]
[Route("details/{id}")]
public IActionResult GetById(int id)
{
// your code
}
}
It depends. In ASP.Net Core, routing can be configured either as conventional routing or as attribute routing.
Conventional routing is configured as below:
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
Here, the first path segment maps to the controller name,
the second maps to the action name,
the third segment is used for an optional id used to map to a model entity.
As a convention, controller file name is usually same as controller class name.
Hence, In conventional routing, url will match with filename.
The URL http://localhost/Products/Index matches below action method in ProductsController.
[Route("[controller]")]
public class ProductsController : Controller
{
[HttpPost("Index")] // Matches 'Products/Index'
public IActionResult Index()
{
return View();
}
}
Attribute Routing
With attribute routing the controller name and action names play no role in which action is selected.
Hence, it is independent of file name.
The URL http://localhost/Items/All matches below action method in ProductsController.
public class ProductsController : Controller
{
[Route("Items/All")]
public IActionResult Index()
{
return View();
}
}
Similarly, [Route] attribute can be added at both Controller and action methods. The same URL http://localhost/Items/All matches the action method shown below:
[Route("Items")]
public class ProductsController : Controller
{
[Route("All")]
public IActionResult Index()
{
return View();
}
}
For more details, you can refer to microsoft docs at https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-3.1

ASP.NET Core Web API: Routing by method name?

I remember from ASP.NET Web API that it's sufficient to prefix Web API REST method names with HTTP commands (e.g. GetList() => HTTP GET, Delete() => HTTP DELETE) to have incoming calls appropriately routed.
I also remember that in ASP.NET Web API parameter matching takes place so that even Get(int id) and Get(int id, string name) get automatically and appropriately routed without requiring any attributes.
public class MyController
{
public ActionResult Get(int id) => ...
public ActionResult Get(int id, string name) => ...
public ActionResult DeleteItem(int id) => ...
}
Isn't this all available in ASP.NET Web API Core?
You just need to add the Route to the top of your controller.
Specify the route with api, controller and action:
[Route("api/[controller]/[action]")]
[ApiController]
public class AvailableRoomsController : ControllerBase
{
...
}
Neither could we do action overloads nor prefix action name as Http verb.The way routing works in ASP.NET Core is different than how it did in ASP.NET Web Api.
However, you can simply combine these actions and then branch inside, since all params are optional if you send as querystring
[HttpGet]
public ActionResult<string> Get(int id, string name)
{
if(name == null){..}
else{...}
}
Or you need to use attribute routing to specify each api if you send in route data:
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
[HttpGet("{id}/{name}")]
public ActionResult<string> Get(int id, string name)
{
return name;
}
Refer to Attribute Routing,Web Api Core 2 distinguishing GETs
The aspnetcore webapi controllers do not natively support inference of http verbs by naming convention, but allow you to create your own convention and achieve this behavior.
Create your convention
public class MyConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
foreach(var controller in application.Controllers)
{
foreach(var action in controller.Actions)
{
if (action.ActionName.StartsWith("Post"))
{
action.Selectors.First().ActionConstraints.Add(new HttpMethodActionConstraint(new[]{ "POST" }));
}
}
}
}
}
Then register it in Program/Startup:
builder.Services.AddControllers(configure => configure.Conventions.Insert(0, new MyConvention()));
This is available for Core 2 yes, but the way that I know how to do it is something like this
[Route("api/[controller]")]
[ApiController]
public class AvailableRoomsController : ControllerBase
{
private readonly ApplicationContext _context;
public AvailableRoomsController(ApplicationContext context)
{
_context = context;
}
// GET: api/AvailableRooms
[HttpGet]
public async Task<ActionResult<IEnumerable<AvailableRoom>>> GetAvailableRooms()
{
return await _context.AvailableRooms.ToListAsync();
}
// POST: api/AvailableRooms
[HttpPost]
public async Task<ActionResult<AvailableRoom>> PostAvailableRoom(AvailableRoom availableRoom)
{
_context.AvailableRooms.Add(availableRoom);
await _context.SaveChangesAsync();
return CreatedAtAction("GetAvailableRoom", new { id = availableRoom.Id }, availableRoom);
}
[HttpPut] .... etc
}
Now depending on what kind of REST action you specify and what type of model you send to "api/AvailableRooms" if the proper Action exists it will be chosen.
Visual Studio 2019 and I think 2017 can create a controller such as this automatically if you right click your Controllers folder and click Add->Controller and then choose "API Controller with actions, using Entity Framework" and choose one of your Model classes.

ASP.NET Core Route inside controller

I am trying to route url with and without parameter to two different methods but for some reason it always start first one.
Here is controller code:
public class ProductController : Controller
{
[Route("")]
[Route("Product")] //If i delete this one then it works how it is intended
public IActionResult Index()
{
//It always start this one
....
}
[Route("Product/{GroupID?}")]
public IActionResult Index(int GroupID)
{
....
}
}

Asp.Net MVC 5 custom action routing under an area

i am currently trying to generate this url "/Cloud/Hosting/RoaringPenguin/Manage/Exclusions".
Here is the area registration
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Hosting_default",
"Cloud/Hosting/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
}
here is the controller
public class RoaringPenguinController : PortalControllerBase
{
public ActionResult Exclusions()
{
return View("Exclusions");
}
}
i have tried added a route onto the action itself like so
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
I have also tried adding some attributes to the controller itself
[RouteArea("Hosting")]
[RoutePrefix("RoaringPenguin")]
public class RoaringPenguinController : PortalControllerBase
but that doesn't seem to work either. If i leave all the attributes off then the final url i get is "/Cloud/Hosting/RoaringPenguin/Exclusions".
Does anyone know how i can get the "Manage" in the url as well?
Just to confirm i have the following set in my RegisterRoutes method under the RouteConfig class
routes.MapMvcAttributeRoutes();
Any help is appreciated. Thanks
Your default area route doesn't allow for the "Manage/Exclusions" part on the end. If you made the URL just /Cloud/Hosting/RoaringPenguin/Exclusions (minus the Manage part of the path) it would work fine.
If you need the route to be exactly that, then attribute routing is your best bet. However, your mentioned attempts at that are all missing something or another. Your controller should be decorated with both RouteArea and RoutePrefix to compose the first part of the path:
[RouteArea("Hosting", AreaPrefix = "Cloud/Hosting")]
[RoutePrefix("RoaringPenguin")]
public class RoaringPenguinController : Controller
However, it's typical to actually implement a base controller when dealing with areas, so that you can specify RouteArea in just one place:
[RouteArea("Hosting", AreaPrefix = "Cloud/Hosting")]
public class HostingBaseController : Controller
[RoutePrefix("RoaringPenguin")]
public class RoaringPenguinController : HostingBaseController
Then, on your action:
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
As you had.
Try with this code
[RouteArea("AreaName", AreaPrefix = "Cloud/Hosting")]
[RoutePrefix("RoaringPenguin")]
public class SampleController : Controller
{
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
{
return View("Exclusions");
}
}
or
[RoutePrefix("Cloud/Hosting/RoaringPenguin")]
public class RoaringPenguinController : PortalControllerBase
{
[Route("Manage/Exclusions")]
public ActionResult Exclusions()
{
return View("Exclusions");
}
}
This will be the first line
routes.MapMvcAttributeRoutes();
After that only you have to write this line
AreaRegistration.RegistrationAllAreas();

Authorization has been denied for this request error when running webapi in MVC project

I have created an ASP.Net MVC project with WebApi option. Then modified the values controller with the code below:
public class ValuesController : ApiController
{
static List<string> data = initList();
private static List<string> initList()
{
var ret = new List<string>();
ret.Add("value1");
ret.Add( "value2" );
return ret;
}
// GET api/values
public IEnumerable<string> Get()
{
return data ;
}
// GET api/values/5
public string Get(int id)
{
return data[id];
}
// POST api/values
public void Post([FromBody]string value)
{
data.Add(value);
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
data[id] = value;
}
// DELETE api/values/5
public void Delete(int id)
{
data.RemoveAt(id);
}
}
When I am running the project and navigating to API/values URL, the following image is showing error.
.
The error description in text is:
<Error>
Authorization has been denied for this request.
</Error>
Have a look at the following article about
Authentication and Authorization in ASP.NET Web API
It will explain the different ways of how to use the [Authorize] and [AllowAnonymous] attribute on your controller/actions and any configurations you would need to do.
The following was taken from the linked article above:
Using the [Authorize] Attribute
Web API provides a built-in authorization filter,
AuthorizeAttribute. This filter checks whether the user is
authenticated. If not, it returns HTTP status code 401 (Unauthorized),
without invoking the action.
You can apply the filter globally, at the controller level, or at the
level of inidivual actions.
Globally: To restrict access for every Web API controller, add the
AuthorizeAttribute filter to the global filter list:
public static void Register(HttpConfiguration config){
config.Filters.Add(new AuthorizeAttribute());
}
Controller: To restrict access for a specific controller, add the
filter as an attribute to the controller:
// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
public HttpResponseMessage Get(int id) { ... }
public HttpResponseMessage Post() { ... }
}
Action: To restrict access for specific actions, add the attribute to
the action method:
public class ValuesController : ApiController
{
public HttpResponseMessage Get() { ... }
// Require authorization for a specific action.
[Authorize]
public HttpResponseMessage Post() { ... }
}
Alternatively, you can restrict the controller and then allow
anonymous access to specific actions, by using the [AllowAnonymous]
attribute. In the following example, the Post method is restricted,
but the Get method allows anonymous access.
[Authorize]
public class ValuesController : ApiController {
[AllowAnonymous]
public HttpResponseMessage Get() { ... }
public HttpResponseMessage Post() { ... }
}
In the previous examples, the filter allows any authenticated user to
access the restricted methods; only anonymous users are kept out. You
can also limit access to specific users or to users in specific roles:
// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}
So, I've been dealing with this error for awhile.
I didn't understand it at first, so I just removed and lived with it.
I finally got sick of it, because it's rather stupid. Microsoft wants a user to be authorized before they have signed in.
My error was looking for GET method which asks for HomeTown. In my case, I had changed it to CityCode.
Since the user is not logged in, there is no CityCode to GET. So, you get either a 402 or a 500 Resource Not Found.
I still don't understand it so, I gave CityCode some default data. So, from MeController I put the following code:
Public Function [Get]() As GetViewModel
Dim userInfo As ApplicationUser = UserManager.FindById(User.Identity.GetUserId())
Return New GetViewModel() With {.CityCode = "94110"}
End Function
App loads completely error free now.
This is a quick fix, not a certified solution.

Resources