I have a small MVC site that uses the Html.ActionLink helper for a navbar. One thing I would like to change is that the default ActionLink will render out an html link to a page even if that is the current page.
For example, it creates a link like this:
Some title...
even if you're already on /myUrl. It would be nice if it would disable that link and maybe insert a special CSS class to show the currently visited page, like this:
My Url
Some Other Url
This problem must have been encountered before on loads of MVC sites, so I'm curious to know how other people have tackled it.
This seems like a good scenario to roll a custom HTML helper. So let's roll it:
public static class LinkExtensions
{
public static MvcHtmlString MyActionLink(
this HtmlHelper htmlHelper,
string linkText,
string action,
string controller
)
{
var currentAction = htmlHelper.ViewContext.RouteData.GetRequiredString("action");
var currentController = htmlHelper.ViewContext.RouteData.GetRequiredString("controller");
if (action == currentAction && controller == currentController)
{
var anchor = new TagBuilder("a");
anchor.Attributes["href"] = "#";
anchor.AddCssClass("currentPageCSS");
anchor.SetInnerText(linkText);
return MvcHtmlString.Create(anchor.ToString());
}
return htmlHelper.ActionLink(linkText, action, controller);
}
}
and inside your view:
<%= Html.MyActionLink("hello foo", "Index", "Home") %>
<%= Html.MyActionLink("hello bar", "About", "Home") %>
...
and depending on where you are the helper will generate the proper anchor.
Related
Coming from the asp.net background, I really appreciated the concept of 'validationGroup' when adding validation to a page. I've been searching for a corresponding concept within mvc.net and haven't had much luck.
Is this concept available in mvc.net? If not, what alternatives do I have?
Unfortunately no, it doesn't come with anything like that.
I blogged about a workaround a wee while back.
ASP.NET MVC - Validation Summary with 2 Forms & 1 View
The jist of the blog post:
namespace System.Web.Mvc
{
public static class HtmlExtensions
{
public static string ActionValidationSummary(this HtmlHelper html, string action)
{
string currentAction = html.ViewContext.RouteData.Values["action"].ToString();
if (currentAction.ToLower() == action.ToLower())
return html.ValidationSummary();
return string.Empty;
}
}
}
And
<h2>Register</h2>
<%= Html.ActionValidationSummary("Register") %>
<form method="post" id="register-form" action="<%= Html.AttributeEncode(Url.Action("Register")) %>">
... blah ...
</form>
<h2>User Login</h2>
<%= Html.ActionValidationSummary("LogIn") %>
<form method="post" id="login-form" action="<%= Html.AttributeEncode(Url.Action("LogIn")) %>">
... blah ...
</form>
HTHs,
Charles
Expanding on Charlino's answer, and including HtmlAttributes and other ValidationSummary properties:
public static MvcHtmlString ActionValidationSummary(this HtmlHelper html, string action, bool excludePropertyErrors, string message, object htmlAttributes = null)
{
var currentAction = html.ViewContext.RouteData.Values["action"].ToString();
if (currentAction.ToLower() == action.ToLower())
{
return html.ValidationSummary(excludePropertyErrors, message, htmlAttributes);
}
return new MvcHtmlString(string.Empty);
}
Charles's method was the only approach I could find that actually worked for my purposes!
(I.e. two forms on one MVC page -> without doing forms inside partials and ajax loads for the partials. This was no good for me, as I wanted to return differing result sets to be rendered outside the form div, depending on which form was submitted)
I would advise a slight modification to the Html Extension though, because you still want a validation summary to be rendered for the non-matched validation summary so that client side validation works:
namespace System.Web.Mvc
{
public static class HtmlExtensions
{
public static MvcHtmlString ActionValidationSummary(this HtmlHelper html, string action)
{
string currentAction = html.ViewContext.RouteData.Values["action"].ToString();
if (currentAction.ToLower() == action.ToLower())
return html.ValidationSummary();
return new MvcHtmlString("<div class=\"validation-summary-valid\" data-valmsg-summary=\"true\"><ul><li style=\"display:none\"></li></ul></div>");
}
}
}
How can I make up a RouteLink in a custom HtmlHelper? I know how to make it in a partial view but I want to build up a new link in a custom htmlhelper extension method with the use of a RouteLink. How to accomplish this?
Update: I noticed HtmlHelper.GenerateRouteLink. But what do I need to put in as parameters?
Here's an example. Let's suppose that you want to wrap the links into a div tag with some given class so that your resulting html looks like this:
<div class="foo">Some text</div>
You could write the following extension method:
public static class HtmlExtensions
{
public static MvcHtmlString CustomRouteLink(
this HtmlHelper htmlHelper,
string className,
string linkText,
object routeValues
)
{
var div = new TagBuilder("div");
div.MergeAttribute("class", className);
div.InnerHtml = htmlHelper.RouteLink(linkText, routeValues).ToHtmlString();
return MvcHtmlString.Create(div.ToString());
}
}
which could be used like this:
<%= Html.CustomRouteLink("foo", "Some text",
new { action = "index", controller = "home" }) %>
and this will produce the desired markup. Any other overloads of RouteLink could be used if necessary.
Once you get an instance of the UrlHelper you should be able to do whatever you want to do in your HtmlHelper method
UrlHelper url = new UrlHelper(helper.ViewContext.RequestContext);
I'm having a bit of trouble using AJAX action links. Whenever a link is clicked, the UpdateTargetID container is not updated unless the link is clicked twice (which throws an exception on the 2nd click, since the item has already been deleted; after this exception, the page updates).
Also, on the first update, the entire page is reloaded into itself, sort of like an iframe. I have a simple 3 column layout (header, left menu, right content) and the entire web page gets re-rendered in the right content portion. But only once. Subsequent action link clicks do not recursively render the page in itself.
I'm using the following code for an AJAX image link (found here: ASP.NET MVC Ajax.ActionLink with Image):
public static class ImageActionLinkHelper
{
public static string ImageActionLink(this AjaxHelper helper, string imageUrl, string altText, string actionName, object routeValues, AjaxOptions ajaxOptions)
{
var builder = new TagBuilder("img");
builder.MergeAttribute("src", imageUrl);
builder.MergeAttribute("alt", altText);
var link = helper.ActionLink("[replaceme]", actionName, routeValues, ajaxOptions);
return link.ToString().Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing));
}
}
This is my action link in .aspx:
<%= Ajax.ImageActionLink("../../content/imgs/delete_icon.png", "Delete error", "Delete", new { id = new Guid(item.ErrorId.ToString()) }, new AjaxOptions { Confirm = "Delete?", UpdateTargetId="errors" }) %>
"errors" is my id to update
[HttpPost]
public ActionResult Delete(Guid id)
{
var error = db.ELMAH_Error.FirstOrDefault(x => x.ErrorId == id);
db.DeleteObject(error);
db.SaveChanges();
return PartialView();
}
You seem to be rendering the whole view. As a result invalid markup would be generated - duplicate body tags etc. Use partial views instead when dealing with Ajax scenarios.
I wish to display content depending on the given role(s) of the active user , in the ASP.NET MVC.
Compare the old fashion way, using WebForms:
protected void Page_Load(Object sender, EventArgs e)
{
if(User.IsInRole("Administrator")) {
adminLink.Visible = true;
}
}
Now how would I go on writing that when using the ASP.NET MVC ?
From my point of view, it would be wrong to place it directly in the View File, and assigning a variable for every single view won't be pretty either.
Create Html helper and check current user roles in its code:
public static class Html
{
public static string Admin(this HtmlHelper html)
{
var user = html.ViewContext.HttpContext.User;
if (!user.IsInRole("Administrator")) {
// display nothing
return String.Empty;
// or maybe another link ?
}
var a = new TagBuilder("a");
a["href"] = "#";
a.SetInnerText("Admin");
var div = new TagBuilder("div") {
InnerHtml = a.ToString(TagRenderMode.Normal);
}
return div.ToString(TagRenderMode.Normal);
}
}
UPDATED:
Or create wrapper for stock Html helper. Example for ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName):
public static class Html
{
public static string RoleActionLink(this HtmlHelper html, string role, string linkText, string actionName, string controllerName)
{
return html.ViewContext.HttpContext.User.IsInRole(role)
? html.ActionLink(linkText, actionName, controllerName)
: String.Empty;
}
}
No you would be placing it in the view file, like so actually:
<% If (User.IsInRole("Administrator")) { %>
<div>Admin text</div>
<% } %>
this worked for me:
<% MembershipUser mu = Membership.GetUser();
if (mu != null)
if (Roles.IsUserInRole(mu.UserName, "Administrator"))
{
%>
<li class="paddingleftThree"><%= Html.ActionLink("User Administration", "GetUsers", "Account")%></li> <%} %>
The separation of concerns approach suggested in ASP.NET MVC 4 How do you serve different HTML based on Role? in my opinion is a better way to go.
Personally I avoid IsInRole check as much as possible in the code and leave it to declarative means to achieve role based restriction as much as possible. This ensures code remains maintainable over time. I am not sure if this is a right or the wrong approach, but has worked well for me.
In a master-page, how can I know, which controller I am currently using? Is there some kind of context-object, that can give me that sort of information?
Standard menu
<ul>
<li>Fire</li>
<li>Ice</li>
<li>Water</li>
</ul>
Menu if I am in the water-controller
<ul>
<li>Fire</li>
<li>Ice</li>
<li class="selected">Water</li>
</ul>
If I have a menu in my Site.master, where each menu-item refers to a different controller, how can I highlight each menu-item depending on, which controller I am currently in?
I know I can get the url from request.Servervariables and then work some string-magic, but there must be a better way - some kind of context-object?
The ViewContext property of the ViewMasterPage contains the Controller and RouteData for the request. You could look at the type name of the controller or the controller key in the route data to find out which controller was invoked.
Here are two helper methods I use for checking if its the current controller or even current action. The you can use the helpers to determine whether to add class="selected" or not.
public static bool IsCurrentController(this HtmlHelper helper,
string controllerName)
{
string currentControllerName = (string)helper.ViewContext.RouteData.
Values["controller"];
if (currentControllerName.Equals(controllerName,
StringComparison.CurrentCultureIgnoreCase))
{
return true;
}
return false;
}
public static bool IsCurrentAction(this HtmlHelper helper, string actionName,
string controllerName)
{
string currentControllerName = (string)helper.ViewContext.RouteData
.Values["controller"];
string currentActionName = (string)helper.ViewContext.RouteData
.Values["action"];
if (currentControllerName.Equals(controllerName,
StringComparison.CurrentCultureIgnoreCase) &&
currentActionName.Equals(actionName,
StringComparison.CurrentCultureIgnoreCase))
{
return true;
}
return false;
}
You can do a
ViewContext.Controller.GetType().Name
That should do it.