I'm trying to use:
var viewEngineResult = ViewEngines.Engines.FindView(ControllerContext, myViewName, null);
as part of a process to render the contents of a view to send nice formatted emails. I'm using it inside an Orchard Controller. I have used similar code outside of Orchard in an MVC project and it works fine.
However in Orchard running this code fails to find the view I'm looking for and returns a view engine result that has searched 0 locations.
viewEngineResult has the following values after it is called:
SearchedLocations: Count = 0,
View: null,
ViewEngine: null
Is there a reason this doesn't work in Orchard and is there a way to make it work?
This answer is based on the advise given me by Bertrand, but I wanted to bring it together with what I'd discovered.
To be able to use FindPartialView I needed to inject an instance of IViewEngineProvider into my controller.
I then used the following code to resolve and render a view:
private String RenderView(String viewName, object model)
{
var paths = new List<string>(); // This can just be an empty list and it still finds it.
var viewEngine = _viewEngineProvider.CreateModulesViewEngine(new CreateModulesViewEngineParams {VirtualPaths = paths});
var viewResult = viewEngine.FindPartialView(ControllerContext, viewName, false);
if (viewResult.View == null) {
throw new Exception("Couldn't find view " + viewName);
}
var viewData = new ViewDataDictionary {Model = model};
using (var sw = new StringWriter())
{
var viewContext = new ViewContext(ControllerContext, viewResult.View, viewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
I think you'll want to take a close look at Orchard.Framework/Mvc/ViewEngines, in particular IViewEngineProvider and ThemeAwareViewEngine. There's a lot more going on when in Orchard, such as themes, multi-tenancy, and a richer environment in general that may be needed to make this work.
What's likely happening here is that the view engines don't have enough information to resolve a view and thus opt out of the chain. You might want to put a breakpoint into ThemeAwareViewEngine.FindView, and then inspect the private dependency fields of that class. I wouldn't be surprised if they were null, because getting to FindView through statics will probably not allow dependency injection to do its stuff properly.
Then again I'm just guessing.
Related
Background
I got a page where I’m showing two list views from two separate lists which both have Custom List as their ListTemplate. They got their separate jslink file cause I don’t want them to look alike.
Problem
The js link file targets both listviews since they use the same Template.
Code
(function () {
var listContext = {};
listContext.Templates = {};
listContext.ListTemplateType = 100;
listContext.Templates.Header = "<div><ul>";
listContext.Templates.Footer = "</ul></div>";
listContext.Templates.Item = LinkTemplate;
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(listContext);
})();
Question
Is there any way to make the js only target a specific list?
Ended up going with Paul Hunts solution that he writes about on myfatblog.co.uk. http://www.myfatblog.co.uk/index.php/2013/09/listview-web-part-issues-with-jslink-and-display-templates-a-solution/
The script ended up looking like this and I pasted it into the jslink function where I define what listContext to override.
// Override the RenderListView once the ClientTemplates.JS has been called
ExecuteOrDelayUntilScriptLoaded(function(){
// Copy and override the existing RenderListView
var oldRenderListView = RenderListView;
RenderListView = function(ctx,webPartID)
{
// Check the title and set the BaseViewId
if (ctx.ListTitle == "List")
ctx.BaseViewID = "list";
//now call the original RenderListView
oldRenderListView(ctx,webPartID);
}
},"ClientTemplates.js");
Good day!
In my Orchard, I have several content types all with my custom part attached. This part defines to what users this content is available. For each logged user there is external service, which defines what content user can or cannot access. Now I need access restriction to apply everywhere where orchard display content lists, this includes results by specific tag from a tag cloud, or results listed from Taxonomy term. I seems can’t find any good way to do it except modifying TaxonomyServices code as well as TagCloud services, to join also my part and filter by it. Is this indeed the only way to do it or there are other solutions? I would like to avoid doing changes to built-in modules if possible but cannot find other way.
Thanks in advance.
I'm currently bumbling around with the same issue. One way I'm currently looking at is to hook into the content manager.
[OrchardSuppressDependency("Orchard.ContentManagement.DefaultContentManager")]
public class ModContentManager : DefaultContentManager, IContentManager
{
//private readonly Lazy<IShapeFactory> _shapeFactory;
private readonly IModAuthContext _modAuthContext;
public ModContentManager(IComponentContext context,
IRepository<ContentTypeRecord> contentTypeRepository,
IRepository<ContentItemRecord> contentItemRepository,
IRepository<ContentItemVersionRecord> contentItemVersionRepository,
IContentDefinitionManager contentDefinitionManager,
ICacheManager cacheManager,
Func<IContentManagerSession> contentManagerSession,
Lazy<IContentDisplay> contentDisplay,
Lazy<ISessionLocator> sessionLocator,
Lazy<IEnumerable<IContentHandler>> handlers,
Lazy<IEnumerable<IIdentityResolverSelector>> identityResolverSelectors,
Lazy<IEnumerable<ISqlStatementProvider>> sqlStatementProviders,
ShellSettings shellSettings,
ISignals signals,
//Lazy<IShapeFactory> shapeFactory,
IModAuthContext modAuthContext)
: base(context,
contentTypeRepository,
contentItemRepository,
contentItemVersionRepository,
contentDefinitionManager,
cacheManager,
contentManagerSession,
contentDisplay,
sessionLocator,
handlers,
identityResolverSelectors,
sqlStatementProviders,
shellSettings,
signals) {
//_shapeFactory = shapeFactory;
_modAuthContext = modAuthContext;
}
public new dynamic BuildDisplay(IContent content, string displayType = "", string groupId = "") {
// So you could do something like...
// var myPart = content.As<MyAuthoPart>();
// if(!myPart.IsUserAuthorized)...
// then display something else or display nothing (I think returning null works for this but
//don't quote me on that. Can always return a random empty shape)
// else return base.BuildDisplay(content, displayType, groupId);
// ever want to display a shape based on the name...
//dynamic shapes = _shapeFactory.Value;
}
}
}
Could also hook into the IAuthorizationServiceEventHandler, which is activated before in the main ItemController and do a check to see if you are rendering a projection or taxonomy list set a value to tell your content manager to perform checks else just let them through. Might help :)
I've started my first project using OrchardCMS and so far so good. It's a brilliant piece of kit and amazingly powerful once you get up to speed with the terminology of it.
One area I'm struggling with is displaying Taxonomies. There seems to be almost no documentation on it, and Shape Tracing is not being very useful.
I'm using a taxonomy to display content items that have the same term. This is all working great now, however the default pagination page size is 10 items. I've dug through lots of code and found this is driven from TermPart
protected override DriverResult Display(TermPart part, string displayType, dynamic shapeHelper) {
return Combined(
ContentShape("Parts_TermPart_Feed", () => {
// generates a link to the RSS feed for this term
_feedManager.Register(part.Name, "rss", new RouteValueDictionary { { "term", part.Id } });
return null;
}),
ContentShape("Parts_TermPart", () => {
var pagerParameters = new PagerParameters();
var httpContext = _httpContextAccessor.Current();
if (httpContext != null) {
pagerParameters.Page = Convert.ToInt32(httpContext.Request.QueryString["page"]);
}
var pager = new Pager(_siteService.GetSiteSettings(), pagerParameters);
var taxonomy = _taxonomyService.GetTaxonomy(part.TaxonomyId);
var totalItemCount = _taxonomyService.GetContentItemsCount(part);
var partSettings = part.Settings.GetModel<TermPartSettings>();
if (partSettings != null && partSettings.OverrideDefaultPagination) {
pager.PageSize = partSettings.PageSize;
}
The question is where is TermPart & TermPartSettings defined?
Thanks in advance
The TermPartSettings are in the Modules/Orchard.Taxonomies/Settings folder. The TermPart itself is in the Modules/Orchard.Taxonomies/Models directory. Don't know what you want with that information though, because you shouldnt edit an Orchard's module code.
When you create a new Taxonomy in the dashboard of Orchard, a new content type is created under Content Definition. It is called YourTaxonomy Term. This content type has a setting called 'override the default page size', where you can define your own page size.
I'm trying to Weld my custom ContentPart SitesPart containing a ContentField of type TaxonomyField but it is not working for me. When i attach this part from UI it works perfectly fine and i see the TaxonomyField in edit mode as well as in display mode.
Following is the Activating method of my ContentHandler.
protected override void Activating(ActivatingContentContext context)
{
if (context.ContentType == "Page")
{
context.Builder.Weld<SitesPart>();
}
}
I tried to go deep into the Weld function and found out that it is not able to find correct typePartDefinition. It goes inside the condition if (typePartDefinition == null) which creates an empty typePartDefinition with no existing ContentFields etc.
// obtain the type definition for the part
var typePartDefinition = _definition.Parts.FirstOrDefault(p => p.PartDefinition.Name == partName);
if (typePartDefinition == null) {
// If the content item's type definition does not define the part; use an empty type definition.
typePartDefinition =
new ContentTypePartDefinition(
new ContentPartDefinition(partName),
new SettingsDictionary());
}
I would be highly thankful for any guidance.
Oh, you are totally right, the part is welded but if there are some content fields, they are not welded. The ContentItemBuilder try to retrieve the part definition through the content type definition on which we want to add the part. So, because it's not possible, a new content part is created but with an empty collection of ContentPartFieldDefinition...
I think that the ContentItemBuilder would need to inject in its constructor and use a ContentPartDefinition or more generally an IContentDefinitionManager... But, for a quick workaround I've tried the following that works
In ContentItemBuilder.cs, replace this
public ContentItemBuilder Weld<TPart>()...
With
public ContentItemBuilder Weld<TPart>(ContentPartDefinition contentPartDefinition = null)...
And this
new ContentPartDefinition(partName),
With
contentPartDefinition ?? new ContentPartDefinition(partName),
And in you part handler, inject an IContentDefinitionManager and use this
protected override void Activating(ActivatingContentContext context) {
if (context.ContentType == "TypeTest") {
var contentPartDefinition = _contentDefinitionManager.GetPartDefinition(typeof(FruitPart).Name);
context.Builder.Weld<FruitPart>(contentPartDefinition);
}
}
Best
To attach a content part to a content type on the fly, you can use this in your handler
Filters.Add(new ActivatingFilter<YourContentPart>("YourContentType"));
There are many examples in the source code
Best
I just upgrade MvcSiteMapProvider from v3 to v4.6.3.
I see the upgrade note indicate:
In general, any reference to System.Web.SiteMap.Provider will need to be updated to MvcSiteMapProvider.SiteMaps.Current
I am trying to get the sitemap node by using:
SiteMaps.Current.FindSiteMapNode(rawUrl)
But it always return null
I looked into the code. In the sitemap it's actually calling the function:
protected virtual ISiteMapNode FindSiteMapNodeFromUrlMatch(IUrlKey urlToMatch)
{
if (this.urlTable.ContainsKey(urlToMatch))
{
return this.urlTable[urlToMatch];
}
return null;
}
It's trying to find a match in the urlTable.
I am using Default implementation of XmlSiteMapProvider .
It define var url = node.GetAttributeValue("url");
siteMapNode.Url = url;
siteMapNode.UrlResolver = node.GetAttributeValue("urlResolver");
So if I did not define url or urlResolver attribute in the .sitemap file. These variables a set to empty string, when generate the node.
And when this nodes are passed to AddNode function in SiteMap.
When adding the node
bool isMvcUrl = string.IsNullOrEmpty(node.UnresolvedUrl) && this.UsesDefaultUrlResolver(node);
this code will check if there is url or urlResolver
// Only store URLs if they are clickable and are configured using the Url
// property or provided by a custom URL resolver.
if (!isMvcUrl && node.Clickable)
{
url = this.siteMapChildStateFactory.CreateUrlKey(node);
// Check for duplicates (including matching or empty host names).
if (this.urlTable
.Where(k => string.Equals(k.Key.RootRelativeUrl, url.RootRelativeUrl, StringComparison.OrdinalIgnoreCase))
.Where(k => string.IsNullOrEmpty(k.Key.HostName) || string.IsNullOrEmpty(url.HostName) || string.Equals(k.Key.HostName, url.HostName, StringComparison.OrdinalIgnoreCase))
.Count() > 0)
{
var absoluteUrl = this.urlPath.ResolveUrl(node.UnresolvedUrl, string.IsNullOrEmpty(node.Protocol) ? Uri.UriSchemeHttp : node.Protocol, node.HostName);
throw new InvalidOperationException(string.Format(Resources.Messages.MultipleNodesWithIdenticalUrl, absoluteUrl));
}
}
// Add the URL
if (url != null)
{
this.urlTable[url] = node;
}
Finally no url is add to the urlTable, which result in FindSiteMapNode cannot find anything.
I am not sure if there needs to be specific configuration. Or should I implement custom XmlSiteMapProvider just add the url.
ISiteMapNodeProvider instances cannot use the FindSiteMapNode function for 2 reasons. The first you have already discovered is that finding by URL can only be done if you set the url attribute explicitly in the node configuration. The second reason is that the SiteMapBuilder doesn't add any of the nodes to the SiteMap until all of the ISiteMapNodeProvider instances have completed running, so it would be moot to add the URL to the URL table anyway.
It might help if you explain what you are trying to accomplish.
The ISiteMapNodeProvider classes have complete control over the data that is added to the SiteMapNode instances and they also have access to their parent SiteMapNode instance. This is generally all that is needed in order to populate the data. Looking up another SiteMapNode from the SiteMap object while populating the data is not supported. But as long as the node you are interested in is populated in the same ISiteMapNodeProvider instance, you can just get a reference to it later by storing it in a variable.
Update
Okay, I reread your question and your comment and it now just seems like you are looking in the wrong place. MvcSiteMapProvider v4 is no longer based on Microsoft's SiteMap provider model, so using XmlSiteMapProvider doesn't make sense, as it would sidestep the entire implementation. The only case where this might make sense is if you have a hybrid ASP.NET and ASP.NET MVC application that you want to keep a consitant menu structure between. See Upgrading from v3 to v4.
There are 2 stages to working with the data. The first stage (the ISiteMapBuilder and ISiteMapNodeProvider) loads the data from various sources (XML, .NET attributes, DynamicNodeProviders, and custom implementations of ISiteMapNodeProvider) and adds it to an object graph that starts at the SiteMap object. Much like Microsoft's model, this data is stored in a shared cache and only loaded when the cache expires. This is the stage you have been focusing on and it definitely doesn't make sense to lookup nodes here.
The second stage is when an individual request is made to access the data. This is where looking up data based on a URL might make sense, but there is already a built-in CurrentNode property that finds the node matching the current URL (or more likely the current route since we are dealing with MVC) which in most cases is the best approach to finding a node. Each node has a ParentNode and ChildNodes properties that can be used to walk up or down the tree from there.
In this second stage, you can access the SiteMap data at any point after the Application_Start event such as within a controller action, in one of the built in HTML helpers, an HTML helper template in the /Views/Shared/DisplayTemplates/ directory, or a custom HTML helper. This is the point in the application life cycle which you might call the lines SiteMaps.Current.FindSiteMapNode(rawUrl) or (more likely) SiteMaps.Current.CurrentNode to get an instance of the node so you can inspect its Attributes property (the custom attributes).
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
var currentNode = MvcSiteMapProvider.SiteMaps.Current.CurrentNode;
string permission = currentNode.Attributes.ContainsKey("permission") ? currentNode.Attributes["permission"].ToString() : string.Empty;
string programs = currentNode.Attributes.ContainsKey("programs") ? currentNode.Attributes["programs"].ToString() : string.Empty;
string agencies = currentNode.Attributes.ContainsKey("agencies") ? currentNode.Attributes["agencies"].ToString() : string.Empty;
// Do something with the custom attributes of the About page here
return View();
}
The most common usage of custom attributes is to use them from within a custom HTML helper template. Here is a custom version of the /Views/Shared/DisplayTemplates/SiteMapNodeModel.cshtml template that displays the custom attributes. Note that this template is called recursively by the Menu, SiteMapPath, and SiteMap HTML helpers. Have a look at this answer for more help if HTML helper customization is what you intend to do.
#model MvcSiteMapProvider.Web.Html.Models.SiteMapNodeModel
#using System.Web.Mvc.Html
#using MvcSiteMapProvider.Web.Html.Models
#if (Model.IsCurrentNode && Model.SourceMetadata["HtmlHelper"].ToString() != "MvcSiteMapProvider.Web.Html.MenuHelper") {
<text>#Model.Title</text>
} else if (Model.IsClickable) {
if (string.IsNullOrEmpty(Model.Description))
{
#Model.Title
}
else
{
#Model.Title
}
} else {
<text>#Model.Title</text>
}
#string permission = Model.Attributes.ContainsKey("permission") ? Model.Attributes["permission"].ToString() : string.Empty
#string programs = Model.Attributes.ContainsKey("programs") ? Model.Attributes["programs"].ToString() : string.Empty
#string agencies = Model.Attributes.ContainsKey("agencies") ? Model.Attributes["agencies"].ToString() : string.Empty
<div>#permission</div>
<div>#programs</div>
<div>#agencies</div>