I'm to .NET and all the associated cool stuff you can do, but am wondering about efficiency with User Controls and JS includes.
My user controls are mainly made up with an ascx display page and .vb.ascx code behind, as is customary with the code-behind coding style of .NET, which is great for coding simplicity although it does double then bear of files required. However, as I understand it, the server compiles these and returns the HTML efficiently.
Where the Control requires JavaScript, as I'm developing, I am making external JS files for each User Control with the same name, so the user controls consist of 'controlName.ascx, controlName.vb.ascx, controlName.js'
If a page requested by the user contains several User Controls the browser will be requesting multiple JS files, probably a master page JS file, jQuery AND each required file for the respective Controls.
This approach makes sense to me whilst developing and as everything's all kept nice and neat, making problem solving easy, but when it goes live there'll be loads of get requests from the browser, given that each time the browser gets a file, even the process of requesting the file to check if its cached or not must take some time.
Would I be best off including my JS inline in the ascx files, or code behind, directly inserting the script, or what is the 'correct' way to handle these multiple files to reduce get requests from the browser.
I'm using CSS sprites for buttons and stuff for the same reason, so wondering what to do with JS files. In my case CSS is generally handled by classes in the primary pages, so these are not an issue.
We actually built a control on top of ScriptManager that automatically extracts all of the js from all controls on a page, including scriptresource.axd and stores them in a single cached file. This has greatly improved performance and reduced maintenance work since it is automated. We built this starting in .Net 2.0, so I am not sure if ScriptManager now provides the same functionality, but I thought it was worth mentioning.
Here is our implementation of this class:
Option Explicit On
Option Strict On
Imports System.Collections.Generic
Imports System.Web.SessionState
Public Class OurScriptManager
Inherits ScriptManager
Implements IRequiresSessionState
Private m_ScriptBuilder As New StringBuilder
'Private m_sSessionIndex As String = ""
Private m_cScripts As List(Of ScriptReference)
Private m_fIsCached As Boolean
Private m_sScriptName As String = ""
Private m_sScriptFileName As String = ""
Const CACHED_SCRIPTS_DIR As String = "/scriptcache/"
Public Sub New()
' default constructor
End Sub
Public Property ScriptName() As String
Get
Return m_sScriptName
End Get
Set(ByVal value As String)
m_sScriptName = value
End Set
End Property
Private ReadOnly Property ScriptFileName() As String
Get
If String.IsNullOrEmpty(m_sScriptFileName) Then
m_sScriptFileName = "~" & CACHED_SCRIPTS_DIR & Me.ScriptName & ".js"
End If
Return m_sScriptFileName
End Get
End Property
Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
' Exceptions are handled by the caller
MyBase.OnInit(e)
If String.IsNullOrEmpty(Me.ScriptName) Then
Me.ScriptName = Me.Page.ToString
End If
' this compiled script should be cached on the server
' check for the file, if it exists, load that file instead of generating it
If Configuration.HasPageScriptBeenCached(Me.ScriptFileName) AndAlso File.Exists(Me.Page.Server.MapPath(Me.ScriptFileName)) Then
m_fIsCached = True
Else
m_cScripts = New List(Of ScriptReference)
End If
End Sub
Protected Overrides Sub OnResolveScriptReference(ByVal e As System.Web.UI.ScriptReferenceEventArgs)
Try
MyBase.OnResolveScriptReference(e)
If Not m_fIsCached Then
' First, check to make sure this script should be loaded
Dim fIsFound As Boolean
For Each oXref As ScriptReference In m_cScripts
If oXref.Assembly = e.Script.Assembly AndAlso oXref.Name = e.Script.Name AndAlso oXref.Path = e.Script.Path Then
fIsFound = True
Exit For
End If
Next
' If this script is found within the list of scripts that this page uses, add the script to the scripthandler.aspx js output
If Not fIsFound Then
Dim oReference As ScriptReference
Dim oElement As ScriptReference
Dim fIsPathBased As Boolean
oElement = e.Script
If String.IsNullOrEmpty(oElement.Path) AndAlso Not String.IsNullOrEmpty(oElement.Name) AndAlso String.IsNullOrEmpty(oElement.Assembly) Then
' If resource belongs to System.Web.Extensions.dll, it does not
' provide assembly info that's why hard-coded assembly name is
' written to get it in profiler
oElement.Assembly = "System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
End If
'check to see what type of script this is
If Not String.IsNullOrEmpty(oElement.Path) Then
' this script is a physical file
oReference = New ScriptReference(oElement.Path)
fIsPathBased = True
ElseIf Not String.IsNullOrEmpty(oElement.Assembly) AndAlso Not String.IsNullOrEmpty(oElement.Name) Then
' this script is generated by an assembly
oReference = New ScriptReference(oElement.Name, oElement.Assembly)
Else
' Couldn't find script, so bail to allow standard processing to take place.
Return
End If
If Not fIsPathBased Then
Dim sUrl As String
Dim oRequest As HttpRequest
Dim sScriptResourcePath As String
sUrl = GetUrl(oReference)
sScriptResourcePath = String.Format("{0}{1}{2}{3}{4}{5}{6}{7}", Context.Request.Url.Scheme, "://", Context.Request.Url.Host, ":", Context.Request.Url.Port, "/", Context.Request.ApplicationPath, "/ScriptResource.axd")
oRequest = New HttpRequest("scriptresource.axd", sScriptResourcePath, sUrl.Substring(sUrl.IndexOf("?"c) + 1))
Try
Using oWriter As New StringWriter(m_ScriptBuilder)
Dim oHandler As IHttpHandler = New System.Web.Handlers.ScriptResourceHandler
oHandler.ProcessRequest(New HttpContext(oRequest, New HttpResponse(oWriter)))
End Using
Catch theException As Exception
Call ReportError(theException)
' Since we couldn't automatically process this, just bail so that standard processing takes over
Return
End Try
Else
' If this script is from a file, open the file and load the
' contents of the file into the compiled js variable
Dim sAbsolutePath As String
sAbsolutePath = Context.Server.MapPath(oElement.Path)
Try
If System.IO.File.Exists(sAbsolutePath) Then
Using oReader As New StreamReader(sAbsolutePath, True)
m_ScriptBuilder.Append(oReader.ReadToEnd())
End Using
Else
Throw New Exception("File " & sAbsolutePath & " does not exist")
End If
Catch theException As Exception
Call ReportError(theException, New ExtraErrorInformation("File", sAbsolutePath))
' Since we couldn't automatically process this, just bail so that standard processing takes over
Return
End Try
End If
m_ScriptBuilder.AppendLine()
' add this script to the script reference library
Dim oNewElement As New ScriptReference
oNewElement.Name = e.Script.Name.ToString()
oNewElement.Assembly = e.Script.Assembly.ToString()
oNewElement.Path = e.Script.Path.ToString()
m_cScripts.Add(oNewElement)
End If
End If
' a script filename is provided for caching
e.Script.Assembly = String.Empty
e.Script.Name = String.Empty
e.Script.Path = Me.ScriptFileName
Catch theException As Exception
HttpContext.Current.Response.Write(ReportError(theException))
HttpContext.Current.Response.End()
End Try
End Sub
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
' Exceptions are handled by the caller
MyBase.Render(writer)
If Not m_fIsCached Then
If Not String.IsNullOrEmpty(Me.ScriptName) Then
' Save script to file for caching
Using fsFile As New FileStream(Me.Page.Server.MapPath(Me.ScriptFileName), FileMode.Create, FileAccess.Write, FileShare.Read)
Using oWriter As New StreamWriter(fsFile)
oWriter.Write(m_ScriptBuilder.ToString)
oWriter.Flush()
oWriter.Close()
End Using
fsFile.Close()
End Using
' Record that the script file has been cached
Configuration.RecordPageScriptCached(Me.ScriptFileName)
End If
m_ScriptBuilder = Nothing
End If
End Sub
Private Function GetUrl(ByVal oReference As ScriptReference) As String
' Exceptions are handled by the caller
If String.IsNullOrEmpty(oReference.Path) Then
Try
Dim oMethod As MethodInfo
oMethod = oReference.GetType.GetMethod("GetUrl", BindingFlags.NonPublic Or BindingFlags.Instance)
If oMethod IsNot Nothing Then
Return DirectCast(oMethod.Invoke(oReference, New Object() {Me, False}), String)
Else
Return String.Empty
End If
Catch ex As Exception
Return String.Empty
End Try
Else
Return Me.ResolveClientUrl(oReference.Path)
End If
End Function
End Class
In the above code, ReportError logs the exception to the event log and/or file; you can replace this with your own mechanism.
Here is the Configuration code:
Private Shared m_cCachedPageScripts As Collections.Generic.List(Of String)
''' <summary>
''' This method is used to determine whether or not the script for the page has been cached.
''' This is used for script combining.
''' </summary>
''' <param name="sKey"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function HasPageScriptBeenCached(ByVal sKey As String) As Boolean
' Exceptions are handled by the caller
SyncLock CacheSyncObject
If m_cCachedPageScripts IsNot Nothing AndAlso m_cCachedPageScripts.Contains(sKey) Then
Return True
End If
End SyncLock
End Function
''' <summary>
''' This method is used to record the fact that the page script has been cached.
''' This is used for script combining.
''' </summary>
''' <param name="sKey"></param>
''' <remarks></remarks>
Public Shared Sub RecordPageScriptCached(ByVal sKey As String)
' Exceptions are handled by the caller
SyncLock CacheSyncObject
If m_cCachedPageScripts Is Nothing Then
m_cCachedPageScripts.Add(sKey)
End If
m_cCachedPageScripts.Add(sKey)
End SyncLock
End Sub
Related
I'm trying to run a JS code same as I would run it from console on webpage.
In code below "myhtml" is string with HTML from page I want to get value of #green
Simpliest answer would be to use webbrowser, but I can't as I'm not working with single-threaded application (so I'd get errors obviously).
The "myhtml" has external scripts that need to be loaded, so answer I'm looking is something like webbrowser that I can use in multithreaded application.
Code I've tried (changed JScript with HTML in js.Language):
Dim js As MSScriptControl.ScriptControlClass = New MSScriptControl.ScriptControlClass()
js.AllowUI = False
js.Language = "HTML"
js.Reset()
js.AddCode("myhtml")
Dim parms As Object() = New Object() {11}
Dim result As Integer = CInt(js.Run("alert(document.getElementById('green').value)", parms))
MsgBox(result)
With result of:
A script engine for the specified language can not be created.
Assume a string variable in code behind containing some HTML content such as
Dim str1 As String = "<h3>hello</h3><p>some paragraph</p><table><tr><td>some table content</td></tr></table>"
A saved file could be opened by registering a script such as
Dim script1 As String = "window.open('popupPage.html', 'myPopup')"
ScriptManager.RegisterStartupScript(Page, Page.GetType(), "someId", script1, true)
Is there any way to output this content in a new tab or window from code behind without saving it to a file?
You can pass an empty string for a file name
'Declare a script with your variable
Dim script1 As String = "<script>var popwin = window.open('','myPopup');popwin.document.write('" & str1 & "');</script>"
'Call your script with ScriptManager for async postback from UpdatePanel
ScriptManager.RegisterStartupScript(Me, Me.GetType(), "poptest", script1, true)
'Call your script with ClientScript for synchronous postback outside/without UpdatePanel
ClientScript.RegisterStartupScript(Me.GetType, "popuptest", script1, True)
check this Call javascript function from asp.net code behind after server side code executes
In my web site I have two pages... one is the Assistance.aspx, and the other is Help01.aspx
What I want to do is to open the second page inside the iFrame which is in a table column this column belongs to a table which is inside of the Assistance.aspx page:
For this purpose I use the following code:
<iframe id="iFrame" runat="server" class="tablecolumndiv" >
And in my code behind I use:
Protected WithEvents iFrame As System.Web.UI.HtmlControls.HtmlGenericControl<br/>
Public frame1 As HtmlControl = CType(Me.FindControl("iFrame"), HtmlControl)
But when I come to the:
Private Sub button1_Click(sender As Object, e As System.EventArgs) Handles button1.Click
frame1.Attributes("src") = "/Pages/Support/Asp/Help01.aspx"
End Sub
It throws me an error of :
Object reference not set to an instance of an object.
Because the Me.FindControl("iFrame") has value of nothing
That error eliminated when I delete the runat from the element.
Why?
* ADDITIONAL INFORMATION *
I also use the following script for the same reason:
<script type="text/javascript">
function setPage(frame, pName) {
document.getElementById(frame).src = pName;
}
</script>
Which I call it from my code behind:
Private Sub button1_Click(sender As Object, e As System.EventArgs) Handles button1.Click
Dim myNewAsp As New AspNetSqlProvider
myNewAsp.InitializeSite(sender, e)
Dim url As String = "/Pages/Support/Asp/Help01.aspx"
Dim urlURI As String = HttpContext.Current.Request.Url.AbsoluteUri
Dim urlPath As String = HttpContext.Current.Request.Url.AbsolutePath
Dim myServerName As String = Strings.Left(urlURI, urlURI.Length - urlPath.Length)
root_url = myServerName
Dim assist As New Assistance
Dim frameName As String = "iFrame" 'assistiFrame.ID
iPageLoad(frameName, sender, root_url + url)
End Sub
Public Sub iPageLoad(FrameId As String, sender As Object, msg As String)
Dim cstype As Type = Me.GetType()
Dim innerMess As String = msg
Dim url As String = HttpContext.Current.Request.Url.AbsoluteUri
Dim script As String = "setPage('" + FrameId + "', '" + innerMess + "')"
If Not Page.ClientScript.IsStartupScriptRegistered(Me.GetType(), "iPage") Then
Page.ClientScript.RegisterStartupScript(cstype, "iPage", script, True)
End If
End Sub
That script also throws me the same error.
Finally we’ve seen that this issue have two solutions, every one of those two solutions have it’s own results.
The first solution is to declare the iFrame like this:
Protected WithEvents iFrame As System.Web.UI.HtmlControls.HtmlGenericControl
And in the Button_Click handler we use the:
iFrame.Attributes("src") = "/Pages/Support/Asp/Help01.aspx"
And in the design view area we use the:
<iframe id="iFrame" runat="server" class="tablecolumndiv" >
This solution works fine, and produces an excellent result.
Of course we have another solution described in the ADDITIONAL INFORMATION but that one needs to delete the runat=”server” from the declaration in the design view area.
Choose and Pick….
Looking for an elegant way of having scripts added once on a page and that's it.
I have a partial view that requires 2 CSS files and 2 JS files. In most places, there is only need for 1 of the partial views. On a single page though, I need 3 of these same partial views, and each partial view has the 4 files, so I have 6 JS links and 6 CSS links. Quite ugly.
I original idea was to use jQuery to see if the tags (by id) are existant on the page yet or not. If they aren't, then add them in. Otherwise, do nothing. This was going to be an inline script like....
<script type="text/javascript" language="javascript">
function(){
var jQueryUICSS = $("#jQueryUICSS");
if(!jQueryUICSS){
document.write('link id="jQueryUICSS" href="/Content/smoothness/jquery-ui-1.8.5.custom.css" rel="stylesheet" type="text/css" />')
}
...And so on for the other 3 tags.
};
But, I'm not sure that will work (or will the lead dev accept it):P
Any other ideas?
David,
I use a couple of static htmlhelpers in my code for exactly this scenario. it works on the principle that the context.items collection gets populated per request and therefore if an item exists in the context.items collection then it doesn't get added twice. anyway, enough of the scottish words of wisdOOOm, 'yill jist be waantin the coade'...
for our Scripts:
public static MvcHtmlString Script(this HtmlHelper html, string path)
{
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 MvcHtmlString.Create("");
// add the beast...
context.Items.Add(filePath, filePath);
return MvcHtmlString.Create(
string.Format("<script type=\"text/javascript\" src=\"{0}\"></script>", filePath));
}
for our cuddly css:
// standard method - renders as defined in as(cp)x file
public static MvcHtmlString Css(this HtmlHelper html, string path)
{
return html.Css(path, false);
}
// override - to allow javascript to put css in head
public static MvcHtmlString 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 null;
// 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 MvcHtmlString.Create( sb.ToString());
}
usage in both cases:
<%=Html.Css("~/Content/Site.Css")%>
<%=Html.Script("~/Scripts/default.js")%>
have fun...
[edit] - pay particular attn to the comment line:
// this of course only works for items going in via the current
// request and by this method
Put them in your master. CSS and Javascript files are cached. Load once and don't worry about it.
I am currently in the process of converting old "Web 1.0" code to meet current standards.
Is there a better way to generate and append a client-side script other than appending a plethora of lines to a StringBuilder and then registering it to the page via ClientScript.RegisterStartupScript(Me.GetType(), "startUpScript", strScript)?
Is there any other way (besides putting all of this into a global .js file) that this example can be improved? If including this into the main .js file is the "best practice" alternative, then why?
Dim lsbScript As New Text.StringBuilder
lsbScript.Append(vbCrLf)
lsbScript.Append("<script language=""javascript>""" & vbCrLf)
lsbScript.Append("<!--" & vbCrLf)
...
lsbScript.Append("//-->" & vbCrLf)
lsbScript.Append("</SCRIPT>" & vbCrLf)
If Not ClientScript.IsStartupScriptRegistered("someScript") Then
ClientScript.RegisterStartupScript(Me.GetType(), "someScript", lsbScript.ToString)
End If
a good middle ground might be offloading the script contents to a new js file, and including a script node from codebehind.
protected void Page_Load(object sender, EventArgs e) {
bool someCondition = Whatever();
if (someCondition)
{
System.Web.UI.HtmlControls.HtmlGenericControl script;
script = new System.Web.UI.HtmlControls.HtmlGenericControl("script");
script.Attributes["src"] = "myscript.js";
this.Controls.Add(script);
}
}
this will just drop the node at the end of the page. alternatively, you could put a placeholder control whereever you like on the page, and add the new HtmlGenericControl to that.
(sorry, vb is not my native language).
Yes you can include a .js file in your project. Set it's Build Action (right click the file in your project explorer) to Embedded Resource
For example: common.js
Above the namespace of your server control (or whereever you need it):
[assembly: System.Web.UI.WebResource(
"MyFullNameSpace.Common.js",
"text/javascript", PerformSubstitution = true)]
OnPreRender event:
if (!Page.ClientScript.IsClientScriptIncludeRegistered("Common"))
{
string url = Page.ClientScript.GetWebResourceUrl(this.GetType(), "MyFullNameSpace.Common.js");
Page.ClientScript.RegisterClientScriptInclude("Common", url);
}
Beware to replace MyFullNameSpace with the complete and exact namespace of where .js is located. If it doesn't work probably .net can't find it and you can use reflector to open your dll and find the .js as embedded resource so you will know the exact namespace it's in.
I always do this from Server Controls but i image it could be done in a web app project as well.
You can get a little mileage out of using AppendLine() instead of concatenating vbCrLf.