I am new to programming with C# and MVC4 the error happens at
#{
Html.RenderAction("Menu", "Nav");
}
It is coming back with an error stating that there is no route in the route table matches the supplied values. I have tried searching the internet to no avail, any help would be greatly appreciated. Thanks
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>#ViewBag.Title</title>
<link href="~/Content/Site.css" type="text/css" rel="stylesheet" />
</head>
<body>
<div id="header">
<div class="title">Bag Labels</div>
</div>
<div id="categories">
#{ Html.RenderAction("Menu", "Nav"); }
</div>
<div id="content">
#RenderBody()
</div>
</body>
</html>
This is the _Layout.cshtml
Here is my NavController.cs
using NavisionStore.Domain.Abstract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace NavisionStore.WebUI.Controllers
{
public class NavController : Controller
{
private IProductRepository repository;
public NavController(IProductRepository repo)
{
repository = repo;
}
public PartialViewResult Menu(string category = null)
{
ViewBag.SelectedCategory = category;
IEnumerable<string> categories = repository.Products
.Select(x => x.Category)
.Distinct()
.OrderBy(x => x);
return PartialView(categories);
}
}
}
Here is my RouteConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace NavisionStore.WebUI
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(null,
"",
new {
controller = "Product", action = "List",
category = (string)null, page = 1
}
);
routes.MapRoute(null,
"Page{page}",
new { controller = "Product", action = "List", category = (string)null },
new { page = #"\d+" }
);
routes.MapRoute(null,
"{category}",
new { controller = "Product", action = "List", page = 1 }
);
routes.MapRoute(null,
"{category}/Page{page}",
new { controller = "Product", action = "List" },
new { page = #"\d+" }
);
routes.MapRoute(null, "{contoller}/{action}");
}
}
}
Here is my ProductController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using NavisionStore.Domain.Abstract;
using NavisionStore.Domain.Entities;
using NavisionStore.WebUI.Models;
namespace NavisionStore.WebUI.Controllers
{
public class ProductController : Controller
{
private IProductRepository repository;
public int PageSize = 1;
public ProductController(IProductRepository productRepository)
{
this.repository = productRepository;
}
public ViewResult List(string category, int page = 1)
{
ProductsListViewModel ViewModel = new ProductsListViewModel
{
Products = repository.Products
.Where(p => category == null || p.Category == category)
.OrderBy(p => p.Id)
.Skip((page - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = repository.Products.Count()
},
CurrentCategory = category
};
return View(ViewModel);
}
}
}
Remove the following routes from your routing config:
routes.MapRoute(null,
"",
new {
controller = "Product", action = "List",
category = (string)null, page = 1
}
);
routes.MapRoute(null, "{contoller}/{action}");
Also, the following routes are likely not doing what you are expecting, as each one is actually returning the same controller/action for each matching url. I recommend removing them.
routes.MapRoute(null,
"Page{page}",
new { controller = "Product", action = "List", category = (string)null },
new { page = #"\d+" }
);
routes.MapRoute(null,
"{category}",
new { controller = "Product", action = "List", page = 1 }
);
routes.MapRoute(null,
"{category}/Page{page}",
new { controller = "Product", action = "List" },
new { page = #"\d+" }
);
They could be replaced with the following:
routes.MapRoute(
"Page",
"Product/List/{category}/{page}",
new { controller = "Product", action = "List", page = UrlParameter.Optional },
new { page = #"\d*" }
);
Finally, add names to all of your routes, not null. Currently, the only one you have named is "default".
In the end, you should be at a much simpler routing configuration for what you are trying to do:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// matches:
// product/list/unicorns
// product/list/carrots/15
routes.MapRoute(
"Page",
"Product/List/{category}/{page}",
new { controller = "Product", action = "List", page = UrlParameter.Optional },
new { page = #"\d*" } //<-- note the asterisk here
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new {controller = "Product", action = "List",
id = UrlParameter.Optional
}
You are using
Html.RenderAction
so rather try
public ActionResult Menu(string category = null)
{
// Your controller code.
return View(categories);
}
instead of
public PartialViewResult Menu(string category = null)
{
// Your controller code.
return PartialView(categories);
}
it also looks like you are missing your default route mapping
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "[Your default controller name goes here]",
action = "[Your default action name goes here]", id = UrlParameter.Optional}
);
Update
I don't know if all those routing rules that is specified in your RouteConfig.cs file is needed. Keep it as simple as possible, for example
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Login",
action = "Index", id = UrlParameter.Optional }
);
}
Update
Your Menu action is expecting a category parameter, so you will have to pass it one, or setup a proper route mapping. So change
#{
Html.RenderAction("Menu", "Nav");
}
to
#{
Html.RenderAction("Menu", "Nav", new {category = null});
}
Charlie Brown has resolved a stupid error on my part. I misspelled "controller" on the last route as "controller". I feel like and idiot, I have missed this spelling error for a month now. Thanks for your help I appreciate it.
If you just need a piece of HTML on every page you can use partial view like this:
#Html.Patial("_Menu")
instead of
#{ Html.RenderAction("Menu", "Nav"); }
Here _Menu is name of partial view _Menu.cshtml (should be placed in Views/Shared)
UPDATE 1
If you need pass some object (lets say collection) to the partial view just specify it as second parameter:
#Html.Patial("_Menu", collection)
collection object will be your model in _Menu.cshtml view, so you can use it as usual:
#model System.Collections.Generic.IEnumerable<string>
<select>
#foreach(var item in Model)
{
<option>#item</option>
}
</select>
UPDATE 2
Also your routes rules are too weird. Why don't you use default routing only:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
);
This will allow you to call your action like this:
.../nav/menu
.../product/list
.../ which will equals to .../product/list since default controller was set to ProductController and default action was set to List method.
Any way in route rule you always have to specify unique name and template. But you are passing empty string as rule name - it's wrong. You should replace whole your RouteConfig.cs as #user65439 mentioned in his answer.
Related
As the title says all about what I want but to be kind of specific I would like to have a URL pattern like localhost/Product/List/Category/Page but I couldn't succeed finding a solution for it. I am sure it's belong to routing and as it is difficult topic in MVC I would need your help to find a solution for it.
My route config is:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(null, "",
new
{
controller = "Home",
action = "Shop",
});
routes.MapRoute(null, "",
new
{
controller = "Product",
action = "list",
category = (string)null,
page = 1
}
);
routes.MapRoute(null, "Page{page}",
new
{
controller = "Product",
action = "List",
category = (string)null,
subcategory = (string)null
},
new { page = #"\d+" }
);
routes.MapRoute(null,
"{category}",
new { controller = "Product", action = "List", page = 1 }
);
routes.MapRoute(null,
"{category}/Page{page}",
new { controller = "Product", action = "List" },
new { page = #"\d+" }
);
routes.MapRoute(null, "{controller}/{action}");
}
}
My Controller product is:
public class ProductController : Controller
{
EFDbContext db = new EFDbContext();
private IProductsRepository repository;
public int PageSize = 4;
public ProductController (IProductsRepository productrepository)
{
this.repository = productrepository;
}
public ViewResult List(string category, int page = 1)
{
ProductsListViewModel model = new ProductsListViewModel()
{
Products = repository.Products
.Where(p => category == null || p.ProductCategory == category || p.MenSubCategory == category)
.OrderBy(p => p.ProductID)
.Skip((page - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = category == null ? repository.Products.Count():repository.Products.Where(e => e.ProductCategory == category).Count()
},
CurrentCategory = category
};
return View(model);
}
public PartialViewResult Menu(string subcategory = null )
{
ViewBag.SelectedCategory = subcategory;
IEnumerable<string> categories = repository.MenSubCategories
.Select(x => x.MenSubCategory)
.Distinct()
.OrderBy(x => x);
return PartialView(categories);
}
}
I hope I get answer for this as far as I really tried but couldn't find how to do it.
To generate an URL like you want: localhost/Product/List/Cars you can create a custom route like this:
routes.MapRoute(
name: "ProductList",
url: "Product/List/{category}",
defaults: new { controller = "Product", action = "List" }
);
Remember that custom routes have to come before the most general route (the one that comes with the default template).
Regarding your page parameter, if you are comfortable with this URL: localhost:3288/Product/List/teste?page=10 the above already work. But if you want this: localhost:3288/Product/List/teste/10 10 meaning the page number, then the simplest solution would be create two different routes:
routes.MapRoute(
name: "ProductList",
url: "Product/List/{category}",
defaults: new { controller = "Product", action = "List" }
);
routes.MapRoute(
name: "ProductListPage",
url: "Product/List/{category}/{page}",
defaults: new { controller = "Product", action = "List" , page = UrlParameter.Optional}
);
Another cleaner way, is to create a custom route constraint for your optional parameter. This question has a lot of answers to that:
ASP.NET MVC: Route with optional parameter, but if supplied, must match \d+
With attribute routing, you just need to decorate your action method with your specific route pattern.
public class ProductController : Controller
{
EFDbContext db = new EFDbContext();
private IProductsRepository repository;
public int PageSize = 4;
public ProductController (IProductsRepository productrepository)
{
this.repository = productrepository;
}
[Route("Product/list/{category}/{page}")]
public ViewResult List(string category, int page = 1)
{
// to do : Return something
}
}
The above route definition will send the request like yourSite/Product/list/phones and yourSite/Product/list/phones/1 to the List action method and the url segments for category and page will be mapped to the method parameters.
To enable attribute routing, you need to call the method MapMvcAttributeRoutes
method inside the RegisterRoutes method.
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
So, basically the web site shows the user a drop down list to choose a category. The following action is used to let the user select a category and show the items:
public ActionResult SelectResourceCategory()
{
return View(new ResourceModel());
}
[HttpPost]
public ActionResult SelectResourceCategory(ResourceModel rm)
{
Resource r = new Resource();
r.CategoryID = rm.Categories.Id;
List<Resource> GetCourses = new UserBL().GetResourcesByCategoryID(r.CategoryID);
var vRM = new List<ResourceModel>();
foreach (Resource rs in GetCourses)
{
ResourceModel frm = new ResourceModel();
frm.ResourceID = rs.Id;
frm.Title = rs.Title;
frm.Description = rs.Description;
frm.Price = (float)rs.Price;
vRM.Add(frm);
}
return View("ShowResources", vRM);
}
public ActionResult ShowResources()
{
return View();
}
After the user chooses, the webpage then shows the user a list of available resources which the user can buy. Using the link below, I pass the resourceID of the chosen item as parameter:
#Html.ActionLink("Buy", "BuyResource", new RouteValueDictionary(new { id = item.ResourceID }))
and the Action "BuyResource" does the following:
public ActionResult BuyResource(int id)
{
Session["Rid"] = id; //GOTO: RedirectFromPayPal
Resource r = new UserBL().GetResourceByID(id);
return View(r);
}
THE PROBLEM: When the user clicks "Buy", for some reason, the page redirects the user back to the page where he needs to select a category, specifically "SelectResourceCategory" action. It should be redirecting to the "BuyResource" Action and I do not understand why it's not doing so.
EDIT:
The SelectResourceCategory VIEW:
#model SSD.Models.ResourceModel
#{
ViewBag.Title = "SelectResourceCategory";
Layout = "~/Views/Shared/UserPage.cshtml";
}
<h2>SelectResourceCategory</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>ResourceModel</legend>
<div class="editor-field">
#Html.DropDownListFor(model => model.Categories.Id, Model.CategoryList)
#Html.ValidationMessageFor(model => model.Categories.Id)
</div>
<p>
<input type="submit" value="Show available resources" />
</p>
</fieldset>
}
Routing settings:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
Try to include the target controller's name:
#Html.ActionLink("Buy", "BuyResource", "YOUR CONTROLLER NAME",
new { id = item.ResourceID })
Also make sure item.ResourceID is convertable to a int.
UPDATE:
in your:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
The id="" is unnecessary, it might disturb your routing. You might want to consider to use the default implementation:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
As i wrote in my comment under your question.
You can try:
#Html.ActionLink("Buy", "BuyResource", new { id = item.ResourceID })
or
#Html.ActionLink("Buy", "BuyResource", new RouteValueDictionary{ { id = item.ResourceID } })
Note:
The new RouteValueDictionary(new { id = item.ResourceID }) syntax could be wrong, I can't verify if that's the root of your problem but it's always better to use annonymous class when passing action arguments.
Edit:
Some other more complex example if you wish:
#{
var routeValues = new RouteValueDictionary();
routeValues.Add("id", item.ResourceID);
}
#Html.ActionLink("Buy", "BuyResource", routeValues)
Be sure that item.ResourceID is of type int and that it's not null. If it meets those requirements it should be working for 100%.
I have problem with routing. I have many pages on my site generated dynamically from database.
First thing which I want to accomplish is to route to these pages like that:
"How to repair a car"
www.EXAMPLE.com/How-to-repair-a-car
For now it works like that: www.EXAMPLE.com/Home/Index/How-to-repair-a-car
Secondly my default page have to be like that: www.EXAMPLE.com
On the Start Page will be news with pagging, so if someone click for instance in the "page 2" button, the address should looks: www.EXAMPLE.com/page =2
CONCLUSION:
default page -> www.EXAMPLE.com (with page = 0)
default page with specific page of news -> www.EXAMPLE.com/page=12
article page -> www.EXAMPLE.com/How-to-repair-car (without parameter 'page') routing sholud point to article or error404
PS: sorry for my english
Try to create route for articles in routing config, like this:
Routing config:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(null, "{article}",
new {controller = "Home", action = "Article" });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
HomeController:
public class HomeController : Controller
{
public ActionResult Index(int? page)
{
var definedPage = page ?? 0;
ViewBag.page = "your page is " + definedPage;
return View();
}
public ActionResult Article(string article)
{
ViewBag.article = article;
return View();
}
}
/?page=10 - works
/How-to-repair-car - works
That approach excellent works.
Here is a basic routing example for www.example.com/How-to-repair-car
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace Tipser.Web
{
public class MyMvcApplication : HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"ArticleRoute",
"{articleName}",
new { Controller = "Home", Action = "Index", articleName = UrlParameter.Optional },
new { userFriendlyURL = new ArticleConstraint() }
);
}
public class ArticleConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
var articleName = values["articleName"] as string;
//determine if there is a valid article
if (there_is_there_any_article_matching(articleName))
return true;
return false;
}
}
}
}
I have functionality on my site to create/edit/delete pages for the front end. Here's my controller:
namespace MySite.Controllers
{
public class ContentPagesController : Controller
{
readonly IContentPagesRepository _contentPagesRepository;
public ContentPagesController()
{
MyDBEntities entities = new MyDBEntities();
_contentPagesRepository = new SqlContentPagesRepository(entities);
}
public ActionResult Index(string name)
{
var contentPage = _contentPagesRepository.GetContentPage(name);
if (contentPage != null)
{
return View(new ContentPageViewModel
{
ContentPageId = contentPage.ContentPageID,
Name = contentPage.Name,
Title = contentPage.Title,
Content = contentPage.Content
});
}
throw new HttpException(404, "");
}
}
}
And in my global.asax:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Page", // Route name
"Page/{name}", // URL with parameters
new { controller = "ContentPages", action = "Index" }, // Parameter defaults
new[] { "MySite.Controllers" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new[] { "MySite.Controllers" }
);
}
So I have a dynamic page in my database, named About. If I go to mysite.com/Page/About, I can view the dynamic content.
I want to create an ActionLink to this page. I've tried it like this:
#Html.ActionLink("About Us", "Index", "ContentPages", new { name = "About" })
But when I look at the link on the page, the url just goes to the current page with Length=12 in the query string. For instance, if I'm on the homepage, the link goes to mysite.com/Home?Length=12
What am I doing wrong here?
You are not using the correct ActionLink overload. Try like this:
#Html.ActionLink(
"About Us", // linkText
"Index", // action
"ContentPages", // controller
new { name = "About" }, // routeValues
null // htmlAttributes
)
whereas in your example:
#Html.ActionLink(
"About Us", // linkText
"Index", // action
"ContentPages", // routeValues
new { name = "About" } // htmlAttributes
)
which pretty obviously explains why your doesn't generate the expected link.
I've tried converting an MVC 2 tutorial webshop to MVC 3 with Razor Syntax, but I don't understand the following problem...
_Layout.cshtml
<div id="header">
<div class="title">SPORTS STORE</div>
</div>
<div id ="categories">
#{Html.RenderAction("Menu", "Nav");}
</div>
"Menu" is an action for a partial View on the "Nav" Controller.
Menu.cshtml
#model IEnumerable<WebShop_1_0.ViewModels.NavLink>
#{foreach(var link in Model)
{
Html.RouteLink(link.Text, link.RouteValues, new Dictionary<string, object>
{
{ "class", link.IsSelected ? "selected" : null }
});
}}
This is the Nav controller
public class NavController : Controller
{
private IProductsRepository productsRepository;
public NavController(IProductsRepository productsRepository)
{
this.productsRepository = productsRepository;
}
public ViewResult Menu(string category)
{
// Just so we don't have to write this code twice
Func<string, NavLink> makeLink = categoryName => new NavLink
{
Text = categoryName ?? "Home",
RouteValues = new RouteValueDictionary(new
{
controller = "Products",
action = "List",
category = categoryName,
page = 1
}),
IsSelected = (categoryName == category)
};
// Put a Home link at the top
List<NavLink> navLinks = new List<NavLink>();
navLinks.Add(makeLink(null));
// Add a link for each distinct category
//navLinks.AddRange(productsRepository.Products.Select(x => x.Category.Trim()).Distinct().OrderBy(x => x));
var categories = productsRepository.Products.Select(x => x.Category.Trim());
foreach (string categoryName in categories.Distinct().OrderBy(x => x))
navLinks.Add(makeLink(categoryName));
return View(navLinks);
}
}
I don't know where the mistake is.
If I use Html.PartialView instead of Html.RenderAction, I get another error message, that VS can't find the PartialView. Most of this is code that I have just copied, just the Views rewritten to MVC 3.
Before this StackOverFlowException problem, the browser would load the webpage for a long time.
This is the routing:
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
}
public static void RegisterRoutes(RouteCollection routes)
{
/*Sorrend geccire számít*/
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(null, "", // Only matches the empty URL (i.e. ~/)
new
{
controller = "Products",
action = "List",
category = (string)null,
page = 1
}
);
routes.MapRoute(null, "Page{page}", // Matches ~/Page2, ~/Page123, but not ~/PageXYZ
new { controller = "Products", action = "List", category = (string)null },
new { page = #"\d+" } // Constraints: page must be numerical
);
routes.MapRoute(null, "{category}", // Matches ~/Football or ~/AnythingWithNoSlash
new { controller = "Products", action = "List", page = 1 }
);
routes.MapRoute(null, "{category}/Page{page}", // Matches ~/Football/Page567
new { controller = "Products", action = "List" }, // Defaults
new { page = #"\d+" } // Constraints: page must be numerical
);
routes.MapRoute(null, "{controller}/{action}");
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactroy());
ModelBinders.Binders.Add(typeof(Cart), new CartModelBlinder());
}
}
Your Menu action needs to return a PartialView(navLinks) instead of View(navLinks), otherwise your layout will be drawn with the menu, which causes recursion. Oh oh! This causes the stack overflow :)