I have a tricky problem and I'm not sure where in the view rendering process to attempt this. I am building a simple blog/CMS in MVC and I would like to inject a some html (preferably a partial view) into the page if the user is logged in as an admin (and therefore has edit privileges).
I obviously could add render partials to master pages etc. But in my system master pages/views are the "templates" of the CMS and therefore should not contain CMS specific <% %> markup. I would like to hook in to some part of the rendering process and inject the html myself.
Does anyone have any idea how to do this in MVC? Where would be the best point, ViewPage, ViewEngine?
Thanks,
Ian
You could use Html.RenderPartial to insert an HTML fragment somewhere in the page. If you want to insert it in a place not available to the view but only on the master you could place a <asp:ContentPlaceHolder ID="Admin" runat="server" /> placeholder inside the master and in the view simply override it and insert the partial. If placing such a placeholder is not acceptable you could use AJAX like: $('#adminHolder').load('/home/admin');, but I would probably go with the previous approach as it will work in case the user has javascript disabled.
OK this took a bit of messing and the result is a little hacky. But it works and that's all that matters right....
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
if (!User.Identity.IsAuthenticated || !User.IsInRole("Admin"))
{
// If not admin continue as normal
base.Render(writer);
return;
}
// Taking a leaf out of the move viewstate to the bottom of page playbook
var stringWriter = new System.IO.StringWriter();
var htmlWriter = new HtmlTextWriter(stringWriter);
base.Render(htmlWriter);
var html = stringWriter.ToString();
var endOfBody = html.IndexOf("</body>") - 1;
if (endOfBody >= 0)
{
var adminConsole = Html.RenderPartialAsString("AdminPanel");
html = html.Insert(endOfBody, adminConsole);
}
writer.Write(html);
}
I implement my own ViewPage overriding the Render method. This checks if the user is logged in as an admin and if they are, it renders a partial at the bottom of the page. Very similar to old skool viewstate hacks in webforms.
Enjoy.
Related
I'm having a hard time with the page cycles when using masterpages and contentpages.
My masterpage has two linkbuttons that are used to select a language (using resources). When these buttons are clicked I create Session["language"].
The goal I have is to 'translate' my masterpage after the buttons are clicked AND to translate the content page.
I've been trying all kinds of different methods (Page_Load etc) based on this url: http://msdn.microsoft.com/en-us/library/dct97kc3.aspx but it never works like it should. Usually the content page only gets translated after two clicks. I can't figure out the cycle problem between the masterpage and the content page combined with the click-events.
Any suggestions?
Thank you.
I used to do this by overriding InitializeCulture method in the master page. The language code is passed via query-string:
protected override void InitializeCulture()
{
if (!string.IsNullOrEmpty(base.Request["language"]))
{
System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CreateSpecificCulture(base.Request["language"]);
System.Threading.Thread.CurrentThread.CurrentCulture = culture;
System.Threading.Thread.CurrentThread.CurrentUICulture = culture;
}
}
And the link will look like the following:
Vis på Dansk
Don't forget to validate an input value first :-)
I have some experience of using paypal with an asp.net website, however this issue has me really stumped.
Root of the problem: You cant embed the html form for the paypal button inside your page form.
Original solution: Originally my website was using multiple aspx pages so I could simply arrange my form tags so that they weren't embedded inside one another.
My website now uses a master aspx page which draws in different ascx controls. This means that I do not have the option of arranging the form tags around the page so need a work around.
NB. I have looked all over the place for simple solutions but it is a jungle out there, paypal is a nightmare. I did find something on ghost form which is all in c#. Might help...
Thanks in advance for any help....
Submit the PayPal information using their APIs rather than submitting a form directly to them.
That way you can keep everything as part of a single form and add a little more robustness around the PayPal input and validation.
PayPal: SDKs and Downloads
Had this issue with another payment provider also. You could either use their API, or you could work around it by:
Making the checkout button a standard imagebutton
Running something like ClientScript.RegisterStartupScript() to output both HTML and Javascript. The HTML should be the actual form with all hidden fields and proper id. The javascript is code which would execute on page load and submit the page.
i.e. ClientScript.RegisterStartupScript(Me.GetType(), "PaypalSubmit", "<AllMarkUp><GoesHere><script>And.Javascript</script>", False)
Hope this helps, otherwise you could use the web service API. By taking this approach you are performing a postback, outputting the HTML form (outside the .NET form because it is at the bottom of the page) and then relying on the javascript to actually submit it.
Here's something that will work for you. In your code-behind:
// Workaround for PayPal form problem
CustomForm mainForm = new CustomForm();
mainForm.RenderFormTag = false;
Create a custom form class which overrides the HtmlForm class:
public class CustomForm : System.Web.UI.HtmlControls.HtmlForm
{
protected bool _render;
public bool RenderFormTag
{
get { return _render; }
set { _render = value; }
}
public CustomForm()
{
//By default, show the form tag
_render = true;
}
protected override void RenderBeginTag(HtmlTextWriter writer)
{
//Only render the tag when _render is set to true
if (_render)
base.RenderBeginTag(writer);
}
protected override void RenderEndTag(HtmlTextWriter writer)
{
//Only render the tag when _render is set to true
if (_render)
base.RenderEndTag(writer);
}
}
I'm trying to take an existing bunch of code that was previously on a full .aspx page, and do the same stuff in a .ashx handler.
The code created an HtmlTable object, added rows and cells to those rows, then added that html table the .aspx's controls collection, then added it to a div that was already on the page.
I am trying to keep the code in tact but instead of putting the control into a div, actually generate the html and I'll return that in a big chunk of text that can be called via AJAX client-side.
HtmlTable errors out when I try to use the InnerHtml property (says it isn't supported), and when I try RenderControl, after making first a TextWriter and next an HtmlTextWriter object, I get the error that Page cannot be null.
Has anyone done this before? Any suggestions?
*Most recent is above.
OK, even after Matt's update there is a workaround ;)
Firstly, we have to use a page with form inside. Otherwise we won't be able to add a ScriptManager control. One more thing: the ScriptManager control should be the first control in the form. Further is easier:
Page page = new Page();
Button button = new System.Web.UI.WebControls.Button
{
ID = "btnSumbit",
Text = "TextButton",
UseSubmitBehavior = true
};
HtmlForm form = new HtmlForm
{
ID="theForm"
};
ScriptManager scriptManager = new ScriptManager
{
ID = "ajaxScriptManager"
};
form.Controls.Add(scriptManager);
form.Controls.Add(button);
page.Controls.Add(form);
using (StringWriter output = new StringWriter())
{
HttpContext.Current.Server.Execute(page, output, false);
context.Response.ContentType = "text/plain";
context.Response.Write(output.ToString());
}
This works. The output is quite large so I decided not to include it into my answer :)
Actually, there is a workaround. Yep, we may render a control in handler.
Firstly, we need a formless page. Because without it we get:
Control 'btnSumbit' of type 'Button'
must be placed inside a form tag with
runat=server.
public class FormlessPage : Page
{
public override void VerifyRenderingInServerForm(Control control)
{
}
}
Secondly, nobody can prevent us from creating an instance of our FormlessPage page. And now let's add a control there (I decided to add a Button control as an example, but you could use any).
FormlessPage page = new FormlessPage();
Button button = new System.Web.UI.WebControls.Button
{
ID = "btnSumbit",
Text = "TextButton",
UseSubmitBehavior = true
};
page.Controls.Add(button);
Thirdly, let's capture the output. For this we use HttpServerUtility.Execute method:
Executes the handler for the specified
virtual path in the context of the
current request. A
System.IO.TextWriter captures output
from the executed handler and a
Boolean parameter specifies whether to
clear the
System.Web.HttpRequest.QueryString and
System.Web.HttpRequest.Form
collections.
Here is the code:
using (StringWriter output = new StringWriter())
{
HttpContext.Current.Server.Execute(page, output, false);
context.Response.ContentType = "text/plain";
context.Response.Write(output.ToString());
}
The result will be:
<input type="submit" name="btnSumbit" value="TextButton" id="btnSumbit" />
In addition I can recommend ScottGu's article Tip/Trick: Cool UI Templating Technique to use with ASP.NET AJAX for non-UpdatePanel scenarios. Hope, you could find a lot of useful there.
Another option is to host the ASP.NET HTTP pipeline in your process, render the page to a stream and read the HTML you need to send from the HttpListenerContext.Response.OutputStream stream after the page has been processed.
This article has details: http://msdn.microsoft.com/en-us/magazine/cc163879.aspx
I am struggling with something that I guess should be standard practice really. I have a number of user controls that use some JQuery plugins. I do not really want to link to the extra CSS and JS files from my main masterpage as this would cause extra load to the user the first time they hit the site, (admittedly it would only be the once), so I was just putting them links into the top of the user control. Then I looked at my source HTML, not nice! Even worse for controls that repeat multiple times on a page.
So I was thinking is there a way of injecting them into the Head of the page when they are needed from the User Control. For that matter is there a way of doing it to the footer for JS stuff?
To dynamically register a script (and ensure that duplicates are merged) in ASP.NET you can call:
Page.ClientScript.RegisterClientScriptInclude(
"mykey", "~/scripts/jquery-1.3.2.js");
And read the full details on this method on MSDN.
To add CSS dynamically you can do something like this:
HtmlLink cssLink = new HtmlLink();
cssLink.Href = "path to CSS";
cssLink.Attributes["some attr1"] = "some value1";
cssLink.Attributes["some attr2"] = "some value2";
Page.Header.Controls.Add(cssLink);
This example of injecting CSS will not merge duplicate entries. To avoid duplication you'll have to keep track of duplicates yourself. One place you can store a list of scripts you've already registered is in HttpContext.Items. Stick a HashSet in there that keeps a list of all registered scripts so that you don't register the same CSS file twice (which is generally harmless, but something to avoid anyway).
I followed a similar approach, but I use CSS directly in the user control so I don't have to import a CSS file. The following is code entirely from a sample user control:
<style id="style1" type="text/css" visible="false" runat="server">
td { font-family: Tahoma; font-size: 8pt; }
</style>
In code-behind:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
HtmlGenericControl style = new HtmlGenericControl("style");
style.Attributes.Add("type", "text/css");
style.InnerHtml = style1.InnerHtml;
Page.Header.Controls.Add(style);
}
You'll notice that the CSS is rendered in the head tag and not inside the body tag.
You can use ClientScript.RegisterClientScriptInclude() for the JavaScript.
For the CSS, one trick is to include them in your Master page, but with Visible="false", so that they aren't rendered into the markup by default.
Then, in your user controls, set a flag in the Items collection, from an early event, such as OnLoad(). For example, this.Context.Items["mycss"] = true;
Finally, in your Master page, from a later event, such as OnPreRender(), check to see if those flags are set. If they are, then set the Visible property to true for the corresponding CSS.
This also allows you to use the control with Master pages that don't use the CSS, since the Items entries could simply be ignored. If you have many Master pages that need the same behavior, you could put this code in a base class or use nested Master pages.
I assume you're using Asp.NET.
Try putting a content placeholder in the of the MasterPage...
<head>
<asp:ContentPlaceHolder ID="AdditionalPageHeader" />
</head>
If you're working in an aspx file or an ascx you need only define a content control...
<asp:Content ContentPlaceHolderID="AdditionalPageHeader" />
If you're working on a code-behind only type of server control, you can can get a pointer to that content place holder:
this.Page.Master.FindControl("AdditionalPageHeader")
... and manipulate it's contents programatically.
To add stylesheets or javascript (inline or not) dynamical I wrote these three functions:
Public Function addScript(ByVal path2js As String) As System.Web.UI.Control
Dim si As New HtmlGenericControl
si.TagName = "script"
si.Attributes.Add("type", "text/javascript")
si.Attributes.Add("src", path2js)
Return si
End Function
Public Function addScript_inline(ByVal js As String) As System.Web.UI.Control
Dim si As New HtmlGenericControl
si.TagName = "script"
si.Attributes.Add("type", "text/javascript")
si.InnerHtml = js
Return si
End Function
Public Function addStyle(ByVal path2css As String) As System.Web.UI.Control
Dim css As New HtmlLink
css.Href = path2css
css.Attributes.Add("rel", "stylesheet")
css.Attributes.Add("type", "text/css")
css.Attributes.Add("media", "all")
Return css
End Function
I call them in page_load on my masterpage, like this:
Me.Page.Header.Controls.Add(modGlobal.addScript("script/json/json2.js"))
or
Me.Page.Header.Controls.Add(modGlobal.addStyle("style/flexigrid/flexigrid.css"))
Regards
I am trying to use the ASP.NET (3.5) "Routing Module" functionality to create custom pages based on the contents of the URL.
Various articles explain how to use ASP.NET Routing to branch to existing pages on the web server.
What I would like to do is create the page on-the-fly using code.
My first attempt looks like this:
public class SimpleRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
string pageName = requestContext.RouteData.GetRequiredString("PageName");
Page myPage = new Page();
myPage.Response.Write("hello " + pageName);
return myPage;
}
}
But this throws an HTTPException saying "Response is not available in this context." at the Response.Write statement.
How to proceed?
UPDATE: In the end, I went with an approach based on IHttpModule, which turned out to be rather easy.
You can't write to the response from an IRouteHandler - it's way too early during the request life cycle. You should only write to the response from within the IHttpHandler, which is what the Page is.
As is shown in other examples, you'll have to get a page instance from somewhere that has all the necessary content.
Here's how you can load an existing page:
Page p = (Page)BuildManager.CreateInstanceFromVirtualPath("~/MyPage.aspx");
Or you can create one from scratch:
Page p = new Page();
p.Controls.Add(new LiteralControl(
#"<html>
<body>
<div>
This is HTML!
</div>
</body>
</html>"));
Instead of trying to write directly to the response, you might want to simply add controls to the page. Since the page is brand-new and has no markup, you may have to add all of the HTML elements to make it legal HTML in order to get it rendered correctly. Having never tried this, I have no idea if it will work.
Page myPage = new Page();
page.Controls.Add( new LiteralControl( "hello " + pageName ) );
return myPage;
It's not clear to me that this will have the required HTML, HEAD, and BODY tags. It might be better to create a base page that has skeleton markup that you can just add your controls to and use the BuildManager as in the example to instantiate this page, then add your controls.
Put requestContext before Response.Write, so requestContext.Response.Write