Using a named route in Form.Begin in ASP.NET MVC - asp.net-mvc

Is there a way to force use of a named route in ASP.NET MVC when using Form.Begin. I'm learning more and more about routing and getting scared that it can be very fragile if you just change the order or whether or not parameters have defaults.
<% Form.Begin(...) %> <!-- no overload for providing a route name --%>
There does not seem to be an overload for a named route for beginning a form so instead the best I could come up with was this :
<form action="/Products/Command/ViewProduct" method="post">
I'm wondering if this missing overload is an oversight (Beta right now), if there is a reason for it or an alternative way to generate the URL.
I tried to use RouteLink and embed it in the Form tag, but RouteLink creates me the full HTML for an <A> tag which is no good.
action="<%= Ajax.RouteLink("Update Status", "product-route-short", new { action = "GetStatus", sku = "" }, new AjaxOptions { UpdateTargetId = "status" })%>"
What alternatives do i have to generate a URL from a named route.
Should I report this missing overload as an issue?

If you need to generate a URL to a named route, you can use Url.RouteUrl(), you just have to put the controller and action into the route values object like so:
<%= Url.RouteUrl("NamedRoute", new { controller="Controller", action="Action", foo="Bar" ... }) %>
As for adding the overload, a lot of these helpers have so many different overloads that they start to conflict and become ambiguous. For example, consider the following overloads
public string BeginForm(string actionName, string controllerName)
public string BeginForm(string actionName)
public string BeginForm(string routeName, string actionName) // Uh oh!
The 1st and 3rd have identical signatures, so they are invalid.
You could always create your own form helper as an extension method to HtmlHelper if you need to use Named Routes often.

Related

MVC Core form tag helper that inherit the current view URL for its action attribute

With MVC5 (and older MVC frameworks), I was able to write my form without specifying the controller/action method:
#using (Html.BeginForm())
{
}
This way, I was able to reuse the form with different URL. If the form was called from the route "/books/2/edit", the HTML generated would be:
<form action="/books/2/edit"></form>
And if I call the form using the URL "/books/add", the HTML generated would be:
<form action="/books/add"></form>
How can I do the same with the tag helper syntax? I have tried all kind of syntaxes but it always generate an empty action attribute:
<form></form>
<form asp-route=""></form>
<form asp-controller="" asp-action=""></form>
Result:
<form></form>
<form action></form>
<form action></form>
When using HTML helpers, values that are not supplied explicitly default to the route values that are in the current request. That is the reason why you can specify BeginForm with no parameters.
When using tag helpers, this default logic no longer applies - the values must be provided explicitly. There are no defaults.
Option 1 - form tag
The simplest way to mimic what the HTML helper does with a form tag is:
<form action="#Url.RouteUrl(this.ViewContext.RouteData.Values)" method="post">
</form>
Option 2 - Html.BeginForm
Note that your current syntax is also still valid in ASP.NET Core MVC:
#using (Html.BeginForm())
{
}
But since you had to ask this question, I would say that it is absolutely not clear how the URL is being generated when using this syntax, which means you should probably change to using Url.RouteUrl to make it more readable despite being a bit more to write.
Option 3 - Tag Helper
Here is an example of how you could use a tag helper to achieve this, although it is a bit ugly.
There is a form tag helper attribute asp-all-route-values that allows you to pass all of the route values in a single parameter. However, according to asp-all-route-data must be IDictionary<string,object> or RouteValueDictionary, it is not possible to pass a RouteValueDictionary to this attribute, you would need to convert it to an IDictionary<string, string>. One way to do that is to build an extension method to make the conversion.
public static class RouteValueDictionaryExtensions
{
public static IDictionary<string, string> ToTagHelperValues(this RouteValueDictionary routeValueDictionary)
{
var result = new Dictionary<string, string>();
foreach (var kvp in routeValueDictionary)
{
result.Add(kvp.Key, kvp.Value as string);
}
return result;
}
}
You can then use a tag helper to generate the current URL as follows:
<form asp-all-route-data="#this.ViewContext.RouteData.Values.ToTagHelperValues()">
</form>
Option 4 - No action attribute
It is also possible to use a form tag with no action attribute. If you omit the action attribute, the default behavior in most (if not all) browsers is to use the current URL.
<form method="post">
</form>
WARNING: It is not standards compliant to use this option and technically the behavior of browsers do not have to default to the expected behavior of using the current URL if it is not supplied.
In the end, which method you use is a matter of preference.

Sitefinity CMS: Html.BeginFormSitefinity with external URL?

I'm hoping someone can help me here - I'm trying to set up a basic MVC widget for Sitefinity that will POST to an external URL to log the user in there. The problem is if I use regular HTML to do this, the widget will only work in pure MVC mode thanks to the way WebForms deals with forms.
I googled around a bit and found people explaining that with the regular "BeginForm" you can specify an external action URL thusly:
Html.BeginForm(null, null, FormMethod.Post, new { #action="EXTERNAL_URL" })
Which produces the following opening form tag:
<form action="EXTERNAL_URL" method="post">
This works very nicely, but still outputs a regular HTML form which doesn't work with Sitefinity, so Sitefinity has it's own method to produce forms that will work in Hybrid mode "Html.BeginFormSitefinity". This method has all of the same overrides but doesn't behave in quite the same way - when I attempt to use this code:
Html.BeginFormSitefinity(null, null, FormMethod.Post, new { #action="EXTERNAL_URL" })
This produces the following opening form tag:
<form action="/TestProject/TestPage/Index" method="POST" name="defaultForm" action="EXTERNAL_URL">
As you can see, it's popping a second action attribute in rather than overriding the action attribute as seen in the default MVC method's behaviour.
I'm hoping that someone with a better understanding of how Sitefinity works might be able to provide some advice here?
Looking at the sources of the HybridForm, it cannot be modified so it will take action htmlAttribute into account: http://pastebin.com/5dfQdzs8
So you may create your own form based on this code.
You'll need html helper:
public static MyHybridForm BeginFormSitefinity(this HtmlHelper htmlHelper, string actionName, string formName, FormMethod method, IDictionary<string, object> htmlAttributes)
{
return new MyHybridForm(htmlHelper.ViewContext, actionName, formName, method, (object) htmlAttributes);
}
and override GenerateActionUrl() in your MyHybridForm by using action htmlAttribute into account.

ASPNET MVC3: how to transform an anchor to Html.ActionLink?

I have the following a href link:
title
That i use for showing SEO-friendly urls; i would like, instead of the anchor tag, to use the Html.ActionLink.
How can i transform the anchor in ActionLink considering that i have not the Action name on the url?
You can use Html.ActionLink even when the action is not present in the URL; you just need an appropriate route. Routes are used for both inbound URL matching and outbound URL generation.
First things first, you'll need a route in the Routes collection to be used as a template for the URLs that you want to generate
routes.MapRoute(
null, // name
"News/{id}/{title}", // URL pattern
new { controller = "News", action = "Index" }, // defaults
new { id = "\d+", title = #"[\w\-]*" }); // constraints
This route will only match if id is a number and title contains only word characters and/or hyphens. The route needs to be registered before any more "general" routes as the order of routes is important; the framework stops on the first matching route, it does not try to find a "best" match.
Now you can use Html.ActionLink to generate routes.
#Html.ActionLink("title", "Index", "News", new { id = item.id, title = item.NewsSeoTitle })
You may also want to look at T4MVC (available as a NuGet package) too as it adds some overloads that removes the need for magic strings all over the place
Assuming your controller action looks like
public class NewsController
{
public ActionResult Index(int id, string title)
{
return View();
}
}
T4MVC adds an overload that allows you to use Html.ActionLink like
#Html.ActionLink("title", MVC.News.Index(item.id, item.NewsSeoTitle))
much neater :)
If you are using the custom links which is not corresponding to the controller/action structure, maybe it's better to use your own html extension
public static string SeoLink(this HtmlHelper helper, string itemId, string title, string seoTitle)
{
return String.Format("{1}",
VirtualPathUtility.ToAbsolute(String.Format("~/News/{0}/{1}", itemId, seoTitle)),
title);
}
As for Html.ActionLink: from the name of extension you can find the it work with actions. Of course you can provide such action and controller names to fit your requirements, but it's not a good idea, especially if your code will be supported by any other developer - he will never find such action in controller which is specified in ActionLink as actionName param.

Customizing the url-from-parameters lookup in asp.net mvc

I have a route added by the code
routes.MapRoute("MyRoute", "TheUrl", new { controller = "MyController", action = "MyAction" });
I can then do a reverse lookup with the arguments like UrlHelper.Action("MyAction", "MyController"), and it will return a nice url like ~/TheUrl
However, for this route I want the generated URL to be ~/TheUrl?p=2354, with the parameter being some versioning parameter. Is there a way of doing this by mapping the route with some customized route handler or something? The versioning parameter will be non-standard and require some custom code to execute every time the Url is looked up.
I think a UrlHelper extension method would be most ideal and simple here specially.
public string MyRoute(this UrlHelper url)
{
string versionNumber = GetVersionNumber(); // or w/e is required to get it
return Url.Action("MyAction", "MyController") + "?p=" + versionNumber;
}
This would make calling that route much easier in html
<%= Url.MyRoute() %>

Passing page for routelink

I have a problem with getting my pager-function to work. For some reason it doesnt like when I try to pass the current pageindex +1 to the same page for it to display next one.
<% if (Model.Users.HasNextPage) { %>
<%= Html.RouteLink(">>>", "Users", new { page = (Model.Users.PageIndex +1) })%>
<% } %>
If I only use: ”>>>”, ”Users) it works, although the next page function doesn’t work since it doesn’t assign next value.
If I debug Model.Users.PageIndex it has the value 0 when it loads the page (which it should have).
Somehow it doesn’t like the “new”-thingy at the end
I have swedish errors on, but it complains something about not finding the path/route/reference to the location of User, or how its set.
The actionresult looks like:
public ActionResult Users(int? page){
const int pagesize = 10;
var pagnatedUsers = new PaginatedList<User>(_us.GetUsers(), page ?? 0, pagesize);
return View("Users", new UserAdminEditViewModel { Users = pagnatedUsers });
}
Thanks in advance
/M
I'm going to guess "Users" in your second parameter to Html.RouteLink is supposed to refer to your controller action name. RouteLink actually doesn't have an overload of (string linkText, string actionName, object routeValues) which is what it appears you are trying to provide.
The overload you are calling is actually asking for the routeName in the second parameter, and you don't have such a route defined!
Try this
Html.RouteLink(">>>", new { controller="Home", action="Users", page = (Model.Users.PageIndex +1) })%>
substituting for your actual controller name.
Update/response: I was trying to explain why your code wasn't working as expected. Indeed, if you use ActionLink instead with your original parameters that is also a solution - and probably the better one since it seems to be what you want.
RouteLink and ActionLink are essentially the same under the covers (they both end up calling the same underlying code that actually generates the link). The difference is only in context of use - RouteLink is there to help you generate links based on your routing configuration (eg. by a route name) and ActionLink is there for links based on your controller actions (eg. by an action name). And there is plenty of overlap where you could use both of them in the exact same way.
I got that RouteLink code from the Nerddinner-example. And now when I changed to ActionLink instead of RouteLink it worked.
Not quite sure what the difference is between having ActionLink or the way Kurt describes.

Resources