I created a project that the nodes are defined using attributes, and I set it in the web.config to scan for attributes, and it works fine.
I don't use an XML file at all.
Now I want to add a dynamic node provider, how do I do it?
Is there a way to do it without the XML (.sitemap) file?
I need to make sure it's under the root, which has been set in code using MvcSiteMapNodeAttribute attribute.
I've read the documentation and I can't really figure out where to place this line:
<mvcSiteMapNode
title="Details" action="Details"
dynamicNodeProvider="Project.StoreDetailsDynamicNodeProvider, Prject" />
What action is it supposed to point to? Additionally as said above, the root element is defined using attributes, so my question is if there is a way to avoid XML, or alternatively what's the efficient way to declare the XML (the less the better) to include my dynamic provider.
Update
I've tried the following and the node provider still isn't reached (From HomeController.cs).
[MvcSiteMapNode(Title = "Home", Key = HomeMenuKey,
DynamicNodeProvider = "Project.Namespace.NodeProvider, Assembly")]
public ActionResult Index()
{
return View();
}
Can you define it in the controller method attributes (and not use XML at all)?
For example:
[MvcSiteMapNode(Title="Details",
DynamicNodeProvider = "Project.StoreDetailsDynamicNodeProvider, Project")]
public ActionResult Index()
{
return View();
}
Seems that the dynamicNodeProvider attribute is ignored in the root node, also when it's defined in attributes.
So the only way to add a dynamic node provider under the root, is either by specifying it on a dummy action etc. or using XML.
An interesting note: the actual difference between defining in XML and attributes is that if it's defined in attributes, it (i.e. the gen. menu items) will be last in the menu, whereas when defined in XML it will be right after the root item (I guess that would be Home), Note that this is still controllable via the Order property in the attributes.
In my Web.Config, I left the siteMapFile empty, relying in what it said in the wiki page, that the default value is ~/Web.sitemap, in fact this is false (I've already corrected that in the updated wiki).
I don't think this behavior should be like this, I do think the MvcSiteMap engine should scan for dynamic node providers just as it scans for dynamic node attributes (here is the issue I posted on site).
Related
I setup MVC breadcumbs for my website using MvcSiteMapProvider (Nuget package MvcSiteMapProvider.MVC5 (version 4.6.22)).
It works fine.
Then I want to update Url of Sitemap dynamically like:
SiteMaps.Current.CurrentNode.Url = Url.Action("Index");
Then I got this error:
SiteMapNode is readonly, property 'Url' cannot be modified
Note that I am still able to update Title:
SiteMaps.Current.CurrentNode.Title = "/Index";
Any idea?
The SiteMap is a statically cached object that is shared between all users. Technically, all of the properties are read-only at runtime. However, some of the properties (such as Title) are request-cached so you can safely update them at runtime without affecting other users.
The Url property is a special property that dynamically builds the URL through the MVC UrlHelper class (which is directly driven from your routes). It makes no sense to set it to Url.Action("Index") because that is effectively what it does just by itself (unless you are using a dynamic node provider or custom ISiteMapNodeProvider - those are startup extension points where you load the node configuration, so the properties are read-write).
You just need to set the correct controller and action in your node configuration (which could be XML, attribute based, or code based) and the URL will resolve on its own.
XML Example
<mvcSiteMapNode title="Projects" controller="Project" action="Index"/>
NOTE: You need to account for all route values in the request, either by adding them as another attribute myId="123" or by using preservedRouteParameters="myId" (which tells it to include the myId from the current request when building the URL). See this article for a detailed description of using these options.
NOTE: Setting a URL in the SiteMap configuration effectively overrides MVC support for that node. So, you shouldn't set a URL at all unless it is a non-MVC URL.
I have to make vanity urls for an already existing site. The requirements are:
The existing controllers are a kind of hierarchical structure and can't go away or be overridden in any way. Examples: domain.com/Dog/vanity and domain.com/Cat/vanity.
Keep existing actions. Any existing actions must take priority so that the page for that action is not stomped on by the vanity url.
take future pages and actions into account so that the above requirement is met (a new vanity url is ignored and the action/view executed instead)
To date, I have tried various solutions with routing that allow me to have domain.com/vanity which is nice but the marketing guys don't like because of the different departments within the company. I've tried routing which will override the existing actions and treats them all as vanities (also not feasible). I've a solution in place that programmatically deals with the url that was requested and redirects to a page that actually exists but this is not scalable in any way.
So far, I know that the vanity portion can be treated as a parameter to the action so that I can fire off the default page in the route (Index) but this is, so far, doesn't preserve the structure.
TL;DR: I need to have a solution that allows for domain/controller/vanity structure while also allowing domain/controller/action
Using AttributeRouting for MVC4 you can accomplish a working solution until you ramp up the replacement project. It'll allow you to keep existing routes while adding new, custom ones with little impact.
[Route("my-vanity/is-cool/post/{id}")]
public ActionResult Index(int id)
{
}
The important part is to remember priority, so you write routes that don't overwrite/are overwritten by existing routes. You can steer this to some degree with properties on the attribute. Below is just an example that will put the added route last in priority for the entire site.
[Route("my-vanity/is-cool", SitePrecedence = -1)]
public ActionResult Index()
{
}
ASP.NET WebApi2 have built in support for attribute routing. With it you can define URL's in whatever way you like (instead of following the /controller/action pattern)
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
I am writing an HtmlHelper extension and I need to search for the existence of a template by name. The template in question may be a display or editor template depending on the context. My initial thought was to use ViewEngines.Engines.FindPartialView method. However, it appears that this method is not searching the ~/Views/Shared/DisplayTemplates and ~/Views/Shared/EditorTemplates directories.
I suppose this is for good reason. After all, how would the ViewEngine know whether to return the display or editor template without some additional information of context?
So, that leads to the question: how can I search for a specific EditorTemplate/DisplayTemplate I've considered adding a custom view engine to the ViewEngines collection to include these locations. I'm concerned, however, that this might be problematic.
My main concern is that the DisplayTemplate/EditorTemplate view might be served up for something unintended. Does anyone else see this as a problem?
Is it a better idea just to new up a specific DisplayTemplateViewEngine/EditorTemplateViewEngine instance when necessary and keep the ViewEngines collection clear of this specific functionality?
Is there something else I'm missing?
I absolutely love that the MVC framework is open source! I was able to determine, from the TemplateHelpers class (internal to the MVC Runtime) that the DataBoundControlMode is considered when rendering a template. The answer was simple! All I have to do is prefix the template name with the appropriate template director. So, to find a display template:
var metadata = ModelMetadata.FromLambdaExpression(expression, HtmlHelper.ViewData);
ViewEngines.Engines.FindPartialView(
_controllerContext,
string.Format("DisplayTemplates/{0}", metadata.TemplateHint))
No additional view engines or routing required! In case you're interested in the application, my helper is auto-generating UI components for a given model. I wanted to enable the existence of a custom template to bypass the automated rendering.
A WebFormViewEngine has a few properties that define (patterns for) locations to search for views.
You either follow the convention of the view engine you use, or create a custom view engine (that for examlpe extends Razor) with custom view paths.
The latter is explained here:
public class CustomViewEngine : RazorViewEngine
{
public CustomViewEngine()
{
var viewLocations = new[] {
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/DisplayTemplates/{0}.cshtml",
"~/Views/Shared/DisplayTemplates/{1}/{0}.cshtml",
// etc
};
this.PartialViewLocationFormats = viewLocations;
this.ViewLocationFormats = viewLocations;
}
}
So I guess in your helper you should look up the current view engine and look up its view location paths and search them in order. Doesn't an Html helper have a method or property for getting the view you're currently running in?
Why you just map the relative path
string path = Server.MapPath("~/View/");
And then check if the file exits base on the .cshtml exit's in that specific directory
string fileName = "MyView.cshtml";
if (File.Exists(path + fileName))
//do somethings
else
//do another things
My Problem: I have an MVC3 application where all views use a common master page. The master page has many links to other (internal) sites. I need to be able to change the domain of these links depending on the deployment environment (e.g. staging.blah.com, www.blah.com, dev.blah.com etc). This domain is stored in the web.config.
There are numerous ways of doing this, but I am looking for some sort of consensus as to the preferred method. Here are some options but I am open to any suggestions:
(1) reference appsettings from master page directly. This is the simplest and most common approach but I am not particularly keen on reading the web.config and concatenate the url throughout the master page code. In fact, I am not sure that I like the idea of the view accessing the web.config at all.
(2) stick the appsetting value in viewdata/viewbag using a custom action filter which reads the config. concatenate in the page as before.
(3) as (2), but inject appsetting value in via contructor injection rather than reading it within the filter.
(4) create a base class for all my strongly typed viewmodels and populate with the appsetting using a custom action filter.
(5) create an htmlhelper that takes in the path and internally reads the appsetting and concatenates.
(6) create a custom view base class, inject in appsetting value and make available as property or function that takes in path and concatenates.
Just to add that typically when the master page requires data, I like to use Html.Action, but this is not possible in the case of these URLs that are used throughout the master page.
Thoughts?
(5) create an htmlhelper that takes in the path and internally reads the appsetting and concatenates.
I would go with this one. Your custom HTML helper could look something like this:
<%= Html.ExternalActionLink(
"link text",
new { path = "/foo/bar.php" }
new { param1 = "value1", param2 = "value2" }
) %>
and could emit the following HTML:
link text
What I have done in the past is use viewdata/viewbag in my master page and populate its values in my base controller. The base controller in turn called another class to do the work of reading the values from the web.config.
This way the view is pretty clean (e.g. it does not contain code to read appsettings) and I don't need to create a base view model that matches all my views that use the master.
This approach has the disadvantage that uses a viewdata/viewbag but I decided that was OK in my case and extremely easy to implement.
I have a details page containing a form field named UserId. On the same page i have another search form with a field also named UserId.
I am using Html.LabelFor(vm > vm.UserId) and Html.TextBoxFor(sm > sm.UserId) on the two different view models, vm being the view model and sm being the search model. (Yes, the UserId property on the two models has identical names - because they are the same domain property.
When i navigate to the page, the populated UserId on the vm is inserted into BOTH form fields named UserId by MVC. Even the sm.UserId are empty.
That is my initial problem. There are a few ways ti avoid that. My solution was to use the Prefix flag for the sm.
[HttpGet]
public ActionResult Search([Bind(Prefix = "Search")] SearchFormViewModel searchFormViewModel, PagingViewModel pagingViewModel)
{
This will provoke MVC to render a Search.UserId on the fieldname in the search form, but the property in code will still be named UserId.
This solution seems to work great!
BUT:
Now i have to address the search.UserId on a route from Global.asax.
I map the route like this:
routes.MapRoute(
"MyRouteName",
"ControllerName/User/{Search.UserId}",
new { controller = "ControllerName", action = "Search" }
);
My problem is that MVC can't map the Search.UserId (because of the .) to fit the UserId (prefixed with Search) in the action shown above.
So it seems like MVC has a prefix-feature, that are actually nok fully supported through the Route-handler.
Ofcourse i could rename the Search.UserId to Search_UserId, but then the name dosent match the name MVC expects in the recieving action above. (expects Search.UserId) Renaming The UserId property of the search model would fix the issue, but since it is the same value in the domain, this seems like a workaround.
Am I missing something here about the usage of the Prefix feature or is this just not possible?
So... I've been thinking about this for a while now. - And a colleague of mine suddently showed me the light.
The problem lies where MVC maps the object to a route dictionary.
See the user
wount work. Because MVC can not handle the .(dot) in the object name.
but since the object name is just a string key in the routevaluedictionary, mapping it my self did the trick:
See the user