Using CsQuery in MasterPage code-behind to modify HTML output? - asp.net

What's the "skeleton" code for using CsQuery in the code-behind of a MasterPage in order to modify the HTML output? I need to be able to modify everything in the <body> of the HTML?
I'm hoping to use CsQuery to "touch-up" the HTML output of a Dynamic Data website without rewriting / messing with the default code.
Just looking for sample code specific to MasterPage code-behind? Thanks.

There is an example in the CsQuery project that shows how to do this (which I just made sure was working right!) in the CsQuery.WebFormsApp project.
The core of the usage looks like this. You must override the Render method in a class that inherits Page, and use this instead of Page as the base class for the codebehind in an aspx page:
public class CsQueryPage: System.Web.UI.Page
{
public CQ Doc { get; protected set; }
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
// most of the work is done for you with the
// `CsQuery.WebForms.CreateFromRender` method
var csqContext = WebForms.CreateFromRender(this, base.Render, writer);
// if you are using update panels, this lets you also manipulate that
// HTML, otherwise you don't need the IsAsync part
if (csqContext.IsAsync)
{
foreach (var item in csqContext.AsyncPostbackData) {
Cq_RenderUpdatePanel(item.Dom,item.ID);
}
}
else
{
Doc = csqContext.Dom;
Cq_Render();
}
// writes the altered content to the base HtmlTextWriter
csqContext.Render();
}
protected virtual void Cq_Render()
{ }
protected virtual void Cq_RenderUpdatePanel(CQ doc, string updatePanelId)
{ }
}
The two virtual methods are where you can alter the dom, which is populated in the Doc property of the CsQueryPage object - the intent of leaving them unimplemented here is that each aspx page that inherits CsQueryPage can optionally override them and make changes to the DOM.
To see how this works in practice just pull down the CsQuery code from github and run the example.
The same technique can be used for a UserControl which is also shown in the example. I don't actually show how to do it with MasterPage but it's very much the same-- MasterPage derives from UserControl, you just override it's Render method same as the other situations.

Related

Alternative to ClientScript.RegisterClientScriptBlock?

The ASP.NET function ClientScript.RegisterClientScriptBlock can be used to register a chunk of JavaScript code that will be added to the page when it's rendered. The idea here is that you could have multiple instances of various user controls trying to register the same script over and over, but this ensures that it will only be included once.
The problem is, you don't really have any control over where the code is added to the page. This will insert the code inside the BODY tag of your page, but I need to add something (not limited to JavaScript code) into the HEAD block.
I'm well aware of various methods of adding something to the HEAD block via a ContentPlaceHolder block or by "Head.Controls.Add but these options do not address the problem of the same thing being added multiple times.
Is there an existing way to do this, or do I have to write a class that does something similar to ClientScript.RegisterClientScriptBlock except targeting the HEAD block?
I threw together a user control. There's nothing in the markup at all, so you can just add a new Web Forms User Control to your project and put this in the code behind:
public partial class ScriptControl : System.Web.UI.UserControl
{
private Dictionary<string, string> _scripts = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public void RegisterScript(string key, string script)
{
if(!_scripts.ContainsKey(key)) _scripts.Add(key, script);
}
protected override void Render(HtmlTextWriter writer)
{
writer.WriteFullBeginTag("script");
foreach(var script in _scripts.Values) writer.Write(script);
writer.WriteEndTag("script");
}
}
Just add the directive to your page markup:
<%# Register TagPrefix="uc" TagName="ScriptControl"
Src="ScriptControl.ascx" %>
(where "ScriptControl.ascx" is whatever you've named the control)
and then you can add it wherever you need to, including in the header.
<head runat="server">
<uc:ScriptControl id="HeaderScriptControl" runat="server"/>
</head>
The usage is pretty much the same:
HeaderScriptControl.RegisterScript("myScript",
#"alert(""hello, world!"")");

Using ASP.NET Server Controls in MVC?

On my current project I need to add a functionality that allows the user to view a thumbnail of their uploaded PDF. I've found a handy component that achieves this (the basic version is free, but it's enough for my current needs). Anyways, the control is pretty outdated (2010), therefore there doesn't seem to be MVC support. On the demos they depict usage of the control as such:
The View's Markup:
<form method="post" runat="server" enctype="multipart/form-data">
<asp:Panel ID="thumbnailsPanel" runat="server" />
</form>
The thumbnail control is instantiated via code, the byte array which represents the thumbnail is passed to the control and the control is added to thumbnailsPanel
<script runat="server">
protected void DisplayThumbs_Click( object sender, System.EventArgs e )
{
Thumbnail thumbnail = new Thumbnail();
thumbnail.SessionKey = sessionID;
thumbnail.Index = i;
thumbnailsPanel.Controls.Add( thumbnail );
}
</script>
Given that I can't declare a Thumbnail control in my razor view, how would I used this control in MVC? I've spent a few hours trying to make this control MVC friendly to no avail, the best I've come up with is to include a .ASPX view (not.cshtml) in my project and render the Thumbnail control on that view. Obviously this is not desirable.
So how would you go about using a ASPX server controls in MVC? Is the idea a bad one altogether and should not be practised?
I worked around it in a project of mine by reimplementing the control as a HtmlHelper. Assuming the control isn't too complicated then it should work for you too. Do this:
Dump the Control's source using Reflector
Massage the source so it actually compiles (as source from Reflector doesn't usually compile straight away)
Identify what state the control has. Convert the state from member properties into members of its own new ViewModel class.
Find the Render method and convert it to a HtmlHelper that uses ViewContext.Writer
For example:
public class FooControl : Control {
public String Message { get; set; }
public override void Render(HtmlTextWriter wtr) {
wtr.WriteLine("<p>");
wtr.WriteLine( message );
wtr.WriteLine("</p>");
}
}
Becomes this:
public class FooViewModel {
public String Message { get; set; }
}
// This method should exist in a static Extensions class for HtmlHelper
public static void Foo(this HtmlHelper html, FooViewModel model) {
HtmlTextWriter wtr = html.ViewContext.Writer;
wtr.WriteLine("<p>");
wtr.WriteLine( model.Message );
wtr.WriteLine("</p>");
}

ASP .NET MVC 2: Dynamic themes

I would like to change dynamically the page theme in a MVC 2 Application.
I found several solutions, but I want to use this method: in the Global.asax, change the current page theme:
protected void Application_PreRequestHandlerExecute(object sender, System.EventArgs e)
{
// cast the current handler to a page object
Page p = HttpContext.Current.Handler as Page;
if (p != null)
{
string strTheme = "Theme1";
if (Convert.ToString(HttpContext.Current.Session["THEME"]) != string.Empty)
strTheme = Convert.ToString(HttpContext.Current.Session["THEME"]);
p.StyleSheetTheme = strTheme;
}
}
But this code always returns null in "p"...
I've also tried a similar code using the event PreRequestHandlerExecute in a HttpModule and the PreInit event of a page, but the code
HttpContext.Current.Handler as Page
always returns null.
Can anyone help me?
Thank you in advance.
I don't use baked in themes, but I do use jQuery UI themes. The way I handle it is in my master page I have logic that gets the current theme from a common viewmodel. The master page is strongly typed to this view model. The common viewmodel properties are updated from user preferences and other sources in a common base controller that all my controllers inherit. I do this in OnActionExecuted. I check if the result is a ViewResult, then cast the result from ViewData on the ActionExecutedContext.Result property to my common view model and set the property. The master page uses the value of the property to build the url for the CSS file.
Model
public abstract class CommonViewModel
{
public string Theme { get; set; }
// ...
}
Controller
public abstract class BaseController : Controller
{
public override void OnActionExecuted( ActionExecutedContext context )
{
if (context.Result is ViewResult)
{
var model = ((ViewResult)context.Result).ViewData.Model as CommonViewModel;
if (model != null)
{
var preferences = ...get from database for current user...
model.Theme = preferences.Theme;
}
}
}
}
Master note it uses a custom HtmlHelper to generate the stylesheet link, you could
do it by hand.
<%# Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage<...CommonViewModel>" >
<%: Html.Stylesheet( "themes/" + Model.Theme + ".css" ) %>
The technique you are talking about works for standard asp.net, not asp.net MVC. The reason is that (in general) asp.net MVC does not use the web control model that standard asp.net does, and as such there is nothing to interpret the theme setting.
#tvanfosson has some great advice. Just remember that with MVC, you have much more control over things.. but that also means you have to do more work to get some of the features that standard asp.net provides for free. MVC makes many things easier, but this is not one of them.

Porting old pages to use masterpages

I have hundreds of legacy webform pages adding header and footer via a BasePage overriding Render
protected override void Render(HtmlTextWriter writer)
{
RenderHeader(writer);
base.Render(writer);
RenderFooter(writer);
}
New pages uses a MasterPage for the default behavior.
I would like to know if it's possible to add the asp:content control from the BasePage without changing every *.aspx?
I made a small test that's working as long there's no content in the aspx
public partial class OldPage : Page
{
private MainContentTemplate mainContentTemplate;
protected override void OnPreInit(EventArgs e)
{
Page.MasterPageFile = "~/Site.Master";
mainContentTemplate = new MainContentTemplate();
AddContentTemplate("Main", mainContentTemplate);
base.OnPreInit(e);
}
}
public class MainContentTemplate : ITemplate
{
#region ITemplate Members
void ITemplate.InstantiateIn(Control container)
{
container.Controls.Add(new LiteralControl("Test string"));
}
#endregion
}
But as soon as i add something to the code in front I will receive:
Content controls have to be top-level controls in a content page or a nested master page that references a master page.
I'm not sure but I think that overriding ControlCollection Controls might help, but I haven't find a solution.
This is no easy fix but I ended up with doing a massive search and replace in the old code base. It took a couple of days, but I think it was worth the effort since every page is now running against the same code base. It will always remind me that keeping the front end DRY is quite as important as taking care of the back end.

asp.net output data from DB without any html markup

I'm new to jquery and asp.net so please forgive if this is an obvious question. I'm using a jquery autocomplete plugin which requires that the page it looks up asynchronously for data is in this format as pure text only:
product1|price1
product2|price2
product3|price3
WITHOUT ANY OTHER HTML MARKUP. Any other html tags seems to cause problems.
Normally for a page like that I would use a repeater and some standard database calls and output the 2 fields. This however creates html tags.
How can I output this data as text only with no other markup whatsoever?
If you have a bare page with no master page referenced a repeater shouldn't produce any html. Make sure in the HTML view you only have:
<asp:Repeater ID="outRepeater" runat="server">
- your template here
</asp:Repeater>
An alternative would be to add a new Handler to your project which is a class that implements the IHttpHandler interface. This would allow you to output your code directly. This would end up looking like:
public class MyOutputHandler : IHttpHandler {
public bool IsReusable { return false; }
public void ProcessRequest(HttpContext context) {
context.Response.Write("product1|price1");
}
}
If you have added this to a project as a new Handler (from add items) it should have a .ashx extension. Otherwise you'll need to register it in your web.config with its type and filename.

Resources