Overloading a route with dynamic http parts - url

I have one route like:
GET /latest/:repo/:artifact controllers.Find.findLatestArtifact(repo: String, artifact: String)
that works as a restful api for us. But now, I have a new view with an html form that need to send actions to that controller filling up the parameters with two html selects from the form.
I have tried adding another route like:
GET /latest controllers.Find.findLatestArtifact()
and overloading the controller method to read the http get parameters manually, but it does not like it.
Previously in the past I already asked here how to fill up parameters from a html form, in a controller that does not have 0 args:
Binding an html form action to a controller method that takes some parameters
and seems that it was not possible. Then, how do I workaround this, without having to rename the controller method?

EDIT:
I've provided an answer to your other question but the clean solution to this is unrelated.
You actually can overload the route with something like
GET /latest controllers.Find.findLatestArtifact()
GET /latest/:repo/:artifact controllers.Find.findLatestRepoArtifact(repo: String, artifact: String)
Make sure they are listed in the correct order. Obviously these will route to different methods (this is cleaner server side and more descriptive to what the method actually does), so in your code you would want a simple redirect or just return the result of the overloaded method:
public static Result findLatestArtifact(){
return findLatestRepoArtifact("DefaultRepo","DefaultArtifact");
}
public static Result findLatestRepoArtifact(String repo, String artifact){
... some code here ...
}
Or you could do it your other way (see other answer)

Related

Previous GET parameters being unintentionally preserved in POST request

As of current I navigate to a view using a GET request, looking something like this:
/batches/install?Id=2&ScheduledDate=07.29%3A12
From there, I send a POST request using a form (where I include what data I wish to include in the request.
Furthermore I set the forms action to "Create" which is the action I wish to send the request to.
My issue is the fact that sending this request keeps the GET arguments in the POST url, making it look the following:
../batches/Create/2?ScheduledDate=07.29%3A12
I do not want this since:
1: it looks weird
2: it sends data I do not intend it to send in this request
3: if my model already has a property named "id" or "scheduledDate" the unintentional GET parameters will get bound to those properties.
How can I ignore the current GET parameters in my new POST request?
I just want to send the form POST data to the url:
../batches/create
(without any GET parameters)
How would this be done?
As requested, here is my POST form:
#using (var f = Html.Bootstrap().Begin(new Form("Create")))
{
//inputs omitted for brevity
#Html.Bootstrap().SubmitButton().Style(ButtonStyle.Success).Text("Create batch")
}
Note that I use the TwitterBootstrapMVC html helpers (https://www.twitterbootstrapmvc.com/), althought this really shouldn't matter.
As requested, to sum up:
I send a get request to : /batches/install?Id=2&ScheduledDate=07.29%3A12.
Through the returned view I send a POST request to: /batches/create.
However the previous get parameters get included in the POST request URL making the POST request query: /batches/Create/2?ScheduledDate=07.29%3A12 (which is NOT intended).
This is not a great Idea though, but will give you what you want.
[HttpPost]
public ActionResult Create() {
//do whatever you want with the values
return RedirectToAction("Create", "batches");
}
This is a "Feature" of MVC that was previously reported as a bug (issue 1346). As pointed out in the post, there are a few different workarounds for it:
Use named routes to ensure that only the route you want will get used to generate the URL (this is often a good practice, though it won't help in this particular scenario)
Specify all route parameters explicitly - even the values that you want to be empty. That is one way to solve this particular problem.
Instead of using Routing to generate the URLs, you can use Razor's ~/ syntax or call Url.Content("~/someurl") to ensure that no extra (or unexpected) processing will happen to the URL you're trying to generate.
For this particular scenario, you could just explicitly declare the parameters with an empty string inside of the Form.
#using (var f = Html.Bootstrap().Begin(new Form("Create").RouteValues(new { Id = "", ScheduledDate = "" })))
{
//inputs omitted for brevity
#Html.Bootstrap().SubmitButton().Style(ButtonStyle.Success).Text("Create batch")
}

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")

MVC: ActionLink Remembers ID Field?

This is a follow-on to an earlier stackoverflow question (link text).
If you use the default routing definition, which ends with {id}, then if you have an ActionLink whose target is the same method as generated the page the ActionLink is on, the framework automagically includes the id in the callback url, even if you didn't request it.
For example, if you're displaying a page from the following URL:
http://www.somedomain.com/AController/SameMethod/456
and the page cshtml file has an ActionLink like the following:
#Html.ActionLink("some text", "SameMethod", ARouteValueDictionary, SomeHtmlAttributes)
then whether or not you have "id" included in ARouteValueDictionary, it will show up in the generated URL.
This only occurs if you call back to the same method that generated the page in the first place. If you call back to a different method on the same controller the {id} field does not get inserted into the generated URL.
I don't necessarily have a problem with this. But I am curious as to why the designers took this approach.
FYI, I discovered this feature because I'd inadvertently been depending on it in my website design. I have to pass the ID field back to the server, along with a bunch of other information...only I'd never explicitly added the ID information to the RouteValueDictionary. But because most of my callbacks were to the same action method that had generated the page in the first place I was having the information included anyway.
You can imagine my surprise when a new component -- which I was sure was "essentially identical" to what was already working -- failed. But because the new component had a different target action method, the magic went away.
Edit:
Modified the explanation to clarify that including the {id} field in the generated URL is contingent upon calling the same method as generated the page in the first place.
...the framework automagically includes the id in the callback url,
even if you didn't request it.
I would prefer the term "ambiently" over "automagically". You can think of route tokens already in the URL as "ambient" to your HtmlHelper and UrlHelpers.
But I am curious as to why the designers took this approach.
Consider a Controller that groups together, say 5 actions. Those 5 may have links to each other, but not a lot of links outside the group. The simplest overload of Html.Action takes only 2 args: the text to render, and the action name.
This makes shorthand for linking around from action to action within these views. Since they are all on the same controller, and that controller is already in the path for the current action, MVC reuses this value when you don't specify the controller name in the helper method. The same behavior extends to {id}, or any other route token you define.

Is it possible to get the controller and the action (NOT THEIR NAME!!) based on the url?

I have found a dozens of threads about getting the name of the controller and method based on the url, I managed that just as well. Can I get the MethodInfo of the method based on their name automatically from the MVC engine, or do I have to do Type.GetType("Namespace.Controllers."+cname+"Controller").GetMethod(mname)? Which is not so nice, since how do I know the namespace in a framework class? How do I know if the default naming patterns are being observed, or is there a different config in use?
I want to get a "What Would MVC execute?" kind of result....
Is it possible?
EDIT: further info:
I have a framework which uses translatable, data-driven urls, and has a custom url rewriting in place. Now it works perfectly when I want to show the url of a news object, I just write #Url.Content("~/"+#Model.Link), and it displays "SomeNewsCategory/SomeNews" instead of "News/29" in the url, without the need to change the RouteTable.Routes dynamically. However in turn there is a problem when I try to write RedirectToAction("SomeStaticPage","Contact"); into a controller. For that, I need to register a static link in db, have it target "/SomeStaticPage/Contact", and then write
Redirect("~/"+DB.Load(linkid).Link); and that's just not nice, when I have 30 of these IDs. The web programmer guy in the team started registering "fake urls", which looked like this:
public class FakeURL
{
public string Controller;
public string Action;
public int LinkID;
}
and then he used it like Redirect(GetFakeUrl("controller","action")); which did the trick, but still was not nice. Now it got me thinking, if I apply a [Link(linkid)] attribute to each statically linked method, then override the RedirectToAction method in the base controller, and when he writes ReturnToAction("action","controller"), I'll actually look up the attribute, load the url, etc. But I'm yet to find a way to get the methodInfo based on the names of the controller and the action, or their url.
EDIT: I've written the reflection by myself, the only thing missing is getting my application's assembly from inside a razor helper, because the CallingAssembly is the dinamically compiled assembly of the .cshtml, not my WebApplication. Can I somehow get that?
To answer your edit, you can write typeof(SomeType).Assembly, where SomeType is any type defined in code in the project (eg, MvcApplication, or any model or controller)
Also, you can write ControllerContext.Controller.GetType() (or ViewContext) to get the controller type of the current request EDIT That's not what you're trying to do.
I found out that it was totally wrong approach. I tried to find the type of the controller based on the name, when instead I had the type all along.
So instead of #Url.Action("SomeAction","SomeController") I'll use #Url.MyAction((SomeController c)=>c.SomeAction()), so I won't even have to find the controller.

MVC actionlink posting List of complex type

I have an actionlink that on click im passing a List of objects to a controller action.
Example:
View:
Html.ActionLink("TestLink", "TestMethod", "Test", Model.SampleList, null)
TestController:
public ActionResult TestMethod(List<SampleList> sampleList)
{
return View(sampleList);
}
When I do this I get a null sampleList. I can pass a single complex object fine just not a collection of it. Do I need the correct routing for this? The reason I'm doing this is instead of passing an id and do a look up in the controller action, I just pass in the data.
It is possible when you perform a form post, have a look at this blog post for more information. You'll probably not be able to use one of the HtmlHelper methods though, the post states:
Currently, we don’t have any helpers
for generating the form, so this is a
very manual process.
Nothing prevents you from writing your own helper though.

Resources