ASP.NET MVC allow very usable way of generating stongly typed URL like:
Business info
or even:
Business info
Using simple custom URL helper:
public static string Action<TController>(
this UrlHelper urlHelper,
Expression<Action<TController>> action,
string fragment = null
) where TController : BaseController
{
var routeValues = InternalExpressionHelper.GetRouteValuesFromExpression(action);
var url = UrlHelper.GenerateUrl(
routeName: null,
actionName: null,
controllerName: null,
protocol: null,
hostName: null,
fragment: fragment,
routeValues: routeValues,
routeCollection: urlHelper.RouteCollection,
requestContext: urlHelper.RequestContext,
includeImplicitMvcValues: true
);
return url;
}
It allows changing URL mapping in one place (RouteConfig) and any Controllers and Actions re-factoring doesn't mean you need to go and update each link.
I like NancyFx for it's simplicity and good IoC out of the box, but what i'm not sure why NanxyFx doesn't have support of reverse-routing (generating URL based on the action name) so it would be possible to create some static-typing helper for it.
Any ideas how to implement it in NancyFx or why if it's not possible to do, then why?
The accepted answer used to be right, but now there's Linker.
It doesn't do the expression parsing out of the box but it basically pulls parameters from property name-value pairs (like a RouteValueDictionary) so adding support for extracting the parameters from an expression tree shouldn't be too hard.
Routes are not named in Nancy so there's currently no way to implement such a feature.
But if you ever find yourself changing routes then I think you have a much bigger issue to begin with, personally this awesome feature (or lack of in your current case) has made me think more about what I'm making my routes, and so I now rarely, if ever, need to change my routes.
If I do need to rename a route, Find All makes it pretty quick to fix.
Related
Question:
Edit: It seems my question is actually not a routing issue but an anchoring issue.
If I have assigned a route:
[Route("~/Envelope/List/AcademicYear/{year}")]
public IActionResult AcademicYear(string year)
{
}
How would I correctly use an asp-action to call this route?
using
<a asp-action="List/AcademicYear/" asp-route-id="#Model.AcademicYear">#Model.AcademicYear</a>
returns a url with a %2f (Envelopes/List%2fAcademicYear/2122) instead of / (Envelopes/List/AcademicYear/2122) and thus results in a 404 error
How do I use Custom URL with asp-action to call a specific Action in my Controller?
or
How do I change the routing so I can call an action from a controller with a non default route mapping?
Context:
I've read https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0
and yet i'm still confused on the whole concept of routing and how to interacts with controllers and actions.
In my application I have a controller called Envelope - it controls everything to do with my Envelopes.
I have a class in the Envelopes controller called
public class EnvelopeController : Controller {
public IActionResult List() {... return View()}
and it returns the List.cshtml view. The current url as set by default route mapping: /Envelope/List
In the List.cshtml I have a link that is intended to filter the List on a year parameter
<a asp-action="AcademicYear" asp-route-academicYear="#Model.AcademicYear"> #Model.AcademicYear</a>
My intention is to pass this into a method in the Envelopes controller called "AcademicYear" that gathers the Envelope data stored in temp data, deseralises it and then returns a filtered version based on the parameter:
public IActionResult AcademicYear(string academicYear) { return View("List", newViewModel)}
The return url after this point is correctly: /Envelope/AcademicYear?academicYear=21%2F22
However I would Like to know how to change this so even though I call the Action
<a asp-action="AcademicYear" asp-route-academicYear="#Model.AcademicYear"/>
the URL returned would look like this /Envelope/List/AcademicYear/2122/
Is there a way of doing this? Am I looking at the problem the wrong way? I have thought about simply passing a parameter in the List action and running some form of control to do either set of operations depending on the parameters existence but realistically the List method and the filtering AcademicYear method aren't really doing the same thing and I'd like to seperate out the code into different methods if possible.
Even if its not the appropriate solution I would still like to know if it is possible to change the URL routing for an action after it has been called.
Edit :
I have tried using HttpGet(List/AcademicYear/{academicYear:[a-zA-Z]} however when I do this I can't actually call List/AcademicYear as an asp-action due to the "/" and how that encodes to %2f
Answer:
With the help of the below solutions I realised I was looking at the problem wrong and was actually having issues creating correct anchors.
Reading: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0#url-generation-and-ambient-values-1
I realised the answer was staring me in the face and I used this alongside the Routes provided for in the answers
<a id="#academicYear.Name" href="#Url.Action("AcademicYear", "Envelope",new {year= academicYear.Name})">
Maybe you just need to decorate your action like that:
[HttpGet("List/AcademicYear/{year:int}")] // or HttpPost it depends on you
public IActionResult AcademicYear(string year) { }
You can add several attribute routes to the action.
[Route("~/Envelope/List/AcademicYear/{year}", Name="ListRoute")]
[Route("~/Envelope/AcademicYear/{year}")]
public IActionResult AcademicYear(string year) { }
and if you need this url
http://localhost:xxxx/Envelope/List/AcademicYear/2021
you can use this html helper, that will create a right anchor tag for you using an attribute route name.
#Html.RouteLink(#Model.AcademicYear, "ListRoute", new { year = #Model.AcademicYear })
I added Name="ListRoute" to the attribute routing (see above)
I'm developing a large ASP.NET Core 2 web application but I still get confused with URLs.
When fist learning, I thought URL's took on the name of the View, but discovered they come from the Controller method.
Here is an example to convey my issue:
I have a controller method named Daysheet, which returns a view and model with the same name.
In the Daysheet view, I call various controller methods from Javascript to perform specific actions. One of them is called AssignStaff which takes two integer parameters.
In the AssignStaff method I again return the Daysheet view with model, but now my URL is "AssignStaff"!
I can't just do a redirect because the whole Daysheet model is not being passed to the AssignStaff method.
I have many situations like this where after calling an action, I end up with another URL that I don't want.
UPDATE/EDIT
Thanks for assistance and apologies if my explanation is confusing. I simply have a view called Daysheet that uses a model. I want to call various controller methods to perform various actions, but I want to stay on the "Daysheet" view/URL.
As mentioned, I can't just redirect because in the action method I no longer have the whole model from the Daysheet view. Also, if I redirect I can't pass the whole model because that causes an error saying the header is too long. I think my only choice may be to use ajax for the actions so that the URL doesn't change.
When you just do Return View("") name in a Controller Action, the URL will be the name of the Action you are using.
If you want to redirect to some specific Action, that will help to make sure the Url matches to where you are. You might want to read more about it here.
To do so, use:
RedirectToAction()
The URLs your application responds to are called "routes", and they are either created by convention or explicitly. The default is by convention, of course, which is a URL in the form of /{controller=Home}/{action=Index}. Index is the default action if that portion of the route is left off, so a request to /foo will by convention map to FooController.Index. HomeController is the default controller, so an empty path (e.g. http://sample.com) will by convention invoke HomeController.Index.
Razor Pages have their own conventions. These do somewhat follow the file system, but exclude the Pages part of the path. So a Razor Page like Pages/Foo/MyRazorPage.cshtml, will load up under /Foo/MyRazorPage.
There there is the Route attribute, which allows you to specify a totally custom route. This attribute can be applied to a controller class and individual actions in the class. For example:
[Route("foo")]
public class MyAwesomeController
{
[Route("bar")]
public IActionResult MyAwesomeAction()
{
return View();
}
}
With that, a request to /foo/bar will actually invoke MyAwesomeController.MyAwesomeAction.
I have an API action defined as the following:
[Route(Name="GetMembersTest"), HttpGet, ResponseType(typeof(MemberHeadersDto))]
public IHttpActionResult GetMembers[FromUri]MemberFilterDto filter, [FromUri]PagingOptionsDto paging)
This method works as expected, routing and all, requests are flowing through just fine. However, I'd like to supply a "NextUri" for paging so that the caller can just keep following NextUri until it is null to get all the results. I have to send back a uri to the same action, 1 page ahead, if that makes sense.
So I tried using UrlHelper.Route. This route is named "GetMembers" for the purpose of this example.
NextUri = Url.Route("GetMembers", new { filter, paging });
The problem is that instead of getting something like
/v1/members?filter.q=&filter.otherproperty=&paging.count=10&paging.startRow=11
I get
/v1/members?filter=WebApi.Models.MemberFilterDto&paging=WebApi.Models.PagingOptionsDto
It looks like UrlHelper.Route doesn't support complex types in the [FromUri] parameter of a GET Request. Is there anything I can do to get this functionality? My workaround right now is to take in all the Dto properties as individual parameters then build my Dtos from them on the server. This isn't ideal because if I ever add any more options I'd have to add more parameters to the action, which also makes the route value dictionary more fragile as well because it has to match with the method signature in UrlHelper.Route(routeName,routeValues).
Unfortunately, there is no way to pass in complex object to routing. Instead, you will need to pass in the simple properties individually.
I was not able to find a way to extend Url.Route, but that would be/have been your best option.
I have an existing API I'm moving over to WebAPI, so I'm not free to change the URL. Breaking existing clients is not an option for me.
Knowing that, the original API would accept either a Guid (an ID) or a string (a name) for a given action method. The old API handler would decipher the URL parameter and send the request to a controller action designed to accept the given parameter type.
As an example:
Get(Guid id)
versus
Get(string name)
With WebAPI, the parameter binding is greedy across value types, so depending on which is first in the controller source file, that action is the one invoked. For my needs, that's not working. I was hoping the binder would realize the conversion to a Guid would fail for a name and then select the more generic string-based action. No dice. The Guid simply comes across as a null value (interestingly since it's a value type, but that's what I'm getting in the debugger at a certain point in the processing).
So my question is how best to handle this? Do I need to go as far as implementing a custom IHttpActionSelector? I tried the attribute routing approach (with constraints), but that isn't working quite right (bummer as it looks cool). Is there a mechanism in WebAPI that accounts for this I don't (yet) know about? (I know I can hack it in by testing the string for Guid-ness and invoking the other controller method, but I'm hoping for a more elegant, WebAPI-based solution...)
I'd spent a lot of time trying to fit attribute-based routing in, but I've not got that to work. However, I did solve my particular issue using route constraints. If you register the more-constrained route first, WebAPI (like MVC) will apply the constraints and skip over more-constrained routes until it finds one that it can select, if any.
So, using my example, I'd set up routes like so:
_config.Routes.MapHttpRoute(name: "ById",
routeTemplate: "product/{id}",
defaults: new { controller = "ProductDetails" },
constraints: new { id = #"^\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?$" });
_config.Routes.MapHttpRoute(name: "ByName",
routeTemplate: "product/{name}",
defaults: new { controller = "ProductDetails" });
The first route accepts a constraint in the form of a regular expression for a Guid. The second accepts all other values, and the controller will deal with non-product names (returns a 404). I tested this in the self-hosted WebAPI server and it works fantastically.
I am sure attribute-based routing would be more elegant, but until I get that to work, it's routing the old way for me. At least I found a reasonable WebAPI-based solution.
This question already has answers here:
asp.net mvc complex routing for tree path
(4 answers)
Closed 8 years ago.
I'd like to put together a forum/message board with ASP.NET MVC. Pretty common on these types of forums are hierarchical board categories, so for instance:
-General Discussion
-Technical Support
--Website Technical support
--Product Technical Support
---Product A Technical Support
---Product B Technical Support
Below each category are then topics and messages belong to those topics. What I'm primarily concerned with is 1.) getting to the correct place, given a URL, 2.) not including boatloads of unnecessary information in my URL, and 3.) being able to recreate a URL from code.
I'd like a URL to be something like this:
mysite.com/Forum/ - forum index
mysite.com/Forum/General-Discussion/ - board index of "general discussion"
mysite.com/Forum/Technical-Support/Product/Product-A/ - board index of "Product A Tech Support"
mysite.com/Forum/Technical-Support/Website/Topic1004/ - Topic index of topic with ID 1004 in the "Website Technical Support" board
mysite.com/Forum/Technical-Support/Website/Topic1004/3 - Page 3 of Topic with ID 1004
Now, I've excluded Action names from this because they can be inferred based on where I am. Each Board entity in my database has a "UrlPart" column, which is indexed, so I expect to be able to do relatively fast queries against that table to figure out where I am.
The question is: in order to figure out the correct place, should I use a custom route handler, a custom route binder, or should I just create obscure routing rules?
This suggestion looks pretty good but it also looks like a lot of work for little benefit:
ASP.NET MVC custom routing for search
This seems to indicate that creating a model binding would be easier:
MVC Dynamic Routes
To fulfill #3 I'm going to have to create my own custom URL generation logic, right?
If you need deep and/or non-conforming URLs, I would suggest that you employ attribute based routing, such as the solution discussed here.
I prefer an attribute based approach over putting every route in Application_Start, because you have better locality of reference, meaning the route specification and the controller which handles it is close together.
Here is how your controller actions would look for your example, using the UrlRoute framework I implemented (available on codeplex):
[UrlRoute(Path = "Forum")]
public ActionResult Index()
{
...
}
[UrlRoute(Path = "Forum/General-Discussion")]
public ActionResult GeneralDiscussion()
{
...
}
[UrlRoute(Path = "Forum/Technical-Support/Product/{productId}")]
public ActionResult ProductDetails(string productId)
{
...
}
[UrlRoute(Path = "Forum/Technical-Support/Website/{topicId}/{pageNum}")]
[UrlRouteParameterDefault(Name = "pageNum", Value = "1")]
public ActionResult SupportTopic(string topicId, int pageNum)
{
...
}
With this approach you can generate outbound URLs using the same helpers (Url.Route*, Url.Action*) that you would use if you manually added the routes using the default route handler, no extra work needed there.
You could have them all go to one controller action which handles the route handling for you by manually splitting the rest of the url out and calls what a method on your BLL which then delegates tasks to other methods finally returning a View() depending on your needs.