Area webapi route not being resolved - asp.net-mvc

am trying to test the web api routes, that exists inisde my administration area.
This is the the route definition in the area
[HttpPut]
[Route("timezone/put/{timezone}", Name = "PutTimeZone")]
[AllowAnonymous]
[ResponseType(typeof(void))]
public IHttpActionResult PutTimeZone(string timezone)
{
/*Action body*/
}
I have a route prefix like so [RoutePrefix("admins/misc")]
Here is my route test
const string route = "/admins/misc/timezone/put/-120";
RouteAssert.HasApiRoute(_httpConfiguration, route, HttpMethod.Put);
_httpConfiguration.ShouldMap(HttpMethod.Put, route)
.To<MiscApiController>(HttpMethod.Put, x => x.PutTimeZone("-120"));
When I run the test, I get MvcRouteTester.Assertions.AssertionException : Expected 'Administration', got no value for 'area' at url '/admins/misc/timezone/put/-120'.
I read in the route tester wiki page
If you are using MVC areas, then as long as you use the standard
layout of namespaces, the area name will be extracted from your
controller type name and tested against the area chosen by the route.
e.g. if your controller's fully qualified type name is
MyWebApp.Areas.Blog.CommentController then the expected area name will
be "Blog".
But frankly, it doesn't give me any clue as to what to do so as to make my test pass. My question is what am I missing?

Try replacing [RoutePrefix("admins/misc")] with [RouteArea("Administration",AreaPrefix = "admins/misc")] or combining both together with [RouteArea("Administration",AreaPrefix = "admins"),RoutePrefix("misc")].
Edit:
I downloaded the source for the MvcRouteTester library and tried to debug it using almost the same example as you provided and it looks like there is an issue with the library itself.
Specifics: one method Common.Verifier.VerifyExpectations asserts that the expected area expected.Area matches route's actual area actual.Area, but there is no previous code that sets the actual area property to any value (although it does set Controller and Action properties in ApiRoute.Generator.ReadRequestProperties).
So I suppose that at its current state (at the time of this post) the library simply does not support areas for WebApi. I suggest that you open an issue on the MvcRouteTester github page with a link to this post.

Related

Specify HTTP VERB for Url.Action?

I am using AttributeRouting: http://attributerouting.net/
If I have two actions with the same name, but the route for the GET is different than the route for the POST, why is Url.Action generating a URL that matches on my GET action, even though my action method is pointing to the one that is a post?
I have tried passing the verb as an
Url.Action("CreateEdit", new { method = "POST" })
However this returns the string /enrollments/create instead of /enrollments/createedit which corresponds to the POST.
I wouldn't expect this to work but tried it as some route configurations use this technique:
Url.Action("CreateEdit", new { HttpMethodConstraint = new HttpMethodConstraint("POST") })
However both of these don't solve the problem, and the URL is still for my GET route.
I have verified that I can post to a hardcoded URL of /enrollments/createedit so the POST action does indeed have that route, just to rule out the possibility that the POST Action's default [POST] is defaulting to the expected route.
Problem is that I usually avoid hardcoded URL's and use Url.Action or Html.BeginForm (which also exhibits the same issue) so that all URLs are derived from action routes.
Here are how I have defined my actions:
[GET("create")]
[GET("edit/{id:guid?}")]
public ActionResult CreateEdit(Guid? id)
...
[POST] //default to "createedit"
public ActionResult CreateEdit()
...
Note this question is not about making the actual request, but is generating route urls.
How do you indicate to Url.Action that it should use the route from the [POST] instead of the GET. There is no overload that take constraints or HTTP Verb? Note that Html.BeginForm exhibits that same issue when generating the action URL attribute, which is clearly a POST oriented method.
It also does not work if I simplify my action routes to(although the above are my goal routes):
[GET("create")]
public ActionResult CreateEdit()
...
[POST("createedit")] //default to "createedit"
public ActionResult CreateEdit()
...
Url.Action("CreateEdit", new { method = "POST" }) returns "/enrollments/create" obviously because method = 'POST' is not the correct way to specify the desired route for Url.Action. Is there a way to tell Url.Action or Html.BeginForm to specify that the POST route is desired? The method param of BeginForm effects the method="post" html attribute, but the action attribute is still generated with the wrong /enrollments/create URL. I've seen area="Admin" used to specify Areas in route parameters. Is there some magic property that can be added to specify the verb? If there is not, then clearly there is no way to get the right URL consistently and I will probably need to rename one of my actions if I need to maintain the desired routes. My hope was there was some route value I could add analogous to new { area = "SomeArea" }. method = "POST" was just my wild guess at what might work.
The request pipeline respects route constraints, if they are both the same route, then it works fine. So if both were [POST("creatededit")] and [GET("create"] then it would work fine because the URLs for both are the same, and then when the actual request is made the distinction is made by the pipeline due to the HttpMethodContraint.
If I use parameters to make the routes distinct, it works but only if I eliminate the [GET("create")]
If Url.Action has no known/supported/documented way to take a route property then the answer may simply be that Url.Action doesn't have any way for you to tell it the desired VERB.
If someone asks: Is there a way to specify the area for Url.Action?
The answer is simply: Url.Action("SomeAction", new { area ="SomeArea" })
The answer is not: muck around with your routes so you don't have two actions of the same name in different areas(or any number of other things that don't address the question).
The answer is not: You shouldn't have actions of the same name in different areas. (It might be a valid point, but it doesn't create a useful resource for others who are in a situation where they absolutely must generate a URL for a different area).
I don't understand what is so complicated about that.
I'm not asking how to make a POST request. I know how to do that, but I don't intend to hardcode URLs into my views. That turns into a maintenance nightmare.
I could change my routes if my boss wasn't so set on SOE. So I can't eliminate the [GET("create")] route.
I can resolve by renaming an action like so:
[GET("create")]
[GET("edit/{id:guid?}")]
public ActionResult CreateEdit(Guid? id)
...
[POST("createedit")]
public ActionResult CreateEditPost()
...
That is a bit dirty, but works. It still doesn't answer the question.
If there is a way to specify the VERB for Url.Action, I think the answer would be a useful resource. Maybe not to as many people as Url.Action("SomeAction", new { area ="SomeArea" }) but it would be documenting a feature of Url.Action that for me has been elusive. Maybe it doesn't support it. Perhaps I will need to delve into the source code.
I goofed in my original answer. I wasn't thinking it through really and probably answered too quickly. Here's what it boils down to:
Url.Action is tied pretty inherently to the controller/action style routing. When there's two matching actions (overloads where one is a GET version and the other is a POST), the URL should be the same, and so the GET or really first version is returned. See, AttributeRouting just sort of layers on by letting you customize the outward facing URL, but internally, Url.Action is simply trying to find a route that will get you to the requested action. Once it finds a match, it assumes that's good enough, and especially pre-MVC5, it should have been.
MVC5 introduced attribute routing as a first-class citizen, but from what I've seen, this edge case (where the GET and POST versions of the same action have different URLs and you want the POST version in particular) has not been covered.
That said, I see a couple of potential workarounds:
Use different action names. If your POST action is named something like CreateEditPost, then you can very easily do Url.Action("CreateEditPost") and get the right URL. Since your routes are not affected directly by the action name, it doesn't really matter what it's called.
Use named routes. If you name your routes, then you can use Url.RouteUrl instead and request exactly the route your want. For example:
[POST("createedit", RouteName = "CreateEditPost")]
public ActionResult CreateEdit()
{
...
}
Then:
#Url.RouteUrl("CreateEditPost")

Composite C1 routes.MapPageRoute - Directing to a page

I have been adding some custom routes which are not working
I can get this MVC route working but the problem is it simple routes directly to the view rather than the page which contains the master layout etc.
routes.MapRoute("Job-Listing", "job-detail/{category}/{title}/{id}", new { controller = "JobSearchModule", action = "JobDetail" });
I tried routing to a page which existed like following. This didn't work and simple went to a page not found.
routes.MapPageRoute("Job-Listing", "job-detail/{category}/{title}/{id}", "~/job-seekers/job-search/job-detail");
I guessed that this might be because this is not a physical path and there is some other routing going off under the hood. So I tested this by adding a Route to a physical page like follows. (this route worked)
routes.MapPageRoute("jobDetail2Route", "job-detail/{category}/{title}/{id}", "~/Text.aspx");
This got my thinking that composite c1 might have a physical URL which the C1 routing maps to. I'm sure I have seen at some point something to do with a /Renderers/Page.aspx. Does anyone know if I could somehow route to a physical page in this way?
Thanks
David
OK so some further information.
I realized I could get the the URL using /Renderers/Page.aspx?pageId=d622ab3b-2d33-4330-9e6e-d94f1402bc80. This URL works fine so I attempted to add a new route to this URL like as follows...
routes.MapPageRoute("Job-Listing", "job-detail/{category}/{title}/{id}", "~/Renderers/Page.aspx?pageId=d622ab3b-2d33-4330-9e6e-d94f1402bc80");
Unfortunately this still didn't work. I got an error on the Renderers/Page.aspx
Value cannot be null.
Parameter name: pageUrlData
Any ideas?
Rather than using the internal representation of a page URL like ~/Renderers/Page.aspx?pageId=d622ab3b-2d33-4330-9e6e-d94f1402bc80 you should use the public representation like /my/page/url.
To get the public representation from the page id you could use a method like this:
using Composite.Data;
...
public string GetPageUrl( Guid pageId )
{
using (var c = new DataConnection())
{
PageNode p = c.SitemapNavigator.GetPageNodeById(pageId);
return p.Url;
}
}
This will give you the public URL of the page with the provided id. If you are running a website with different content languages this method will automatically fetch you the URL matching the language of the page your MVC Function is running on.
Should you have the need to explicitly change the language of this URL use the overload DataConnection(CultureInfo culture) instead of the default constructor.
With the above method in place you should be able to do this:
routes.MapPageRoute(
"Job-Listing",
"job-detail/{category}/{title}/{id}",
GetPageUrl(Guid.Parse("d622ab3b-2d33-4330-9e6e-d94f1402bc80"));

ASP.Net MVC redirecttoaction not passing action name in url

I have a simple create action to receive post form data, save to db and redirect to list view.
The problem is, after redirecttoaction result excutes, the url on my browser lost the action section. Which it should be "http://{hotsname}/Product/List" but comes out as "http://{hotsname}/Product/".
Below is my code:
[HttpPost]
public ActionResult Create(VEmployee model, FormCollection fc)
{
var facility = FacilityFactory.GetEmployeeFacility();
var avatar = Request.Files["Avatar"].InputStream;
var newModel = facility.Save(model, avatar);
return RedirectToAction("List");
}
The page can correctly render list view content, but since some links in this view page use relative url, the functions are interrupted. I am now using return Redirect("/Employee/List") to force the url. But I just wonder why the action name is missing. I use MVC3 and .Net framwork 4.
I am new to ASP.Net MVC, thanks for help.
Your route table definitely says that "List" action is default, so when you redirect to it as RedirectToAction("List") - routing ommits the action because it is default.
Now if you remove the default value from your routes - RedirectToAction will produce a correct (for your case) Url, but you'll have to double check elsewhere that you are not relying on List being a default action.
Well, Chris,
If you get the right content on http://{hotsname}/Product/ then it seems that routing make that URL point to List either indirectly (using pattern like {controller}/{action}) and something wrong happens when resolving URL from route or {action} parameter is just set wth default value List. Both URLs can point to the same action but the routing engine somehow takes the route without explicit action name.
You should check:
Order in which you define your routes
How many routes can possibly lead to EmployeeController.List()
Which one of those routes has the most priority
Default values for your routes
Just make the route with explicit values: employee/list to point to your List action and make sure that is the route to select when generating links (it should be most specific route if possible).
It would be nice if you provide your routes mappings here.
but since some links in this view
page use relative url, the functions
are interrupted.
Why do you make it that way? Why not generate all the links through routing engine?
When using the overload RedirectToAction("Action") you need to be specifying an action that is in the same controller. Since you are calling an action in a different controller, you need to specify the action with the alternate overload e.g. RedirectToAction("List", "Employee").

ASP.NET MVC Action Parameters with Binded prefix not compatible with Url.Route in Global.asax

I have a details page containing a form field named UserId. On the same page i have another search form with a field also named UserId.
I am using Html.LabelFor(vm > vm.UserId) and Html.TextBoxFor(sm > sm.UserId) on the two different view models, vm being the view model and sm being the search model. (Yes, the UserId property on the two models has identical names - because they are the same domain property.
When i navigate to the page, the populated UserId on the vm is inserted into BOTH form fields named UserId by MVC. Even the sm.UserId are empty.
That is my initial problem. There are a few ways ti avoid that. My solution was to use the Prefix flag for the sm.
[HttpGet]
public ActionResult Search([Bind(Prefix = "Search")] SearchFormViewModel searchFormViewModel, PagingViewModel pagingViewModel)
{
This will provoke MVC to render a Search.UserId on the fieldname in the search form, but the property in code will still be named UserId.
This solution seems to work great!
BUT:
Now i have to address the search.UserId on a route from Global.asax.
I map the route like this:
routes.MapRoute(
"MyRouteName",
"ControllerName/User/{Search.UserId}",
new { controller = "ControllerName", action = "Search" }
);
My problem is that MVC can't map the Search.UserId (because of the .) to fit the UserId (prefixed with Search) in the action shown above.
So it seems like MVC has a prefix-feature, that are actually nok fully supported through the Route-handler.
Ofcourse i could rename the Search.UserId to Search_UserId, but then the name dosent match the name MVC expects in the recieving action above. (expects Search.UserId) Renaming The UserId property of the search model would fix the issue, but since it is the same value in the domain, this seems like a workaround.
Am I missing something here about the usage of the Prefix feature or is this just not possible?
So... I've been thinking about this for a while now. - And a colleague of mine suddently showed me the light.
The problem lies where MVC maps the object to a route dictionary.
See the user
wount work. Because MVC can not handle the .(dot) in the object name.
but since the object name is just a string key in the routevaluedictionary, mapping it my self did the trick:
See the user

How Can I Stop ASP.Net MVC Html.ActionLink From Using Existing Route Values?

The website I'm working on has some fairly complicated routing structures and we're experiencing some difficulties working with the routing engine to build URLs the way we need them to be built.
We have a search results page that uses RegEx based pattern matching to group several variables into a single route segment (i.e. "www.host.com/{structuralParameters}" can be the following: "www.host.com/variableA-variableB-variableC" - where variables A through C are all optional). This is working for us fine after a bit of work.
The problem we are experiencing resolves around an annoying feature of the ActionLink method: if you point to the same controller/action it will retain the existing route values whether you want them or not. We prefer to have control over what our links look like and, in some cases, cannot have the existing parameters retained. An example would be where our site's main navigation leads to a search results page with no parameters set - a default search page, if you like. I say this is an annoying feature because it is a rare instance of the ASP.Net MVC Framework seemingly dictating implementation without an obvious extension point - we would prefer not to create custom ActionLink code to write a simple navigation link in our master page!
I've seen some say that you need to explicitly set such parameters to be empty strings but when we try this it just changes the parameters from route values into query string parameters. It doesn't seem right to me that we should be required to explicitly exclude values we aren't explicitly passing as parameters to the ActionLink method but if this is our only option we will use it. However at present if it is displaying in the query string then it is as useless to us as putting the parameters directly into the route.
I'm aware that our routing structure exasperates this problem - we probably wouldn't have any issue if we used a simpler approach (i.e. www.host.com/variableA/variableB/variableC) but our URL structure is not negotiable - it was designed to meet very specific needs relating to usability, SEO, and link/content sharing.
How can we use Html.ActionLink to generate links to pages without falling back on the current route data (or, if possible, needing to explicitly excluding route segments) even if those links lead to the same action methods?
If we do need to explicitly exclude route segments, how can we prevent the method from rendering the routes as query string parameters?
This seemingly small problem is causing us a surprising amount of grief and I will be thankful for any help in resolving it.
EDIT: As requested by LukLed, here's a sample ActionLink call:
// I've made it generic, but this should call the Search action of the
// ItemController, the text and title attribute should say "Link Text" but there
// should be no parameters - or maybe just the defaults, depending on the route.
//
// Assume that this can be called from *any* page but should not be influenced by
// the current route - some routes will be called from other sections with the same
// structure/parameters.
Html.ActionLink(
"Link Text",
"Search",
"Item",
new { },
new { title = "Link Text" }
);
Setting route values to be null or empty string when calling Html.ActionLink or Html.RouteLink (or any URL generation method) will clear out the "ambient" route values.
For example, with the standard MVC controller/action/id route suppose you're on "Home/Index/123". If you call Html.RouteLink(new { id = 456 }) then MVC will notice the "ambient" route values of controller="Home" and action="Index". It will also notice the ambient route value of id="123" but that will get overwritten by the explicit "456". This will cause the generated URL to be "Home/Index/456".
The ordering of the parameters matters as well. For example, say you called Html.RouteLink(new { action = "About" }). The "About" action would overwrite the current "Index" action, and the "id" parameter would get cleared out entirely! But why, you ask? Because once you invalidate a parameter segment then all parameter segments after it will get invalidated. In this case, "action" was invalidated by a new explicit value so the "id", which comes after it, and has no explicit value, also gets invalidated. Thus, the generated URL would be just "Home/About" (without an ID).
In this same scenario if you called Html.RouteLink(new { action = "" }) then the generated URL would be just "Home" because you invalidated the "action" with an empty string, and then that caused the "id" to be invalidated as well because it came after the invalidated "action".
Solution at the root of the problem
It seems that the optimal solution (that doesn't smell like a workaround) is the one that solves the problem where it has roots and that's in routing.
I've written a custom Route class called RouteWithExclusions that is able to define route value names that should be excluded/removed when generating URLs. The problem is when routing falls through routes table and subsequent routes don't have the same route value names...
The whole problem is detailed and explained in my blog post and all the code is provided there as well. Check it out, it may help you solve this routing problem. I've also written two additional MapRoute extension methods that take an additional parameter.
If you want total control of the link, just build the link yourself:
Click Here
Substitute whatever you need inside the href attribute.

Resources