ASP.NET MVC - Current Page highlighting in navigation - asp.net-mvc-3

I'm wondering how is it possible to add a CSS Class to the current page in your navigation when using ASP.NET MVC 3? Here is my navigation in my _Layout.cshtml file:
<p>#Html.ActionLink("Product Search", "Index", new { controller = "Home" }, new { #class = "current" })
| #Html.ActionLink("Orders", "Index", new { controller = "Orders" })
| #Html.ActionLink("My Account", "MyAccount", new { controller = "Account" })
| #Html.ActionLink("Logout", "LogOff", new { controller = "Account" })</p>
As you can see I have 4 links in my navigation with the first one having the CSS class "current" applied to it, I'd like to be able to add/remove this class to the different links in my navigation depending on which page the user is at. Is this possible?
Cheers

You can do this
#{
var currentController = ViewContext.RouteData.Values["controller"] as string ?? "Home";
var currentAction = ViewContext.RouteData.Values["action"] as string ?? "Index";
var currentPage = (currentController + "-" + currentAction ).ToLower();
}
#Html.ActionLink("Product Search", "Index", "Home", null,
new { #class = currentPage == "home-index" ? "current" : "" })
#Html.ActionLink("MyAccount", "MyAccount", "Account", null,
new { #class = currentPage == "account-myaccount" ? "current" : "" })

I would recommend using an extension method for this. Something like:
public static HtmlString NavigationLink(
this HtmlHelper html,
string linkText,
string actionName,
string controllerName)
{
string contextAction = (string)html.ViewContext.RouteData.Values["action"];
string contextController = (string)html.ViewContext.RouteData.Values["controller"];
bool isCurrent =
string.Equals(contextAction, actionName, StringComparison.CurrentCultureIgnoreCase) &&
string.Equals(contextController, controllerName, StringComparison.CurrentCultureIgnoreCase);
return html.ActionLink(
linkText,
actionName,
controllerName,
routeValues: null,
htmlAttributes: isCurrent ? new { #class = "current" } : null);
}
Then you can use it in your View by including the namespace of your extension and just calling your method:
#using MyExtensionNamespace;
...
#Html.NavigationLink("Product Search", "Index", "Home")
| #Html.NavigationLink("Orders", "Index", "Orders")
| #Html.NavigationLink("My Account", "MyAccount", "Account")
| #Html.NavigationLink("Logout", "LogOff", "Account")
This has the benefit of keeping your razor a little cleaner and is easily reusable in other views.

#{
var controller = ViewContext.RouteData.Values["controller"].ToString();
var action = ViewContext.RouteData.Values["action"].ToString();
var isActiveController = new Func<string, string, string, string, string>((ctrl, act, activeStyle, inactiveStyle) => controller == ctrl && action == act ? activeStyle : inactiveStyle);
}
Then in your class attribute in your HTML you can do:
class="#isActiveController("controlername","action","activecssstyleclass","inactiveccsstyle")"
Just an other way of #dknaack his answer.. bit more generic and less functionality to repeat in your code.

In my case,assume I have a Home page and a menu.
Add a ViewBag.Active as a placeholder in Home page like this:
#{
ViewBag.Title = "Home";
ViewBag.Active = "Home";
}
Then place it to your li class as a condition to active it or not:
<li class="#(ViewBag.Active=="Home"? "active" : "")">
<span>#ViewBag.Title</span>
</li>

I used this tutorial to get this done, it's a lot simpler to understand and takes 2 minutes
Hightlight Active menu item

You can also override the AnchorTagHelper (the default <a> tag helper) to create your own tag helper. The advantage is that it already has all required information by providing the asp-controller and even allows for autocomplete etc. by your IDE.
This is my TagHelper:
public class NavAnchorTagHelper : AnchorTagHelper
{
public NavAnchorTagHelper(IHtmlGenerator generator) : base(generator)
{
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
var contextController = (string)ViewContext.RouteData.Values["controller"];
if (contextController?.Equals(this.Controller) == true)
{
output.AddClass("text-dark", HtmlEncoder.Default);
}
output.TagName = "a";
}
}
And I use it like that:
<nav-anchor class="nav-link" asp-controller="MyController" asp-action="Index">Test</nav-anchor>

Related

Using TagHelper in Custom TagHelper

I'm writing a simple pagination TagHelper, where I want the output to look like this:
<ul>
<li>Some Text</li>
<li>Some Other Text</li>
...
</ul>
I had the idea that in defining my class I'd generate "a" elements, and decorate them with asp-controller, asp-action, etc., attributes, which would get rendered as the correct href link. Here's the code I came up with:
protected TagBuilder CreatePageLink( int page, bool enabled, string inner )
{
TagBuilder a = new TagBuilder( "a" );
a.MergeAttribute( "asp-controller", AspController );
a.MergeAttribute( "asp-action", AspAction );
a.MergeAttribute( "asp-route-page", page.ToString() );
a.MergeAttribute( "asp-route-itemsPerPage", ItemsPerPage.ToString() );
a.MergeAttribute( "title", $"goto page {page}" );
if( !enabled ) a.AddCssClass( "disabled" );
if( (page == Page) && String.IsNullOrEmpty(inner) ) a.AddCssClass( "active" );
if( String.IsNullOrEmpty( inner ) ) inner = page.ToString();
a.InnerHtml.AppendHtml( inner );
TagBuilder li = new TagBuilder( "li" );
li.InnerHtml.Append( a );
return li;
}
But it doesn't work. The "MVC magic attributes" like asp-controller show up in the output, but the resulting links don't work. I have to add, in code, a specific href attribute to the embedded "a" element for the link to work.
How do I go about nesting TagHelpers, like the LinkTagHelper, inside my own custom TagHelper?
This video on tag helpers might be helpful: https://channel9.msdn.com/Shows/Web+Camps+TV/Update-on-TagHelpers-with-Taylor-Mullen
Skip ahead to about 35 minutes in.
Also they put the code up on github: https://github.com/NTaylorMullen/WebCampsTV_TagHelpers1
The part that you will find relevant is here:
[OutputElementHint("ul")]
public class ControllerNavigationTagHelper : TagHelper
{
public ControllerNavigationTagHelper(IUrlHelper urlHelper)
{
UrlHelper = urlHelper;
}
private IUrlHelper UrlHelper { get; }
public Type ControllerType { get; set; }
public string Exclude { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
output.TagName = "ul";
var actionNames = ControllerType.GetTypeInfo().DeclaredMethods
.Where(methodInfo => methodInfo.IsPublic)
.Select(methodInfo => methodInfo.Name);
var controllerName = ControllerType.Name;
if (controllerName.EndsWith("Controller", StringComparison.OrdinalIgnoreCase))
{
controllerName = controllerName.Substring(0, controllerName.Length - "Controller".Length);
}
foreach (var name in actionNames)
{
if (!string.Equals(name, Exclude, StringComparison.OrdinalIgnoreCase))
{
var displayName =
string.Equals(name, "Index", StringComparison.OrdinalIgnoreCase) ? controllerName : name;
output.PostContent.Append($"<li><a href='{UrlHelper.Action(name, controllerName)}'>{displayName}</a></li>");
}
}
}
}
The basic premises is that there is an IUrlHelper service, I think built in to mvc (asp.net core mvc), that can be injected into your tag helper which you can use to construct the reference.
Once the IUrlHelper is injected you can use it to construct the url:
UrlHelper.Action(name, controllerName)

Invalid anonymous type member declarator. Anonymous ...?

I am building an htmlhelper extension but getting this error:
Invalid anonymous type member declarator. Anonymous type members must be declared
with a member assignment, simple name or member access.
I tried to cast the #User.IsInRole to a boolean but to no avail:(
this is the Razor markup:
#using htmlHelperstring.Models
#{
ViewBag.Title = "Home Page";
}
<ul>
#Html.MyActionLink(
"<span>Hello World</span>",
"about",
"home",
new { id = "123" },
new { #class = "foo",(bool)(#User.IsInRole("Chef"))}
)
</ul>
helper:
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace htmlHelperstring.Models
{
public static class LabelExtensions
{
public static IHtmlString MyActionLink(
this HtmlHelper htmlHelper,
string linkText,
string action,
string controller,
object routeValues,
object htmlAttributes,
bool UserAuthorized
)
{
var li = new TagBuilder("li");
if (UserAuthorized)
{
var urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
var anchor = new TagBuilder("a");
anchor.InnerHtml = linkText;
anchor.Attributes["href"] = urlHelper.Action(action, controller, routeValues);
anchor.MergeAttributes(new RouteValueDictionary(htmlAttributes));
li.InnerHtml = anchor.ToString();
}
else
{
li.InnerHtml = string.Empty;
}
return MvcHtmlString.Create(li.ToString());
}
}
}
Looks like you are missing a member assignment in:
new { #class = "foo",(bool)(#User.IsInRole("Chef"))}
What do you want to assign the boolean to?
You need something like this:
new { #class = "foo", HTMLATTRIBUTENAME = (bool)(#User.IsInRole("Chef"))}
Replace the HTMLATTRIBUTENAME with the attribute name you want to be set.
I don't write asp.net (never a single line, actually; so take it with a grain of salt); but I would suspect that the construct:
new { id = "123" }
(and the similar one beneath it) is what the message is referring to by "Anonymous type", and I have a few thoughts on why what you have might be erroneous (with the third feeling most likely).
First, If it's anything like C-style structs, you may need to use a '.' preceding the "member" identifier (which, to me, makes sense for that error message):
new { .id = "123}
Second, the wording of that error makes me wonder if in this environment you aren't allowed to pass in an anonymous object like that; and you need to assign it to a variable first and then pass the variable. Forgive any syntax errors:
#using htmlHelperstring.Models
#{
ViewBag.Title = "Home Page";
myID = new { id = 123 };
myClass = new { #class = "foo",(bool)(#User.IsInRole("Chef"))}
}
<ul>
#Html.MyActionLink(
"<span>Hello World</span>",
"about",
"home",
myID,
myClass
)
</ul>
Third, the syntax of new { #class = "foo",(bool)(#User.IsInRole("Chef"))} looks wonky to me. Perhaps (note adding a member name): new { #class = "foo", isChef = (bool)(#User.IsInRole("Chef"))}
I just made a stupid typo(end of the day etc ) it should be:
#{
ViewBag.Title = "Home Page";
myID = new { id = 123 };
myClass = new { #class = "foo"},(bool)(#User.IsInRole("Chef"))
}

Best way to sort a DropDownList in MVC3 / Razor using helper method

Hi so I'm pretty new to MVC3 and Razor and I've been trying to get my head around it the past few days. I've been given a task by my project architect to create a helper method that sorts a drop down list in an MVC View. I have a View that retrieves various data from a Controller and I'm returning some values that I want to appear in a drop down list. I've been told not to sort it in the Controller and also to pass the field that we want to sort by into the helper method. I could do it like below but the architect wants to keep the view free of c sharp code:
#Html.DropDownListFor(model => model.StudyName, new SelectList(ViewBag.StudyTypes, "Value", "Text").OrderBy(l => l.Text))
So I've created some sample code and some extension methods to try and get it to work. My idea is to replicate the existing Html.DropDownList method and allow the passing of 'object htmlAttributes' so I can set the style as part of the method call.
Here's my code so far. I'm returning the data for the drop down in ViewBag.StudyTypes in the Edit Controller method:
public ActionResult Edit(int id)
{
IEnumerable<SelectListItem> mySelectList = new List<SelectListItem>();
IList<SelectListItem> myList = new List<SelectListItem>();
for (int i = 0; i < 5; i++)
{
myList.Add(new SelectListItem()
{ Value = i.ToString(), Text = "My Item " + i.ToString(), Selected = i == 2 }
);
}
mySelectList = myList;
ViewBag.StudyTypes = mySelectList;
StudyDefinition studydefinition = db.StudyDefinitions.Find(id);
return View(studydefinition);
}
Here's my View code:
#model MyStudyWeb.Models.StudyDefinition
#using MyStudyWeb.Helpers
#{
ViewBag.Mode = "Edit";
}
<div>
#Html.DropDownListSorted(new SelectList(ViewBag.StudyTypes, "Value", "Text"))<br />
#Html.DropDownListSorted("MyList", new SelectList(ViewBag.StudyTypes, "Value", "Text"))<br />
</div>
Finally below are the extension methods I'm trying to get to work. The first extension method does nothing, I just get a blank space at that point in the View. The second method kind of works but it's ugly. For the 3rd method I don't know how to specify an 'order by' parameter as the OrderBy on an IEnumerable expects a Linq expression.
namespace StudyDefinition.Helpers
{
public static class HtmlHelperExtensions
{
// 1st sort method: sort the passed in list and return a new sorted list
public static SelectList DropDownListSorted(this HtmlHelper helper, IEnumerable<SelectListItem> selectList)
{
var x = new SelectList(selectList.ToList()).OrderBy(l => l.Text);
return x as SelectList;
}
// 2nd sort method: return IHtml string and create <select> list manually
public static IHtmlString DropDownListSorted(this HtmlHelper helper, string name, SelectList selectList)
{
StringBuilder output = new StringBuilder();
(selectList).OrderBy(l => l.Text);
output.Append("<select id=" + name + " name=" + name + ">");
foreach (var item in selectList)
{
output.Append("<option value=" + item.Value.ToString() + ">" + item.Text + "</option>");
}
output.Append("</select>");
return MvcHtmlString.Create(output.ToString());
}
// 3rd sort method: pass in order by parameter - how do I use this?
public static IHtmlString DropDownListSorted(this HtmlHelper helper, string name, SelectList selectList, string orderBy)
{
StringBuilder output = new StringBuilder();
//How do I use the orderBy parameter?
(selectList).OrderBy(l => l.Text);
output.Append("<select id=" + name + " name=" + name + ">");
foreach (var item in selectList)
{
output.Append("<option value=" + item.Value.ToString() + ">" + item.Text + "</option>");
}
output.Append("</select>");
return MvcHtmlString.Create(output.ToString());
}
}
}
I really don't know the best approach to take, there may be a much simpler way that I'm totally missing and I might be at the point where I can't see the wood for the trees anymore. Some questions
Should I return a SelectList or an MvcHtmlString, or something else entirely?
For the first extension method how do I get the returned SelectList to render in the View?
How to I pass in a parameter to my extension methods that specifies the sort order?
How do I pass an 'object htmlAttributes' parameter, and how do I apply this object / parameter to the SelectList?
If anyone has some ideas or suggestions then I'd appreciate some feedback :)
The first and most important part of your code would be to get rid of any ViewBag/ViewData (which I personally consider as cancer for MVC applications) and use view models and strongly typed views.
So let's start by defining a view model which would represent the data our view will be working with (a dropdownlistg in this example):
public class MyViewModel
{
public string SelectedItem { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
then we could have a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
// I am explicitly putting some items out of order
Items = new[]
{
new SelectListItem { Value = "5", Text = "Item 5" },
new SelectListItem { Value = "1", Text = "Item 1" },
new SelectListItem { Value = "3", Text = "Item 3" },
new SelectListItem { Value = "4", Text = "Item 4" },
}
};
return View(model);
}
}
and a view:
#model MyViewModel
#Html.DropDownListForSorted(
x => x.SelectedItem,
Model.Items,
new { #class = "foo" }
)
and finally the last piece is the helper method which will sort the dropdown by value (you could adapt it to sort by text):
public static class HtmlExtensions
{
public static IHtmlString DropDownListForSorted<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> items,
object htmlAttributes
)
{
var model = helper.ViewData.Model;
var orderedItems = items.OrderBy(x => x.Value);
return helper.DropDownListFor(
expression,
new SelectList(orderedItems, "Value", "Text"),
htmlAttributes
);
}
}
Just add in the sorting before you return the items to the dropdown list.
Do this:
Models: StudyViewModel.cs
public class StudyViewModel {
public string StudyName { get; set; }
public string StudyTypes { get; set; }
}
Controller: StudyController.cs
using System.Web.Mvc;
public class StudyController
{
public List<SelectListItem> studyTypes()
{
List<SelectListItem> itemList = new List<SelectListItem>();
for (var i=0; i<5; i++)
{
itemList.Add = new SelectListItem({
Value = i.ToString();
Text = "My Item";
});
}
// You can sort here....
List<SelectListItem> sortedList = itemList.OrderBy(x=>x.Text);
return sortedList;
}
public ActionResult Edit(int id)
{
//You won't need this because you get it using your
//controller's routine, instead
//ViewBag.StudyTypes = studySlots.OrderBy(e => e.Value);
//-- unless you need to add these values to the model for
// some reason (outside of filling the ddl), in which case....
// StudyViewModel svm = new StudyViewModel();
// svm.StudyTypes = studySlots.OrderBy(e => e.Value);
// svm.StudyName = "My Item";
// return View(svm);
// Otherwise, just....
return View();
}
}
View: Edit.cshtml
#Html.DropDownListFor(model => model.StudyName)
.OptionLabel('Select...')
.DataTextField('Text')
.DataValueField('Value')
.Datasource(source =>
{
// This is where you populate your data from the controller
source.Read(read =>
{
read.Action("studyTypes", "Study");
});
})
.Value(Model.StudyName != null ? Model.StudyName.ToString() : "")
)
This way will avoid ViewBags and just use a function to fill in the values, directly.
If you are using a database you can use a query to define the sort element
using (BDMMContext dataContext = new BDMMContext())
{
foreach (Arquiteto arq in dataContext.Arquitetos.SqlQuery("SELECT * FROM Arquitetos ORDER BY Nome"))
{
SelectListItem selectItem = new SelectListItem { Text = arq.Nome, Value = arq.Arquiteto_Id.ToString() };
//
list.Add(selectItem);
}
}

Checkbox for nullable boolean

My model has a boolean that has to be nullable
public bool? Foo
{
get;
set;
}
so in my Razor cshtml I have
#Html.CheckBoxFor(m => m.Foo)
except that doesn't work. Neither does casting it with (bool). If I do
#Html.CheckBoxFor(m => m.Foo.Value)
that doesn't create an error, but it doesn't bind to my model when posted and foo is set to null. Whats the best way to display Foo on the page and make it bind to my model on a post?
I got it to work with
#Html.EditorFor(model => model.Foo)
and then making a file at Views/Shared/EditorTemplates/Boolean.cshtml with the following:
#model bool?
#Html.CheckBox("", Model.GetValueOrDefault())
Found answer in similar question - Rendering Nullable Bool as CheckBox.
It's very straightforward and just works:
#Html.CheckBox("RFP.DatesFlexible", Model.RFP.DatesFlexible ?? false)
#Html.Label("RFP.DatesFlexible", "My Dates are Flexible")
It's like accepted answer from #afinkelstein except we don't need special 'editor template'
I have bool? IsDisabled { get; set; } in Model. Inserted if in View.
<div class="inputClass" id="disabled">
<div>
#if(Model.IsDisabled==null)
{
Model.IsDisabled = false;
}
#Html.CheckBoxFor(model => model.IsDisabled.Value)
</div>
</div>
My model has a boolean that has to be nullable
Why? This doesn't make sense. A checkbox has two states: checked/unchecked, or True/False if you will. There is no third state.
Or wait you are using your domain models in your views instead of view models? That's your problem. So the solution for me is to use a view model in which you will define a simple boolean property:
public class MyViewModel
{
public bool Foo { get; set; }
}
and now you will have your controller action pass this view model to the view and generate the proper checkbox.
Complicating a primitive with hidden fields to clarify whether False or Null is not recommended.
Checkbox isn't what you should be using -- it really only has one state: Checked. Otherwise, it could be anything.
When your database field is a nullable boolean (bool?), the UX should use 3-Radio Buttons, where the first button represents your "Checked", the second button represents "Not Checked" and the third button represents your null, whatever the semantics of null means. You could use a <select><option> drop down list to save real estate, but the user has to click twice and the choices aren't nearly as instantaneously clear.
1 0 null
True False Not Set
Yes No Undecided
Male Female Unknown
On Off Not Detected
The RadioButtonList, defined as an extension named RadioButtonForSelectList, builds the radio buttons for you, including the selected/checked value, and sets the <div class="RBxxxx"> so you can use css to make your radio buttons go horizontal (display: inline-block), vertical, or in a table fashion (display: inline-block; width:100px;)
In the model (I'm using string, string for the dictionary definition as a pedagogical example. You can use bool?, string)
public IEnumerable<SelectListItem> Sexsli { get; set; }
SexDict = new Dictionary<string, string>()
{
{ "M", "Male"},
{ "F", "Female" },
{ "U", "Undecided" },
};
//Convert the Dictionary Type into a SelectListItem Type
Sexsli = SexDict.Select(k =>
new SelectListItem
{
Selected = (k.Key == "U"),
Text = k.Value,
Value = k.Key.ToString()
});
<fieldset id="Gender">
<legend id="GenderLegend" title="Gender - Sex">I am a</legend>
#Html.RadioButtonForSelectList(m => m.Sexsli, Model.Sexsli, "Sex")
#Html.ValidationMessageFor(m => m.Sexsli)
</fieldset>
public static class HtmlExtensions
{
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
String rbClassName = "Horizontal")
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var sb = new StringBuilder();
if (listOfValues != null)
{
// Create a radio button for each item in the list
foreach (SelectListItem item in listOfValues)
{
// Generate an id to be given to the radio button field
var id = string.Format("{0}_{1}", metaData.PropertyName, item.Value);
// Create and populate a radio button using the existing html helpers
var label = htmlHelper.Label(id, HttpUtility.HtmlEncode(item.Text));
var radio = String.Empty;
if (item.Selected == true)
{
radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id, #checked = "checked" }).ToHtmlString();
}
else
{
radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id }).ToHtmlString();
}// Create the html string to return to client browser
// e.g. <input data-val="true" data-val-required="You must select an option" id="RB_1" name="RB" type="radio" value="1" /><label for="RB_1">Choice 1</label>
sb.AppendFormat("<div class=\"RB{2}\">{0}{1}</div>", radio, label, rbClassName);
}
}
return MvcHtmlString.Create(sb.ToString());
}
}
For me the solution was to change the view model. Consider you are searching for invoices. These invoices can be paid or not. So your search has three options: Paid, Unpaid, or "I don't Care".
I had this originally set as a bool? field:
public bool? PaidInvoices { get; set; }
This made me stumble onto this question. I ended up created an Enum type and I handled this as follows:
#Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.NotSpecified, new { #checked = true })
#Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.Yes)
#Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.No)
Of course I had them wrapped in labels and had text specified, I just mean here's another option to consider.
Checkbox only offer you 2 values (true, false). Nullable boolean has 3 values (true, false, null) so it's impossible to do it with a checkbox.
A good option is to use a drop down instead.
Model
public bool? myValue;
public List<SelectListItem> valueList;
Controller
model.valueList = new List<SelectListItem>();
model.valueList.Add(new SelectListItem() { Text = "", Value = "" });
model.valueList.Add(new SelectListItem() { Text = "Yes", Value = "true" });
model.valueList.Add(new SelectListItem() { Text = "No", Value = "false" });
View
#Html.DropDownListFor(m => m.myValue, valueList)
I would actually create a template for it and use that template with an EditorFor().
Here is how I did it:
Create My template, which is basically a partial view I created in the EditorTemplates directory, under Shared, under Views name it as (for example): CheckboxTemplate:
#using System.Globalization
#using System.Web.Mvc.Html
#model bool?
#{
bool? myModel = false;
if (Model.HasValue)
{
myModel = Model.Value;
}
}
<input type="checkbox" checked="#(myModel)" name="#ViewData.TemplateInfo.HtmlFieldPrefix" value="True" style="width:20px;" />
Use it like this (in any view):
#Html.EditorFor(x => x.MyNullableBooleanCheckboxToBe, "CheckboxTemplate")
Thats all.
Templates are so powerful in MVC, use them.. You can create an entire page as a template, which you would use with the #Html.EditorFor(); provided that you pass its view model in the lambda expression..
The cleanest approach I could come up with is to expand the extensions available to HtmlHelper while still reusing functionality provided by the framework.
public static MvcHtmlString CheckBoxFor<T>(this HtmlHelper<T> htmlHelper, Expression<Func<T, bool?>> expression, IDictionary<string, object> htmlAttributes) {
ModelMetadata modelMeta = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
bool? value = (modelMeta.Model as bool?);
string name = ExpressionHelper.GetExpressionText(expression);
return htmlHelper.CheckBox(name, value ?? false, htmlAttributes);
}
I experimented with 'shaping' the expression to allow a straight pass through to the native CheckBoxFor<Expression<Func<T, bool>>> but I don't think it's possible.
I had a similar issue in the past.
Create a Checkbox input in HTML, and set the attribute name="Foo" This should still post properly.
<input type="checkbox" name="Foo" checked="#model.Foo.Value" /> Foo Checkbox<br />
If checked = True and not checked = null
Model
public NotNullFoo
{
get { return this.Foo?? false; }
set { this.Foo= (value == false ? null : true as bool?); }
}
public bool? Foo
{
get;
set;
}
View
#Html.CheckBoxFor(x => Model.NotNullFoo })
Just check for the null value and return false to it:
#{ bool nullableValue = ((Model.nullableValue == null) || (Model.nullableValue == false)) ? false : true; }
#Html.CheckBoxFor(model => nullableValue)
I also faced the same issue. I tried the following approach to solve the issue
because i don't want to change the DB and again generate the EDMX.
#{
bool testVar = (Model.MYVar ? true : false);
}
<label>#Html.CheckBoxFor(m => testVar)testVar</label><br />
When making an EditorTemplate for a model which contains a nullable bool...
Split the nullable bool into 2 booleans:
// Foo is still a nullable boolean.
public bool? Foo
{
get
{
if (FooIsNull)
return null;
return FooCheckbox;
}
set
{
FooIsNull = (value == null);
FooCheckbox = (value ?? false);
}
}
// These contain the value of Foo. Public only so they are visible in Razor views.
public bool FooIsNull { get; set; }
public bool FooCheckbox { get; set; }
Within the editor template:
#Html.HiddenFor(m => m.FooIsNull)
#if (Model.FooIsNull)
{
// Null means "checkbox is hidden"
#Html.HiddenFor(m => m.FooCheckbox)
}
else
{
#Html.CheckBoxFor(m => m.FooCheckbox)
}
Do not postback the original property Foo, because that is now calculated from FooIsNull and FooCheckbox.
All the answers above came with it's own issues. Easiest/cleanest way IMO is to create a helper
MVC5 Razor
App_Code/Helpers.cshtml
#helper CheckBoxFor(WebViewPage page, string propertyName, bool? value, string htmlAttributes = null)
{
if (value == null)
{
<div class="checkbox-nullable">
<input type="checkbox" #page.Html.Raw(htmlAttributes)>
</div>
}
else if (value == true)
{
<input type="checkbox" value="true" #page.Html.Raw(htmlAttributes) checked>
}
else
{
<input type="checkbox" value="false" #page.Html.Raw(htmlAttributes)>
}
}
Usage
#Helpers.CheckBoxFor(this, "IsPaymentRecordOk", Model.IsPaymentRecordOk)
In my scenario, a nullable checkbox means that a staff member had not yet asked the question to the client, so it's wrapped in a .checkbox-nullable so that you may style appropriately and help the end-user identify that it is neither true nor false
CSS
.checkbox-nullable {
border: 1px solid red;
padding: 3px;
display: inline-block;
}
Extension methods:
public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression)
{
return htmlHelper.CheckBoxFor<TModel>(expression, null);
}
public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression, object htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
bool? isChecked = null;
if (metadata.Model != null)
{
bool modelChecked;
if (Boolean.TryParse(metadata.Model.ToString(), out modelChecked))
{
isChecked = modelChecked;
}
}
return htmlHelper.CheckBox(ExpressionHelper.GetExpressionText(expression), isChecked??false , htmlAttributes);
}
Radio buttons are useful, but they don't allow you to deselect. If you want this behaviour, then consider using a drop-down/select. The following code will generate the SelectList and this binds successfully to a nullable boolean:
public static SelectList GetBoolTriState()
{
var items = new List<SelectListItem>
{
new SelectListItem {Value = "", Text = ""},
new SelectListItem {Value = "True", Text = "Yes"},
new SelectListItem {Value = "False", Text = "No"},
};
return new SelectList(items, "Value", "Text");
}
#{ bool testVar = ((bool)item.testVar ? true : false); }
#Html.DisplayFor(modelItem => testVar)
This is an old question, and the existing answers describe most of the alternatives. But there's one simple option, if you have bool? in your viewmodel, and you don't care about null in your UI:
#Html.CheckBoxFor(m => m.boolValue ?? false);

ASP.NET MVC Ajax.ActionLink with Image

is there anyway to have an image act as an ajax actionlink? I can only get it to work using text. Thanks for your help!
From Stephen Walthe, from his Contact manger project
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.Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing));
}
}
You can now type in your aspx file :
<%= Ajax.ImageActionLink("../../Content/Delete.png", "Delete", "Delete", new { id = item.Id }, new AjaxOptions { Confirm = "Delete contact?", HttpMethod = "Delete", UpdateTargetId = "divContactList" })%>
Here's the easiest solution I've found:
<%= Ajax.ActionLink("[replacethis]", ...).Replace("[replacethis]", "<img src=\"/images/test.gif\" ... />" %>
The Replace() call is used to push the img tag into the action link. You just need to use the "[replaceme]" text (or any other safe text) as a temporary placeholder to create the link.
This is a Razor/MVC 3 (and later) update to Black Horus' answer:
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
public static class ImageActionLinkHelper
{
public static IHtmlString ImageActionLink(this AjaxHelper helper, string imageUrl, string altText, string actionName, object routeValues, AjaxOptions ajaxOptions, object htmlAttributes = null)
{
var builder = new TagBuilder("img");
builder.MergeAttribute("src", imageUrl);
builder.MergeAttribute("alt", altText);
builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
var link = helper.ActionLink("[replaceme]", actionName, routeValues, ajaxOptions).ToHtmlString();
return MvcHtmlString.Create(link.Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing)));
}
}
You can now type in your .cshtml file :
#Ajax.ImageActionLink("../../Content/Delete.png", "Delete", "Delete", new { id = item.Id }, new AjaxOptions { Confirm = "Delete contact?", HttpMethod = "Delete", UpdateTargetId = "divContactList" })
Oct 31. 2013: Updated with an extra parameter to allow for setting additional HTML attributes to the image element. Usage:
#Ajax.ImageActionLink("../../Content/Delete.png", "Delete", "Delete", new { id = item.Id }, new AjaxOptions { Confirm = "Delete contact?", HttpMethod = "Delete", UpdateTargetId = "divContactList" }, new{ style="border: none;" })
Another solution is to create your own extension method:
ActionLink<TController>(this HtmlHelper helper, Expression<Action<TController>> action, string linkText, object htmlAttributes, LinkOptions options)
and as the last parameter is the enumeration LinkOptions
[Flags]
public enum LinkOptions
{
PlainContent = 0,
EncodeContent = 1,
}
and then you can use it as follows:
Html.ActionLink<Car>(
c => c.Delete(item.ID), "<span class=\"redC\">X</span>",
new { Class = "none left" },
LinkOptions.PlainContent)
I'll post whole description of this solution on my blog: http://fknet.wordpress.com/
The short answer is that is not possible. Your options are to write your own extension method to have an ImageActionLink, not too hard to do. Or add an attribute to the actionLink and replace the innerhtml with the image tag.
See version 7 the Contact Manager Tutorial on http://asp.net/mvc. Stephen Walther has an example of creating an Ajax.ActionLink that is an image.
MVC3, Html.ActionImageLink and Ajax.ActionImageLink
Thank you to all the other answers in helping me with these.
public static MvcHtmlString ActionImageLink(this HtmlHelper helper, string imageUrl, string altText, string actionName, string controller, object routeValues)
{
var builder = new TagBuilder("img");
builder.MergeAttribute("src", imageUrl);
builder.MergeAttribute("alt", altText);
var link = helper.ActionLink("[replaceme]", actionName, controller, routeValues);
return new MvcHtmlString(link.ToHtmlString().Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing)));
}
public static MvcHtmlString ActionImageLink(this AjaxHelper helper, string imageUrl, string altText, string actionName, string controller, object routeValues, AjaxOptions ajaxOptions)
{
var builder = new TagBuilder("img");
builder.MergeAttribute("src", imageUrl);
builder.MergeAttribute("alt", altText);
var link = helper.ActionLink("[replaceme]", actionName, controller, routeValues, ajaxOptions);
return new MvcHtmlString(link.ToHtmlString().Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing)));
}
General solution: include any Razor you want inside the action link
There's a much better solution using Razor template delegates, which allows to insert any Razor code inside the action link in a very natural way. So you can add an image, or any other code.
This is the extension method:
public static IHtmlString ActionLink<T>(this AjaxHelper ajaxHelper,
T item, Func<T,HelperResult> template, string action,
string controller, object routeValues, AjaxOptions options)
{
string rawContent = template(item).ToHtmlString();
MvcHtmlString a = ajaxHelper.ActionLink("$$$", action,
controller, routeValues, options);
return MvcHtmlString.Create(a.ToString().Replace("$$$", rawContent));
}
An this is how it can be used:
#Ajax.ActionLink(car,
#<div>
<h1>#car.Maker</h1>
<p>#car.Description</p>
<p>Price: #string.Format("{0:C}",car.Price)</p>
</div>, ...
This allows to write Razor with intellisense, and use any object you want for the template (the ViewModel, or any other object, like the car in my sample). And you can use any helper inside the template to nest images or whatver element you want.
Note for Resharper Users
If you are using R# in your project, you can add R# annotations to improve Intellisense:
public static IHtmlString ActionLink<T>(this AjaxHelper ajaxHelper, T item,
Func<T, HelperResult> template,
[AspMvcAction] string action, [AspMvcController] string controller,
object routeValues, AjaxOptions options)
Every answer is good but I found the easiest one:
#Html.ActionLink( " ", "Index", "Countries", null, new
{
style = "background: url('../../Content/Images/icon.png') no-repeat center right;display:block; height:24px; width:24px;margin-top:-2px;text-decoration:none;"
} )
Note that it is using a white space (" ") for the link text. It will not work with an empty text.
The first solution is to use a helper static method DecodeLinkContent like the following:
DecodeLinkContent(Html.ActionLink<Home>(c => c.Delete(item.ID), "<span class=\"redC\">X</span>",new { Class = "none left"}))
DecodeLinkContent has to find first '>' and last '<' and has to replace the content with HttpUtility.Decode(content).
This solution is little bit a hack but I think it's the most easy.
Update for MVC3 using Templated Razor Delegates relies on T4Mvc,but brings so much power.
Based on various other answers on this page.
public static HelperResult WrapInActionLink(this AjaxHelper helper,ActionResult result, Func<object,HelperResult> template,AjaxOptions options)
{
var link=helper.ActionLink("[replaceme]",result,options);
var asString=link.ToString();
var replaced=asString.Replace("[replaceme]",template(null).ToString());
return new HelperResult(writer =>
{
writer.Write(replaced);
});
}
Allows:
#Ajax.WrapInActionLink(MVC.Deal.Details(deal.ID.Value),#<img alt='Edit deal details' src='#Links.Content.Images.edit_16_gif'/>, new AjaxOptions() { UpdateTargetId="indexDetails" })
.li_inbox { background: url(inbox.png) no-repeat; padding-left:40px;
/image background wight 40px/ }
<li class="li_inbox" >
#Ajax.ActionLink("Inbox", "inbox","Home", new { },
new AjaxOptions
{
UpdateTargetId = "MainContent",
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET"
})
Try this
#Html.Raw(HttpUtility.HtmlDecode(Ajax.ActionLink( "<img src=\"/images/sjt.jpg\" title=\"上一月\" border=\"0\" alt=\"上一月\" />", "CalendarPartial", new { strThisDate = Model.dtCurrentDate.AddMonths(-1).ToString("yyyy-MM-dd") }, new AjaxOptions { #UpdateTargetId = "calendar" }).ToString()))
Nice solutions here, but what if you want to have more then just an image in the actionlink? This is how I do it:
#using (Ajax.BeginForm("Action", "Controler", ajaxOptions))
{
<button type="submit">
<img src="image.png" />
</button>
}
The drawback is that I still have to do a bit of styling on the button-element, but you can put all the html you want in there.
All are very Nice solutions, but if you dislike having a replace in your solution you can try this:
{
var url = new UrlHelper(helper.ViewContext.RequestContext);
// build the <img> tag
var imgBuilder = new TagBuilder("img");
imgBuilder.MergeAttribute("src", url.Content(imageUrl));
imgBuilder.MergeAttribute("alt", altText);
string imgHtml = imgBuilder.ToString(TagRenderMode.SelfClosing);
//build the <a> tag
var anchorBuilder = new TagBuilder("a");
anchorBuilder.MergeAttribute("href", url.Action(actionName, controller, routeValues));
anchorBuilder.InnerHtml = imgHtml; // include the <img> tag inside
anchorBuilder.MergeAttributes<string, object>(ajaxOptions.ToUnobtrusiveHtmlAttributes());
string anchorHtml = anchorBuilder.ToString(TagRenderMode.Normal);
return MvcHtmlString.Create(anchorHtml);
}
Furthermore, in my case, if I don't use url.Content(imageUrl), the image doesn't display.
I have found that far and away the best solution to this is to use the input tag with type="image"
#using (Ajax.BeginForm( "LoadTest","Home" , new System.Web.Mvc.Ajax.AjaxOptions { UpdateTargetId = "[insert your target tag's id here]" }))
{
<input type="image" class="[css style class here]" src="[insert image link here]">
}
It's easy and it's fast.
I've used it in combination with other controls libraries that interfere with AjaxOptions, so I tend to type out the whole System.Web.Mvc.Ajax.AjaxOptions just in case I end up trying a different set in the future.
NOTE:
I have noticed that this does appear to have issues within MVC3 (something to do with type="image"), it does work for MVC 4 though
Use this Extension to generate ajax link with glifyphicon:
/// <summary>
/// Create an Ajax.ActionLink with an associated glyphicon
/// </summary>
/// <param name="ajaxHelper"></param>
/// <param name="linkText"></param>
/// <param name="actionName"></param>
/// <param name="controllerName"></param>
/// <param name="glyphicon"></param>
/// <param name="ajaxOptions"></param>
/// <param name="routeValues"></param>
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcHtmlString ImageActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, string controllerName, string glyphicon, AjaxOptions ajaxOptions, RouteValueDictionary routeValues = null, object htmlAttributes = null)
{
//Example of result:
//<a id="btnShow" href="/Customers/ShowArtworks?customerId=1" data-ajax-update="#pnlArtworks" data-ajax-success="jsSuccess"
//data-ajax-mode="replace" data-ajax-method="POST" data-ajax-failure="jsFailure" data-ajax-confirm="confirm" data-ajax-complete="jsComplete"
//data-ajax-begin="jsBegin" data-ajax="true">
// <i class="glyphicon glyphicon-pencil"></i>
// <span>Edit</span>
//</a>
var builderI = new TagBuilder("i");
builderI.MergeAttribute("class", "glyphicon " + glyphicon);
string iTag = builderI.ToString(TagRenderMode.Normal);
string spanTag = "";
if (!string.IsNullOrEmpty(linkText))
{
var builderSpan = new TagBuilder("span") { InnerHtml = " " + linkText };
spanTag = builderSpan.ToString(TagRenderMode.Normal);
}
//Create the "a" tag that wraps
var builderA = new TagBuilder("a");
var requestContext = HttpContext.Current.Request.RequestContext;
var uh = new UrlHelper(requestContext);
builderA.MergeAttribute("href", uh.Action(actionName, controllerName, routeValues));
builderA.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
builderA.MergeAttributes((ajaxOptions).ToUnobtrusiveHtmlAttributes());
builderA.InnerHtml = iTag + spanTag;
return new MvcHtmlString(builderA.ToString(TagRenderMode.Normal));
}
Use Html data- attributes
<a data-ajax="true" data-ajax-begin="..." data-ajax-success="..." href="#Url.Action("Delete")">
<i class="halflings-icon remove"></i>
</a>
Replace the
<i class="halflings-icon remove"></i>
with your own image
Others didn't work for me as the .ToHtmlString() spat out a string in MVC 4.
the below passes an id to the edit control and displays an edit image instead of the text spag:
#MvcHtmlString.Create(Ajax.ActionLink("Spag", "Edit", new { id = item.x0101EmployeeID }, new AjaxOptions() { UpdateTargetId = "selectDiv", InsertionMode = InsertionMode.Replace, HttpMethod = "GET" }).ToHtmlString().Replace("Spag", "<img src=\"" + Url.Content("../../Images/edit.png") + "\" />"))
actionName+"/"+routeValues Proje/ControlName/ActionName/Id
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
namespace MithatCanMvc.AjaxHelpers
{
public static class ImageActionLinkHelper
{
public static IHtmlString ImageActionLink(this AjaxHelper helper, string imageUrl, string altText, string actionName, string routeValues, AjaxOptions ajaxOptions)
{
var builder = new TagBuilder("img");
builder.MergeAttribute("src", imageUrl);
builder.MergeAttribute("alt", altText);
var link = helper.ActionLink("[replaceme]", actionName+"/"+routeValues, ajaxOptions).ToHtmlString();
return MvcHtmlString.Create(link.Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing)));
}
}
}
I don't know, this seems easier to me:
<a href="#Url.Action("index", "home")">
<img src="~/Images/rocket.png" width="25" height="25" title="Launcher" />
</a>

Resources