Reputation: 26514
I am in the process of creating tabbed navigation where the route location can vary. The parameter used to render the tabs should be based on the presently viewed data (which when it is a user, may not be the logged in user).
In the example image this is a user. Therefore, if I am looking at Andrew Steele then the links should be contextual to Andrew Steele (Andrew's summary, computers, accounts etc.). When I am looking at Bruce Hamilton the links should be contextual to Bruce Hamilton (Bruce's summary, computers, accounts etc.).
I've solved this by sticking the necessary parameter value in each ViewModel and then passing the value onto a partial to render the links; this feels kludgey. I'd prefer to not shove the linking parameter data into each ViewModel. It would seem reasonable to use Html.Action
or Html.RenderAction
combined with a ViewData Dictionary value, but I tend to shy away from using "magic strings" where possible.
Is there a better way to get the parameter value to the view that I am missing?
Upvotes: 1
Views: 1021
Reputation: 15663
Personally, I'd use either RenderAction from the MVC futures or a render partial call here depending on performance requirements and taste.
Advantage to RenderAction is you won't need to pass all the required rendering data to the delegate controller action as it could do the lookup itself. Whereas a partial view would require your main page have enough view data to render the tab. Either way its just two for loops (one for the tab list, one for the tab rendering) and a bit of extra data in your Model.
Upvotes: 1
Reputation: 8272
I wrote a site a while back where I had different tabs that went to different places for different users. I'll walk you through my solution hopefully I understood the question correctly and some of this helps.
As far as getting data to and from the View, I do use the ViewDataDictionary. To the best of my knowledge, that's what it's for when your model doesn't consist of a single simple object. In order to get around the "magic strings" of view keys, I create a bunch of extension methods on the ViewDataDictionary. This has the drawback that you end up with a slew of extra methods, but at least all of your string keys are isolated in a single location. You could even go the extra step of create constants in the class, but it seems redundant when only this class uses them. Extension properties would be better but...
/// <summary>
/// Gets the list of tabs to show.
/// </summary>
/// <param name="dictionary"></param>
/// <returns></returns>
public static IList<TabItemDisplay> TabListGet(this ViewDataDictionary dictionary)
{
IList<TabItemDisplay> result;
if (dictionary.ContainsKey("TabList"))
result = dictionary["TabList"] as IList<TabItemDisplay>;
else
result = null;
return result;
}
/// <summary>
/// Sets the list of tabs to show.
/// </summary>
/// <param name="dictionary"></param>
/// <param name="tabList"></param>
/// <returns></returns>
public static IList<TabItemDisplay> TabListSet(this ViewDataDictionary dictionary, IList<TabItemDisplay> tabList)
{
dictionary["TabList"] = tabList;
return tabList;
}
You'll notice that I have an explicit view object, TabItemDisplay, that I pass into the dictionary. This contains all of the values necessary to pass to Html.ActionLink.
public class TabItemDisplay
{
public string Name { get; set; }
public string Action { get; set; }
public string Controller { get; set; }
public object RouteValues { get; set; }
}
Since this view is not the main content of the page, I prefer to put the logic of creating the tab items, including destination parameters, into an ActionFilter. This allows me to reuse the tab creation logic across different actions and controllers. Any View that contains the tab partial control gets the CreatTabAttribute slapped across the corresponding Action or Controller and it's good to go.
This may be more than you needed, but I hope some of it helps.
EDIT: Just realized I didn't include what this looks like in the partial view. I actually have an HtmlHelper extension that renders a more intricate tab, but you get the idea.
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<div id="tabs">
<%
if (null != ViewData.TabListGet()) {
foreach(var item in ViewData.TabListGet()) {
%>
<%= Html.ActionLink(item.Name, item.Action, item.Controller, item.RouteValues, null)%>
<%
}
}
%>
</div>
EDIT: Adding a short example of the ActionFilter I use.
public class CreateContentTabsAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as ViewResultBase;
if (null == result) return;
var routeValues = filterContext.RouteData.Values;
var repository = ObjectFactory.GetInstance<ITabRepository>();
var context = filterContext.HttpContext;
var userName = context.User.Identity.Name; // Or get id from Membership.
var tabs = repository.ReadByUserId(userName);
TabItemDisplay defaultTab = null;
var tabItems = new List<TabItemDisplay>();
foreach (var tab in tabs)
{
var tabItem = new TabItemDisplay
{
Name = tab.Name,
Action = "View",
Controller = "Tab",
RouteValues = new { key = tab.Key }
};
tabItems.Add(tabItem);
}
if (context.Request.IsAuthenticated)
{
tabItems.Add(new TabItemDisplay
{
Name = "Account",
Action = "ChangePassword",
Controller = "Account",
RouteValues = new { siteKey = site.Key }
});
}
result.ViewData.TabListSet(tabItems);
}
}
This is only a basic example of pulling tabs from a repository (instantiated using StructureMap), and a simple check to see if the user is authenticated. But you can do other things such as pull the requested user id for the user being displayed from routeValues.
Upvotes: 1
Reputation: 10276
You can create a viewmodel for this that exposes the tab preference. If each tab is totally different you could have a viewmodel base class that exposes the tab preference and each of the tabs could have their own view model.
Upvotes: 1
Reputation: 17794
one solution is to create base controller and have all ur controllers inherit from it. in base controller u can add necessary value into viewmodel
public applicationController:Controller
{
ViewData["linkvals"] = someValues;
}
public HomeContorller:ApplicationController{}
second solution is to create a base view model and have all ur viewmodels inherit it. in constructor of base viewmodel u can create link values and assign them to ur properties
public baseViewModel
{
public LinkVal{get;set;}
public baseViewModel()
{
//calculate link vals
}
}
public ViewModelA:baseViewModel{}
Upvotes: -1