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.
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.
I have an app full of pages and user controls, many of which have one or more label controls on them for displaying various different messages to the user.
Example: the AddCompany.ascx user control, normally used on the Company.aspx page (with App.Master MasterPage) has a label on it called "OutOfCreditLabel" with Text="Error: You cannot create a new company, as you are out of credit". The code checks the credit and shows the label if needed.
I want to get rid of all these labels all over the place and just have a method I can call from anywhere like
ShowUserMessage("Text goes here");
In previous projects I've implemented this as a single label on the master page and public method in the .master.cs - as long as the page has the line
<%# MasterType VirtualPath="~/App.master" %>
in it, this works - but I can't get it to work on user controls (.ascx.cs). Is there a trick to this?
Or... Is there a better way?
What would you recommend for a "global" user message method that works from anywhere in the site?
Any nice jQuery solutions, perhaps?
Update
RPM1984 has asked for further clarification, so I'm trying to ask this a different way:
I need a method I can call from the code-behind (of a page or a user control) which will then display whatever text I specify to the user, like how stackoverflow tells you about new answers to your question when you next visit the site. It can be up the top of the window (like SO) or somewhere on the page, doesn't matter. What matters is that it's a "global" approach, rather than having various javascript alerts and asp:label's littered over every other page and user control in the project.
Something like this scenario:
A user clicks the "Create Widget" button on my "widget manager" user control on my page. In the event handler is:
if (User.IsOutOfCredit)
{
ShowUserMessage("Sorry, you cannot create widgets; you are out of credit.");
}
This results in the user seeing "Sorry, you cannot create widgets; you are out of credit." Either in a pop-up or red text in the page somewhere or at the top like StackOverflow, anything is fine
Does that make sense?
Why not a simple HTML "old-school" extension method? (i.e static method)
namespace Web.Helpers
{
public class HtmlHelpers
{
public static string Label(string target)
{
var dynamicText = SomeWhere.GetSomeCoolText();
return String.Format("<label for='{0}'>{1}</label>", target, dynamicText);
}
}
}
Then in your HTML:
<%= HtmlHelpers.Label("sometarget") =>
In your main master page, just import the namespace:
<%# Import Namespace="Web.Helpers" %>
EDIT after question updated
Ok, i see what you're trying to do now. I would recommend doing it all client-side.
That is, in your main master page, create an initially hidden <div> with a unique id:
<div id="message" style="display: none"></div>
Then create a helper method somewhere in your web project, rendering out some script.
public void DisplayMessage()
{
string message = SomeWhere.GetSomeCoolText();
StringBuilder script = new StringBuilder();
script.AppendFormat("$('message').show().append("<span>{0}</span>")", message);
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), script.ToString(), true);
}
Of course, this is just a guideline. You dont have to use jQuery, you don't have to hardcode the script in the method. You could create a JavaScript function that accepts a string and modifies the HTML of the div, then just call the function.
It all depends on how complicated your "message" is, if you need special server controls, internationalization, etc.
But this is certainly the easiest way to accomplish what you want (IMHO).
Personally, I'd prefer using the HttpContext.Current.Items dictionary with a custom control. Similar to Keith Bluestone's approach, but doesn't require an arbitrarily named control on the page; it makes it very clear what's going on. You could package it into one class as a server control easily enough, but here it is to drop into a standard web project.
To register a message in your code behind:
SiteMessageUtility.Add("Test message");
To display in your page, or master page, or wherever, really:
<%# Register TagPrefix="custom" TagName="SiteMessage" Src="~/Controls/SiteMessage.ascx" %>
<custom:SiteMessage runat="server" />
Here's the files you'll need:
~\App_Code\SiteMessageUtility.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
public static class SiteMessageUtility
{
public static void Add(string message)
{
string currMessage = HttpContext.Current.Items["message"] as string;
if (currMessage == null)
{
HttpContext.Current.Items["message"] = message;
}
else
{
HttpContext.Current.Items["message"] = currMessage + "<br/>" + message;
}
}
}
~\Controls\SiteMessage.aspx
<%# Control Language="C#" AutoEventWireup="true" CodeFile="SiteMessage.ascx.cs" Inherits="Controls_SiteMessage" %>
<asp:Literal runat="server" ID="message" />
~\Controls\SiteMessage.aspx.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class Controls_SiteMessage : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected override void OnPreRender(EventArgs e)
{
message.Text = (string)HttpContext.Current.Items["message"];
base.OnPreRender(e);
}
}
On the master page add the following controls:
the hfMsg hidden field will hold the message that will be displayed
and the hfCtrl will hold the name of the html control that will display this message which will be in this example lblMsg.
<label id="lblMsg" style="background-color:Yellow; border:solid 1px; width:200px;height:100px;display:none;"></label>
<asp:HiddenField ID="hfMsg" runat="server" />
<asp:HiddenField ID="hfCtrl" runat="server" />
NOTE:you can add another html control with a different id on an ASCX control and use it to display the message instead of the lblMsg.
and the following script:
<script language="javascript" type="text/javascript">
var msg = $('#<%= hfMsg.ClientID %>').attr("value");
var ctrl = $('#<%= hfCtrl.ClientID %>').attr("value");
if (msg != undefined && msg != "") {
$("#" + ctrl).html(msg);
$("#" + ctrl).show();
}
else {
$("#" + ctrl).hide();
}
</script>
NOTE:the script simply checks to see if the hfMsg control has a message to display or not.
and add the following two methods to the master page:
public void ShowMessage(string control, string message)
{
this.hfCtrl.Value = control;
this.hfMsg.Value = message;
}
public void ClearMessage()
{
this.hfMsg.Value = string.Empty;
}
finally you can call the ShowMessage method from any page like this to display a message on the master page:
Master.ShowMessage("lblMsg","hello world!");
and if you have a user control that holds an html label (e.g lblUserMsg) to display the message you can simply call ShowMessage and pass it the name of the label:
Master.ShowMessage("lblUserMsg","hello world!");
Having a SetStatus method in the MasterPage is always the way I've done this, to access the MasterPage from inside a user control, just create a private property in the usercontrol (or create a baseControl class that all user controls inherit from):
private TheNameSpaceOfMyMasterPage.MyMasterPage Master
{
get { return (TheNameSpaceOfMyMasterPage.MyMasterPage)Page.Master; }
}
protected void btnSave_OnClick(object sender, EventArgs e)
{
// Do your stuff...
// Set the Status
this.Master.ShowStatus("blah blah blah");
}
Also if you're using a control to hold the status message remember to disable ViewState on it, otherwise you'll end up with the same status message across postbacks which you don't want.
Some good answers, but...
To Robert W: the master page impl works, but it does require strong coupling (knowledge of) with the master page type. Master pages should generally affect style only, right? E.g. I should be able to change master pages without breaking my app.
SubPortal: yes, very clever, but... too clever for me. Also has master page issue.
RPM1984: again, too clever for me!
MGOwen didn't mention any special DHTML requirements (e.g. update from client side). So why not just implement a simple server-side helper method as he mentioned, find the target control which displays the message, and display it?
The following ShowMessage will find the target message control wherever it is on the page, as well as enable any page to override the default application's ShowMessage behavior if desired.
public static class ASPHelper {
// Defines something that shows a message
public interface IShowMessage
{
void ShowMessage(string msg);
}
// default implementation: finds a target message display control
// wherever it is on the page.
static void ShowMessage_Default(string msg)
{
const string SHOWMESSAGE_CONTROL_ID = "ctlShowMessage"; // or whatever
// find the control
Page currentPage = HttpContext.Current.CurrentHandler as Page;
Control ctlMessage = currentPage.FindControlRecursive(SHOWMESSAGE_CONTROL_ID);
if (ctlMessage == null)
throw new Exception("ShowMessage control not found!" );
Literal ctlMessageLiteral = ctlMessage as Literal;
if (ctlMessageLiteral == null)
throw new Exception("ShowMessage control must be a Literal!");
// Message control should not implement viewstate -- unneccesary
Debug.Assert(ctlMessageLiteral.EnableViewState == false);
// show it
ctlMessageLiteral.Text = msg;
ctlMessageLiteral.Visible = true;
}
// public version for use across app & in pages
static public void ShowMessage(string msg)
{
// Allow the current page to implement its own way of showing
// messages -- provides flexibility.
Page currentPage = HttpContext.Current.CurrentHandler as Page;
IShowMessage showMessageImpl = currentPage as IShowMessage;
if (showMessageImpl != null)
{
// Show using custom message displayer
showMessageImpl.ShowMessage(msg);
}
else
{
// static/global/default version
ShowMessage_Default(msg);
}
}
} // end ASPHelper
To use, simply place a literal control on the page, as in:
<div><asp:Literal
ID="ctlShowMessage" runat="server"
Visible="false"
EnableViewState="false"
/>
</div>
To use, simply call ShowMessage, as in ASPHelper.ShowMessage("insufficient credit"). Hope this is what you're looking for... Keith
Javascript safe popup. Works with Ajax/jQuery.
Namespace Utilities
''' <summary>
''' Utility class for injecting JavaScript into pages. Used primarily for throwing a JavaScript alert box, with correct focus.
''' </summary>
''' <remarks></remarks>
Public Class WriteJava
Region "Public Subs"
Public Shared Sub Script(ByVal script As String, ByRef p As Page, Optional ByVal scriptName As String = "")
If String.IsNullOrEmpty(scriptName) Then
scriptName = GetScriptName(p)
End If
If Not p.ClientScript.IsStartupScriptRegistered(scriptName) Then
ToolkitScriptManager.RegisterStartupScript(p, p.GetType, scriptName, script, True)
End If
End Sub
Public Overloads Shared Sub Alert(ByVal MyAlert As String, ByRef p As Page)
Script("alert('" & Format(MyAlert) & "');", p)
End Sub
Private Shared Function Format(ByVal value As String) As String
Return value.Replace("'", "\'").Replace(Chr(13), "\r").Replace(Chr(10), "\n")
End Function
Private Shared Function GetScriptName(ByVal p As Page) As String
Dim i As Integer = p.AppRelativeVirtualPath.LastIndexOf("/")
Dim pageName As String = p.AppRelativeVirtualPath.Substring(i + 1)
Return pageName
End Function
End Region
End Class
End Namespace
Use by calling:
Utilities.WriteJava.Alert("DANGER WILL ROBINSON", Me.Page)
I was looking for a site-wide way to display message to the user. I found jnotifica. It's similar to stackoverflow's bar at the top of the page.
The author's site appears to be down at the moment.
Raise an event in the user control, subscribe to the event from the page. The event handler updates your custom page property. Seems like the least amount of moving parts.
I would like to suggest to use a class for custom page(Inherited from System.Web.UI.Page) inside that add follwing as
protected void AlertError(string ErrorString)
{
StringBuilder scriptBuidler =
new StringBuilder(#"<script type='text/javascript' language='javascript'>");
scriptBuidler.Append(#"alert('");
scriptBuidler.Append(ErrorString);
scriptBuidler.Append(#"');");
scriptBuidler.Append(#"</script>");
AjaxControlToolkit.ToolkitScriptManager.RegisterClientScriptBlock
(this, typeof(Page),
"Patient Detail Not Found",
scriptBuidler.ToString(),
false
);
}
and inherit all your pages from this class instead of inherinting from system.web.ui.page
and use this function to to show your messages to your clients
So, I've been working on this project for a few days now, and have been unable to resolve the issue of getting intellisense support for my custom-defined inner properties for a user control (ascx, mind you).
I have seen the solution to this (using server controls, .cs mind you) many times. Spelled out in this article very well. Everything works for me while using ascx controls except intellisense.
Here's the outline of my code:
[PersistChildren(true)]
[ParseChildren(typeof(BreadCrumbItem))]
[ControlBuilder(typeof(BreadCrumbItem))]
public partial class styledcontrols_buttons_BreadCrumb : System.Web.UI.UserControl
{
...
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public List<BreadCrumbItem> BreadCrumbItems
{
get { return _breadCrumbItems; }
set { _breadCrumbItems = value; }
}
...
protected override void AddParsedSubObject(object obj)
{
base.AddParsedSubObject(obj);
if (obj is BreadCrumbItem)
BreadCrumbItems.Add(obj as BreadCrumbItem);
}
...
public class BreadCrumbItem : ControlBuilder
{
public string Text { get; set; }
public string NavigateURL { get; set; }
public override Type GetChildControlType(string tagName, System.Collections.IDictionary attribs)
{
if (String.Compare(tagName, "BreadCrumbItem", true) == 0)
{
return typeof(BreadCrumbItem);
}
return null;
}
}
}
Here's my mark up (which works fine, just no intellisense on the child object declarations):
<%# Register src="../styledcontrols/buttons/BreadCrumb.ascx" tagname="BreadCrumb" tagprefix="uc1" %>
...
<uc1:BreadCrumb ID="BreadCrumb1" runat="server" BreadCrumbTitleText="Current Page">
<BreadCrumbItem Text="Home Page" NavigateURL="~/test/breadcrumbtest.aspx?iwentsomewhere=1" />
<BreadCrumbItem Text="Secondary Page" NavigateURL="~/test/breadcrumbtest.aspx?iwentsomewhere=1" />
</uc1:BreadCrumb>
I think the issue lies with how the intellisense engine traverses supporting classes. All the working examples I see of this are not ascx, but Web Server Controls (cs, in a compiled assembly).
If anyone could shed some light on how to accomplish this with ascx controls, I'd appreciate it.
I have solved this before by removing all the user controls and directives from the page and then switching the markup to design view. You then drag the ascx file from the solution explorer to the design window. If you swich back to markup view suddenly intelisense picks up all the properties. I am sure there is a better way but i have never found one that works.
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.