I'm learning ASP.NET MVC, and I have some problems, I would like to know what's the advantage of using Html.ActionLink() method instead of a normal Anchor tag, I don't see any obvious advantage yet, mostlyu because I have more problems using ASP's built-in method. Something else I would like to know, is how to add various attributes to Html.ActionLink(), I'm using this:
#Html.ActionLink("About", "About", "Home", new{ area="" }, new Dictionary<string, Object>{ { "class", "about-link" }, { "aria-role", "button" }, { "title", "About us..." } })
I found this in StackOverflow, but it just doesn't work, and I've been trying many things, but nothing.
This is the method signature of most likely interest to you:
public static MvcHtmlString ActionLink(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName,
object routeValues,
object htmlAttributes
)
For your instance, that would result in the following code:
#Html.ActionLink("About", "About", "Home", new { area = "" }, new { #class = "about-link", aria_role = "button", title = "About us..." })
A few things to note:
The first anonymous object is for route values, and the second anonymous object is for HTML attributes to apply to the anchor tag.
Since class is reserved keyword, you must prefix it with # inside anonymous objects. It will still be output as class on the anchor tag.
Similarly, aria-role is invalid C# syntax, so you have to use an underscore instead of a dash: aria_role. The built-in HtmlHelper extensions that accept htmlAttributes process the _ and turn it into a - when rendering the HTML.
Now, in terms of why you want to use this in the first place, #Christos points out correctly that by letting MVC construct the URL, your links continue to work even if your routing changes, while if you hardcoded the URL, it would fail. However, he misses the point that this doesn't require using Html.ActionLink. You can just as easily do something like the following:
<a href="#Url.Action("About", "Home", new { area = "" })" class="about-link" aria-role="button" title="About us...">
About
</a>
This is especially handy when you need something inside the link other than just straight text, like an icon, perhaps. If you find it easier to work directly with the HTML attributes, then you can do that. There's nothing wrong with this and you don't have to use Html.ActionLink. However, do still use Url.Action or one of its siblings, so that your URLs are constructed dynamically based on your route configuration.
The basic advantage of using #Html.ActionLink for creating a link instead of using an anchor element is the fact that the output is derived from the routing configuration. That means if you change something in the routes, automatically this change would be reflected to the links you have created using this helper.
Here you will find a detailed list with all the signatures of this method.
Related
I am writing a new Razor Pages application (this is my first time with Razor pages rather than MVC), and I came to the point where I was needing to create a link and for the life of me I could not figure out why it was not working even though I tried every single variation of the parameters to the ActionLink method that I could think of. I then instead used the Anchor Tag Helper methodology instead with the exact smae parameters and it worked beautifully.
I was already confused when I scaffolded my views because the code generation uses both the Tag Helpers and the Html Helpers where as my brain tells me you should really pick one or the other and try to be as consistent as possible.
The code in the cshtml file is as follows:
<a asp-page="./DeleteIngredient" asp-route-MenuItemId="#item.MenuItemId" asp-route-IngredientId="#item.IngredientId">Delete</a>
#Html.ActionLink("Delete", "DeleteIngredient", new { MenuItemId = #item.MenuItemId, IngredientId = #item.IngredientId })
#Html.ActionLink("Delete", "./DeleteIngredient", new { MenuItemId = #item.MenuItemId, IngredientId = #item.IngredientId })
The Tag Helper correctly generates the following url:
https://localhost:44308/MenuItems/DeleteIngredient?MenuItemId=c469f993-9cac-4adc-bf63-2aba9be249c9&IngredientId=2f2863b1-27f4-4c62-93d6-84f27acb98fd
The Html Helpers generate the following:
https://localhost:44308/MenuItems/Details/c469f993-9cac-4adc-bf63-2aba9be249c9?MenuItemId=c469f993-9cac-4adc-bf63-2aba9be249c9&IngredientId=2f2863b1-27f4-4c62-93d6-84f27acb98fd&action=DeleteIngredient
Note: the links are on a page with the URL:
https://localhost:44308/MenuItems/Details/c469f993-9cac-4adc-bf63-2aba9be249c9
In summary, am I doing something wrong? Or does this appear to be a bug in the asp net core Html.ActionLink method?
Html.ActionLink is for MVC pattern, it requires Action and Controller names. Since you are using razor pages it will not work because you don't have controllers and action but you have Pages! So, your first approach is correct.
As an alternative you may use Url.Page to generate the link as below:
Delete
I would like to create some HTML helper functions to create some links with generated HTML content. I would follow the default API as much as possible. This gets tricky when I want to pass in a routevalues object. A routevalue object of type RouteValueDictionary is (intentionally?) cumbersome to create in MVC. I would like to pass in an object routevalues as is done with i.e. Html.ActionLink. The tricky part is that I seem to need UrlHelper.CreateUrl which requires a RouteValueDictionary. I checked how ActionLink does this internally, and it uses TypeHelper.ObjectToDictionary. TypeHelper however is an internal class, so I can't access that. I could copy-paste the thing in, but - apart from that firstly i'd be violating the license if I do that and don't license under the Apache 2.0 license or compatible, and secondly copy-paste programming gives me the heeby-jeebies.
The following is what I'd roughly like to do:
public static MvcHtmlString MyFancyActionLink(this HtmlHelper helper,
Foo foo,
object routevalues){
TagBuilder inner = fancyFooContent(foo);
RouteValueDictionary routedict = TypeHelper.ObjectToDictionary(routevalues);
//alas! TypeHelper is internal!
string url = UrlHelper.GenerateUrl(null,
"myaction",
"mycontroller",
routedict,
helper.ViewContext.RequestContext,
true);
TagBuilder link = new TagBuilder("a");
link.MergeAttribute("href", url);
link.InnerHtml = inner.toString();
return MvcHtmlString.Create(link.ToString());
}
RouteValueDictionary has a constructor that accepts an object and uses its properties to populate the dictionary. Unless I am missing something obvious here, you should be able to use that:
RouteValueDictionary routedict = new RouteValueDictionary(routevalues);
Is it possible to show routeName somehow in Html.Action
Something like this
Html.Action("Languages", "UIHelper", new {routeName="Default"});
Currently my application has many areas, and Html.Action is used in main Layout. When I redirect user to area page then my Html.Action could not find action/controller because mvc try to find it in current (redirected) area
There is no Html.Action() extension method that would make a use of a route name. Of course, you can go and create your own extension method that suits your needs, which I recommend doing anyway as a good exercise, but in your case solution might be much simpler.
By default, all routes use the current area for matching. You can override area by providing Area parameter, like this:
#Html.ActionLink("MyLink", "MyAction", new {Area = "MyArea" });
In your case, you want to use a controller in the root area, which is simply represented as a blank string. So all you need to do is:
#Html.Action("Languages", "UIHelper", new { Area = "" });
I need to have all my scripts at the bottom of the page, problem is when I have a Partial View I cannot use the "RenderSection" approach. Found a great example of how to add a HtmlHelper extension which takes the name of a script file, loads into a stack, then another helper renders that out on the base layout:
Razor section inclusions from partial view
That's great - but I don't want to have to create an entire JS file for a little chunk of script, or maybe even HTML, that I want to drop in. And I don't want to pass it all as a string, I want the nice formatting and intellisense, so I want to use a template ie:
#{Html.AddScript("test", #<text>
<script type="text/javascript">
function RefreshPreview() {
$('#AutoReplyHtml_Preview').html(
$('#htmlTemplate').html()
.replace('##MESSAGE_TITLE##', $('#AutoReplySubject').val())
.replace('##PRE_HEADER##', $('#AutoReplyPreHeader').val())
.replace('##MESSAGE_BODY##', $('#AutoReplyHtml').val())
);
$('#AutoReplyPlainText_Preview').html(
$('#plainTextTemplate').html()
.replace('##MESSAGE_BODY##', $('#AutoReplyPlainText').val())
);
}
$(document).ready(function() {
RefreshPreview();
});
</script>
</text>);}
Problem is - how to I get the value of the template into my method, I have this code which complies, but no clue how to get the data out of the "code" parameter:
public static string AddScript(this HtmlHelper htmlHelper, string title, Func<object, object> code) {
var ctx = htmlHelper.ViewContext.HttpContext;
Dictionary<string, string> scripts = ctx.Items["HtmlHelper.AddScript"] as Dictionary<string, string>;
if (scripts == null) {
scripts = new Dictionary<string, string>();
ctx.Items.Add("HtmlHelper.AddScript", scripts);
}
scripts.Add(title, code.ToString()); //Doens't work!
return string.Empty;
}
How do I need to tweak the delegate parameter to get the value inside the template??
The helper architecture is designed so that it can accomodate scenarios where you are providing a template that will operate, for example, on each item in a list. In such a scenario, you'd of course want to be able to pass it the "current" item when iterating through the list.
However, in other scenarios (such as yours), there is no current item. Yet, as you've discovered, you still have to declare a delegate as a parameter to your method that defines a method that consumes one parameter. That's ok -- since you're not consuming that argument in your helper (you don't make use of the somewhat magical item parameter in your template) you can just pass it null in your implementation. Preferably, declare your parameter as a Func<object, IHtmlString> rather than a Func<object, object>, but regardless, just invoke code(null).ToString() to get the HTML-encoded string you need to render.
I'm curious whether this is possible, or as I suspect, by design not.
In an ASP.NET MVC project I have multiple routes like this:
new Route(
url, // This can be arbitrary
new RouteValueDictionary {
{"area", "MyArea"},
{"controller", "MyController"},
{"action", "Index"}
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "MyArea"},
},
new MvcRouteHandler()))
I'd like to generate urls (or links) in the (Razor) views used by the actions of MyController. These urls should point to another action of MyController.
Now the problem is, there are multiple routes like above registered under different urls, so simply calling Html.ActionLink() or Url.Action() with the current route values yields a link that points to the url that's route first matches it. That's not necessarily the url the action is currently invoked from.
So basically what I'd like is take the current route and substitute the action with another one. I couldn't find any way to do that.
The urls can be arbitrary, but if necessary, constraints can be applied, e.g. so that the url must contain an action token. Actually all of them currently do, so urls have the following structure:
/AnotherArea/SubSegment/{action} // Routes point from other areas to MyArea/MyController
These urls are there in Html.ViewContext.RouteData.Route.Url of the view, so that action token should be changed somehow when generating a new url. (Well, one could do that with string replacement, but I guess if there is a solution, it should be better than that.)
Thank you for your time!
Now I found a solution, pretty simple:
#Url.RouteUrl("RouteName", new { Action = "OtherAction" })
However this implies the knowledge of the currently used route's name. Since this isn't stored in the Route object itself I opted with the kind of hackish solution of storing the name in the route's DataTokens dictionary. That seemingly doesn't harm and since routes are filled through a service this convention of using DataTokens doesn't need to be kept in mind.
I'm wondering if there's a better solution, though.