For example, here in StackOverflow you can se a top menu with the options: Questions, Tags, Users, Badges, Unanswered and Ask Question. When you are in one of those sections, it is highlighted in orange.
What is the best way to achieve that in ASP.NET MVC?
So far, and as proof of concept, I have done this helper:
public static String IsCurrentUrl(this UrlHelper url, String generatedUrl, String output)
{
var requestedUrl = url.RequestContext.HttpContext.Request.Url;
if (generatedUrl.EndsWith("/") && !requestedUrl.AbsolutePath.EndsWith("/"))
generatedUrl=generatedUrl.Substring(0, generatedUrl.Length - 1);
if (requestedUrl.AbsolutePath.EndsWith(generatedUrl))
return output;
return String.Empty;
}
That method add the output string to the element if the current request match that link. So it can be used like this:
<li>
About Us</span>
</li>
First problem, I am basically calling twice to Url.Action, first for the "href" attribute, and after in the helper, and I think there has to be a better way to do this. Second problem, that is not the best way to compare two links. I think I could create a new Html.ActionLink overload so I don't need to call the Url.Action twice, but is there any buil-in way to do this?
Bonus: if I add "class=\"on\"", MVC renders class=""on"". Why?
Regards.
For a project that i'm working on we've had the exact same problem. How to highlight the current tab? This is the approach that was taken at the time:
In the master page view:
<%
var requestActionName =
ViewContext.RouteData.Values["action"].ToString();
var requestControllerName =
ViewContext.RouteData.Values["controller"].ToString();
%>
<li class="<%= requestActionName.Equals("Index",
StringComparison.OrdinalIgnoreCase)
&& requestControllerName.Equals("Home",
StringComparison.OrdinalIgnoreCase) ?
"current" : string.Empty %>">
<%: Html.ActionLink("Home", "Index", "Home") %>
</li>
Basically what's happening is that we're just string comparing the action and controller values with values associated with a link. If they match, then we're calling that the current link, and we assign a 'current' class to the menu item.
Now so far, this works, but as we've gotten bigger in size, this setup starts to get pretty large with a whole lot of 'or' this 'or' that. So keep that mind if you decide to try this.
Good luck, and hope this helps you out some.
Do it using CSS. On the server, create a function to identify the section of the site that should be highlighted and output that in your body tag as a css class:
This articles explains it:
http://hicksdesign.co.uk/journal/highlighting-current-page-with-css
Another way is to use an extension method like this (Razor and C# in example):
#Html.MenuItem("MainPage","Index", "Home")
method:
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName
)
{
string currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
string currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
if (actionName == currentAction && controllerName == currentController)
{
return htmlHelper.ActionLink(
linkText,
actionName,
controllerName,
null,
new
{
#class = "current"
});
}
return htmlHelper.ActionLink(linkText, actionName, controllerName);
}
Not sure about the first bit, but for the bonus:
\ is an escape character in C# (and most languages, for that matter), and it will cause the next character to be interpreted as a string, rather than a C# operator.
Related
For example, here in StackOverflow you can se a top menu with the options: Questions, Tags, Users, Badges, Unanswered and Ask Question. When you are in one of those sections, it is highlighted in orange.
What is the best way to achieve that in ASP.NET MVC?
So far, and as proof of concept, I have done this helper:
public static String IsCurrentUrl(this UrlHelper url, String generatedUrl, String output)
{
var requestedUrl = url.RequestContext.HttpContext.Request.Url;
if (generatedUrl.EndsWith("/") && !requestedUrl.AbsolutePath.EndsWith("/"))
generatedUrl=generatedUrl.Substring(0, generatedUrl.Length - 1);
if (requestedUrl.AbsolutePath.EndsWith(generatedUrl))
return output;
return String.Empty;
}
That method add the output string to the element if the current request match that link. So it can be used like this:
<li>
About Us</span>
</li>
First problem, I am basically calling twice to Url.Action, first for the "href" attribute, and after in the helper, and I think there has to be a better way to do this. Second problem, that is not the best way to compare two links. I think I could create a new Html.ActionLink overload so I don't need to call the Url.Action twice, but is there any buil-in way to do this?
Bonus: if I add "class=\"on\"", MVC renders class=""on"". Why?
Regards.
For a project that i'm working on we've had the exact same problem. How to highlight the current tab? This is the approach that was taken at the time:
In the master page view:
<%
var requestActionName =
ViewContext.RouteData.Values["action"].ToString();
var requestControllerName =
ViewContext.RouteData.Values["controller"].ToString();
%>
<li class="<%= requestActionName.Equals("Index",
StringComparison.OrdinalIgnoreCase)
&& requestControllerName.Equals("Home",
StringComparison.OrdinalIgnoreCase) ?
"current" : string.Empty %>">
<%: Html.ActionLink("Home", "Index", "Home") %>
</li>
Basically what's happening is that we're just string comparing the action and controller values with values associated with a link. If they match, then we're calling that the current link, and we assign a 'current' class to the menu item.
Now so far, this works, but as we've gotten bigger in size, this setup starts to get pretty large with a whole lot of 'or' this 'or' that. So keep that mind if you decide to try this.
Good luck, and hope this helps you out some.
Do it using CSS. On the server, create a function to identify the section of the site that should be highlighted and output that in your body tag as a css class:
This articles explains it:
http://hicksdesign.co.uk/journal/highlighting-current-page-with-css
Another way is to use an extension method like this (Razor and C# in example):
#Html.MenuItem("MainPage","Index", "Home")
method:
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string linkText,
string actionName,
string controllerName
)
{
string currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
string currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
if (actionName == currentAction && controllerName == currentController)
{
return htmlHelper.ActionLink(
linkText,
actionName,
controllerName,
null,
new
{
#class = "current"
});
}
return htmlHelper.ActionLink(linkText, actionName, controllerName);
}
Not sure about the first bit, but for the bonus:
\ is an escape character in C# (and most languages, for that matter), and it will cause the next character to be interpreted as a string, rather than a C# operator.
When localizing an ASP.NET app (MVC or webforms, does't matter), how do you handle HTML strings in your resource file? In particular, how do you handle something like a paragraph with an embedded dynamic link? My strategy so far has been to use some sort of placeholder for the href attribute value and replace it at runtime with the actual URL, but this seems hokey at best.
As an example, suppose my copy is:
Thank you for registering. Click
here
to update your preferences.
To login and begin using the app, click
here.
Using MVC (Razor), what could be a simple:
<p>#Resources.Strings.ThankYouMessage</p>
now turns into
<p>#Resources.Strings.ThankYouMessage
.Replace("{prefs_url}", Url.Action("Preferences", "User"))
.Replace("{login_url}", Url.Action("Login", "User"))</p>
It's not horrible, but I guess I'm just wondering if there's a better way?
There isn't really a better way, beyond some syntax and performance tweaks. For example, you might add a cache layer so that you aren't doing these string operations for every request. Something like this:
<p>#Resources.LocalizedStrings.ThankYouMessage</p>
which calls a function perhaps like this:
Localize("ThankYouMessage", Resources.Strings.ThankYouMessage)
which does a hashtable lookup by resource + culture:
//use Hashtable instead of Dictionary<> because DictionaryBase is not thread safe.
private static System.Collections.Hashtable _cache =
System.Collections.Hashtable.Synchronized(new Hashtable());
public static string Localize(string resourceName, string resourceContent) {
string cultureName = System.Threading.Thread.CurrentThread.CurrentCulture.Name;
if (string.IsNullOrEmpty(resourceName))
throw new ArgumentException("'resourceName' is null or empty.");
string cacheKey = resourceName + "/" + cultureName;
object o = _cache[cacheKey];
if (null == o) { //first generation; add it to the cache.
_cache[cacheKey] = o = ReplaceTokensWithValues(resourceContent);
}
return o as string;
}
Notice the call to ReplaceTokensWithValues(). That is the function that contains all the "not horrible" string-replacement fiffery:
public static string ReplaceTokensWithValues(string s) {
return s.Replace("{prefs_url}", Url.Action("Preferences", "User"))
.Replace("{login_url}", Url.Action("Login", "User")
.Replace("{any_other_stuff}", "random stuff");
}
By using a caching approach as above, ReplaceTokensWithValues() is only called once per culture, per resource for the lifetime of the application--instead of once per resource call. The difference may be on the order of 100 vs. 1,000,000.
I have an ASP.NET page which takes a number of parameters in the query string:
search.aspx?q=123&source=WebSearch
This would display the first page of search results. Now within the rendering of that page, I want to display a set of links that allow the user to jump to different pages within the search results. I can do this simply by append &page=1 or &page=2 etc.
Where it gets complicated is that I want to preserve the input query string from the original page for every parameter except the one that I'm trying to change. There may be other parameters in the url used by other components and the value I'm trying to replace may or may not already be defined:
search.aspx?q=123&source=WebSearch&page=1&Theme=Blue
In this case to generate a link to the next page of results, I want to change page=1 to page=2 while leaving the rest of the query string unchanged.
Is there a builtin way to do this, or do I need to do all of the string parsing/recombining manually?
You can't modify the QueryString directly as it is readonly. You will need to get the values, modify them, then put them back together. Try this:
var nameValues = HttpUtility.ParseQueryString(Request.QueryString.ToString());
nameValues.Set("page", "2");
string url = Request.Url.AbsolutePath;
string updatedQueryString = "?" + nameValues.ToString();
Response.Redirect(url + updatedQueryString);
The ParseQueryString method returns a NameValueCollection (actually it really returns a HttpValueCollection which encodes the results, as I mention in an answer to another question). You can then use the Set method to update a value. You can also use the Add method to add a new one, or Remove to remove a value. Finally, calling ToString() on the name NameValueCollection returns the name value pairs in a name1=value1&name2=value2 querystring ready format. Once you have that append it to the URL and redirect.
Alternately, you can add a new key, or modify an existing one, using the indexer:
nameValues["temp"] = "hello!"; // add "temp" if it didn't exist
nameValues["temp"] = "hello, world!"; // overwrite "temp"
nameValues.Remove("temp"); // can't remove via indexer
You may need to add a using System.Collections.Specialized; to make use of the NameValueCollection class.
You can do this without all the overhead of redirection (which is not inconsiderable). My personal preference is to work with a NameValueCollection which a querystring really is, but using reflection:
// reflect to readonly property
PropertyInfo isReadOnly = typeof(System.Collections.Specialized.NameValueCollection).GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
// make collection editable
isReadOnly.SetValue(this.Request.QueryString, false, null);
// remove
this.Request.QueryString.Remove("foo");
// modify
this.Request.QueryString.Set("bar", "123");
// make collection readonly again
isReadOnly.SetValue(this.Request.QueryString, true, null);
Using this QueryStringBuilder helper class, you can grab the current QueryString and call the Add method to change an existing key/value pair...
//before: "?id=123&page=1&sessionId=ABC"
string newQueryString = QueryString.Current.Add("page", "2");
//after: "?id=123&page=2&sessionId=ABC"
Use the URIBuilder Specifically the link textQuery property
I believe that does what you need.
This is pretty arbitrary, in .NET Core at least. And it all boils down to asp-all-route-data
Consider the following trivial example (taken from the "paginator" view model I use in virtually every project):
public class SomeViewModel
{
public Dictionary<string, string> NextPageLink(IQueryCollection query)
{
/*
* NOTE: how you derive the "2" is fully up to you
*/
return ParseQueryCollection(query, "page", "2");
}
Dictionary<string, string> ParseQueryCollection(IQueryCollection query, string replacementKey, string replacementValue)
{
var dict = new Dictionary<string, string>()
{
{ replacementKey, replacementValue }
};
foreach (var q in query)
{
if (!string.Equals(q.Key, replacementKey, StringComparison.OrdinalIgnoreCase))
{
dict.Add(q.Key, q.Value);
}
}
return dict;
}
}
Then to use in your view, simply pass the method the current request query collection from Context.Request:
<a asp-all-route-data="#Model.NextPageLink(Context.Request.Query)">Next</a>
Trying to create a select list with a first option text set to an empty string. As a data source I have a List of a GenericKeyValue class with properties "Key" & "Value". My current code is as follows.
<%= this.Select(x => x.State).Options(ViewData[Constants.StateCountry.STATES] as IList<GenericKeyValue>, "Value", "Key").Selected(Model.State) %>
This gets fills the select list with states, however I am unsure at this point of an elegant way to get a first option text of empty string.
"Trying to create a select list with a first option text set to an empty string." The standard way isn't fluent but feels like less work:
ViewData[Constants.StateCountry.STATES] = SelectList(myList, "Key", "Value");
in the controller and in the view:
<%= Html.DropDownList(Constants.StateCountry.STATES, "")%>
Sure you can, but you add it to your list that you bind to the dropdown...
List<State> list = _coreSqlRep.GetStateCollection().OrderBy(x => x.StateName).ToList();
list.Insert(0, new State { Code = "Select", Id = 0 });
ViewData["States"] = new SelectList(list, "Id", "StateName", index);
Or this...
Your view;
<%=Html.DropDownList("selectedState", Model.States)%>
Your controller;
public class MyFormViewModel
{
public SelectList States;
}
public ActionResult Index()
{
MyFormViewModel fvm = new MyFormViewModel();
fvm.States = new SelectList(Enumerations.EnumToList<Enumerations.AustralianStates>(), "Value", "Key", "vic");
return(fvm);
}
Without extending anything - you can't.
Here's what author says:
One final point. The goal of MvcFluentHtml was to leave the opinions to you. We did this by allowing you to define your own behaviors. However, it is not without opinions regarding practices. For example the Select object does not have any “first option” functionality. That’s because in my opinion adding options to selects is not a view concern.
Edit:
On the other hand - there is 'FirstOption' method for Select in newest source code.
Download MvcContrib via svn, build and use.
I want to set up an ASP.NET custom control such that it has a custom name, specifically, with a hyphen within it, so it might look like this in markup:
<rp:do-something runat="server" id="doSomething1" />
I don't mind if this syntax requires setting up a tag mapping in web.config or something to that effect, but the tagMapping element doesn't quite match up for what I'd like to do.
I wouldn't think this is possible due to the restrictions on class namings. I don't believe you can refer to a control class in markup without refering to it by name
Is there a specific reason you need the hyphen?
John, you're right. I did some searching in Reflector and it looks like it doesn't get there:
Type ITagNameToTypeMapper.GetControlType(string tagName, IDictionary attribs)
{
string str;
string str2 = this._nsRegisterEntry.Namespace;
if (string.IsNullOrEmpty(str2))
{
str = tagName;
}
else
{
str = str2 + "." + tagName;
}
if (this._assembly != null)
{
Type type = null;
try
{
type = this._assembly.GetType(str, true, true);
}
Implemented in System.Web.UI.NamespaceTagNameToTypeMapper, System.Web.
#Jonathan: I have a specific business reason for wanting to do it this way. Oh well.