ActionLink generate empty href using MVC 5 with Umbraco 7 - asp.net-mvc

I am using MVC 5 with Umbraco 7. Trying to use ActionLink in my View but the markup it generates have empty href. any idea how to get it working?
Start Date
View:
#Html.ActionLink(
"Start Date",
"SearchV1",
"SearchV1",
new { sitetypeid = #Request.QueryString["sitetypeid"], leaNo = #Request.QueryString["leaNo"], orderBy = "VacStart" },
null)
Controller:
public class SearchV1Controller : RenderMvcController
{
public override ActionResult Index(RenderModel model)
{
return base.Index(model);
}
public ActionResult SearchV1(RenderModel model, int sitetypeId , int leaNo, string orderBy = "VacRelDate")
{
List<GetJobSearchResults_Result> searchResultsList = Workflow.Vacancy.GetJobSearchResults(sitetypeId , leaNo, "SCH", orderBy, "desc");
ViewBag.leaNo = leaNo;
return View(searchResultsList);
}
}

This cannot be possible in Umbraco unless you inherit your controller class from Surface controller.

Related

Dynamic menus from database in MVC

I have read some similar topics here and on the web, but I don't think I have seen one that would classify this as a duplicate, so I am going to go ahead and post it. I am currently loading my dynamic menus from the database like so:
public void LoadMenus()
{
var dbContext = new ContentClassesDataContext();
var menus = from m in dbContext.Menus
where m.MenuName != "Home" && m.MenuGroup == "RazorHome" && m.RoleID == "Facility"
orderby m.Sequence, m.MenuName
select m;
var html = "";
if (menus.Any())
{
html += "<span/>";
foreach (var menu in menus)
{
html = html + $"<a href='{menu.URL}'>{menu.MenuName}</a><br/>";
}
html += "<hr>";
}
Session["Menus"] = html;
}
LoadMenus() is in my controller class, so I am not able (to my knowledge) to use Razor syntax. I would prefer to load the menus from the view instead, so that I am able to use #Html.ActionLink(linkText, actionName, controllerName). Loading the HTML the way I am currently doing it will generate different link text depending on the current controller, so the links are not always correctly rendered. Is it possible to access the database from the view? Or perhaps to just pass in the content from the database from the controller to the view and then render the menu that way?
You should keep your html in the cshtml views.
You should pass the data through the viewmodel and not through the session.
1)
In the controller, get the menu data (in this example we fetch some fake data).
Create a viewmodel that can hold the menu data and pass it to the view, as shown below:
public class HomeController : Controller
{
public ActionResult Index()
{
var menu = GetMenu();
var vm = new ViewModel() {Menu = menu};
return View(vm);
}
private Menu GetMenu()
{
var menu = new Menu();
var menuItems = new List<MenuItem>();
menuItems.Add(new MenuItem() { LinkText = "Home" , ActionName = "Index", ControllerName = "Home"});
menuItems.Add(new MenuItem() { LinkText = "About", ActionName = "About", ControllerName = "Home" });
menuItems.Add(new MenuItem() { LinkText = "Help", ActionName = "Help", ControllerName = "Home" });
menu.Items = menuItems;
return menu;
}
}
2)
This is the viewmodel
public class ViewModel
{
public Menu Menu { get; set; }
}
This view is an example of how you could render the menu data as a html menu
#model WebApplication1.Models.ViewModel
<ul id="menu">
#foreach (var item in #Model.Menu.Items)
{
<li>#Html.ActionLink(#item.LinkText, #item.ActionName,
#item.ControllerName)</li>
}
</ul>
3)
This is the example menu classes used (representing your entities from the dbcontext)
public class Menu
{
public List<MenuItem> Items { get; set; }
}
public class MenuItem
{
public string LinkText { get; set; }
public string ActionName { get; set; }
public string ControllerName { get; set; }
}
Here are some links to get you started:
http://www.codeproject.com/Articles/585873/Basic-Understanding-On-ASP-NET-MVC
http://www.asp.net/mvc/overview/getting-started/introduction/getting-started

Is there a way to have a RoutePrefix that starts with an optional parameter?

I want to reach the Bikes controller with these URL's:
/bikes // (default path for US)
/ca/bikes // (path for Canada)
One way of achieving that is using multiple Route Attributes per Action:
[Route("bikes")]
[Route("{country}/bikes")]
public ActionResult Index()
To keep it DRY I'd prefer to use a RoutePrefix, but multiple Route Prefixes are not allowed:
[RoutePrefix("bikes")]
[RoutePrefix("{country}/bikes")] // <-- Error: Duplicate 'RoutePrefix' attribute
public class BikesController : BaseController
[Route("")]
public ActionResult Index()
I've tried using just this Route Prefix:
[RoutePrefix("{country}/bikes")]
public class BikesController : BaseController
Result: /ca/bikes works, /bikes 404s.
I've tried making country optional:
[RoutePrefix("{country?}/bikes")]
public class BikesController : BaseController
Same result: /ca/bikes works, /bikes 404s.
I've tried giving country a default value:
[RoutePrefix("{country=us}/bikes")]
public class BikesController : BaseController
Same result: /ca/bikes works, /bikes 404s.
Is there another way to achieve my objective using Attribute Routing?
(And yes, I know I can do this stuff by registering routes in RouteConfig.cs, but that's what not I'm looking for here).
I'm using Microsoft.AspNet.Mvc 5.2.2.
FYI: these are simplified examples - the actual code has an IRouteConstraint for the {country} values, like:
[Route("{country:countrycode}/bikes")]
I am a bit late to the party, but i have a working solution for this problem. Please find my detailed blog post on this issue here
I am writing down summary below
You need to create 2 files as given below
_3bTechTalkMultiplePrefixDirectRouteProvider.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Web.Http.Controllers;
using System.Web.Http.Routing;
namespace _3bTechTalk.MultipleRoutePrefixAttributes {
public class _3bTechTalkMultiplePrefixDirectRouteProvider: DefaultDirectRouteProvider {
protected override IReadOnlyList GetActionDirectRoutes(HttpActionDescriptor actionDescriptor, IReadOnlyList factories, IInlineConstraintResolver constraintResolver) {
return CreateRouteEntries(GetRoutePrefixes(actionDescriptor.ControllerDescriptor), factories, new [] {
actionDescriptor
}, constraintResolver, true);
}
protected override IReadOnlyList GetControllerDirectRoutes(HttpControllerDescriptor controllerDescriptor, IReadOnlyList actionDescriptors, IReadOnlyList factories, IInlineConstraintResolver constraintResolver) {
return CreateRouteEntries(GetRoutePrefixes(controllerDescriptor), factories, actionDescriptors, constraintResolver, false);
}
private IEnumerable GetRoutePrefixes(HttpControllerDescriptor controllerDescriptor) {
Collection attributes = controllerDescriptor.GetCustomAttributes (false);
if (attributes == null)
return new string[] {
null
};
var prefixes = new List ();
foreach(var attribute in attributes) {
if (attribute == null)
continue;
string prefix = attribute.Prefix;
if (prefix == null)
throw new InvalidOperationException("Prefix can not be null. Controller: " + controllerDescriptor.ControllerType.FullName);
if (prefix.EndsWith("/", StringComparison.Ordinal))
throw new InvalidOperationException("Invalid prefix" + prefix + " in " + controllerDescriptor.ControllerName);
prefixes.Add(prefix);
}
if (prefixes.Count == 0)
prefixes.Add(null);
return prefixes;
}
private IReadOnlyList CreateRouteEntries(IEnumerable prefixes, IReadOnlyCollection factories, IReadOnlyCollection actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) {
var entries = new List ();
foreach(var prefix in prefixes) {
foreach(IDirectRouteFactory factory in factories) {
RouteEntry entry = CreateRouteEntry(prefix, factory, actions, constraintResolver, targetIsAction);
entries.Add(entry);
}
}
return entries;
}
private static RouteEntry CreateRouteEntry(string prefix, IDirectRouteFactory factory, IReadOnlyCollection actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) {
DirectRouteFactoryContext context = new DirectRouteFactoryContext(prefix, actions, constraintResolver, targetIsAction);
RouteEntry entry = factory.CreateRoute(context);
ValidateRouteEntry(entry);
return entry;
}
private static void ValidateRouteEntry(RouteEntry routeEntry) {
if (routeEntry == null)
throw new ArgumentNullException("routeEntry");
var route = routeEntry.Route;
if (route.Handler != null)
throw new InvalidOperationException("Direct route handler is not supported");
}
}
}
3bTechTalkRoutePrefix.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
namespace _3bTechTalk.MultipleRoutePrefixAttributes
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class _3bTechTalkRoutePrefix : RoutePrefixAttribute
{
public int Order { get; set; }
public _3bTechTalkRoutePrefix(string prefix) : this(prefix, 0) { }
public _3bTechTalkRoutePrefix(string prefix, int order) : base(prefix)
{
Order = order;
}
}
}
Once done, open WebApiConfig.cs and add this below given line
config.MapHttpAttributeRoutes(new _3bTechTalkMultiplePrefixDirectRouteProvider());
That's it, now you can add multiple route prefix in your controller. Example below
[_3bTechTalkRoutePrefix("api/Car", Order = 1)]
[_3bTechTalkRoutePrefix("{CountryCode}/api/Car", Order = 2)]
public class CarController: ApiController {
[Route("Get")]
public IHttpActionResult Get() {
return Ok(new {
Id = 1, Name = "Honda Accord"
});
}
}
I have uploaded a working solution here
Happy Coding :)
You're correct that you can't have multiple route prefixes, which means solving this particular use case is not going to be straight forward. About the best way I can think of to achieve what you want with the minimal amount of modifications to your project is to subclass your controller. For example:
[RoutePrefix("bikes")]
public class BikeController : Controller
{
...
}
[RoutePrefix("{country}/bikes")]
public class CountryBikeController : BikeController
{
}
You subclassed controller will inherit all the actions from BikeController, so you don't need to redefine anything, per se. However, when it comes to generating URLs and getting them to go to the right place, you'll either need to be explicit with the controller name:
#Url.Action("Index", "CountryBike", new { country = "us" }
Or, if you're using named routes, you'll have to override your actions in your subclassed controller so you can apply new route names:
[Route("", Name = "CountryBikeIndex")]
public override ActionResult Index()
{
base.Index();
}
Also, bear in mind, that when using parameters in route prefixes, all of your actions in that controller should take the parameter:
public ActionResult Index(string country = "us")
{
...
You could use attribute routes with two ordered options.
public partial class GlossaryController : Controller {
[Route("~/glossary", Order = 2)]
[Route("~/{countryCode}/glossary", Order = 1)]
public virtual ActionResult Index()
{
return View();
}
}
If you're planning to have region specific routes for all your pages you could add a route to the route config above the default. This will work only for views/controllers without attribute routes.
routes.MapRoute(
name: "Region",
url: "{countryCode}/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
constraints: new { countryCode = #"\w{2}" }
);
The best solution I've come across is detailed by NightOwl888 in response to the following question: ASP.NET MVC 5 culture in route and url. The code below is my trimmed down version of his post. It's working for me in MVC5.
Decorate each controller with a single RoutePrefix, without a culture segment. When the application starts up, the custom MapLocalizedMvcAttributeRoutes method adds a localized route entry for each controller action.
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
// Omitted for brevity
MapLocalizedMvcAttributeRoutes(routes, "{culture}/", new { culture = "[a-z]{2}-[A-Z]{2}" });
}
static void MapLocalizedMvcAttributeRoutes(RouteCollection routes, string urlPrefix, object constraints)
{
var routeCollectionRouteType = Type.GetType("System.Web.Mvc.Routing.RouteCollectionRoute, System.Web.Mvc");
var subRouteCollectionType = Type.GetType("System.Web.Mvc.Routing.SubRouteCollection, System.Web.Mvc");
var linkGenerationRouteType = Type.GetType("System.Web.Mvc.Routing.LinkGenerationRoute, System.Web.Mvc");
FieldInfo subRoutesInfo = routeCollectionRouteType.GetField("_subRoutes", BindingFlags.NonPublic | BindingFlags.Instance);
PropertyInfo entriesInfo = subRouteCollectionType.GetProperty("Entries");
MethodInfo addMethodInfo = subRouteCollectionType.GetMethod("Add");
var localizedRouteTable = new RouteCollection();
var subRoutes = Activator.CreateInstance(subRouteCollectionType);
Func<Route, RouteBase> createLinkGenerationRoute = (Route route) => (RouteBase)Activator.CreateInstance(linkGenerationRouteType, route);
localizedRouteTable.MapMvcAttributeRoutes();
foreach (var routeCollectionRoute in localizedRouteTable.Where(rb => rb.GetType().Equals(routeCollectionRouteType)))
{
// routeCollectionRoute._subRoutes.Entries
foreach (RouteEntry routeEntry in (IEnumerable)entriesInfo.GetValue(subRoutesInfo.GetValue(routeCollectionRoute)))
{
var localizedRoute = CreateLocalizedRoute(routeEntry.Route, urlPrefix, constraints);
var localizedRouteEntry = new RouteEntry(string.IsNullOrEmpty(routeEntry.Name) ? null : $"{routeEntry.Name}_Localized", localizedRoute);
// Add localized and default routes and subroute entries
addMethodInfo.Invoke(subRoutes, new[] { localizedRouteEntry });
addMethodInfo.Invoke(subRoutes, new[] { routeEntry });
routes.Add(createLinkGenerationRoute(localizedRoute));
routes.Add(createLinkGenerationRoute(routeEntry.Route));
}
}
var routeEntries = Activator.CreateInstance(routeCollectionRouteType, subRoutes);
routes.Add((RouteBase)routeEntries);
}
static Route CreateLocalizedRoute(Route route, string urlPrefix, object constraints)
{
var routeUrl = urlPrefix + route.Url;
var routeConstraints = new RouteValueDictionary(constraints);
// combine with any existing constraints
foreach (var constraint in route.Constraints)
{
routeConstraints.Add(constraint.Key, constraint.Value);
}
return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler);
}
}

MVC 3 Regularly re-occurring elements...best practices

On my site i wan't to display a list of categories from a db on almost every page. At the time being i use the ViewBag to store the categories, but I know there must be some better way. Therefore i'm wondering about best practices when loading re-occurring elements in MVC3.
Here's some code:
public class HomeController : Controller
{
private AlltForMusikContext db = new AlltForMusikContext();
//
// GET: /Admin/
public ViewResult Index()
{
var ads = db.Ads.Include(a => a.Category).OrderByDescending(a => a.Date);
ViewBag.Categories = db.Categories.ToList();
return View(ads.ToList());
}
public ViewResult Category(int id)
{
var ads = db.Ads.Where(a => a.Category.CategoryId == id).OrderByDescending(a => a.Date);
ViewBag.Categories = db.Categories.ToList();
ViewBag.Category = db.Categories.Where(a => a.CategoryId == id).FirstOrDefault();
return View(ads.ToList());
}
}
I use this code in the _Layout.cshtml
#Html.Partial("_GetCategories", (IEnumerable<AlltForMusik.Models.Category>)#ViewBag.Categories)
This is my partial view that I load into the layout view:
#model IEnumerable<AlltForMusik.Models.Category>
#foreach (var cat in Model)
{
<img src="#Url.Content("~/Content/img/icon_arrow.gif")" />
#Html.ActionLink(cat.CategoryName, "Category", "Home", new { id = cat.CategoryId })<br />
}
This works but I have to load the categories into the ViewBag every time I wanna load a view, othervise I get an error.
What is the best way to load content like this?
Answer:
I followed the advice and used a HtmlHelper. I stumbled upon some problems at first because i hade referenced the HtmlHelper in System.Web.Webpages instead of System.Web.Mvc. Here's the code i'm using:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using AlltForMusik.Models;
using System.Web.Mvc;
namespace AlltForMusik.Helpers
{
public static class HtmlHelpers
{
public static string GetCategories(this HtmlHelper helper)
{
AlltForMusikContext db = new AlltForMusikContext();
var categories = db.Categories.OrderBy(a => a.CategoryName).ToList();
string htmlOutput = "";
foreach (var item in categories)
{
htmlOutput += item.CategoryName + "<br />";
}
return htmlOutput.ToString();
}
}
}
Create custom HttpHelper with Caching. for example ShowCategories(). and then just put it on the view or on the common layout like:
#Html.ShowCategories()

Asp.net Mvc Convert return Action result(view) to string

I am working on a project in asp.net mvc3(c#).
I need a solution for convert a view(not a partial view) to string from different controllers.
Code Explantion:
1) Calling "details" action of proposalmoduleController from proposalsController.
2) proposalmoduleController action "details" returns a view and convert this view(return result) as a string in proposalsController.
Code
public class proposalmoduleController : ControllerBase
{
[HttpGet]
public ActionResult details(int id, int widgetuniqueid)
{
//id - widgetid of div container
List<ModuleViewModel> listmoduleviewmodel = new List<ModuleViewModel>();
List<ModuleFieldViewModel> listmodulefieldviewmodel = new List<ModuleFieldViewModel>();
var objProposalModuleService = new ProposalModuleService();
var objModuleViewModel = new ModuleViewModel();
string WidgetTitle = "";
Int64 ModuleTemplateID = 0;
//objModuleViewModel.ProposalID = proposalid;
objModuleViewModel.ProposalModuleWidgetID = id;
listmoduleviewmodel=objProposalModuleService.Select(1, objModuleViewModel,out listmodulefieldviewmodel, out WidgetTitle, out ModuleTemplateID);
return View(listmoduleviewmodel);
}
}
public class proposalsController : ControllerBase
{
public string SaveHtml(int ProposalID)
{
var objProposalSortOrderViewModelList = new List<ProposalSortOrderViewModel>();
proposalmoduleController objModuleController = new proposalmoduleController(); // Initilize the object of proposalmoduleController for accessing details method
objProposalSortOrderViewModelList = GetProposalSortorders(ProposalID);
string result;
foreach (var item in objProposalSortOrderViewModelList)
{
ViewResult viewResult = (ViewResult)objModuleController.details(Convert.ToInt32(item.KeyID), Convert.ToInt32(item.SortOrder)); // Fetch the result returned from proposalmodulecontroller,details action
result=viewResult.ToString(); // Need to get result fetch from the proposalmodulecontroller,details action as a string
}
}
}
enter code here
Please suggest any solution.
A ViewResult is not a View. ViewResult is used by the MVC engine to determine the view that must be rendered.
I think it's better if you change your perspective:
if you want to include a partial view in a view just work on the presentation code using #Html.Partial
if you want to get the details data in your proposalsController don't call the action of the proposalmoduleController but call a service method that gives you the data

Change ordering in MVC3 Index view

Would like to have clickable column titles eg click on TagCode once and it orders by that, and again it reverses. Same for Number.
Using MVC3/Razor and LightSpeed (ORM).
I know that a grid eg http://mvccontrib.codeplex.com/ may be the way forward. But as I don't need paging or filtering, I'd like to keep it simple for now.
Problem Is there a simple example of code (maybe with an up/down icon) that would help?
#Dave
Sorry, I missed your main point in my first answer
If you want to implement sorting using pure MVC3,
I can do that with following steps
list action method passing sortcolumn and order info to viewpage via ViewBag
Html Helper method to build actionlink with column ordering display
list viewpage calling the helper method
I uploaded source code here
List action method
public ActionResult Index(string sortColumn, bool? asc)
{
if (string.IsNullOrWhiteSpace(sortColumn))
sortColumn = "Number";
asc = asc ?? true;
SortDirection sortDirection = asc == true ? SortDirection.Ascending : SortDirection.Descending;
var query = _service.GetTags().OrderBy(sortColumn, sortDirection);
return View(query);
}
Html helper method
public static MvcHtmlString ActionLinkWithColumnOrder(this HtmlHelper helper,
string columnName,string action,string currentColumn,bool currentOrder)
{
object routeValues;
object htmlAttributes = null;
if (columnName == currentColumn)
{
routeValues = new { sortColumn = columnName, asc = !currentOrder };
htmlAttributes = new { #class = currentOrder ? "sort_asc" : "sort_desc" };
}
else
{
routeValues = new { sortColumn = columnName };
}
return helper.ActionLink(columnName, action, routeValues, htmlAttributes);
}
List View page
...
#Html.ActionLinkWithColumnOrder("TagCode", "Index", (string)ViewBag.sortColumn, (bool)ViewBag.asc)
...
#Html.ActionLinkWithColumnOrder("Number", "Index", (string)ViewBag.sortColumn, (bool)ViewBag.asc)
Happy Mvcing!
Sangsu PARK (http://supremeware.blogspot.com)
#Dave
How about using mvccontribgrid with ordering only as follows:
IMO, Using mvccontribgrid may bring more simple code.
This is controller code for example
public class HomeController : Controller
{
private AlbumService _service;
public HomeController()
{
_service = new AlbumService();
}
public ActionResult Index(GridSortOptions gridSortOptions)
{
var vm = new ViewModel<Album>()
{
DefaultSort = "AlbumId",
GridSortOptions = gridSortOptions,
List = _service.GetAlbums()
.OrderBy(gridSortOptions.Column, gridSortOptions.Direction),
};
return View(vm);
}
public ActionResult Details(int id)
{
var album = _service.GetAlbum(id);
ViewBag.RouteDicForList = Request.QueryString.ToRouteDic();
return View(album);
}
}
I attached simple sort function source code here with simple service & EF4.
Also, I posted full featured mvccontrib grid filtering & paging article here.

Resources