ASPxGridView not works in my ASP.NET MVC solution - asp.net-mvc

I'm using ASPxGridView and updating with data from DataTable which, works fine.
But when I'm trying to use edit, update, delete, paging, sorting functions I get in trouble.
The buttons are displayed but the function doesn't work when I click them.
Here is my Sample.aspx file:
[ASPx]
<dx:aspxgridview ID="grid" ClientInstanceName="grid" runat="server"
OnDataBinding="grid_DataBinding" Width="100%" EnableCallBacks="false">
<Settings ShowTitlePanel="True" />
<SettingsText Title="Tabla" />
<SettingsPager PageSize ="10" ShowSeparators="true" PageSizeItemSettings-Items="20" >
<PageSizeItemSettings Visible="true" />
</SettingsPager>
</dx:aspxgridview>
The code behind Sample.aspx.cs :
[C#]
using System;
using System.Data;
using System.Web.Mvc;
using DataConnector;
using DevExpress.Web.ASPxGridView;
using DevExpress.Web.Data;
namespace Sample.Views.Actions
{
public partial class Sample: ViewPage
{
private DataTable dt;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
grid.DataBind();
grid.RowValidating += new ASPxDataValidationEventHandler(grid_RowValidating);
if (!this.IsPostBack)
{
GridViewCommandColumn c = new GridViewCommandColumn();
grid.Columns.Add(c);
c.Caption = "Operations";
c.EditButton.Visible = true;
c.UpdateButton.Visible = true;
c.NewButton.Visible = true;
c.DeleteButton.Visible = true;
c.CancelButton.Visible = true;
}
}
DataTable BindGrid()
{
DataConnection DM = new DataConnection();
if (DM.login("ADMIN", "PASS"))
dt = DM.getDataSet("SELECT * FROM Table_Name");
grid.DataSource = dt;
dt.PrimaryKey = new DataColumn[] { dt.Columns[0] };
return dt;
}
protected void grid_DataBinding(object sender, EventArgs e)
{
// Assign the data source in grid_DataBinding
BindGrid();
}
protected void grid_RowUpdating(object sender, ASPxDataUpdatingEventArgs e)
{
int id = (int)e.OldValues["id"];
DataRow dr = dt.Rows.Find(id);
dr[0] = e.NewValues[""];
dr[1] = e.NewValues[""];
dr[2] = e.NewValues[""];
dr[3] = e.NewValues[""];
dr[4] = e.NewValues[""];
ASPxGridView g = sender as ASPxGridView;
UpdateData(g);
g.CancelEdit();
e.Cancel = true;
}
void grid_RowValidating(object sender, ASPxDataValidationEventArgs e)
{
int id = (int)e.NewValues["id"];
if ((!e.OldValues.Contains("id") || ((int)e.OldValues["id"] != id))
&& (dt.Rows.Find(id) != null))
{
ASPxGridView grid = sender as ASPxGridView;
e.Errors[grid.Columns["id"]] = String.Format("Column 'Id' is constrained to be unique. Value '{0}' is already present.", id);
}
}
private void UpdateData(ASPxGridView g)
{
g.DataBind();
}
}}
The Sample is called on the controller page:
[C#]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Data;
using DataConnector;
namespace Sample.Controllers
{
public class ActionsController : Controller
{
public ActionResult Sample()
{
return View();
}
}
}
The data displayed on the Grid.
I created a new solution where is only the Sample.aspx and Sample.aspx.cs and I'm using without MVC and the functions are working. The difference is "public partial class Sample: System.Web.UI.Page ".
If I try without System.Web.UI.Page on my mentioned code it gives me the following error:
The view must derive from ViewPage, ViewPage<TModel>, ViewuserControl,or ViewuserControl<TModel>.
What am I doing wrong? Why does not the function of the ASPxGridView working?
I'm sorry in advance if it is poorly drafted but not my mother tongue is English, and I am a new programmer :)

The ASPxGridView is a WebForms control and cannot be used within ASP.NET MVC. Instead you should use the DevExpress MVC extensions. Specifically, the DevExpress MVC GridView extension
Watch my "Getting started with DevExpress MVC video" to learn more.

Related

InvalidOperationException: Sequence contains no elements

I have written a code in .net using mvc and entity framework:
#{
List<DAL.Project> oldProjectList = new BL.ProjectLogic().getProjects(userName).Where(s => s.Status == "Not Active").ToList();
}
#foreach (DAL.Project p in oldProjectList)
{
{some code}
}
The first line of code should return a list of "Not Active" projects, and it actually works.
but it only works on users that have "Non Active" projects, other users get an exception in the foreach line saying:
[InvalidOperationException: Sequence contains no elements]
How to fix it?
Thanks
Are you sure that the exception is being thrown in your foreach? In this small reproducible example, I can't get the same error while executing:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqPlayground
{
class Program
{
public class Project
{
public string Status { get; set; }
}
static void Main(string[] args)
{
var projects = GetProjects("test").Where(project => project.Status == "Not Active").ToList();
foreach (var project in projects)
{
Console.WriteLine(project.Status);
}
}
public static IEnumerable<Project> GetProjects(string userName)
{
return new List<Project>();
}
}
}
Use the first part of that line, but not the ToList().
Instead, save the result of the Where() in a var, and then check that VAR using. Any():
var temp = new BL.Proje s ctLogic().getProjects(userName)
.Where( => s.Status == "Not Active");
List<DAL.Project> oldProjectList =
temp.Any() ?
temp.ToList() :
new List<DAL.Project>() ;

MVC extending controller to have ajax-aware Redirect functionality

Hi I am learning Ajax + MVC. I figured it would be nice for the Controller to automatically handle ajax-aware Redirect(). After some digging, I found the code from this link. The code below is totally transparent to user, a user can just call Redirect(someUrlString) without needing to worry about difference between normal/ajax calls. Makes it very neat and cool.
public abstract class BaseController : System.Web.Mvc.Controller {
//turn into ajax aware redirect
protected override RedirectResult Redirect(string url) {
return new AjaxAwareRedirectResult(url);
}
}
and ...
public class AjaxAwareRedirectResult : RedirectResult {
public AjaxAwareRedirectResult(string url) : base(url) { }
public override void ExecuteResult(ControllerContext context) {
if (context.RequestContext.HttpContext.Request.IsAjaxRequest()) {
string desturl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
JavaScriptResult result = new JavaScriptResult() {
Script = "window.location='" + desturl + "';" };
result.ExecuteResult(context);
}
else { base.ExecuteResult(context); }
}
}
However, it is not complete. Challenge is:
RedirectToRouteResult RedirectToAction(ActionResult result)
is not there yet (Very handy especially for T4MVC).
As I am still new to MVC, I tried, but I am not knowledgeable enough to sufficiently figure out how to write this myself. Could any of you experts please help me with this? so I can learn it from your code? Thank you very much.
Here is the quick simple solution I use for Ajax aware redirection in my project..
Create a class AjaxRedirectAttribute for action.
public class AjaxRedirectAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as RedirectResult;
if (result != null && filterContext.HttpContext.Request.IsAjaxRequest())
{
string destinationUrl = UrlHelper.GenerateContentUrl(result.Url, filterContext.HttpContext);
filterContext.Result = new JavaScriptResult()
{
Script = "window.location = '" + destinationUrl + "';"
};
}
}
}
User this attribute as below to either redirect to other page or to return some result from action.
[AjaxRedirect]
public ActionResult MyAction(FormCollection frmcol)
{
// some code here
if (UserId != 0)
{
return Redirect(this.Url.Action("Action", "Controller"));
}
else
{
return Content("Error message here.");
}
}
This is a good solution that work with MVC exception handling for Ajax (Ajax.BeginForm or jquery.ajax) call.
So, as you know there is an Application_Error in global.asax.cs file. Can you let me know how to handle ajax call here with for "JavaScriptResult" result.
I am using "RedirectToRoute" to handle it, but in some of code it is appending the html with existing header. My error controller has no layout (_Layout=null), but still it is showing header and footer.
I am just working to a fresh RedirectToRoute call.
Response.RedirectToRoute("Default", new {controller = "Error", action = "Error"});
protected void Application_Error(object sender, EventArgs e)
{
RouteValueDictionary routeDataCollection = HttpContext.Current.Request.RequestContext.RouteData.Values;
if (null != routeDataCollection && routeDataCollection.Count()>1)
{
Server.ClearError();
Response.RedirectToRoute("Default", new { controller = "Error", action = "Error"});
}
}
Thanks
Sagar

Read image from database and display in view

I'm trying to convert an older ASP.NET application to MVC (I am just learning MVC). and I have a need to display an image in a Gridview. The image itself is stored in a SQL Server table as datatype image. The code that was used previously is below. Can someone suggest an approach using MVC? I was thinking of creating a partial page that I could embed in a standard view, but not sure if that is the right design to implement.
Thanks is advance!
` string sqlText = "SELECT * FROM Images WHERE img_pk = " + id;
SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["LocalSqlServer"].ConnectionString);
SqlCommand command = new SqlCommand(sqlText, connection);
connection.Open();
SqlDataReader dr = command.ExecuteReader();
if (dr.Read())
{
//Response.Write("test");
Response.BinaryWrite((byte[])dr["img_data"]);
}
connection.Close();
}
Then it can be referenced using this image tag:
<asp:Image Height="73" Width="80" ID="Image1" ImageAlign="Middle" ImageUrl='<%#"viewimage.aspx?id=" + Eval("ImageId") %>' runat="server"/></a></td>
The first thing is to forget about GridView in an ASP.NET MVC application. Server side controls, postbacks, viewstate, events, ... all those are notions that no longer exists.
In ASP.NET MVC you work with Models, Controllers and Views.
So you could write a controller action which will fetch the image from the database and serve it:
public class ImagesController: Controller
{
public ActionResult Index(int id)
{
string sqlText = "SELECT img_data FROM Images WHERE img_pk = #id";
using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["LocalSqlServer"].ConnectionString))
using (var command = conn.CreateCommand())
{
conn.Open();
command.CommandText = sqlText;
command.Parameters.AddWithValue("#id", id);
using (var reader = command.ExecuteReader())
{
if (!reader.Read())
{
return HttpNotFound();
}
var data = GetBytes(reader, reader.GetOrdinal("img_data"));
return File(data, "image/jpg");
}
}
}
private byte[] GetBytes(IDataReader reader, int columnIndex)
{
const int CHUNK_SIZE = 2 * 1024;
byte[] buffer = new byte[CHUNK_SIZE];
long bytesRead;
long fieldOffset = 0;
using (var stream = new MemoryStream())
{
while ((bytesRead = reader.GetBytes(columnIndex, fieldOffset, buffer, 0, buffer.Length)) > 0)
{
byte[] actualRead = new byte[bytesRead];
Buffer.BlockCopy(buffer, 0, actualRead, 0, (int)bytesRead);
stream.Write(actualRead, 0, actualRead.Length);
fieldOffset += bytesRead;
}
return stream.ToArray();
}
}
}
and then in your view simply:
<img src="#Url.Action("Index", "Images", new { id = "123" })" alt="" />
Now of course all this controller action is nice and dandy, but you should really abstract all data access into a repository:
public interface IImagesRepository
{
byte[] GetImageData(int id);
}
then implement this method for the data provider you are using:
public class ImagesRepositorySql: IImagesRepository
{
public byte[] GetImageData(int id)
{
// you already know what to do here.
throw new NotImplementedException();
}
}
Finally you will have your controller become database agnostic. Layers in your application are now weakly coupled between them which would allow you to reuse and unit test them in isolation:
public class ImagesController: Controller
{
private readonly IImagesRepository _repository;
public ImagesController(IImagesRepository repository)
{
_repository = repository;
}
public ActionResult Index(int id)
{
var data = _repository.GetImageData(id);
return File(data, "image/jpg");
}
}
and the last part would be to configure your favorite DI framework to inject the proper implementation of the repository into the controller.

How to create dynamic view page in mvc2.0?

I am trying to create view page at run time means, when the user type some text in textbox at run time then the view page with that name should get created.
May be my code can help you with this
Controller and descendant of ViewPage:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.UI;
using System.Web.UI.WebControls;
using Site.Models;
using System.Text.RegularExpressions;
using System.Web.Mvc.Html;
namespace Site.Controllers
{
public class MPSViewPage : ViewPage
{
// Here master page is being seted
protected override void OnPreInit(EventArgs e)
{
Random rnd = new Random();
int i = rnd.Next(0, 20);
string masterPageName = (i % 2) == 0 ? "Admin.Master" : "Main.Master";
string pathMasterPageFile = "~/Views/Shared/" + masterPageName;
MasterPageFile = pathMasterPageFile;
base.OnPreInit(e);
}
protected override void OnInitComplete(EventArgs e)
{
//List of ContentPlaceHolder's id is being got. See later
MasterPageAnalizer analizer = new MasterPageAnalizer();
IList<string> contentHolders = analizer.GetBodyPlaceholders(Regex.Match(MasterPageFile, "[^/]*$").ToString());
//Add content to ContentPlaceHolder
foreach (string holder in contentHolders)
{
ContentPlaceHolder placeHolder = (ContentPlaceHolder)Master.FindControl(holder);
if (placeHolder != null)
{
Content content = new Content();
placeHolder.Controls.Add(content);
//Set function for render each content
content.SetRenderMethodDelegate(RenderIndexDeletegate);
}
}
base.OnInitComplete(e);
}
protected void RenderIndexDeletegate(HtmlTextWriter w, Control c)
{
//You can use any html helpers for rendering content
w.Write("Render to <b>" + ((Content)c).Parent.ID +
"</b> url: " + Request.Params["URL"] +
" with query parameter " + ViewData["parameters"] + " <br />" +
Html.Action("GetHtmlStatic", "HtmlStatic", new{area = "HtmlStatic"}));
}
}
public class IndexController : Controller
{
public ActionResult Index(string parameters)
{
ViewData["parameters"] = parameters;
return View();
}
}
}
Master page analizer:
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text.RegularExpressions;
namespace Site.Models
{
public class MasterPageAnalizer
{
private DirectoryInfo dirInfo = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory + "Views\\Shared\\");
public IList<string> MasterPages{
get
{
IList<String> masterPageNames = new List<string>();
FileInfo[] files = dirInfo.GetFiles("*.Master");
foreach (FileInfo file in files)
{
masterPageNames.Add(file.Name);
}
return masterPageNames;
}
}
public IList<string> GetBodyPlaceholders(string masterPageName)
{
IList<string> placeholders = new List<string>();
string masterPagePath = dirInfo + masterPageName;
if (File.Exists(masterPagePath))
{
string masterPageContent = File.ReadAllText(masterPagePath);
Regex expression = new Regex(#"<asp:ContentPlaceHolder.*?ID=""(?<placeHolderId>\w+)"".*?>");
masterPageContent = Regex.Match(masterPageContent, "<body>(.|\n)*</body>",RegexOptions.Multiline).ToString();
MatchCollection matches = expression.Matches(masterPageContent);
foreach (Match match in matches)
{
placeholders.Add(match.Groups["placeHolderId"].Value);
}
}
return placeholders;
}
}
}
Simple view:
<%# Page Title="" Language="C#" Inherits="Site.Controllers.MPSViewPage" %>
Good Luck.
Everything is possible. But if you are after to create a project like CMS, it's not right approach. You have to store the pages' information (such as title, description and etc.) in a data store. Thus, you have merely a single page.

How can dynamic breadcrumbs be achieved with ASP.net MVC?

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.

Resources