C# access element in master page from logic layer - c#

I have a content page connected to a master page. I can access an element on the master page and modify it directly from the content page .cs file by calling a method on the site master. (this is probably the most standard bug people have in this type of area)
My problem is that I wanted to extend this functionality to update the site master page from an AJAX request as well. The ajax file calls a different page which in turns starts an instance of the logic layer which I use for all the calculations and connections. What I am trying to do is access the sitemaster directly from the logic layer (only a .cs file).
My current code is this:
SiteMaster sm = new SiteMaster();
sm.MyMethod("param1", "param2");
This successfully accesses the method called "MyMethod" in the site master but inside this method I have this code:
mySpan.InnerText = "this is a test";
which doesn't work because I get the "Object refernce not set to an instance of an object...." error. This is because mySpan is NULL. If I call it using this.mySpan.InnerText though, if I hover over "this" then I can see the ID "mySpan".
Does anyone know how I can get around this problem? Every search I have made is regarding people who want to access the elements from the content page which already works for me.

I believe you've got a misunderstanding here. If I understand correctly you've got a page with a MasterPage. On that aspx page you're doing an ajax call (perhaps to a WebService) which does something like:
[WebMethod]
public void UpdateText(string message)
{
var master = new SiteMaster();
master.mySpan.Text = message;
}
There are a couple of things wrong here.
When you use this approach is an aspx page you're updating that Page's master. For example:
public void OnSomeRandomButtonClick(object sender, EventArgs e)
{
((SiteMaster)this.Page.Master).mySpan.Text = "Some Text";
}
What you're doing here is updating the span on the master page before it's being sent to your browser. The other subtly is that you're not creating a new SiteMaster, you're using the Page's existing Master and casting it to a SiteMaster.
There are a couple of reasons you can't do this with ajax:
A webservice doesn't have a MasterPage
By the time you send an ajax request your Master page has already been created and sent to the browser.
So your question becomes how do we update a span in the Master without posting back to the server?
Lets look at the html which is actually on your box, it will look something like this:
<html>
<head>
<title>My Awesome Page</title>
</head>
<body>
<h1>This is my Awesome Website</h1>
<span id="mySpan">I'm sure you'll like it</span>
<div>
<p>Page Content</p>
<div>
</body>
</html>
Lets assume that everything here is generated by the master and only the <p>Page Content</p> is your aspx page (There will also be loads of ASP.NET junk added, we'll ignore that for the time being).
What you want to do is update the text in mySpan without posting back to the server. You can do this via the javascript - don't get ajax involved at all!
I'm going to assume you're using jQuery (mostly because I'm more familiar with it that plain old JS). You've got the ID of your span ("mySpan") so the rest is easy:
$('#mySpan').html('This is the updated message');
You can put this in either a click or a page load.

No. You can not simply construct an ASP.NET page and use its state.
ASP.NET pages (and controls and Master pages) are being constructed and initialized from inside the ASP.NET engine based on the Markup provided for them. There is for example no initialization for mySpan inside the codeBehind of your master page, that will be constructed when the code generated based on the Markup is invoked based on a user request.
So you define this in your class:
protected HtmlGenericControl mySpan;
But the ASP.NET engine will compile this markup
<span id="mySpan" style="color:green"></span>
to this code:
this.mySpan = new HtmlGenericControl();
this.mySpan.Style.Add("color", "green);
and that is why you can use this object inside your code.
So if you want to use a property of your Master page from your Business layer, you have so many choices. On of the fastest one to implement is to make your Logic class singleton inside the Session scope, store the value you want to use inside the master page into that singleton object and then read that value from the master Page. This is an example of what you should do, of course it is rough.
class Logic
{
public static Logic Instance
{
get
{
if (HttpContext.Current.Session["LogicInstance"] == null)
HttpContext.Current.Session["LogicInstance"] = new Logic();
return (Logic) HttpContext.Current.Session["LogicInstance"];
}
}
public string TextForSpan {get;}
// The rest of your implementation
}
Instead of the code to assign the inner text, write:
Logic.Instance.TextForSpan = "This is my text";
And inside your master page:
this.mySpan.InnerText = Logic.Instance.TextForSpan;

Related

Access a method which fills a gridview with a button click from a different code-behind page

I'm trying to bind/fill a GridView. The GridView is the only thing on that page. No editing or anything, just a list of data. The event that triggers the GridView fill method is on a different page. The call to the method seems to work but when it gets to the GridView it complains that I need to reference an instance of the object.
I've tried at least a dozen approaches. This should be simple. I'm currently doing different variations on FindControl.
Here is the C# code that is failing:
Default.aspx.cs
public class ImportCSV : System.Web.UI.Page
{
public ImportCSV()
{
// A bunch of code that is working.
GridView LoadedDrivers = (GridView)Page.FindControl("LoadedDrivers");
LoadedDrivers.DataSource = csvDataTable; //This is where it errors
LoadedDrivers.DataBind();
)
Here is the code that triggers it:
Control.aspx.cs
namespace ControlPanelLab
{
public partial class Control : System.Web.UI.Page
{
//Working code
public void LoadDriversDriversButton_Click(object sender, EventArgs e)
{
//_Default.ImportCSV();
_Default.ImportCSV importCSV = new _Default.ImportCSV();
}
And the GridView that needs to be filled:
Default.aspx
<%# Page Title="Driver Table" Language="C#" AutoEventWireup="true" MasterPageFile="~/Site.Master" CodeBehind="Default.aspx.cs" Inherits="ControlPanelLab._Default" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
<asp:GridView ID="LoadedDrivers" runat="server" AutoGenerateColumns="true"></asp:GridView>
</asp:Content>
I've tried every combination I can think of and this at least compiles without an error. It just blows up when I run it complaining about referencing an instance of the LoadedDrivers GridView. I tried to put all the logic in a separate class file but I couldn't get that to work. The ImportCSV method was originally in a button handler that worked but I had to relocate the button that triggers the event to a different page. I tried to trigger that button with a button event sent by the new control page.
What am I missing? Thank you for any help.
Update -
I should point out that the only thing happening on the Control page is the button that triggers the method which loads, parses and ultimately needs to repopulate the GridView.
The button event on the Control page does in fact start the process just fine. All of the other activity is occurring on the Default page. And the only thing that is not working is the GridView which exists on the Default page is not binding.
I do understand that I do not understand inheritance all that well. That is why I am looking for guidance.
I have read many posts about triggering button clicks with JS as well as many posts about referencing precompiled classes.
The reference that I show in my code does not work, I get that. That's why I'm asking. Logically, there needs to be a way to trigger an event from a location that is not explicitly on the same page as the element being triggered. It's all under the same site, indeed, shares a namespace.
How do I properly address it?
Edit to include full ImportCSV method:
public ImportCSV()
{
//Static path to csv file
string csvPath = ("C:/Data/drivers.csv");
//Create a DataTable.
DataTable csvDataTable = new DataTable();
csvDataTable.Columns.AddRange(new DataColumn[4] { new DataColumn("id", typeof(int)),
new DataColumn("fName", typeof(string)),
new DataColumn("lName", typeof(string)),
new DataColumn("Country",typeof(string)) });
//Read the contents of CSV file.
string csvData = File.ReadAllText(csvPath);
//Execute a loop over the rows.
foreach (string row in csvData.Split('\n'))
{
if (!string.IsNullOrEmpty(row))
{
csvDataTable.Rows.Add();
int i = 0;
//Execute a loop over the columns.
foreach (string cell in row.Split(','))
{
csvDataTable.Rows[csvDataTable.Rows.Count - 1][i] = cell;
i++;
}
}
};
GridView LoadedDrivers = (GridView)FindControl("LoadedDrivers");
LoadedDrivers.DataSource = csvDataTable;
LoadedDrivers.DataBind();
When I debug the code, it runs right up to the GridView statement where it throws the exception. All of the variables populate in the locals pane. I can see the data in csvDataTable.
There is a Site.Master page which very well could be an elephant.
The other elephant might be that I was instantiating the ImportCSV from the Controls page button handler. I have changed that so the only thing being done on Controls is to click a button handler on Default and let it all exist in that space. I'm still missing something. The ImportCSV method works fine in several other elements of the project. I haven't needed to use FindControl in other places.
Unfortantly, you VERY much miss understand how a web server works.
Unlike desktop software?
The web system is called state-less. That means a copy of the web page ONLY exists client side. the web server does not keep in memory a copy of each users web page. In fact it does not even KEEP ONE copy in memory!!!
If 5 users all have the same web page open? There are NOT 5 instances of the web page class server side. In fact the .net class that represents the web page is NOT even existing, nor in memory.
If one user clicks a button on their web page? the ONE web server is just sitting there. If a post back occurs? Then the web server springs into action, load the page class, creates a NEW instance (and thus all variables in that page start from SCRATCH EACH TIME a post is made. The class instance is created, it now runs based on that page posted by the user. Rendering is done, and then the page is sent back to the client side and the CLASS COPY IN MEMEORY IS DISPOSED AND TOSSED OUT!!!
It does NOT exist anymore. So, if you try to reference a instance of that page class in another web browser from code behind, you find that the page class is NOT even memory, and all of its values (code variables) don't even exist!!!
So, the web server does not manage a page in memory for EACH user! The web server is just sitting there, waiting for ANY user to post back a page for processing. It is ONLY during that VERY short time when code behind runs does the instance of that class exists. Once the server has send the page back to the user, that class instance is tossed out and is gone!!!
So, you in fact have this setup:
Note how the page is sitting CLIENT side. On the server, THERE IS NOT a instance of that page class.
NOTE VERY careful here - the web page is ON CLIENT computer - it is NOT existing at all on the web server side.
You do not have this:
And you DO NOT HAVE this either:
Ok, You click the button. Now the so called round trip starts.
our web page is sent up to the server. You now have this:
So NOW your page is sitting on the server.
NOW a instance of the page class is created, and your code behind starts running.
Your code behind can modify controls (even visible), but the page is NOT interacting with the user - ONLY code can MODIFY the web page.
It is THEN sent back to the client side, and the server side class instance and code is TOSSED OUT - DOES NOT exist!!!
You now have this:
So, you can't do as you ask!! The server side instance of that other page is GONE!!!
And you would never know what user in question that class instance of that page belonged to if you could do this.
The web server is NOT like desktop software. The web server serves pages out to EVERYONE - the class instances of those pages don't exist until a post back occurs.
What you could do is have a timer on the first page. It could fire say every second, check some session value, and if your other page sets that session value, then the first page could load the grid. But you can't as a normal rule "garb" and paly with a instance of another page, since that instance DOES NOT exist!!!
(the page is sitting client side - NOT server side.

Including a Handler Response inside a Web Form

I am generating HTML in the Page Load method in more than 1 page.
All those generated HTML are the same across all pages.
I found that it's a pain to have the same codes in every page because once I need to change something, I need to change them in all pages.
Example:
I have a div in each of those page:
<div id="Questions" runat="server"></div>
In the Page Load method of each page, I generate the same HTML.
Questions.InnerHTML = "<span>...etc...</span>";
So I decided to make a page that generates those contents, then load this page inside the div of the other pages, means, if I ever need to change, I only change this page.
I created a Handler, Questions.ashx. This handler generates that HTML and sends back a response.
Now to include it, I know I can use JQUERY's .load() function, but I would like to generate those HTML from server side.
What I've tried:
Questions.InnerHTML = LoadControl("~/Handlers/Questions.ashx").ToString();
But I received this error:
Type 'Questions' does not inherit from 'System.Web.UI.UserControl'.
"LoadControl" is for "User Controls", not HTTP Handlers..
You will probably be better off creating a User Control, which is an .ascx file. This can contain HTML, ASPX controls and code behind, and can be referenced by any ASPX page.
More Info here:
http://msdn.microsoft.com/en-us/library/y6wb1a0e(v=vs.100).aspx

ASP.Net Class References a Control from ASPX Form

So, I've seen many discussions that are in this area, but it seems like they are mostly discussing Windows Forms, or they don't get around to answering this specific scenario enough to point me in the right direction.
Exactly what I need to do (Generic Example):
HTML (fragment.aspx)
<div id="html_fragment_1" runat="server">Contents</div>
<div id="html_fragment_2" runat="server">Contents</div>
...
Code Behind (fragment.aspx.cs)
Fragments fragment = new Fragments();
fragment.return_fragment( AN INSTANCE THAT REFERS TO html_fragment_N );
Class (Fragments.cs)
public void return_fragment ( AN INSTANCE THAT REFERS TO html_fragment_N ) {
string html = INSTANCE.html_fragment_N;
// DO SOMETHING WITH html
HttpContext.Current.Response.Write(html);
}
The reason I need to do this is that every aspx form on my site needs to be manipulated in the same way by Fragments.return_fragment(), where the content for several DIVs need to be read from the Form and arranged into an XElement to be returned.
Instead of doing the manipulation in the CodeBehind for every page, I'd rather have each Form use Fragments.return_fragment() so that it saves effort implementing a new Form page, and the code can be changed easily without having to change it in each Form.
Looks like you want your app's Global.asax file:
http://msdn.microsoft.com/en-us/library/1xaas8a2(v=vs.71).aspx
Look at your app's page events here as a place to specify things that should happen on every page request.

Dynamic data for JQuery in ASP.NET pages

Problem
I have some pages that need dynamic data from website to generate output to users.
A simple solution is an aspx(php, ...) page to generate data and create another html page serving as GUI retrieving data from first page and showing it to users. in this method I can call my GUI page for example form1.aspx and my data page form1.json.aspx.
although I personally like this method, it is not suitable when creating components for it.
Another method that currently I'm using is using same GUI page call itself with a querystring to retrieve data. this page should check for that query string and if it exists, only generate data and remove everything else from page. As an example for this method if I call my page form1.aspx, to retrieve data, I need to call it like form1.aspx?JSON
Here is an example of what I'm doing:
protected void Page_Load(object sender, EventArgs e) {
if (Request.QueryString.ToString().IndexOf("JSON") == 0){
this.Controls.Clear();
Response.Clear();
// send pure data to client
} else {
// render page as GUI
}
}
However this method becomes too messy if I add master page and/or inherit my page from some template page. Master pages can only removed in Page_PreInit and that adds another extra method.
Security controls cause another problem, if user leaves page open for long time until session expires any attempt to retrieve data will fail cause security module will redirect the request to login page.
Next problem is I cannot consolidate my component in package because it needs modification in page (removing master page, clearing page components ...).
What I'm looking for:
1- I'm looking for a solution that I can call my page and get pure data (JSON or XML format) and doing so run a server side method that generates data, so I don't have to worry about what another designer puts in their master page or template.
2- I think it is possible to use axd extension to do this but I don't have a clue about it and couldn't find a helping document either.
3- Is there any better way of doing this. any suggestion or solution to improve this much appreciated.
Page methods. Check this article: http://weblogs.asp.net/craigshoemaker/archive/2008/09/29/using-jquery-to-call-asp-net-ajax-page-methods.aspx or http://encosia.com/using-jquery-to-directly-call-aspnet-ajax-page-methods/
WCF JSON service: http://www.codeproject.com/Articles/327420/WCF-REST-Service-with-JSON
Other ways of doing is using an HTTP Handler. Implement IHttpHandler interface and register your implementation in your Web.config file. Later call it using jQuery ($.get / $.post):
http://msdn.microsoft.com/en-us/library/46c5ddfy.aspx
EDIT
As OP pointed out, in order to access session state in a page method you should use WebMethodAttribute this way:
[WebMethod(EnableSession = true)]
I think you can use webservice instead of aspx page to return a JSON or XML string and then the caller page (any aspx page) will response after process is success.
So with this webservice, any third party page will have access to your server side method.
To create a webservice pls Check this link: Create and use Asp.net web service basic
Regards

Element-Enhancing Javascript in ASP.NET Master Pages

I have run in to a bit of a problem and I have done a bit of digging, but struggling to come up with a conclusive answer/fix.
Basically, I have some javascript (created by a 3rd party) that does some whizzbang stuff to page elements to make them look pretty. The code works great on single pages (i.e. no master), however, when I try and apply the effects to a content page within a master, it does not work.
In short I have a master page which contains the main script reference. All pages will use the script, but the parameters passed to it will differ for the content pages.
Master Page Script Reference
<script src="scripts.js" language="javascript" type="text/javascript" />
Single Page
<script>
MakePretty("elementID");
</script>
As you can see, I need the reference in each page (hence it being in the master) but the actual elements I want to "MakePretty" will change dependant on content.
Content Pages
Now, due to the content page not having a <head> element, I have been using the following code to add it to the master pages <head> element:
HtmlGenericControl ctl = new HtmlGenericControl("script");
ctl.Attributes.Add("language", "javascript");
ctl.InnerHtml = #"MakePretty(""elementID"")";
Master.Page.Header.Controls.Add(ctl);
Now, this fails to work. However, if I replace with something simple like alert("HI!"), all works fine. So the code is being added OK, it just doesn't seem to always execute depending on what it is doing..
Now, having done some digging, I have learned that th content page's Load event is raised before the master pages, which may be having an effect, however, I thought the javascript on the page was all loaded/run at once?
Forgive me if this is a stupid question, but I am still relatively new to using javascript, especially in the master pages scenario.
How can I get content pages to call javascript code which is referenced in the Master page?
Thanks for any/all help on this guys, you will really be helping me out with this work problem.
NOTES:
RegisterStartupScript and the like does not seem to work at any level..
The control ID's are being set fine, even in the MasterPage environment and are rendering as expected.
Apologies if any of this is unclear, I am real tired so if need be please comment if a re-word/clarification is required.
Put a ContentPlaceHolder in the head section of the master page, then add a asp:Content control on the content page referring to the placeholder and put your script in that control. You can customize it for each page this way.
Also, the reference by ID may not be working because when you use Master Pages, the control IDs on the page are automatically created based on the container structure. So instead of "elementID" as expected, it may be outputting "ctl00_MainContentPlaceHolder_elementID" View your source or use firebug to inspect your form elements to see what the IDs outputted are.
Isn't it possible to do with clean javascript ?-)
-- just add something similar to this inside the body-tag:
<script type="text/javascript">
window.onload = function(){
MakePretty("elementID");
}
</script>
By the way the script-tag has to have an end-tag:
<script type="text/javascript" src="myScript.js"></script>
Why not use jQuery to find all the controls? Something like this:
$(document).ready(function(){
$("input[type='text'], input[type='radio'], input[type='checkbox'], select, textarea").each(function(){
MakePretty(this);
});
});
This way you'll get all elements on the page, you can wait until the page is ready (so you don't modify the DOM illigally). The jQuery selector can get the elements in a bit more of a specific format if you need (ie, add a root element, like the ID of the body div).
It'd also be best to modify the MakePretty method so it takes the element not the ID as the parameter to reduce processing overhead.
Once you use Master Pages, the ids of controls on the client side aren't what you think they are. You should use Control.ClientID when you generate the script.
When using master pages, you need to be careful with the html attribute ID, since .NET will modify this value as it needs to keep ids unique.
I would assume your javascript is applying css styles via ID, and when you are using master pages the ID is different than what is in your aspx. If you verify your javascript is always being added, your answer needs to take into account the following:
ALWAYS set your master page id in page load (this.ID = "myPrefix";)
Any HTML element in your master page will be prefixed by the master page id (i.e.: on the rendered page will be "myPrefix_myDiv")
Any HTML element in your content place holder id will be prefixed with an additional prefix (i.e. myPrefix_ContentPlaceHolderId1_myDiv)
Please let me know if I can clarify anything. Hope this helps!

Categories