Description
I am using only Attribute Routing in one of my MVC Controllers:
[RoutPrefix("Groups")] // NOTE: I want to use "Groups" with an "s" here.
public class GroupController : Controller
{
[HttpGet]
[Route("Edit/{id}")]
public ActionResult Edit(Guid id)
{
//...
}
[HttpPost]
[Route("Edit")]
public ActionResult Edit(GroupEditViewModel model)
{
// ...
}
}
In the Razor view, whenever using helpers such as:
#Html.ActionLink("Text", "Edit", new {controller = "Groups", id = "someId"})
or
#Html.BeginForm(actionName: "Edit", controllerName: "Groups")
{
}
The routes generated by the helper function are null:
<a href>Text</a>
and
<form action></form>
Question
Is this happenning because the controller's name is unknown maybe?
Is there a way I can set the name of the controller with using Only Attribute Routing ?
Common practices known for attribute routing are setting RoutePrefix with same name as controller's name:
[RoutePrefix("Group")]
public class GroupController : Controller
{
// ...
}
However, as I had seen here, custom name(s) can be set without altering/removing original route:
[RoutePrefix("Groups")]
public class GroupController : Controller
{
// ...
}
Remember that RoutePrefix attribute only giving alternative way to access corresponding controller, not changing its original name.
Route Prefixes are nothing but the prefix for any route that we want to apply, all we need to do is to define the route prefix on a controller so that all the action methods inside it can follow the prefix.
OTP, since "Groups" is just a RoutePrefix parameter value instead of controller class, all HTML helpers used "Groups" as controller name will return null value on respective HTML attributes.
Thus, original controller's name must be used instead of RoutePrefix value:
#Html.ActionLink("Text", "Edit", new {controller = "Group", id = "someId"})
#Html.BeginForm(actionName: "Edit", controllerName: "Group")
{
}
or use MapRoute inside RegisterRoutes method to manipulate URL together with original controller's name:
// note that "Group" controller declared with "Groups" as part of relative URL
routes.MapRoute("Groups", "Groups/{action}/{id}", new { controller = "Group", action = "Edit", id = UrlParameter.Optional });
To activate RoutePrefix effect when routing, MapMvcAttributeRoutes() should inserted into RegisterRoutes method.
CMIIW.
The route prefix attribute, [RoutePrefix("Groups")], controls the resulting URL. MVC, however will always use the class name as the name of the controller.
They don't have to be the same. You can simply use the route prefix to control the resulting URL and use the class name as the controller name where required and the resulting URL will be correct.
In your example:
[RoutePrefix("Groups")]
public class GroupController : Controller
{
[HttpGet]
[Route("Edit/{id}")]
public ActionResult Edit(Guid id)
{
//...
}
[HttpPost]
[Route("Edit")]
public ActionResult Edit(GroupEditViewModel model)
{
// ...
}
}
The following
#Html.ActionLink("Text", "Edit", new {controller = "Group", id = "someId"})
would result in a link that looked like this (note the "Groups" in the URL despite the "Group" controller name):
Text
Related
This is my controller:
public class HomeController : Controller
{
public IActionResult Index()
{
var route = Request.Path.Value;
return View("index" as object);
}
[HttpGet("{id}")]
public IActionResult Index(int id)
{
return View("index id" as object);
}
}
This is my route:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
url : /1 -> return index with id
url : /Home/Index/1 -> return index without id
I don't understand why?
You're using mixed routing - you've got conventional routing for the first action and attribute routing for the second.
When you navigate to /1, you hit the second action with the id parameter, because it's been set to use attribute routing for a path of /{id} (by using [HttpGet("{id}")]): Attribute routing overrides conventional routing.
When you navigate to /Home/Index/1, you hit your first action without the id parameter, simply because the other action you have is no longer a match given that it's been set to use attribute routing (/{id}), so it no longer matches /Home/Index/1 at all. With your conventional routing template from UseMvc, you've said that id is optional and so the match is still valid.
In order to achieve what you're looking for, you can use attribute routing exclusively for this controller. Here's what that would look like:
[Route("/")]
[Route("[controller]/[action]")]
public class HomeController : Controller
{
public IActionResult Index()
{
...
}
[HttpGet("{id}")]
public IActionResult Index(int id)
{
...
}
}
The addition of the two [Route(...)] attributes here adds support for the following two routes:
/ and /{id}.
/Home/Index and /Home/Index/{id}.
[controller] and [action] are placeholders that represent the name of the controller and action respectively - You could also just use the literal values Home and Index if you'd prefer something more fixed.
You don't necessarily need both [Route(...)] attributes, but the / version ensures the root of the site also matches the same controller/action pair.
I need to generate a navigation that has 'System' and 'Category' archons or buttons. System and Category are using the same controller. I would like to use [Route()] instead of the Startup file for routing.
For category archon works great:
<a asp-area="Admin" asp-controller="#value.Controller" asp-action="#Constants.Route.List" asp-route-valueId="#value.ValueId">#value.Value</a>
but I dont kow how to use the same controller for 'admin/system/list' instead of admin/categorytype/list, so far I tried:
1)
<a asp-area="Admin" asp-route="System" asp-action="#Constants.Route.List" asp-route-valueId="#value.ValueId">#value.Value</a>
2)
<a asp-route="Admin/System/List" asp-route-valueId="#value.ValueId">#value.Value</a>
and at controller
[Area("Admin")]
[Route("Admin/[controller]")]
[Route("Admin/System")]
[Authorize]
public class CategoryTypeController : Controller {}
[Route("[action]")]
public ViewResult List(int valueId) {}
3)
#value.Value
and at controller
[Area("Admin")]
[Route("Admin/[controller]")]
[Route("Admin/System", Name = "SystemValue")]
[Authorize]
public class CategoryTypeController : Controller {}
[Route("[action]")]
public ViewResult List(int valueId) {}
none of the above routes generated url that allow me to go to the CategoryType controller and List action by /admin/system/list
I also have app.UseMvcWithDefaultRoute(); at Startup file.
Edit:
When I enter the address manually /admin/system/list - the router works,
but I can't generate such a url for button or archon to match /admin/system/list
as in the attached picture, the archon does not have any url
EDIT:
I found a solution, but I'm not happy with it, it turns out that I do not understand how routing works. For me, the [Route ()] attribute is one way to define routs and app.UseMvc() is the second way. I had to define routes in two places.
at Startup.cs
app.UseMvc(routes =>
{
routes.MapRoute(
name: "SystemValue",
template: "Admin/System/List/{id?}",
defaults: new { area = "Admin", controller = "CategoryType", action = "List" });
});
controller
[Area("Admin")]
[Route("Admin/[controller]")]
[Route("Admin/System", Name = "SystemValue")]
[Authorize]
public class CategoryTypeController : Controller
{}
[Route("[action]")]
public ViewResult List(int valueId) {}
view
#value.Value
I will still be looking for a solution how to do it using only [Route ("")]
If you want to specify the routes individualy, you can use named routes, but it is needed to be specified on the method and not on the controller.
[Route("/admin/system/list/{id}",name ="SystemList")]
[Route("/admin/category/list/{id}",name ="CategoryList")]
public IActionResult List(int id)
{
...
}
you can generate the url for this route as
<a asp-route="CategoryList" asp-route-id="#value.ValueId">#value.Value</a>
The route system can use the routes declared at the startup or use routes declared by attributes, but they are not mixed, when you sepecify a route attribute at controller level the routes from the startup are ignored.
We just started using ASP.NET MVC3 and we want to unit test our controller.
Here is my controller function:
[HttpGet]
Public ActionResult Action()
{
Guid Id = Guid.Empty;
string[] UrlSegments = Request.Url.Segments;
Guid.TryParse(UrlSegments[UrlSegments.Count() - 1], out Id);
if(Id == Guid.Empty)
return RedirectToAction("ErrorPage");
}
I want to test this controller function.I have put the matching route to match this function in my global.asax file. Basically I am trying to retrieve the guid from the URl and if it is not good then take him to error page.
HttpRequestBase class URl is no setter,so I am confused how to test this functionality?
As for testing read the link provided in the comments above but I would recommend doing some reading on MVC Routing or here, it does exactly what you are trying to achieve.
Have a look in your global.ascx.cs file you will find something like this:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
This is the out of the box route for the MVC framework.
Routes will match a url based on the convention you give them.
So based on the default route above and given a url like this:
http://localhost:portnumber/MyController/MyAction/8B4B93DE-76CA-4552-B4AA-460400761EAD
It will try and match this url to a controller called MyController with an action called MyAction, which receives and Id. It will only hit this action if all the criteria match.
If there is no id in the URL and id defined on your action method is not a nullable type then it simple wont execute. It will rather try match another Url, if it cant find one it will give a 404.
Like this:
public class MyController : Controller {
[HttpGet]
Public ActionResult MyAction(Guid id)
{
}
}
So if I have a URL such as mysite.com/user/1234/show which object can I use in the view that is displayed to get at the 1234 int in the URL above? For instance I want to have an action link that uses the user id again for the next controller action call such as mysite.com/user/1234/edit.
Thanks
You shouldn't have to go directly to the URL for the data. Instead, add a parameter to your route and action. Your route could look something like "{controller}/{id}/{action}" and your action something like:
ViewResult Show(int userId){
var viewData = new MyViewData(){
UserID = userId
};
return View(viewData);
}
Then, in your view, use Model.UserID.
EDIT: This is initially more work than just doing a (dare I write it?)
Int32.Parse(Request["userId])
in your view. However, the approach I have shown above is the preferred approach, since you will be leveraging the power of MVC's routing and binding capabilities. If you want to change parameter name someday, you just need to fix your route and the action's parameter name, as opposed to having to sort through all of your application searching for places where you pull values directly from the Request collection.
I believe you can get at it via the RouteData property of the request context. But a much easier way would be to just add a field for it to your model and have the controller set it there.
It depends upon your your routing scheme.
have look at these tutorial
Creating Custom Routes
mysite.com/user/show/1234
routes.MapRoute(
"User", // Route name
"user/show/{ID}", // URL with parameters
new { controller = "User", action = "show" } // Parameter defaults
);
above url wiil hopefully call, 'show' Action for 'user' controller and pass the ID, 1234
Change the default route in your global.asax.cs to -
routes.MapRoute(
"Default", // Route name
"{controller}/{id}/{action}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
Create a UserController with a Show method like this -
public class UserController : Controller
{
public ActionResult Show(int id)
{
var model = new UserViewModel {Id = id};
// Retrieve user from data layer and update model with other user details here
return View(model);
}
public ActionResult Edit(int id)
{
// Deal with edit action in here
}
}
public class UserViewModel
{
public int Id { get; set; }
}
In your aspx view, make sure that you page inherits from ViewPage<UserViewModel> by declaring in the page directive of your aspx view -
<%# Page Language="C#" Inherits="System.Web.Mvc.ViewPage<UserViewModel>" %>
Then you can create an edit link in your page like this -
<%=Html.ActionLink("Edit User", "Edit", new { id = Model.Id }) %>
Using the default route provided, I'm forced to name my parameters "id". That's fine for a lot of my Controller Actions, but I want to use some better variable naming in certain places. Is there some sort of attribute I can use so that I can have more meaningful variable names in my action signatures?
// Default Route:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
// Action Signature:
public ActionResult ByAlias(string alias)
{
// Because the route specifies "id" and this action takes an "alias", nothing is bound
}
Use the [Bind] attribute:
public ActionResult ByAlias([Bind(Prefix = "id")] string alias) {
// your code here
}
This still works, your query string will just look like "/Controller/ByAlias?alias=something".
You can customize the routes with whatever identifiers you like..
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{alias}", // URL with parameters
new { controller = "Home", action = "Index", alias = "" } // Parameter defaults
);
Edit: Here's an overview from the ASP.NET site
Just because your route uses the name "id" for the ID variable doesn't mean that you have to use the same name in your controller action methods.
For example, given this controller method...
public Controller MailerController
{
public ActionResult Details(int mailerID)
{
...
return View(new { id = mailerID });
}
}
...and this action method call from the view...
<%= Html.ActionLink("More Info", "Details", new { mailerID = 7 }) %>
...you can use whatever naming convention you wish for the ID parameter in your controller action methods. All you need to do is resolve the new name to the default, whether it's "id", "alias", or whatever.
The above example should resolve to :
More Info