I would like generate an absolute Url in a razor view
It might look something like this:
#Html.ActionLink("Register Now", "action", "controller",
new { area = "Area", id = #Model.Id }, null)
I have seen many attempts at this but have not found something that gives me the full link I need.
I don't believe there is a way to use Html.ActionLink to generate a link with an absolute URL. For example, using the Html.ActionLink from your question will produce the following HTML output:
#Html.ActionLink("Register Now", "Action", "Controller", new { #area = "Area", #id = Model.Id}, null)
// output: Register Now
To generate absolute URLs, I suggest implementing a custom extension method.
public static string AbsoluteActionUrl(this UrlHelper url, string actionName, string controllerName, object routeValues)
{
string scheme = url.RequestContext.HttpContext.Request.Url.Scheme;
return url.Action(actionName, controllerName, routeValues, scheme);
}
Of course, you will have to write the HTML markup yourself and use the extension to generate the URL for the href attribute, like so:
Register Now
You don't have to create the custom extension method, but then you would need to use a magic string to specify the scheme when using Url.Action.
The MSDN documentation for the Url.Action overload used above is available here. There are also other overloads available.
Related
I am converting some existing HTML to work with ASP.NET MVC, and have some forms containing input fields that have additional attributes (that, for now, I need to preserve) that contain a namespace prefix:
<input type="text" foo:required="true" foo:message="Please enter a username" />
I would like to use the TextBoxFor helper method overload that allows htmlAttributes to be specified using collection initializer syntax:
#Html.TextBoxFor(
model => model.Username,
new { foo:required="true", foo:message="Please enter a username" })
However, this syntax is not valid, due to the colon in foo:required (etc).
Instead, I am having to use this more verbose dictionary initializer syntax:
#Html.TextBoxFor(
model => model.Username,
new Dictionary<string, object>
{
{ "foo:required", "true" },
{ "foo:message", "Please enter a username" }
})
Is it possible to use a variation on the first syntax, somehow escaping (for want of a better word) the colons?
Or
Is this a scenario where the TextBoxFor helper method does not really help, and would it be simpler to just keep the existing raw HTML, and add value="#Model.UserName" to the input element?
The first syntax is using an anonymous object, for which the same rules regarding how to create identifiers in C# apply:
You can use any unicode character of the classes Lu, Ll, Lt, Lm, Lo, or Nl
You can scape C# keywords using "#" as in new { #class = "foo" };
Since the colon belongs to the Po unicode class it cannot be used in an identifier. (In C# you can use the static method char.GetUnicodeCategory in order to check the class of any character)
Additionally, and only in MVC, when using an anonymous object for the html attributes in the helper, any attribute name with underscores will be replaced by hyphens. This is due to the method HtmlHelper.AnonymousObjectToHtmlAttributes
Back to your case and regarding your options, if those are not too widely used (like in a couple of views) I would consider staying with the Dictionary syntax of the TextBoxFor helper. You will still get the automatic generation of the id\name properties in sync with the model binding, and you will get any other attributes from the model metadata like the unobtrusive validation attributes. (although looking at the attributes you want to preserve, it seems you won´t need the unobtrusive validation ones :) )
However if the id\name will be as simple as the name of the property and you don´t need any other html attributes that would be generated automatically using the helper, then going for the raw HTML makes sense. (as the dictionary syntax is quite ugly)
In case you use it widely across the application, then in my opinion the most sensible approach may be creating your own helper, like #Html.LegacyTextBoxFor(...). That helper will render those legacy attributes you want to kepp, and you can incorporate additionaly logic similar to the standard helpers for id\name attribute creation.
Something like this:
public class FooAttributes
{
public bool Required { get; set; }
public string Message { get; set; }
}
public static class FooHelper
{
public static MvcHtmlString LegacyTextboxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, FooAttributes fooAttributes)
{
var fieldName = ExpressionHelper.GetExpressionText(expression);
var fullBindingName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(fieldName);
var fieldId = TagBuilder.CreateSanitizedId(fullBindingName);
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var value = metadata.Model;
TagBuilder tag = new TagBuilder("input");
tag.Attributes.Add("name", fullBindingName);
tag.Attributes.Add("id", fieldId);
tag.Attributes.Add("type", "text");
tag.Attributes.Add("value", value == null ? "" : value.ToString());
if (fooAttributes != null)
{
if (fooAttributes.Required) tag.Attributes.Add("foo:required", "true");
if (!String.IsNullOrEmpty(fooAttributes.Message)) tag.Attributes.Add("foo:message", fooAttributes.Message);
}
return new MvcHtmlString(tag.ToString(TagRenderMode.SelfClosing));
}
}
That can be used as:
#Html.LegacyTextboxFor(model => model.UserName, new FooAttributes {Required=true, Message="Please enter a value." })
And will generate this html:
<input foo:message="Please enter a value." foo:required="true" id="UserName" name="UserName" type="text" value="">
And once you have your own helper, you could add additionaly logic, for example logic that will generate those attributes from the model metadata and its data annotation attributes...
I have extended my answer more than intended, but I hope it helps!
How would you use an Html.ActionLink to render the following link -
It may seem silly to do this, but sometimes I need a link that has link functionality (rollover pointers, etc.) but does not go anywhere. And I want to use an Html.ActionLink for code consistency.
I have tried different variations of Html.ActionLink but I keep getting messages about things not allowed to be null.
#Html.ActionLink(" ", "", "", new {href="javascript:void(0)"})
will render as
Instead of forcing ActionLink to do something it isn't made for, consider creating your own helper method:
public static class MyHtmlExtensions
{
public static MvcHtmlString EmptyLink(this HtmlHelper helper, string linkText)
{
var tag = new TagBuilder("a");
tag.MergeAttribute("href", "javascript:void(0);");
tag.SetInnerText(linkText);
return MvcHtmlString.Create(tag.ToString());
}
}
Import the namespace into your view and you'll be able to do this:
#Html.EmptyLink("My link text")
I'm trying to do something simple in ASP.NET MVC:
RouteValuesDictionary routeValues = GetMyRouteData();
var url = new UrlHelper(Html.ViewContext.RequestContext);
return url.RouteUrl(routeValues);
The problem is that no matter what I do, the url includes route data from the current request context. I want to generate a URL based on ONLY the route values from GetMyRouteData().
Thanks
The problem is that no matter what I do, the url includes route data
from the current request context
That's by design. You have to explicitly set the route values that were present in the original request and that you don't want in the resulting url to null:
var routeValues = GetMyRouteData();
// remove values that you want to exclude from the resulting url
// by setting their values to null
routeValues["id"] = null;
var url = new UrlHelper(Html.ViewContext.RequestContext);
return url.RouteUrl(routeValues);
This is not specific to ASP.NET MVC, but due to ASP.NET Routing's route resolution. The entry point to this is RouteCollection.GetVirtualPath, which has two signatures.
The first takes a RequestContext and a RouteValueDictionary. This is used for default route resolution, which relies on pattern matching to find a route. The route search incorporates all tokens from RequestContext as well as RouteValueDictionary; in other words, the two sets of route tokens are combined to form the basis for the route search. A special case exists whereby null parameters in the RouteValueDictionary remove that parameter from search. However, if such a null-valued parameter has a value in the RequestContext, that value will still appear in the generated URL as a query string value.
The other signature additionally accepts a route name. It is a little strange because it alters both the route resolution as well as the query string creation. Routes are found, obviously, using name resolution. Given the named route is found, only tokens for those parameters specified in route's URL pattern will appear in the generated URL.
Why is it this way? It's ASP.NET MVC's interpretation of Ruby on Rails' parameter-handling convention.
So default route resolution and "fallback" token resolution are comingled. If you don't want tokens to fallback to the RequestContext, (and you still want to use ASP.NET Routing) you have to use named route resolution.
This might help clarify. Use the source, Luke!
The RouteUrl helper calls into this static method to generate the URL:
public static string GenerateUrl(string routeName, string actionName, string controllerName, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, bool includeImplicitMvcValues) {
if (routeCollection == null) {
throw new ArgumentNullException("routeCollection");
}
if (requestContext == null) {
throw new ArgumentNullException("requestContext");
}
RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues);
VirtualPathData vpd = routeCollection.GetVirtualPathForArea(requestContext, routeName, mergedRouteValues);
if (vpd == null) {
return null;
}
string modifiedUrl = PathHelpers.GenerateClientUrl(requestContext.HttpContext, vpd.VirtualPath);
return modifiedUrl;
}
Note the line:
RouteValueDictionary mergedRouteValues = RouteValuesHelpers.MergeRouteValues(actionName, controllerName, requestContext.RouteData.Values, routeValues, includeImplicitMvcValues);
This is merging in the current requestcontext values under the covers. So, you could make your own helper that just calls into this static method and passes empty collections to avoid getting the current route context values being merged in. Experiment and debug into the MVC code and you should be able to see which values you need to nuke.
I want to use BegingForm with Get method and this is what I do
#using (Html.BeginForm("Search","Home",FormMethod.Get))
{
//My input elements
}
public class HomeController : Controller
{
public ActionResult Search(string queryString)
{
}
}
but query string always comes back as null. I think, I need to do something with route but no luck
routes.MapRoute(
"SearchRoute", // Route name
"Home/Search{queryString}", // URL with parameters
new { controller = "Home", action = "Search", filter = UrlParameter.Optional } // Parameter defaults
);
Obviously, the coming url to the server is something like
Home/Search?query="blah"&query2="blah"&query3="blah"
What am I doing wrong? What is the correct way to get the query parameters in my controller when I want to use get with beginform?
Also, what if the content of my BeginForm can change and so the query string parameter names could be different depending on the rendered page but I want one Search method that analyze the query string and do the right thing?
Also, is a way for them to query parameters to come in a dictionary?
Obviously, the coming url to the server is something like
Home/Search?query="blah"&query2="blah"&query3="blah"
That's how HTML <form> with method GET works and this has nothing to do with ASP.NET MVC, it's plain HTML. You can't do much about it other than having your controller action look like this:
public ActionResult Search(SearchViewModel model)
{
...
}
Where SearchViewModel will contain properties for each input field on this form. Also you don't need this SearchRoute as it won't work that way.
This being said you could probably use javascript in order to subscribe for the onsubmit event of the form, cancel the default submission (which exhibits the behavior you are observing currently), manually fetch all the values inside your form and then manually generate the url you want and redirect to it using window.location.href = '....';. I am mentioning this only for completeness but absolutely not as something that I recommend or that you should ever do.
If you want to get the items from the query string, just use the "Request" object from the ControllerBase:
public ActionResult Search()
{
var queries = new List<string>();
foreach (var parameter in Request.QueryString)
{
queries.Add(parameter.ToString());
}
//Do Stuff with the query parameters...
return View("Index");
}
And "Request.QueryString" is a dictionary just as you wanted :)
I want to write an HtmlHelper to render an ActionLink with pre-set values, eg.
<%=Html.PageLink("Page 1", "page-slug");%>
where PageLink is a function that calls ActionLink with a known Action and Controller, eg. "Index" and "Page".
Since HtmlHelper and UrlHelper do not exist inside a Controller or class, how do I get the relative URL to an action from inside a class?
Update: Given the additional three years of accrued experience I have now, here's my advice: just use Html.ActionLink("My Link", new { controller = "Page", slug = "page-slug" }) or better yet,
<a href="#Url.Action("ViewPage",
new {
controller = "Page",
slug = "my-page-slug" })">My Link</a>
Your extension method may be cute and short, but it adds another untested point-of-failure and a new learning requirement for hires without adding any real value whatsoever. Think of it as designing a complex system. Why add another moving part, unless it adds reliability (no), readability (little, once you read more docs), speed (none) or concurrency (none).
Not sure I actually understood your question clearly, but, let me try.
To create a HtmlHelper extension like you described, try something like:
using System;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace Something {
public static class PageLinkHelper
{
public static string PageLink(
this HtmlHelper helper,
string linkText, string actionName,
string controllerName, object routeValues,
object htmlAttributes)
{
return helper.ActionLink(
linkText, actionName, controllerName,
routeValues, htmlAttributes);
}
}
}
As for your question on getting a URL from a class, depends on what kind of class you'll implement it. For example, if you want to get the current controller and action from a HtmlHelper extension, you can use:
string currentControllerName = (string)helper.ViewContext
.RouteData.Values["controller"];
string currentActionName = (string)helper.ViewContext
.RouteData.Values["action"];
If you want to get it from a controller, you can use properties/methods from the base class (Controller) to build the URL. For example:
var url = new UrlHelper(this.ControllerContext.RequestContext);
url.Action(an_action_name, route_values);