I am trying to include different scripts on different pages in Sitecore and can't seem to find a very good way of doing this. In a normal mvc project I could add the #Section{} helper and use that on different partial views to specify where I want those scripts in the layout view, but I haven't been able to find an equivalent for the way that Razor helper is implemented with Sitecore. I'd like to do this without using a place holder, I don't want to add a view in Sitecore every time I need to add a script file.
Thanks in advance.
I'm afraid you're out of luck here.
#Section is not supported because Sitecore doesn't render the Razor views in the same way as MVC does.
A Sitecore MVC layout is basically just a regular view that is rendering several other partial views or controller actions.
So when the placeholders in the <body> of your layout view are being rendered, the <head> section of that layout has already been rendered.
There is no such thing as deferred rendering in Sitecore MVC like you can do with #Section.
Everything in the view is executed from top to bottom, so if you can put your scripts at the end of your layout (like before the </body>), you can still manipulate data in the views or actions that are executed earlier.
The way I have it setup in my current Sitecore MVC solution is my layout has an extension method call to RenderScripts() at the bottom before the closing body tag.
#Html.RenderScripts()
That extension method looks like this:
public static IHtmlString RenderScripts(this HtmlHelper htmlHelper)
{
var templates = (from object key in htmlHelper.ViewContext.HttpContext.Items.Keys
where key.ToString().StartsWith("_script_")
select htmlHelper.ViewContext.HttpContext.Items[key]).OfType<Func<object, HelperResult>>()
.Select(template => template(null)).ToList();
foreach (var template in templates)
{
htmlHelper.ViewContext.Writer.Write(template);
}
return MvcHtmlString.Empty;
}
Then on each MVC Razor View when I want to include a .js file that is specific to that rendering I call something like below at the bottom of the file:
#Html.Script(
#<script src="#Url.Content("~/js/custom/orderdetail.js?t=11172015")" type="text/javascript"></script>
)
Below is the Script extension method:
public static MvcHtmlString Script(this HtmlHelper htmlHelper, Func<object, HelperResult> template)
{
htmlHelper.ViewContext.HttpContext.Items["_script_" + Guid.NewGuid()] = template;
return MvcHtmlString.Empty;
}
This has worked out well for us and I think it is what you are trying to do.
Indeed, the Section-helper isn't supported in Sitecore. If you're using MVC4 you can maybe use Bundles to solve your problem. For more information see: http://www.asp.net/mvc/tutorials/mvc-4/bundling-and-minification
You can also create multiple bundles for specific views. In a single Bundle you can add multiple script and output it in your view by adding #Scripts.Render()
Related
Background: We run dozens of sites for clients; all use an identical code base (ASP.NET WebForms) but completely different designs. Page structure is generated programmatically from SQL Server meta-data, using controls such as Panel, which are added to the ASP.NET page's Controls collection, and become DIVs in the rendered HTML.
Objective: We want to migrate eventually to ASP.NET CORE. However, there seems to be no equivalent to the page's controls collection. The closest thing I can find is the RenderTreeBuilder to add a Blazor component.
Question: Is it possible use BuildRenderTree to add a component which contains our home-baked HTML (for instance, to contain everything between <body> and </body>?
I've read articles such as:
https://chrissainty.com/building-components-via-rendertreebuilder/
https://learn.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#manual-rendertreebuilder-logic
... and experimented with adding HTML elements, but it's extremely cumbersome, and I'd like to programmatically generate the HTML for pretty much the whole page, and add it as one RenderFragment (is that the right term?).
Is this possible? Is there an alternative?
Edit:
#Henk's answer, using the MarkupString struct, and mine, using RenderTreeBuilder.AddMarkupContent seem similar in terms of effort and plumbing required.
Are there any pros and cons of the two approaches I should consider?
If you just want HTML (plain, dead) then you don't need a rendertree:
<h1>My Page</h1>
#MyHtmlComposer()
#code{
private MarkupString MyHtmlComposer()
{
string html = "<p>Hello</p>";
return new MarkupString(html);
}
}
I hadn't come across the MarkupString struct, so #Henk's answer is really helpful. I've now also come across the RenderTreeBuilder.AddMarkupContent method, so I'll offer this as an alternate answer:
My markup:
#page "/"
<PageBuilder></PageBuilder>
PageBuilder is a class that inherits from ComponentBase:
public class PageBuilder : ComponentBase
{
protected override void BuildRenderTree(RenderTreeBuilder b)
{
base.BuildRenderTree(b);
b.OpenElement(0, "div");
b.AddMarkupContent(1, TheContent());
b.CloseElement();
}
public string TheContent()
{
return "<div>This is the generated content</div>";
}
I'll edit my original question a little, as I'd like to know whether there's anything to choose between this approach and #Henk's.
You can also render a string as HTML like this:
#page "/test";
<h1>My Page</h1>
#((MarkupString)content)
#code{
//Get content from Database
string content = "<p>Hello</p>";
}
See section "Raw HTML" here
I know what View Engine is, I preferred to use Razor view engine just because of its simple syntax over ASPX engine. Inbuilt view engine performs almost all task for you, then in what scenario I should create my own view engine,
I googled it but getting answers for How to create it and not when and why to create it.
Can any one help me to describe the real time scenario?
For example, you can change the view files locations that Razor searches with the help of custom view engine.
Normally, in MVC these locations are searched for partial views:
// Part of the RazorViewEngine implementation from the Asp.net MVC source code
PartialViewLocationFormats = new[]
{
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml"
};
Then add for example LayoutsPartialViews folder to Shared folder and add partial views which for example will be used only for layouts. And add for example ColorfuleHeader.cshtml to that location. And try to render that view via this:
#Html.Partial("ColorfulHeader");
Such exception will be throwned:
The partial view 'ColorfulHeader' was not found or no view engine
supports the searched locations. The following locations were
searched...:
So we must add this location to the searched locations. And for doing this we must create our custom view engine:
public class CustomLocationViewEngine : RazorViewEngine
{
public CustomLocationViewEngine()
{
PartialViewLocationFormats = new[] {
"~/Views/{1}/{0}.cshtml",
"~/Views/{1}/{0}.vbhtml",
"~/Views/Shared/{0}.cshtml",
"~/Views/Shared/{0}.vbhtml",
"~/Views/Shared/LayoutsPartialViews/{0}.cshtml",
"~/Views/Shared/LayoutsPartialViews/{0}.vbhtml",
};
}
}
Also, remember that the action invoker goes to each view engine in turn to see if a view can be found. By
the time that we are able to add our view to the collection, it will already contain the standard Razor View
Engine. To avoid competing with that implementation, we call the Clear method to remove any other
view engines that may have been registered, and then call the Add method to register our custom
implementation.
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new CustomLocationViewEngine());
I'm converting an MVC aspx content place holder -> master page to an MVC razor section -> layout.
In the past when my aspx view came to something like this:
<asp:Content ID="HelpContent" ContentPlaceHolderID="HelpLink" runat="server">
Help
</asp:Content>
And the master page didn't have a corresponding HelpContent place holder (perhaps because a user was not authenticated) everything rendered fine (with no HelpContent section).
Now when I have a razor section defined that does not have a corresponding #RenderSection in the layout, I get this error:
The following sections have been defined but have not been rendered
for the layout page "~/Views/Shared/New.cshtml": "HelpLink".
Do I need to redesign this?
Is there a way I can have the view's HelpLink section render optionally if the layout gives it the green light?
EDIT:
I think there's some confusion, so let me re-summarize:
The layout logic looks like this:
if (User.IsLoggedIn) {
#RenderSection( "HelpLinks", false);
}
But then the user isn't logged in, it doesn't render, and then .NET throws an exception because the layout doesn't know what to do with the section.
You can indicate that the section is optional by passing false as the second argument:
#RenderSection("HelpLink", false);
Edit: In the case of control flow logic for rendering, you can use .NET in the razor view (like this c# example):
#if(IsSectionDefined("HelpLink"))
{
#RenderSection("HelpLink", false);
}
Or, if you want to base rendering on whether the user is logged in, you could replace the if logic in the above sample with your security check.
Edit 2:
Make sure you have defined the section:
#section HelpLink {
//This needs to be defined in any view that uses the layout with the #RenderSection. It can be empty.
}
Alternatively, you can add the check to see if the section exists and only define the #section in the required view:
if (IsSectionDefined("HelpLink") && User.IsLoggedIn) {
#RenderSection( "HelpLinks", false);
}
If a section is declared in a razor view it has to be rendered in the layout.
I found this in Freeman's Pro ASP.NET MVC 5 book.
Seems like a bad design to me.
In my view, would like to render the contents of an HTML file as a partial view. It is giving me this error though when I add this to the .cshtml view:
#Html.Partial(Url.Content("~/Test/main.html"))
Errors:
Exception Details: System.InvalidOperationException: The partial view '/Scripts/main.html' was not found or no view engine supports the searched locations. The following locations were searched:
/Scripts/main.html
The file is physically there though. Is there a different way I should be doing this?
You can't use Html.Partial for this.It is a special helper method for rendering Partial Views. Instead you can add an Action like this:
[ChildActionOnly]
public ActionResult GetHtmlPage(string path)
{
return new FilePathResult(path, "text/html");
}
And you can call it from your View with using Html.Action helper Method:
#Html.Action("GetHtmlPage","controllername", new { path = "~/Test/main.html" })
Follow these steps
Create a view in ~/views/shared folder. give it name test.cshtml.
Copy the content of HTML in it.
Use Html.Partial("test") on page to render the html of that view.
I think it's a better solution:
Use WriteFile from the Response object
#Response.WriteFile(pathToMyHtmlFile)
taken from here
The simple answer is to rename the main.html file to main.cshtml which will be recognized by the Razor view engine for rendering.
You could use this in your cshtml view:
#{this.GetOutputWriter().Write(File.ReadAllText(Server.MapPath("/your/static/file.html")));}
A total different way is just load the file in javascript
<div id="loadId">
</div>
<script>
$(document).ready(function () {
$('#loadId').load('filepath');
});
</script>
You can set the filepath in the controller, or put it in the model, if you want, and use:
$('#loadId').load('#ViewBag.filepath');
Just use an ajax request to fetch the html file output the content into your main html. Since it is an html file, it will be cached in subsequent calls and there will be no performance impact.
I want to use pure html page instead of cshtml with MVC .net.
But when I add view by right clicking Index i can see only two options.
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}}
Cshtml (Razor)
Aspx
I followed
Can I serve .html files using Razor as if they were .cshtml files without changing the extension of all my pages?
forum but still no help. I still don’t see an option to add html instead of cshtml
I also tried adding html page directly to view folder but i dont know how to point that view from my controller function.
Replacing Index.cshtml with Index.html gave me this error
The view 'Index' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Home/Index.aspx
~/Views/Home/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
~/Views/Home/Index.cshtml
~/Views/Home/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.vbhtml
In order to render plain HTML file you can use
return new FilePathResult(HtmlPath, "text/html");
where HtmlPath is
Server.MapPath(string.Format("~/Views/{0}/{1}.html", YourControllerName, YourHtmlfileName))
You can create a View with a regular cshtml file add it to the controller and in the View itself just use pure html and add the following to the top:
#{
Layout = null;
}
This way you use a cshtml file that doesn't use you master layout file. And just serves whatever html you put in it.
If it's a static html file you don't need to point your controller to it, because IIS can serve them just fine. Add an html file to your project (anywhere but NOT in the viewsfolder), and point your browser to that file.
If you right click on your Content folder you can select 'Add new item...'. You can then select 'Web' in the tree on the left and choose the 'HTML Page' on the right.
This should add you an HTML page.
Add a file. Rename it.
F2 in solution explorer.