after I read data from my database, I try to show those datas in Html.Helper checkbox and I do that. But later when I try to get checked values back to the controller, model always returns null. Here's my controller part:
[HttpGet]
public ActionResult NewClient()
{
HizmetModel hizmetModel = new HizmetModel();
hizmetModel.hizmet = db.Hizmet.ToList<Hizmet>();
return View(hizmetModel);
}
[HttpPost]
public ActionResult NewClientPost(string name, string lastname, string telephone, string plate, HizmetModel hizmet)
{
Musteri musteri = new Musteri();
if (!db.Musteri.Where(x => x.plaka == plate).Any())
{
musteri.isim = name;
musteri.soyisim = lastname;
musteri.telefon = telephone;
musteri.plaka = plate;
db.Musteri.Add(musteri);
db.SaveChanges();
}
Islem islem = new Islem();
IslemHizmet islemhizmet = new IslemHizmet();
islem.giristarihi = DateTime.Now;
islem.plaka = plate;
var selectedHizmet = hizmet.hizmet.Where(x => x.isChecked == true).ToList<Hizmet>();
db.Islem.Add(islem);
db.SaveChanges();
var onprocessplate = db.Islem.Where(x => x.plaka == plate).FirstOrDefault();
foreach (var item in selectedHizmet)
{
islemhizmet.islem_id = onprocessplate.islem_id;
islemhizmet.hizmet_id = item.hizmet_id;
db.IslemHizmet.Add(islemhizmet);
db.SaveChanges();
islemhizmet = new IslemHizmet();
}
TempData["Success"] = "Müşteri başarıyla eklendi...";
return RedirectToAction("CurrentClients", "Admin");
}
This is my model for the list:
public class HizmetModel
{
public List<Hizmet> hizmet { get; set; }
}
I use this model in the cshtml file:
#model otoyikama.Models.Model.HizmetModel
And this is the loop for displaying checkboxes
#for (int i = 0; i < Model.hizmet.Count; i++)
{
<li>
<label>#Model.hizmet[i].hizmetisim</label>
#Html.CheckBoxFor(m => m.hizmet[i].isChecked)
#Html.HiddenFor(m => m.hizmet[i].hizmet_id)
#Html.HiddenFor(m => m.hizmet[i].hizmetisim)
</li>
}
I couldn't figure what's the problem here, my get action works fine, I can see all the data from database but I can't pass them back to controller.
As a first think , u need to create a object in your controller parameters such as like List<int> serviceIDs or List<Service> services so you can keep more data than one.
The html part:
#foreach(item in Model.Service)
{
<label>#item.ServiceName</label><br>
<input type="checkbox" name="services" value="#item.ServiceID">
<input type="hidden" name="services" value="#item.ServiceName">
}
The backend part:
[HttpPost]
public ActionResult NewClientPost(string name, string lastname, string telephone, string plate, List<Service> services)
{
}
when u do in that way, u will able to hold more services than to one and i think u can pass them the controller more easly.
when i face with that stuation, im taking those services ID's and calling them on the controller side like bellow;
The html part:
#foreach(item in Model.Service)
{
<label>#item.ServiceName</label><br>
<input type="checkbox" name="serviceIDs" value="#item.ServiceID">
}
The backend part:
[HttpPost]
public ActionResult NewClientPost(string name, string lastname, string telephone, string plate, List<int> serviceIDs)
{
List<Service> services= new List<Service>();
foreach(var item in serviceIDs)
{
var service=db.Service.Where(x => x.ServiceID == item).Any()
if(service.Count!=0)
{
services.Add(service);
}
}
}
I have a multiselect ListBoxFor control as shown in mvc view
#using ExampleViewModel
#using (Html.BeginForm("SaveExample", "Example", FormMethod.Post, new { #id
= "exampleForm" }))
{
#Html.ListBoxFor(x => x.AssignedContainers, new
MultiSelectList(Model.RegisteredContainers, "ID", "Description", null),
new { #class = "fieldNoFocusRequiredListBox", #size = 15,
id="RegisteredContainers" })
}
View model code looks like this:
public class ExampleViewModel
{
public int[] AssignedContainers { get; set; }
public List<Container_Sizes> RegisteredContainers { get; set; }
}
Controller code:
public ActionResult SaveExample(ExampleViewModel model)
{
// code
return Json(true, JsonRequestBehavior.AllowGet);
}
I was able to bind the listbox with model property RegisteredContainers. I do some some jQuery validation and then submit the form via ajax like this:
function SaveProduct() {
if (!ValidateProductForm()) {
return;
}
$.ajax({
type: "POST",
data: $('#exampleForm').serialize(),
url: '/Example/SaveExample/',
success: function (result) {
},
error: function (jqXhr, error, errorThrown) {
}
});
}
When the break point hits the controller ExampleViewModel -> AssignedContainers is null. Please note that i used ICollection/IEnumerable/List for this property AssignedContainers but no luck. Also when i see $('#exampleForm').serialize() in debugger watch i see ".....&AssignedContainers=17" as there is one item in the list box but received null on controller.
Do i need any updates to model binder or what is going wrong here?
On a mass-edit form page I display about 50 objects that have some boolean properties as well. The controller receives a FormCollection with all values from the edit page.
public void _EditAll(FormCollection c)
{
int i = 0;
if (ModelState.IsValid)
{
var arrId = c.GetValues("channel.ID");
var arrName = c.GetValues("channel.displayedName");
var arrCheckbox = c.GetValues("channel.isActive");
for (i = 0; i < arrId.Count(); i++)
{
Channel chan = db.Channels.Find(Convert.ToInt32(arrId[i]));
chan.displayedName = arrName[i];
chan.isActive = Convert.ToBoolean(arrCheckbox[i]);
db.Entry(chan).State = EntityState.Modified;
}
db.SaveChanges();
}
}
Now, for checkboxes, MVC creates hidden inputs on the form (otherwise "false" could not be posted back). In the controller, when receiving the FormCollection, this leads to the case that I receive an array of say
50 IDs,
50 names and ..
71 or so values for the checkboxes,
since the hidden checkbox has the same name as the visible one.
What's a good way to handle that and get the proper value of the checkbox?
Sample for editing array of entities that have boolean field.
Entity:
public class Entity
{
public int Id { get; set; }
public bool State { get; set; }
}
Controller:
public ActionResult Index()
{
Entity[] model = new Entity[]
{
new Entity() {Id = 1, State = true},
new Entity() {Id = 2, State = false},
new Entity() {Id = 3, State = true}
};
return View(model);
}
[HttpPost]
public ActionResult Index(Entity[] entities)
{
// here you can see populated model
throw new NotImplementedException();
}
View:
#model Entity[]
#{
using (Html.BeginForm())
{
for (int i = 0; i < Model.Count(); i++ )
{
#Html.Hidden("entities[" + i + "].Id", Model[i].Id)
#Html.CheckBox("entities[" + i + "].State", Model[i].State)
}
<input type="submit"/>
}
}
The only tricky thing is html elements naming.
More info about binding arrays.
I'm converting all arrays containing checkbox-values:
"false" => "false", if not preceded by "true"
I have a View(FilerOverall) and inside the view i am calling some method using renderaction method.
#{
Html.RenderAction("Gettemplate", "FinancialDisclosure",
new { FormId = "100",ScheduleId= "10" });
};
and in controller i have written the action method like
public ActionResult Gettemplate(string FormId ,string ScheduleId)
{
List<FDDTO> FD1 = FDService.GetScheduleDetails(100, 10).ToList();
return View ("EditorTemplates/FDDTO", FD1);
}
when I executed the app I am getting this error:
"A public action method 'Gettemplate' was not found on controller
'WorldBank.DOI.Presentation.Controllers.FinancialDisclosureController'."}
Try #Html.Action instead of Html.RenderAction
#Html.Action("Gettemplate", "FinancialDisclosure", new { FormId = "100",ScheduleId= "10" })
you should try this
Create new view like Demo.cshtml
now
public ActionResult Gettemplate(string FormId ,string ScheduleId)
{
List<FDDTO> FD1 = FDService.GetScheduleDetails(100, 10).ToList();
return View ("Demo", FD1);
}
now put in your FilerOverall.cshtml below code
#{
Html.RenderAction("Gettemplate", "FinancialDisclosure",
new { FormId = "100",ScheduleId= "10" });
};
How can dynamic breadcrumbs be achieved with ASP.net MVC?
If you are curious about what breadcrumbs are:
What are breadcrumbs? Well, if you have ever browsed an online store or read posts in a forum, you have likely encountered breadcrumbs. They provide an easy way to see where you are on a site. Sites like Craigslist use breadcrumbs to describe the user's location. Above the listings on each page is something that looks like this:
s.f. bayarea craigslist > city of san francisco > bicycles
EDIT
I realize what is possible with the SiteMapProvider. I am also aware of the providers out there on the net that will let you map sitenodes to controllers and actions.
But, what about when you want a breadcrumb's text to match some dynamic value, like this:
Home > Products > Cars > Toyota
Home > Products > Cars > Chevy
Home > Products > Execution Equipment > Electric Chair
Home > Products > Execution Equipment > Gallows
... where the product categories and the products are records from a database. Some links should be defined statically (Home for sure).
I am trying to figure out how to do this, but I'm sure someone has already done this with ASP.net MVC.
Sitemap's are definitely one way to go... alternatively, you can write one yourself! (of course as long as standard MVC rules are followed)... I just wrote one, I figured I would share here.
#Html.ActionLink("Home", "Index", "Home")
#if(ViewContext.RouteData.Values["controller"].ToString() != "Home") {
#:> #Html.ActionLink(ViewContext.RouteData.Values["controller"].ToString(), "Index", ViewContext.RouteData.Values["controller"].ToString())
}
#if(ViewContext.RouteData.Values["action"].ToString() != "Index"){
#:> #Html.ActionLink(ViewContext.RouteData.Values["action"].ToString(), ViewContext.RouteData.Values["action"].ToString(), ViewContext.RouteData.Values["controller"].ToString())
}
Hopefully someone will find this helpful, this is exactly what I was looking for when I searched SO for MVC breadcrumbs.
ASP.NET 5 (aka ASP.NET Core), MVC Core Solution
In ASP.NET Core, things are further optimized as we don't need to stringify the markup in the extension method.
In ~/Extesions/HtmlExtensions.cs:
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace YourProjectNamespace.Extensions
{
public static class HtmlExtensions
{
private static readonly HtmlContentBuilder _emptyBuilder = new HtmlContentBuilder();
public static IHtmlContent BuildBreadcrumbNavigation(this IHtmlHelper helper)
{
if (helper.ViewContext.RouteData.Values["controller"].ToString() == "Home" ||
helper.ViewContext.RouteData.Values["controller"].ToString() == "Account")
{
return _emptyBuilder;
}
string controllerName = helper.ViewContext.RouteData.Values["controller"].ToString();
string actionName = helper.ViewContext.RouteData.Values["action"].ToString();
var breadcrumb = new HtmlContentBuilder()
.AppendHtml("<ol class='breadcrumb'><li>")
.AppendHtml(helper.ActionLink("Home", "Index", "Home"))
.AppendHtml("</li><li>")
.AppendHtml(helper.ActionLink(controllerName.Titleize(),
"Index", controllerName))
.AppendHtml("</li>");
if (helper.ViewContext.RouteData.Values["action"].ToString() != "Index")
{
breadcrumb.AppendHtml("<li>")
.AppendHtml(helper.ActionLink(actionName.Titleize(), actionName, controllerName))
.AppendHtml("</li>");
}
return breadcrumb.AppendHtml("</ol>");
}
}
}
~/Extensions/StringExtensions.cs remains the same as below (scroll down to see the MVC5 version).
In razor view, we don't need Html.Raw, as Razor takes care of escaping when dealing with IHtmlContent:
....
....
<div class="container body-content">
<!-- #region Breadcrumb -->
#Html.BuildBreadcrumbNavigation()
<!-- #endregion -->
#RenderBody()
<hr />
...
...
ASP.NET 4, MVC 5 Solution
=== ORIGINAL / OLD ANSWER BELOW ===
(Expanding on Sean Haddy's answer above)
If you want to make it extension-driven (keeping Views clean), you can do something like:
In ~/Extesions/HtmlExtensions.cs:
(compatible with MVC5 / bootstrap)
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace YourProjectNamespace.Extensions
{
public static class HtmlExtensions
{
public static string BuildBreadcrumbNavigation(this HtmlHelper helper)
{
// optional condition: I didn't wanted it to show on home and account controller
if (helper.ViewContext.RouteData.Values["controller"].ToString() == "Home" ||
helper.ViewContext.RouteData.Values["controller"].ToString() == "Account")
{
return string.Empty;
}
StringBuilder breadcrumb = new StringBuilder("<ol class='breadcrumb'><li>").Append(helper.ActionLink("Home", "Index", "Home").ToHtmlString()).Append("</li>");
breadcrumb.Append("<li>");
breadcrumb.Append(helper.ActionLink(helper.ViewContext.RouteData.Values["controller"].ToString().Titleize(),
"Index",
helper.ViewContext.RouteData.Values["controller"].ToString()));
breadcrumb.Append("</li>");
if (helper.ViewContext.RouteData.Values["action"].ToString() != "Index")
{
breadcrumb.Append("<li>");
breadcrumb.Append(helper.ActionLink(helper.ViewContext.RouteData.Values["action"].ToString().Titleize(),
helper.ViewContext.RouteData.Values["action"].ToString(),
helper.ViewContext.RouteData.Values["controller"].ToString()));
breadcrumb.Append("</li>");
}
return breadcrumb.Append("</ol>").ToString();
}
}
}
In ~/Extensions/StringExtensions.cs:
using System.Globalization;
using System.Text.RegularExpressions;
namespace YourProjectNamespace.Extensions
{
public static class StringExtensions
{
public static string Titleize(this string text)
{
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(text).ToSentenceCase();
}
public static string ToSentenceCase(this string str)
{
return Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1]));
}
}
}
Then use it like (in _Layout.cshtml for example):
....
....
<div class="container body-content">
<!-- #region Breadcrumb -->
#Html.Raw(Html.BuildBreadcrumbNavigation())
<!-- #endregion -->
#RenderBody()
<hr />
...
...
There is a tool to do this on codeplex: http://mvcsitemap.codeplex.com/ [project moved to github]
Edit:
There is a way to derive a SiteMapProvider from a database: http://www.asp.net/Learn/data-access/tutorial-62-cs.aspx
You might be able to modify the mvcsitemap tool to use that to get what you want.
I built this nuget package to solve this problem for myself:
https://www.nuget.org/packages/MvcBreadCrumbs/
You can contribute here if you have ideas for it:
https://github.com/thelarz/MvcBreadCrumbs
For those using ASP.NET Core 2.0 and looking for a more decoupled approach than vulcan's HtmlHelper, I recommend having a look at using a partial view with dependency injection.
Below is a simple implementation which can easily be molded to suit your needs.
The breadcrumb service (./Services/BreadcrumbService.cs):
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.Collections.Generic;
namespace YourNamespace.YourProject
{
public class BreadcrumbService : IViewContextAware
{
IList<Breadcrumb> breadcrumbs;
public void Contextualize(ViewContext viewContext)
{
breadcrumbs = new List<Breadcrumb>();
string area = $"{viewContext.RouteData.Values["area"]}";
string controller = $"{viewContext.RouteData.Values["controller"]}";
string action = $"{viewContext.RouteData.Values["action"]}";
object id = viewContext.RouteData.Values["id"];
string title = $"{viewContext.ViewData["Title"]}";
breadcrumbs.Add(new Breadcrumb(area, controller, action, title, id));
if(!string.Equals(action, "index", StringComparison.OrdinalIgnoreCase))
{
breadcrumbs.Insert(0, new Breadcrumb(area, controller, "index", title));
}
}
public IList<Breadcrumb> GetBreadcrumbs()
{
return breadcrumbs;
}
}
public class Breadcrumb
{
public Breadcrumb(string area, string controller, string action, string title, object id) : this(area, controller, action, title)
{
Id = id;
}
public Breadcrumb(string area, string controller, string action, string title)
{
Area = area;
Controller = controller;
Action = action;
if (string.IsNullOrWhiteSpace(title))
{
Title = Regex.Replace(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(string.Equals(action, "Index", StringComparison.OrdinalIgnoreCase) ? controller : action), "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1]));
}
else
{
Title = title;
}
}
public string Area { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public object Id { get; set; }
public string Title { get; set; }
}
}
Register the service in startup.cs after AddMvc():
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddTransient<BreadcrumbService>();
Create a partial to render the breadcrumbs (~/Views/Shared/Breadcrumbs.cshtml):
#using YourNamespace.YourProject.Services
#inject BreadcrumbService BreadcrumbService
#foreach(var breadcrumb in BreadcrumbService.GetBreadcrumbs())
{
<a asp-area="#breadcrumb.Area" asp-controller="#breadcrumb.Controller" asp-action="#breadcrumb.Action" asp-route-id="#breadcrumb.Id">#breadcrumb.Title</a>
}
At this point, to render the breadcrumbs simply call Html.Partial("Breadcrumbs") or Html.PartialAsync("Breadcrumbs").
Maarten Balliauw's MvcSiteMapProvider worked pretty well for me.
I created a small mvc app to test his provider: MvcSiteMapProvider Test (404)
For whoever is interested, I did an improved version of a HtmlExtension that is also considering Areas and in addition uses Reflection to check if there is a Default controller inside an Area or a Index action inside a Controller:
public static class HtmlExtensions
{
public static MvcHtmlString BuildBreadcrumbNavigation(this HtmlHelper helper)
{
string area = (helper.ViewContext.RouteData.DataTokens["area"] ?? "").ToString();
string controller = helper.ViewContext.RouteData.Values["controller"].ToString();
string action = helper.ViewContext.RouteData.Values["action"].ToString();
// add link to homepage by default
StringBuilder breadcrumb = new StringBuilder(#"
<ol class='breadcrumb'>
<li>" + helper.ActionLink("Homepage", "Index", "Home", new { Area = "" }, new { #class="first" }) + #"</li>");
// add link to area if existing
if (area != "")
{
breadcrumb.Append("<li>");
if (ControllerExistsInArea("Default", area)) // by convention, default Area controller should be named Default
{
breadcrumb.Append(helper.ActionLink(area.AddSpaceOnCaseChange(), "Index", "Default", new { Area = area }, new { #class = "" }));
}
else
{
breadcrumb.Append(area.AddSpaceOnCaseChange());
}
breadcrumb.Append("</li>");
}
// add link to controller Index if different action
if ((controller != "Home" && controller != "Default") && action != "Index")
{
if (ActionExistsInController("Index", controller, area))
{
breadcrumb.Append("<li>");
breadcrumb.Append(helper.ActionLink(controller.AddSpaceOnCaseChange(), "Index", controller, new { Area = area }, new { #class = "" }));
breadcrumb.Append("</li>");
}
}
// add link to action
if ((controller != "Home" && controller != "Default") || action != "Index")
{
breadcrumb.Append("<li>");
//breadcrumb.Append(helper.ActionLink((action.ToLower() == "index") ? controller.AddSpaceOnCaseChange() : action.AddSpaceOnCaseChange(), action, controller, new { Area = area }, new { #class = "" }));
breadcrumb.Append((action.ToLower() == "index") ? controller.AddSpaceOnCaseChange() : action.AddSpaceOnCaseChange());
breadcrumb.Append("</li>");
}
return MvcHtmlString.Create(breadcrumb.Append("</ol>").ToString());
}
public static Type GetControllerType(string controller, string area)
{
string currentAssembly = Assembly.GetExecutingAssembly().GetName().Name;
IEnumerable<Type> controllerTypes = Assembly.GetExecutingAssembly().GetTypes().Where(o => typeof(IController).IsAssignableFrom(o));
string typeFullName = String.Format("{0}.Controllers.{1}Controller", currentAssembly, controller);
if (area != "")
{
typeFullName = String.Format("{0}.Areas.{1}.Controllers.{2}Controller", currentAssembly, area, controller);
}
return controllerTypes.Where(o => o.FullName == typeFullName).FirstOrDefault();
}
public static bool ActionExistsInController(string action, string controller, string area)
{
Type controllerType = GetControllerType(controller, area);
return (controllerType != null && new ReflectedControllerDescriptor(controllerType).GetCanonicalActions().Any(x => x.ActionName == action));
}
public static bool ControllerExistsInArea(string controller, string area)
{
Type controllerType = GetControllerType(controller, area);
return (controllerType != null);
}
public static string AddSpaceOnCaseChange(this string text)
{
if (string.IsNullOrWhiteSpace(text))
return "";
StringBuilder newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]) && text[i - 1] != ' ')
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
}
If can definitely can be improved (probably does not cover all the possible cases), but it did not failed me until now.