Blazor pages: how to use BuildRenderTree to add dynamic content - asp.net

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

Related

Equivalent to Razor Section Helper In Sitecore

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()

Is there an ASP.NET WebControl for the <p> tag?

Simple question, difficult to search for (due to the main part of the question being the single letter p)!
In ASP.NET, <asp:Panel/> is rendered as a <div></div> block, <asp:Label/> is rendered as a <span></span> block... is there one that renders as <p></p> block?
It doesn't look like it from MSDN for the WebControl class, but I thought I would ask in case I've missed something obvious.
(I realise the obvious solution is to just use <p runat="server" id="p1"></p> and use the generic html control class)
No, there is no built-in control specifically for <p>. A LiteralControl or the <p runat="server" /> version you gave are the closest you will get.
You could always create your own control, though. You can create a class that implements WebControl, and override the Render method:
protected override void Render(HtmlTextWriter output)
{
output.WriteFullBeginTag("p");
output.Write(this.Text);
output.WriteEndTag("p");
}
There are more instructions on how to write your own server controls here:
http://msdn.microsoft.com/en-us/library/ms366537(v=vs.100).aspx
And a list of all of the .NET web and server controls here:
http://support.microsoft.com/kb/306459
I've had exactly the same issue. After I started to use a similar technique Cory uses here, I found an even easier one, that basically does the same and solves your issues. However, it has one benefit when compared with the solution above: you don't have to render the whole control yourself.
Basically all you need to do is the following:
Create you own control, inherit e.g. from the Label control.
Override the RenderBeginTag() method.
Write your own tag, e.g. p.
The end tag will be written automatically.
Your new tag will now replace the default tag (span in this example).
See code below:
public class P : Label
{
public override void RenderBeginTag(HtmlTextWriter writer)
{
writer.RenderBeginTag("p");
}
}
Although there is no special class for <p> element, you can use HtmlGenericControl to generate <p> tag like this:
var p = new HtmlGenericControl("p");

update CSS class dynamically for a whole website

I have a reference site for a series of books, and I'd like to make it so that readers can avoid spoilers. The idea I had was to make a setup webpage where they click on a checkbox for each book from which they want to see info. I could then store that (somehow) as a cookie for each time that they visit the site, plus have it work for each page in the site. So, one page might have this:
<li class="teotw">Rand killed a Trolloc</li>
and another page might have
<li class="teotw">Nynaeve tugged her braid</li>
and that information would not show up on the page unless they had checked the box for the "teotw" book. My initial thoughts are to do something like toggling the CSS class value like this:
document.styleSheets[0]['cssRules'][0].class['teotw'] = 'display:none';
document.styleSheets[0]['cssRules'][0].class['teotw'] = 'display:inherit';
but I'm not sure if this is the best method. Firstly, it would only apply to the current document only so I'd need a way to re-apply it to each page they visit. I'm using YUI as well, if it matters.
Any ideas on the best way of doing this?
There are many ways to implement it. You can use the YUI Stylesheet module (read its User Guide for how to use it) which will do what you're considering with cross-browser support and it's much easier to use than using the DOM directly.
Another way would be to add classes to the body of the page. You can define styles like this:
.teotw {
display: none;
}
.teotw-visible .teotw {
display: block;
}
With the following JS code:
if (someCondition) {
// show the hidden content
Y.one('body').addClass('teotw-visible');
}
For the persistance of the visible state you can use cookies with the YUI Cookie utilty or local storage with CacheOffline. Code using cookies would look something like this:
var body = Y.one('body');
if (Y.Cookie.get('teotwVisible')) {
body.addClass('teotw-visible');
}
// change the cookie
Y.one('#teotw-toggle').on('click', function (e) {
var checked = this.get('checked');
Y.Cookie.set('teotwVisible', checked);
body.toggleClass('teotw-visible', checked);
});
You should probably store the different sections in a JS object and avoid hard-coding class names in every JS line. Or maybe use a convention between checkboxes IDs and section class names.

How to inject a CSS file from an ASP.NET MVC partial view (server-side)?

I have a partial view (.ascx) that should include its own CSS file as it's used in multiple other views. How do I inject a stylesheet in the page server-side, i.e. without using JavaScript?
Dario - due to using this for partialviews, you're going to always have the problem that the <head> section of the document is already in place and therefore can't be modified. If you want to remain WC3 compliant, then you'll have to put any further css into the head section via javascript. This may or may not be desirable (if you've got to cater for downsteam browsers with javascript turned off).
the main problem that you may be aluding to is the fact that you can't put <asp:contentplaceholders> into your partials. this is a pain (tho understandable as the masterpage ref would tie the partial too closely to a particular master page).
To this end, I've created a little helper method that does the basic grunt work to put the css file into the head section automatically.
edit - (as per Omu's js suggestion) this is a nice little halfway house:
// standard method - renders as defined in as(cp)x file
public static string Css(this HtmlHelper html, string path)
{
return html.Css(path, false);
}
// override - to allow javascript to put css in head
public static string Css(this HtmlHelper html, string path, bool renderAsAjax)
{
var filePath = VirtualPathUtility.ToAbsolute(path);
HttpContextBase context = html.ViewContext.HttpContext;
// don't add the file if it's already there
if (context.Items.Contains(filePath))
return "";
// otherwise, add it to the context and put on page
// this of course only works for items going in via the current
// request and by this method
context.Items.Add(filePath, filePath);
// js and css function strings
const string jsHead = "<script type='text/javascript'>";
const string jsFoot = "</script>";
const string jsFunctionStt = "$(function(){";
const string jsFunctionEnd = "});";
string linkText = string.Format("<link rel=\"stylesheet\" type=\"text/css\" href=\"{0}\"></link>", filePath);
string jsBody = string.Format("$('head').prepend('{0}');", linkText);
var sb = new StringBuilder();
if (renderAsAjax)
{
// join it all up now
sb.Append(jsHead);
sb.AppendFormat("\r\n\t");
sb.Append(jsFunctionStt);
sb.AppendFormat("\r\n\t\t");
sb.Append(jsBody);
sb.AppendFormat("\r\n\t");
sb.Append(jsFunctionEnd);
sb.AppendFormat("\r\n");
sb.Append(jsFoot);
}
else
{
sb.Append(linkText);
}
return sb.ToString();
}
usage:
<%=Html.Css("~/Content/yourstyle.Css")%>
or:
<%=Html.Css("~/Content/yourstyle.Css", true)%> // or false if you want!!
worth a back-pocket approach if all else fails. it may also be possible to adapt the logic above to hit an actionfilter and add the css to the reponse headers etc.., rather than outputting the js string.
Is there a reason you cannot just put the stylesheet in the main view page? Unless it is extremely large, it should not cause your page to load significantly slower when not being used, especially after compression.
Furthermore, if the partial view is being retrieved by ajax, you will end up re-downloading the css file multiple times (depending on the browser's caching).
I'm not an ASP.NET or MVC expert, but I am currently working on a project with it. Would it be possible to include something like this in the head of your master page?
<asp:ContentPlaceHolder ID="HeadContent" runat="server"></asp:ContentPlaceHolder>
Then somehow get your CSS into the content placeholder?
<asp:Content ID="styleSheetHolder" ContentPlaceHolderID="HeadContent" runat="server">
<link rel='stylesheet' href='yourstyle.css' type='text/css' />
</asp:Content>
It looks like you'd have to do this from the calling page, which would introduce duplication of code. I'm not sure how to get around that. Maybe it could it be done with an HTML helper.
A place I worked had a site with a lot of conditional partial views, which could and would change from time to time as new things were added to the application. The best way we found was to use naming conventions - naming the partial view, stylesheet and script the same (except extension, of course).
We added one or more stylesheets, scripts and partial views to the page's view model. We then used a custom HTML helper to loop through the relevant array in each content section and insert the required content. The pain was having to manage three or more files for each component, but at least we were able to reduce the amount of maintenance in the hosting pages.
Never did resolve the problem of putting a partial view inside a partial view, though.
Why not put the link tag in your partial view:
<link rel="stylesheet" href="yourstyle.css" type="text/css" />

Dynamically adding CSS cuts down page weight?

Ok, this is bizaare and the only shop I've seen do this..granted we only have 2 developers in this shop. My boss thinks that adding css files individually to pages using a helper method makes our site more efficient because when adding the style references in the Masterpage, then every page gets those styles making the site load slower.
So we have a company.css which is our main styles for stuff that is global like the main buckets such as header, footer, main, etc.
Then he wants me to go into each code-behind of lets say a certain set of pages for one of our features and use a utility method we created to dynamically add the product-related style sheet to those pages. So for instance lets say we have a Reviews & Ratings feature. We put all our reviews & ratings related classes and ids in Reviews.css. Ok great.
So for instance in the .aspx.cs pages Init, we'd put this:
Util.AddStylesheet("Ratings.css");
Now he wants me to include that stylesheet in the codebehind of those pages where our reviews code touches in .aspx pages. But says do not put a reference to Reviews.css in the master page.
Am I the only one who questions this method and says what's the point of having a master page then? I mean really, if you put all your .css references in your master page does the argument of saying that now all pages have all these styles (some not used because they do not relate to that page) add weight to your load time for end users?
I mean common right? Maybe people do this but I've never seen it and it's a management nightmare cause you have references all over the fing place dynamically adding certain .css pages to subsets of .aspx pages.
To me, even though we are an e-commerce site that receives 1 million hits a month, please, I mean seriously adding stylesheets manually and bypassing just putting them all in a master page just boggles my mind. Am I the only one who thinks this is nuts and questions his assumption that doing that would make page weight excessive because they would be inheriting all stylesheets, some which would be wasted .css because it doesn't necessarily relate to that particular page? For me, adding them to the master page is a done deal...simple, maintainable, and who cares.
I can understand page weight with JS, but .css?
What you actually want to do is minimize the number of total http requests made, as well as the total bandwidth used. If you have your reviews.css file linked from the master page, you need an extra http request for every page on your site, whether not you ever use the styles. So in that sense, your boss is right. Don't put this on the master page if it won't be used from all or most of your pages.
That said, I wouldn't have anything in the code-behind for this either, but rather a user control you can put on the required pages to render a style section or link.
I actually do do the compromise fairly often.
Most pages in my sites use all the standard css files declared in the masterpage but there's the odd one page here and there that use a special stylesheet. A high resolution view, a print view or maybe a jquery lighbox/gallery type control.
My ad-hoc stylesheet loading code is as follows:
using System;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
namespace ExtensionMethods
{
public static class PageExtensions
{
public static void AddStyleSheet(this Page page, String Href)
{
PageExtensions.AddStyleSheet(page, Href, "all");
}
public static void AddStyleSheet(this Page page, String Href, String Media)
{
Href = VirtualPathUtility.ToAppRelative(Href);
if (!PageExtensions.StyleSheetAlreadyExists(page, Href))
{
HtmlLink htmlLink = new HtmlLink();
htmlLink.Href = Href;
htmlLink.Attributes.Add("rel", "stylesheet");
htmlLink.Attributes.Add("type", "text/css");
htmlLink.Attributes.Add("media", Media);
page.Header.Controls.Add(htmlLink);
}
}
static private bool StyleSheetAlreadyExists(Page page, String Href)
{
var preExisting = from Control c in page.Header.Controls
where c is HtmlLink && ((HtmlLink)c).Attributes["Href"] == Href
select c;
return preExisting.Any();
}
}
}
Remember, by default CSS files are being CACHED in the browser, so CSS will be loaded only ONCE anyway.
From my experience more seperate CSS files == more duplicated rules == bigger mess.
If you want to save up bit of bandwidth make sure you compress your CSS when writing it, so instead of:
#review
{
margin-top:10px;
margin-left:12px;
margin-bottom:12px;
margin-right:35px;
background-color:#ffffff;
background-image:url(images/blah.png);
background-repeat: repeat-x;
}
You write:
#review{margin:10px 35px 12px 12px;background:#fff url(images/blah.png) no-repeat;}
How about suggesting a compromise?
One master page with the most-commonly used elements defined (headers, links, navigation lists, etc) and then a location-specific stylesheet, ie catalogue.css, for such pages as need those styles. So the css is still able to be cached, but isn't forced on everyone that just visits the about-us page.
It's not ideal, but if the goal is to reduce the bandwidth of a single monolithic css file, it might get you some headway.
As it is, my personal approach is always to use the monolithic stylesheet; though having thought about it I'm tempted to try the suggested approach. It should work? Maybe... =/

Resources