ASP.NET MVC - Style List Item Based on Controller

ASP.NET MVC - style list item based on controller

In a recent project of mine I did it using HtmlHelper extensions and getting data from the ViewContext.RouteData.Values collection.

So building off a simple extension like this:

public static string OnClass(this HtmlHelper html, bool isOn)
{
if (isOn)
return " class=\"on\"";

return string.Empty;
}

You can build up any number of combinations, e.g.

Just testing the current action:

public static string OnClass(this HtmlHelper html, string action)
{
string currentAction = html.ViewContext.RouteData.Values["action"].ToString();

return html.OnClass(currentAction.ToLower() == action.ToLower());
}

Testing for a number of actions:

public static string OnClass(this HtmlHelper html, string[] actions)
{
string currentAction = html.ViewContext.RouteData.Values["action"].ToString();

foreach (string action in actions)
{
if (currentAction.ToLower() == action.ToLower())
return html.OnClass(true);
}

return string.Empty;
}

Testing for action and controller:

public static string OnClass(this HtmlHelper html, string action, string controller)
{
string currentController = html.ViewContext.RouteData.Values["controller"].ToString();

if (currentController.ToLower() == controller.ToLower())
return html.OnClass(action);

return string.Empty;
}

Etc, etc.

Then you simply call it in your view(s) like so

<ul id="left-menu">
<!-- simple boolean -->
<li <%= Html.OnClass(something == somethingElse) %>>Blah</li>
<!-- action -->
<li <%= Html.OnClass("Index") %>>Blah</li>
<!-- any number of actions -->
<li <%= Html.OnClass(new string[] { "Index", "Details", "View" }) %>>Blah</li>
<!-- action and controller -->
<li <%= Html.OnClass("Index", "Home") %>>Blah</li>
</ul>

Which ever way you look at it, HtmlHelper extensions are your friend! :-)

HTHs

Charles

ASP.NET MVC Dynamically set CSS Class to list item based on route

Writing an html helper for those buttons could be one way of doing it. Assuming the standard routing is set:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

here's how the helper might look like:

public static MvcHtmlString MyButton(this HtmlHelper htmlHelper, string id, string text)
{
var button = new TagBuilder("input");
button.MergeAttribute("type", "button");
button.MergeAttribute("value", text);
// get the id from the current route:
var routeId = htmlHelper.ViewContext.RouteData.Values["id"] as string;
if (id == routeId)
{
button.MergeAttribute("class", "active");
}
return MvcHtmlString.Create(button.ToString(TagRenderMode.SelfClosing));
}

and finally add to your view:

<%= Html.MyButton("questions", "Questions") %>
<%= Html.MyButton("tags", "Tags") %>
<%= Html.MyButton("users", "Users") %>

To further improve the helper you could add additional parameters that will contain the action and the controller this button will redirect to when clicked.

ASP.NET MVC loading of CSS based off of controller

In your master page create the following placeholder inside the <head> section.

Master page

<asp:ContentPlaceHolder ID="HeadContent" runat="server"></asp:ContentPlaceHolder>

Then inside your controller you should determine the list of .css files to be used as well as create a string that the views can use to easily place the content inside the page. Here is what I've used.

Controller

public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";

List<string> css = new List<string>()
{
"one.css",
"two.css",
"three.css",
"four.css"
};

IList<string> cssTags = new List<string>();

StringBuilder cssTag = new StringBuilder();

css.ForEach(c =>
{
cssTag.AppendLine(string.Format(@"<link href=""{0}"" rel=""stylesheet"" type=""text/css"" />", HttpUtility.HtmlEncode(c)));
});

ViewData["css"] = cssTag.ToString();

return View();
}

Then inside your view just place the follwing

View

<asp:Content ID="headContent" ContentPlaceHolderID="HeadContent" runat="server">
<%= ViewData["css"] %>
</asp:Content>

The reason why I build up the list of .css inside the controller and then create a string that the view uses, is because I want the views to be as stupid as possible. An option would be to just place the list of .css files into the ViewData itself and then let the view do the looping and create the link tag, but it's better to do it elsewhere if possible.

Another option to using ViewData is to use a strongly typed view with a string property and then the view just picks that guy off. I personally don't use the ViewData dictionary. Instead all my views are strongly typed.

Add item into List from View and pass to Controller in MVC5

You can refer this post. It works perfect for me.

http://ivanz.com/2011/06/16/editing-variable-length-reorderable-collections-in-asp-net-mvc-part-1/

I will quote it below:

The aspects I will consider are:

  1. Dynamically adding, removing and reordering items to/from the
    collection
  2. Validation implications
  3. Code Reusability and Refactoring implications I will assume that you are already familiar with ASP.NET MVC and basic JavaScript concepts.

Source Code
All source code is available on GitHub

The Sample
What I am going to build is a little sample where we have a user who has a list of favourite movies. It will look roughly like on the image below and will allow for adding new favourite movies, removing favourite movies and also reordering them up and down using the drag handler.

In Part 1 I look at implementing collection editing by sticking to facilities provided to us by ASP.NET MVC such as views, partial views, editor templates, model binding, model validation, etc.

Domain Model
The domain model is basically:

public class User
{
public int? Id { get; set; }
[Required]
public string Name { get; set; }
public IList<Movie> FavouriteMovies { get; set; }
}

and

public class Movie
{
[Required]
public string Title { get; set; }
public int Rating { get; set; }
}

Let’s get cracking!

An Edit View
Let’s start by creating a first-pass edit view for our Person to look like the one on the image above:

@model CollectionEditing.Models.User
@{ ViewBag.Title = "Edit My Account"; }

<h2>Edit</h2>

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>My Details</legend>

@Html.HiddenFor(model => model.Id)

<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
</fieldset>

<fieldset>
<legend>My Favourite Movies</legend>

@if (Model.FavouriteMovies == null || Model.FavouriteMovies.Count == 0) {
<p>None.</p>
} else {
<ul id="movieEditor" style="list-style-type: none">
@for (int i=0; i < Model.FavouriteMovies.Count; i++) {
<li style="padding-bottom:15px">
<img src="@Url.Content("~/Content/images/draggable-icon.png")" style="cursor: move" alt="Sample Image"/>

@Html.LabelFor(model => model.FavouriteMovies[i].Title)
@Html.EditorFor(model => model.FavouriteMovies[i].Title)
@Html.ValidationMessageFor(model => model.FavouriteMovies[i].Title)

@Html.LabelFor(model => model.FavouriteMovies[i].Rating)
@Html.EditorFor(model => model.FavouriteMovies[i].Rating)
@Html.ValidationMessageFor(model => model.FavouriteMovies[i].Rating)

<a href="#" onclick="$(this).parent().remove();">Delete</a>
</li>
}
</ul>
<a href="#">Add another</a>
}

<script type="text/javascript">
$(function () {
$("#movieEditor").sortable();
});
</script>
</fieldset>

<p>
<input type="submit" value="Save" />
<a href="/">Cancel</a>
</p>
}

he view is creating a list of editing controls for each of the movies in Person.FavouriteMovies. I am using a jQuery selector and dom function to remove a movie when the user clicks “Delete” and also a jQuery UI Sortable to make the items from the HTML list drag and droppable up and down.

With this done we immediately face the first problem: We haven’t implemented the “Add another”. Before we do that let’s consider how ASP.NET MVC model binding of collections works.

ASP.NET MVC Collection Model Binding Patterns
There are two patterns for model binding collections in ASP.NET MVC. The first one you have just seen:

@for (int i=0; i < Model.FavouriteMovies.Count; i++) {
@Html.LabelFor(model => model.FavouriteMovies[i].Title)
@Html.EditorFor(model => model.FavouriteMovies[i].Title)
@Html.ValidationMessageFor(model => model.FavouriteMovies[i].Title)

}

which generates similar HTML:

<label for="FavouriteMovies_0__Title">Title</label>
<input id="FavouriteMovies_0__Title" name="FavouriteMovies[0].Title" type="text" value="" />
<span class="field-validation-error">The Title field is required.</span>

This is really great for displaying collections and editing static length collections, but problematic when we want to edit variable length collections, because:

1. Indices have to be sequential (0, 1, 2, 3, …). If they aren’t ASP.NET MVC stops at the first gap. E.g. if you have item 0, 1, 3, 4 after the model binding has finished you will end up with a collection of two items only – 1 and 2 instead of four items.
2. If you were to reorder the list in the HTML ASP.NET MVC will apply the indices order not the fields order when doing model binding.

This basically means that add/remove/reorder scenarios are no go with this. It’s not impossible but it will be big big mess tracking add/remove/reorder actions and re-indexing all field attributes.

Now, someone might say – “Hey, why don’t you just implement a non-sequential collection model binder?” .

Yes, you can write the code for a non-sequential collection model binder. You will however face two major issues with that however. The first being that the IValueProvider doesn’t expose a way to iterate through all values in the BindingContext which you can workaround* by hardcoding the model binder to access the current HttpRequest Form values collection (meaning that if someone decides to submit the form via Json or query parameters your model binder won’t work) or I’ve seen one more insane workaround which checks the *BindingContext one by one from CollectionName[0] to CollectionName[Int32.MaxValue] (that’s 2 billion iterations!).

Second major issue is that once you create a sequential collection from the non-sequential indices and items and you have a validation error and you re-render the form view your ModelState will no longer match the data. An item that used to be at index X is now at index X-1 after another item before it was deleted, however the ModelState validation message and state still point to X, because this is what you submitted.

So, even a custom model binder won’t help.

Thankfully there is a second pattern, which mostly helps for what we want to achieve (even though I don’t think it was designed to solve exactly this):

<input type="hidden" name="FavouriteMovies.Index" value="indexA"/>
<input name="FavouriteMovies[indexA].Title" type="text" value="" />
<input name="FavouriteMovies[indexA].Rating" type="text" value="" />
<input type="hidden" name="FavouriteMovies.Index" value="indexB"/>
<input name="FavouriteMovies[indexB].Title" type="text" value="" />
<input name="FavouriteMovies[indexB].Rating" type="text" value="" />

Notice how we have introduced an “.Index” hidden field for each collection item. By doing that we tell ASP.NET MVC’s model binding “Hey, don’t look for a standard numeric collection index, but instead look for the custom Index value we have specified and just get me the list of items in a collection when you are done”. How does this help?

We can specify any index value we want
The index doesn’t have to be sequential and items will be put in the collection in the order they are in the HTML when submitted.
Bam! That’s solves most, but not all of our problems.

The Solution

Firstly, ASP.NET MVC doesn’t have HTML helpers to generate the “[something].Index” pattern which is major problem since it means we can’t use validation and custom editors. We can fix that by utilizing some ASP.NET templating fu. What we are going to do is move the Movie editor to a its own partial view (MovieEntryEditor.cshtml):

@model CollectionEditing.Models.Movie

<li style="padding-bottom:15px">
@using (Html.BeginCollectionItem("FavouriteMovies")) {
<img src="@Url.Content("~/Content/images/draggable-icon.png")" style="cursor: move" alt="Sample Image"/>

@Html.LabelFor(model => model.Title)
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)

@Html.LabelFor(model => model.Rating)
@Html.EditorFor(model => model.Rating)
@Html.ValidationMessageFor(model => model.Rating)

<a href="#" onclick="$(this).parent().remove();">Delete</a>
}
</li>

And update our Edit view to use it:

<ul id="movieEditor" style="list-style-type: none">
@foreach (Movie movie in Model.FavouriteMovies) {
Html.RenderPartial("MovieEntryEditor", movie);
}
</ul>
<p><a id="addAnother" href="#">Add another</a>

Notice two things – firstly the Movie partial edit view uses standard Html helpers and secondly there is a call to something custom called Html.BeginCollectionItem. *You might even ask yourself: Wait a second. This won’t work, because the partial view will produce names such as “Title” instead of “FavouriteMovies[xxx].Title”, so let me show you the source code of *Html.BeginCollectionItem:

public static IDisposable BeginCollectionItem<TModel>(this HtmlHelper<TModel> html,                                                       string collectionName)
{
string itemIndex = Guid.NewGuid().ToString();
string collectionItemName = String.Format("{0}[{1}]", collectionName, itemIndex);

TagBuilder indexField = new TagBuilder("input");
indexField.MergeAttributes(new Dictionary<string, string>() {
{ "name", String.Format("{0}.Index", collectionName) },
{ "value", itemIndex },
{ "type", "hidden" },
{ "autocomplete", "off" }
});

html.ViewContext.Writer.WriteLine(indexField.ToString(TagRenderMode.SelfClosing));
return new CollectionItemNamePrefixScope(html.ViewData.TemplateInfo, collectionItemName);
}

private class CollectionItemNamePrefixScope : IDisposable
{
private readonly TemplateInfo _templateInfo;
private readonly string _previousPrefix;

public CollectionItemNamePrefixScope(TemplateInfo templateInfo, string collectionItemName)
{
this._templateInfo = templateInfo;

_previousPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = collectionItemName;
}

public void Dispose()
{
_templateInfo.HtmlFieldPrefix = _previousPrefix;
}
}

This helper does two things:

  • Appends a hidden Index field to the output with a random GUID value
    (remember that using the .Index pattern an index can be any string)
  • Scopes the execution of the helper via an IDisposable and sets the
    template rendering context (html helperes and display/editor
    templates) to be “FavouriteMovies[GUID].”, so we end up with HTML
    like this:


    Title

This solves the problem of using Html field templates and basically reusing ASP.NET facilities instead of having to write html by hand, but it leads me to the second quirk that we need to address.

Let me show you the second and final problem. Disable client side validation and delete the title of e.g. “Movie 2” and click submit. Validation will fail, because Title of a movie is a required field, but while we are shown the edit form again** there are no validation messages**:

Why is that? It’s the same problem I mentioned earlier in this post. Each time we render the view we assign different names to the fields, which do not match the ones submitted and leads to a *ModelState *inconsistency. We have to figure out how to persist the name and more specifically the Index across requests. We have two options:

Add a hidden CollectionIndex field and CollectionIndex property on the Movie object to persist the FavouriteMovies.Index. This however is intrusive and suboptimal.
Instead of polluting the Movie object with an extra property be smart and in our helper Html.BeginCollectionItem reapply/reuse the submitted FavouriteMovies.Index form values.
Let’s replace in Html.BeginCollectionItem this line:

string itemIndex = Guid.New().ToString();

with:

string itemIndex = GetCollectionItemIndex(collectionIndexFieldName);

And here’ is the code for GetCollectionItemIndex:

private static string GetCollectionItemIndex(string collectionIndexFieldName)
{
Queue<string> previousIndices = (Queue<string>) HttpContext.Current.Items[collectionIndexFieldName];
if (previousIndices == null) {
HttpContext.Current.Items[collectionIndexFieldName] = previousIndices = new Queue<string>();

string previousIndicesValues = HttpContext.Current.Request[collectionIndexFieldName];
if (!String.IsNullOrWhiteSpace(previousIndicesValues)) {
foreach (string index in previousIndicesValues.Split(','))
previousIndices.Enqueue(index);
}
}

return previousIndices.Count > 0 ? previousIndices.Dequeue() : Guid.NewGuid().ToString();
}

We get all submitted values for e.g. “FavouriteMovie.Index” put them in a queue, which we store for the duration of the request. Each time we render a collection item we dequeue its old index value and if none is available we generate a new one. That way we preserve the Index across requests and can have a consistent ModelState and see validation errors and messages:

All that is left is to implement the “Add another” button functionality and we can do that easily by appending a new row to the movie editor, which we can fetch using Ajax and use our existing MovieEntryEditor.cshtml partial view like that:

public ActionResult MovieEntryRow()
{
return PartialView("MovieEntryEditor");
}

And then add the follwing “Add Another” click handler:

$("#addAnother").click(function () {
$.get('/User/MovieEntryRow', function (template) {
$("#movieEditor").append(template);
});
});

Done;

Conclusion
While not immediately obvious editing variable length reorderable collections with standard ASP.NET MVC is possible and what I like about this approach is that:

We can keep using traditional ASP.NET html helpers, editor and display templates (Html.EditorFor, etc.) with in our collection editing
We can make use of the ASP.NET MVC model validation client and server side
What I don’t like that much however is:

That we have to use an AJAX request to append a new row to the editor.
That we need to use the name of the collection in the movie editor partial view, but otherwise when doing the standalone AJAX get request the name context won’t be properly set for the partial template fields.
I would love to hear your thoughts. The sample source code is available on my GitHub


Other way: http://blog.stevensanderson.com/2008/12/22/editing-a-variable-length-list-of-items-in-aspnet-mvc/

How to Pass a List of Items from View to Controller (ASP.NET MVC 4)

I couldn't really find a way to pass a list of items directly from View to Controller, so I decided to use AJAX.

I changed the parameters of my Controller from type List<> to int[ ] to take an array of item IDs:

public ActionResult SubmitExpenses(int[] expenseIDs, DateTime? expenseDate = null, DateTime? expenseDate2 = null, int? userId = 0)
{
expenseDate = (DateTime)Session["FirstDate"];
expenseDate2 = (DateTime)Session["SecondDate"];

if (expenseDate == null || expenseDate2 == null)
{
expenseDate = DateTime.Now.AddMonths(-1);
expenseDate2 = DateTime.Today;
}

string currentUserId = User.Identity.Name;

var query = from e in db.Expenses
join user in db.UserProfiles on e.UserId equals user.UserId
where e.ExpenseDate >= expenseDate && e.ExpenseDate <= expenseDate2 && e.DateSubmitted == null
orderby e.ExpenseDate descending
select new { e, user };

if (User.IsInRole("admin") && userId != 0)
{
query = query.Where(x => x.user.UserId == userId);
}
else if (!User.IsInRole("admin"))
{
query = query.Where(x => x.user.UserName == currentUserId);
}

//var localExpenseIDs = expenseIDs;

var joined = from dbExpense in query.Select(x => x.e).AsEnumerable()
join localExpense in expenseIDs on dbExpense.ExpenseId equals localExpense
where localExpense == dbExpense.ExpenseId
select dbExpense;

foreach (Expense exp in joined)
{
exp.DateSubmitted = DateTime.Today;
exp.IsSubmitted = true;
}

try
{
db.SaveChanges();
return RedirectToAction("Index");
}
catch (Exception e)
{
Console.WriteLine(e);
return RedirectToAction("Submit");
}

}

In my view, I assigned the ID of each item to the id of it's own HTML checkbox :

@foreach (var item in Model)
{
<tr>
<td class="checkbox-td">
@Html.CheckBox("isSubmitted", new
{
@id = @Html.DisplayFor(modelItem => item.ExpenseId),
@class = "submitBox"
})
</td>
</tr>
}

<div>
@Html.ActionLink("Submit Expenses", "", "", null, new { @id = "submitExpensesLink" })
</div>

I wrote some jQuery, so that each checkbox that is checked, will add the id of the input element to an array and the array of integers would be POSTed to the SubmitExpenses action:

var checkedArray = [];

$(':checkbox[name=isSubmitted]').on('change', function () {

checkedArray = $(':checkbox[name=isSubmitted]:checked').map(function () {
return this.id;
})
.get();

//alert(checkedArray);
});

$('#submitExpensesLink').click(function () {

$.ajax({
type: "POST",
traditional: true,
url: "@Url.Action("SubmitExpenses", "Expenses")",
data: { expenseIDs: checkedArray },
success: function () {
alert("Success!");
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
if (debug) {
alert(XMLHttpRequest.responseText);
alert(textStatus);
alert(errorThrown);
}
}
});
})

How can I style specific item in a mvc dropdownlist

Change SelectListItem2 to be like follows

public class SelectListItem2
{
public string Text {get;set;}
public string Value {get;set;}
public bool Selected {get;set;}
public bool Disabled {get;set;}
public bool Level0 {get;set;}
}

And then use a loop, and Html as follows

  <select>
@foreach (SelectListItem2 item in selectItems)
{
if (item.Level0)
{
<option style="background-color:red" selected='@(item.Selected ? "selected" : "")' id="@item.Value">@item.Text</option>
}
else
{
<option id="@item.Value" selected='@(item.Selected ? "selected" : "")'>@item.Text</option>
}
}
</select>

MVC C#: Best way to bind a list of dropdowns with model

Well, I'll end answering my own question in case it helps anyone else facing something similar.

If you have a List in your model one way to handle it is the next:

In model I add a new property to hold SelectedInjury (in my case) from the dropdown.

public int SelectedInjury { get; set; }

In view I create the DropDownList this way:

@Html.DropDownListFor(model => model.Accident.Injuries[0].SelectedInjury, Model.Injuries, Model.SelectInjuryText, new { @class = "form-control", @style = "width: 300px;", onchange = @"" })

Then, in javascript I have a Injury counter and whenever adding new DropDowns (that come from the controller from a template via an ajax call) I just replace the "0" in Accident_Injuries[0] (this is more or less the generated html id) with the corresponding new index before adding the html to DOM. This way when you select a new item in the dropdown a new Injury object is autommatically added to the list so the model is updated without the need to do an ajax call to controller or anything else (this was my mail issue).

Cheers, and happy coding!

MVC - How to display a list?

In your Index function, you need to populate the model and pass to the view. Something like

Public ActionResult Index()
{
var myList = new List<example>();
return view(myList)
}

and in your view:

@model List<example>

That is what populates your index view model. It would help if you show us the controller function returning your index view.



Related Topics



Leave a reply



Submit