This code works fine:
[RouteArea("Main", AreaPrefix = "Hello")]
[RoutePrefix("{orgCode}")]
public class ResponseController : BaseController {
[Route("Save/{formCode}/{responseId}")]
public ActionResult Save(string formCode, int responseId, string questionCode){}
}
and Url.Action("Save", "Response") produces, for example, /Hello/org123/Save/form/123
However, if the Route attribute is changed and another segment added:
[Route("Save/{formCode}/{responseId}/{questionCode}")]
then Url.Action("Save", "Response") produces an empty string.
Is there a limit to how many sections can be defined in the route?
Is there a limit to how many sections can be defined in the route?
No.
But MVC only can build URLs using route values that it knows about. This can be a combination of route values that are passed to the Url.Action() method and values that are in the current request (usually passed through the current URL).
When the framework tries to determine which route to use, it selects the first route that matches all of the route values (controller, action, and anything else) and matches all of the (optional) constraints.
The route will not match if all of the conditions below are true for any of the parameters:
Have no defaults
Are not marked UrlParameter.Optional (NOTE: Optional parameters may not have any non-optional parameters to the right of them)
Are not present in the current request
Are not explicitly passed as route values to Url.Action() (or other method that calls UrlHelper to generate a URL)
Have constraint rules that do not match
In short, a route only matches if all of the required parameters are provided and constraints are satisfied.
So, apparently there is no questionCode in the current context, and since it is required to build the URL, you get an empty string. Most likely you need to pass it explicitly.
Url.Action("Save", "Response", new { questionCode = "123" })
You should also be careful to always explicitly pass other parameters if there may be cases where they are not present in the URL.
Related
I want to apply asp.net api-versioning to my web app (which didn't have versioning). However, the tricky issue is that I must ensure that APIs should work both with and without the api-version.
[ApiVersion("1.0")]
[Route("api/products/{productId}/[controller]")]
[Route("api/v{version:apiVersion}/products/{productId}/[controller]")]
[ValidateModel]
[Produces("application/json")]
public partial class ProductController : ControllerBase {
internal const string GetLatestRoute = "GET Product/GetLatestAsync";
[HttpGet(Name = GetLatestRoute)]
public async Task<IActionResult> GetLatestAsync() {
}
}
I have a controller with multiple actions, each of them is defined with a unique route name. When I add two routes (with and without versions) to the controller, there comes a route-name conflict error:
Attribute routes with the same name 'GET Products/GetLatestAsync' must have the same template:
Action: 'Service.Controllers.ProductController.GetLatestAsync (ProductFD)' - Template: 'api/products/{productId}/Product'
Action: 'Service.Controllers.ProductController.GetLatestAsync (ProductFD)' - Template: 'api/v{version:apiVersion}/products/{productId}/Product'
There are several answers on StackOverflow that say the issue can be solved by removing the route names defined for the action methods. However, in my scenario, the route names are used to create Url Links in several places in the project.
Is there an approach that I can get rid of the issue? I'm wondering whether I could append version to the route name variable or mapping the non-version api to the version/1.0 ...? On the other hand, there is a rare case that I update all the methods in a controller. So is it possible that I only define a route-prefix on the top-level of the controller and only apply the api-version on the method-level?
Route names and the route table are not API version aware. In order for this to work, you need to use double route registration like you have because you are versioning by URL segment (not recommended). If clients are properly following the links returned by the server, then always using the route generated with the explicit version in it will do. If the client doesn't honor that and just calls the APIs directly without the API version, the second template will handle that for you. If you are only generating links with the same controller, then I would suggest using CreatedAtAction instead because it will not rely on the route name. If memory serves me correct, you can specify the order of each [Route] for precedence. If unspecified, it will be the first attribute specified - which matters.
You'll also need to enable:
services.AddApiVersioning(options => options.AssumeDefaultVersionWhenUnspecified = true);
If you haven't already.
Last, but not least, beware the known, breaking change: Async suffix trimmed from controller action names. This has snared many people.
I am not able to get how to pass a parameter along with a main url from a controller. I tried like this:
return new ModelAndView(new RedirectView("home?var=ss", true));
But I am getting null value for var . What is the correct way?
The documentation says:
By default all model attributes are considered to be exposed as URI
template variables in the redirect URL. Of the remaining attributes
those that are primitive types or collections/arrays of primitive
types are automatically appended as query parameters.
So you don't have anything to do except making sure that you have a model attribute named var, with the value ss.
I'm using nUnit to test the routing of my MVC2 project.
When I register my routes in Global.asax.cs, I give each route a unique name and specify its RouteData, eg:
routes.MapRoute(
"ShowRecord",
"{controller}/{id}",
new { action = "Show" },
new { id = #"^\d+$" }
);
In my unit tests I then invoke RegisterRoutes on a RouteCollection object, and inspect the resulting RouteValueDictionary against each url that I want to test. I use a mocked HttpContext for this, and everything works fine.
However, what I'd really like to know is, which named route(s) matched the supplied URL? Once my unit test has obtained the RouteData object corresponding to the URL under test, can I discover specifically which route was matched? Either by name (eg "ShowRecord" in the example above), or by its index in the RouteCollection object?
Unfortunately, you cannot do this. See How do I get Route name from RouteData? for more context.
What you could do is add the route name to the route's DataTokens dictionary. DataTokens are used to mark a route in some way that's significant to you and your application. They don't affect anything with regarding to route matching and url generation.
The purpose of the route name is to provide a lookup key to the routing engine for looking up routes. It acts much like an index in SQL Server.
I have a route like following, ideally I would like it to match:
domain.com/layout/1-slug-is-the-name-of-the-page
routes.MapRoute(
"Layout", // Route name
"layout/{id}-{slug}", // URL with parameters
new { controller = "Home", action = "Index"}, new {id = #"\d+$"}
);
But when I hit the url, I am keep on getting this exception:
The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Index(Int32)' in ....
The above route will match the following though:
domain.com/layout/1-slug or domain.com/layout/1-slug_permalink
Seems like the hyphen that separates the ID from the Slug is causing issues.
As the first step of processing, the Routing module performs pattern matching of the incoming URL against the declared route. This pattern matching is eager (so the id gets all hyphens up to the last one, which marks the beginning of the slug parameter). Constraints (like "\d+") run after pattern matching. So what's tripping you up is that the eager pattern matching is setting id to an invalid value, then it's failing the constraint, which causes the overall route not to match, so the pipeline moves on trying to match the incoming request to the next route in the collection.
The best (e.g. easiest to understand, non-trickery) way to work around this is to match the entire segment as an idAndSlug parameter, then use a proper regex within the controller to split this string back out into its id and slug constituents.
Alternatively, consider using the slash, as suggested by mxmissile.
i wish to have a simple Action in my controller that accepts a few optional values and some integer values.
this is my route i wish to have:
HTTP.POST
/review/create
and this is the Action method i would like...
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult Create(int userId,
int addressId,
byte baseScore,
byte reviewType,
string subject,
string description)
{ ... }
I'm under the uneducated impression that all of those arguments above will be populated by the forms collection values ... but it's not happening. Also, I have no idea how I would write a route, to handle those ... because those values are form post data....
here's my global.asax....
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// Api - Search methods.
routes.MapRoute(
"Search Methods",
"{controller}/{action}"
);
In fact, the action method is never called because it doesn't seem to find it :(
But, if create and action without any of those arguments, then it finds it ?????????
How would you write a route and action method to accept some require and some optional arguments, for the route /review/create ?
As far as i can see you may rewrite your controller action like this:
public ActionResult Create(int foo, int bar, byte blah, string name, int? xxx) {
// code here
}
The ModelBinder will then ensure that foo,bar and blah are set. Name and xxx may be null. I can't test it a the moment, but i think return type of the action should be ActionResult.
If you are POST'ing a form, just make sure that the elements in your form (textboxes, checkboxes, textarea, etc) have id's that match the parameters in your method. As an alternative you can pass a FormCollection to the method, and do myFormCollection["foo"] to get a string representation of the value (which can then be parsed to an int).
From my experience, you are missing a number of key elements and concepts with this question.
First and foremost, I don't believe you can execute a POST without a form. The form has to contain the controls from which you pull the values that get passed to the controller method. If the goal is to simply unit test your POST controller method, then just call the method directly in your test, which it appears that you're doing, based on one of your comments. If you involve the view, then you're doing integration testing, not unit testing. Regardless of the test type, the test will always fail because you are choosing not to build the form. Even if you manage to force the POST using Fiddler, Firebug or any other mechanism, you're still not testing the view, you're testing the HTTP protocol.
I highly recommend that you employ a web application testing tool, such as WatiN or Selenium, to test your web pages, rather than throw together a quick-and-dirty test that really doesn't test anything useful.
In your post request set content-type="application/json; charset=UTF-8" and pass the values for the method parameter in JSON format. This should make Asp.MVC not to look in FormCollection for those values.