For a site I'm developing I have two html buttons, not ASP because I do not want them to postback. For the submit button I am calling a javascript function that implements PageMethods to call a C# method from the codebehind. Here is the code for the buttons and the javascript.
<fieldset id="Fieldset">
<button onclick="SendForm();">Send</button>
<button onclick="CancelForm();">Cancel</button>
</fieldset>
<asp:ScriptManager ID="ScriptManager1" EnablePageMethods="true" EnablePartialRendering="true" runat="server" />
<script type="text/javascript">
function SendForm() {
var email = $get("txtEmail").value;
PageMethods.SendForm(email, OnSucceeded, OnFailed);
}
function OnSucceeded() {
$get("Fieldset").innerHTML = "<p>Thank you!</p>";
}
function OnFailed(error) {
alert(error.get_message());
}
</script>
The codebehind method shown here:
[WebMethod]
public static void SendForm(string email)
{
if (string.IsNullOrEmpty(email))
{
throw new Exception("You must supply an email address.");
}
else
{
if (IsValidEmailAddress(email))
{
bool[] desc = new bool[14];
bool[] local = new bool[14];
bool[] other = new bool[14];
for (int i = 1; i <= 14; i++)
{
desc[i] = ((CheckBox)Page.FindControl("chkDesc" + i.ToString())).Checked;
local[i] = ((CheckBox)Page.FindControl("chkLocal" + i.ToString())).Checked;
other[i] = ((CheckBox)Page.FindControl("chkOther" + i.ToString())).Checked;
/* Do stuff here */
}
}
else
{
throw new Exception("You must supply a valid email address.");
}
}
}
does not work unless it is declared as static. Declaring it as static blocks me from checking the checkboxes on the page because it generates a "An object reference is required for the non-static field, method, or property" error. So my problem can be fixed from either of two directions. A) Is there a way I can have this method work without declaring it as static? B) How do I check the checkboxes if the method is static.
It has to be static, no way around that; But you can access the Page like this
Page page = HttpContext.Current.Handler as Page;
and do FindControl on this page instance.
desc[i] = ((CheckBox)page.FindControl("chkDesc" + i.ToString())).Checked;
Page Methods are a special case of the legacy ASMX web service technology. They allow you to place the service in the codebehind class for the page, and keep you from needing a separate project for the service.
But they will never be able to access anything on the page itself. You'll have to do that from the client side, and pass the values of the check boxes to the service.
If you need to check the checkboxes, then you need to either use an UpdatePanel to do your AJAX stuff, or return something from your page method (ideally a string) and check the checkboxes based on what's returned in javascript on client.
Related
Hallo guys!
Sorry for the dump question, this is my last resort. I swear i triend countless of other Stackoverflow questions, different Frameworks, etc., but those didnt seem to help.
Ich have the following Problem:
A website displays a list of data (there is a TON of div, li, span etc. tags infront, its a big HTML.)
Im writing a tool that fetches data from a specific list inside a ton of other div tags, downloads it and outputs an excel file.
The website im trying to access, is dynamic. So you open the website, it loads a little bit, and then the list appears (probably some JS and stuff).
When i try to download the website via a webRequest in C#, the html I get ist almost empty with a ton on white spaces, lots of non-html stuff, some garbage data as well.
Now: Im pretty used to C#, HTMLAgillityPack, and countless other libraries, not so much in web related stuff tho. I tried CefSharp, Chromium etc. all of those stuff, but couldnt get them to work properly unfortunately.
I want to have a HTML in my program to work with that looks exactly like the HTML that you see when
you open the dev console in chrome wenn visting the website mentined above.
The HTML parser works flwalessly there.
This is how I image how the code could look like simplified.
Extreme C# pseudocode:
WebBrowserEngine web = new WebBrowserEngine()
web.LoadURLuntilFinished(url); // with all the JS executed and stuff
String html = web.getHTML();
web.close();
My Goal would be that the string html in the pseudocode looks exactly like the one in the Chrome dev tab.
Maybe there is a solution posted somewhere else but i swear i coudlnt find it, been looking for days.
Andy help is greatly appreciated.
#SpencerBench is spot on in saying
It could be that the page is using some combination of scroll state, element visibility, or element positions to trigger content loading. If that's the case, then you'll need to figure out what it is and trigger it programmatically.
To answer the question for your specific use case, we need to understand the behaviour of the page you want to scrape data from, or as I asked in the comments, how do you know the page is "finished"?
However, it's possible to give a fairly generic answer to the question which should act as a starting point for you.
This answer uses Selenium, a package which is commonly used for automating testing of web UIs, but as they say on their home page, that's not the only thing it can be used for.
Primarily it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should) also be automated as well.
The web site I'm scraping
So first we need a web site. I've created one using ASP.net core MVC with .net core 3.1, although the web site's technology stack isn't important, it's the behaviour of the page you want to scrape which is important. This site has 2 pages, unimaginatively called Page1 and Page2.
Page controllers
There's nothing special in these controllers:
namespace StackOverflow68925623Website.Controllers
{
using Microsoft.AspNetCore.Mvc;
public class Page1Controller : Controller
{
public IActionResult Index()
{
return View("Page1");
}
}
}
namespace StackOverflow68925623Website.Controllers
{
using Microsoft.AspNetCore.Mvc;
public class Page2Controller : Controller
{
public IActionResult Index()
{
return View("Page2");
}
}
}
API controller
There's also an API controller (i.e. it returns data rather than a view) which the views can call asynchronously to get some data to display. This one just creates an array of the requested number of random strings.
namespace StackOverflow68925623Website.Controllers
{
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Text;
[Route("api/[controller]")]
[ApiController]
public class DataController : ControllerBase
{
[HttpGet("Create")]
public IActionResult Create(int numberOfElements)
{
var response = new List<string>();
for (var i = 0; i < numberOfElements; i++)
{
response.Add(RandomString(10));
}
return Ok(response);
}
private string RandomString(int length)
{
var sb = new StringBuilder();
var random = new Random();
for (var i = 0; i < length; i++)
{
var characterCode = random.Next(65, 90); // A-Z
sb.Append((char)characterCode);
}
return sb.ToString();
}
}
}
Views
Page1's view looks like this:
#{
ViewData["Title"] = "Page 1";
}
<div class="text-center">
<div id="list" />
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script>
var apiUrl = 'https://localhost:44394/api/Data/Create';
$(document).ready(function () {
$('#list').append('<li id="loading">Loading...</li>');
$.ajax({
url: apiUrl + '?numberOfElements=20000',
datatype: 'json',
success: function (data) {
$('#loading').remove();
var insert = ''
for (var item of data) {
insert += '<li>' + item + '</li>';
}
insert = '<ul id="results">' + insert + '</ul>';
$('#list').html(insert);
},
error: function (xht, status) {
alert('Error: ' + status);
}
});
});
</script>
</div>
So when the page first loads, it just contains an empty div called list, however the page loading trigger's the function passed to jQuery's $(document).ready function, which makes an asynchronous call to the API controller, requesting an array of 20,000 elements. While the call is in progress, "Loading..." is displayed on the screen, and when the call returns, this is replaced by an unordered list containing the received data. This is written in a way intended to be friendly to developers of automated UI tests, or of screen scrapers, because we can tell whether all the data has loaded by testing whether or not the page contains an element with the ID results.
Page2's view looks like this:
#{
ViewData["Title"] = "Page 2";
}
<div class="text-center">
<div id="list">
<ul id="results" />
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script>
var apiUrl = 'https://localhost:44394/api/Data/Create';
var requestCount = 0;
var maxRequests = 20;
$(document).ready(function () {
getData();
});
function getDataIfAtBottomOfPage() {
console.log("scroll - " + requestCount + " requests");
if (requestCount < maxRequests) {
console.log("scrollTop " + document.documentElement.scrollTop + " scrollHeight " + document.documentElement.scrollHeight);
if (document.documentElement.scrollTop > (document.documentElement.scrollHeight - window.innerHeight - 100)) {
getData();
}
}
}
function getData() {
window.onscroll = undefined;
requestCount++;
$('results2').append('<li id="loading">Loading...</li>');
$.ajax({
url: apiUrl + '?numberOfElements=50',
datatype: 'json',
success: function (data) {
var insert = ''
for (var item of data) {
insert += '<li>' + item + '</li>';
}
$('#loading').remove();
$('#results').append(insert);
if (requestCount < maxRequests) {
window.setTimeout(function () { window.onscroll = getDataIfAtBottomOfPage }, 1000);
} else {
$('#results').append('<li>That\'s all folks');
}
},
error: function (xht, status) {
alert('Error: ' + status);
}
});
}
</script>
</div>
This gives a nicer user experience because it requests data from the API controller in multiple smaller chunks, so the first chunk of data appears fairly quickly, and once the user has scrolled down to somewhere near the bottom of the page, the next chunk of data is requested, until 20 chunks have been requested and displayed, at which point the text "That's all folks" is added to the end of the unordered list. However this is more difficult to interact with programmatically because you need to scroll the page down to make the new data appear.
(Yes, this implementation is a bit buggy - if the user gets to the bottom of the page too quickly then requesting the next chunk of data doesn't happen until they scroll up a bit. But the question isn't about how to implement this behaviour in a web page, but about how to scrape the displayed data, so please forgive my bugs.)
The scraper
I've implemented the scraper as a xUnit unit test project, just because I'm not doing anything with the data I've scraped from the web site other than Asserting that it is of the correct length, and therefore proving that I haven't prematurely assumed that the web page I'm scraping from is "finished". You can put most of this code (other than the Asserts) into any type of project.
Having created your scraper project, you need to add the Selenium.WebDriver and Selenium.WebDriver.ChromeDriver nuget packages.
Page Object Model
I'm using the Page Object Model pattern to provide a layer of abstraction between functional interaction with the page and the implementation detail of how to code that interaction. Each of the pages in the web site has a corresponding page model class for interacting with that page.
First, a base class with some code which is common to more than one page model class.
namespace StackOverflow68925623Scraper
{
using System;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
public class PageModel
{
protected PageModel(IWebDriver driver)
{
this.Driver = driver;
}
protected IWebDriver Driver { get; }
public void ScrollToTop()
{
var js = (IJavaScriptExecutor)this.Driver;
js.ExecuteScript("window.scrollTo(0, 0)");
}
public void ScrollToBottom()
{
var js = (IJavaScriptExecutor)this.Driver;
js.ExecuteScript("window.scrollTo(0, document.body.scrollHeight)");
}
protected IWebElement GetById(string id)
{
try
{
return this.Driver.FindElement(By.Id(id));
}
catch (NoSuchElementException)
{
return null;
}
}
protected IWebElement AwaitGetById(string id)
{
var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10));
return wait.Until(e => e.FindElement(By.Id(id)));
}
}
}
This base class gives us 4 convenience methods:
Scroll to the top of the page
Scroll to the bottom of the page
Get the element with the supplied ID, or return null if it doesn't exist
Get the element with the supplied ID, or wait for up to 10 seconds for it to appear if it doesn't exist yet
And each page in the web site has its own model class, derived from that base class.
namespace StackOverflow68925623Scraper
{
using OpenQA.Selenium;
public class Page1Model : PageModel
{
public Page1Model(IWebDriver driver) : base(driver)
{
}
public IWebElement AwaitResults => this.AwaitGetById("results");
public void Navigate()
{
this.Driver.Navigate().GoToUrl("https://localhost:44394/Page1");
}
}
}
namespace StackOverflow68925623Scraper
{
using OpenQA.Selenium;
public class Page2Model : PageModel
{
public Page2Model(IWebDriver driver) : base(driver)
{
}
public IWebElement Results => this.GetById("results");
public void Navigate()
{
this.Driver.Navigate().GoToUrl("https://localhost:44394/Page2");
}
}
}
And the Scraper class:
namespace StackOverflow68925623Scraper
{
using OpenQA.Selenium.Chrome;
using System;
using System.Threading;
using Xunit;
public class Scraper
{
[Fact]
public void TestPage1()
{
// Arrange
var driver = new ChromeDriver();
var page = new Page1Model(driver);
page.Navigate();
try
{
// Act
var actualResults = page.AwaitResults.Text.Split(Environment.NewLine);
// Assert
Assert.Equal(20000, actualResults.Length);
}
finally
{
// Ensure the browser window closes even if things go pear-shaped
driver.Quit();
}
}
[Fact]
public void TestPage2()
{
// Arrange
var driver = new ChromeDriver();
var page = new Page2Model(driver);
page.Navigate();
try
{
// Act
while (!page.Results.Text.Contains("That's all folks"))
{
Thread.Sleep(1000);
page.ScrollToBottom();
page.ScrollToTop();
}
var actualResults = page.Results.Text.Split(Environment.NewLine);
// Assert - we expect 1001 because of the extra "that's all folks"
Assert.Equal(1001, actualResults.Length);
}
finally
{
// Ensure the browser window closes even if things go pear-shaped
driver.Quit();
}
}
}
}
So, what's happening here?
// Arrange
var driver = new ChromeDriver();
var page = new Page1Model(driver);
page.Navigate();
ChromeDriver is in the Selenium.WebDriver.ChromeDriver package and implements the IWebDriver interface from the Selenium.WebDriver package with the code to interact with the Chrome browser. Other packages are available containing implementations for all popular browsers. Instantiating the driver object opens a browser window, and calling its Navigate method directs the browser to the page we want to test/scrape.
// Act
var actualResults = page.AwaitResults.Text.Split(Environment.NewLine);
Because on Page1, the results element doesn't exist until all the data has been displayed, and no user interaction is required in order for it to be displayed, we use the page model's AwaitResults property to just wait for that element to appear and return it once it has appeared.
AwaitResults returns an IWebElement instance representing the element, which in turn has various methods and properties we can use to interact with the element. In this case we use its Text property which returns the element's contents as a string, without any markup. Because the data is displayed as an unordered list, each element in the list is delimited by a line break, so we can can use String's Split method to convert it to a string array.
Page2 needs a different approach - we can't use the presence of the results element to determine whether the data has all been displayed, because that element is on the page right from the start, instead we need to check for the string "That's all folks" which is written right at the end of the last chunk of data. Also the data isn't loaded all in one go, and we need to keep scrolling down in order to trigger the loading of the next chunk of data.
// Act
while (!page.Results.Text.Contains("That's all folks"))
{
Thread.Sleep(1000);
page.ScrollToBottom();
page.ScrollToTop();
}
var actualResults = page.Results.Text.Split(Environment.NewLine);
Because of the bug in the UI that I mentioned earlier, if we get to the bottom of the page too quickly, the fetch of the next chunk of data isn't triggered, and attempting to scroll down when already at the bottom of the page doesn't raise another scroll event. That's why I'm scrolling to the bottom of the page and then back to the top - that way I can guarantee that a scroll event is raised. You never know, the web site you're trying to scrape data from may itself be buggy.
Once the "That's all folks" text has appeared, we can go ahead and get the results element's Text property and convert it to a string array as before.
// Assert - we expect 1001 because of the extra "that's all folks"
Assert.Equal(1001, actualResults.Length);
This is the bit that won't be in your code. Because I'm scraping a web site which is under my control, I know exactly how much data it should be displaying so I can check that I've got all the data, and therefore that my scraping code is working correctly.
Further reading
Absolute beginner's introduction to Selenium: https://www.guru99.com/selenium-csharp-tutorial.html
(A curiosity in that article is the way that it starts by creating a console application project and later changes its output type to class library and manually adds the unit test packages, when the project could have been created using one of Visual Studio's unit test project templates. It gets to the right place in the end, albeit via a rather odd route.)
Selenium documentation: https://www.selenium.dev/documentation/
Happy scraping!
If you need to fully execute the web page, then a complete browser like CefSharp is your only option.
It could be that the page is using some combination of scroll state, element visibility, or element positions to trigger content loading. If that's the case, then you'll need to figure out what it is and trigger it programmatically. I know that CefSharp can simulate user actions like clicking, scrolling, etc.
I'm opening a new window to another .aspx page in which I pass a couple of parameters and I wanted to re-pass the parameter ID from the actual page:
<asp:Button ID="Button1" runat="server" CausesValidation="False" meta:resourceKey="btnAddRow2"
OnClientClick="window.open('SecondPage.aspx?type=Usuaris&id=SPECIALID', '_blank')" Text="Miau" />
As you can see, the type parameter works well but I don't have the slightest idea how to get the "specialID" from the current page which would be:
http://blablabla.com/FirstPage.aspx?SPECIALID=36
So i want to get that 36 (which is a dynamic number so I can't actually put a 36 directly over there) in order to open the second page as follows:
http://blablabla.com/SecondPage.aspx?type=Usuaris&SPECIALID=36
As I said at the beginning the user IS at he FirstPage.aspx and upon pressing a button will go to the SecondPage.aspx
hi you can change the OnClientClick to call a javascript function which will get the specialId and then call the window.open with the full string.
for example
function openWindow(){
var specialId = document.getElementById('someElement').value;
window.open('SecondPage.aspx?type=Usuaris&id=' + specialId, '_blank')"
}
I finally could do it doing the following in the FirstPage.aspx:
function getParameterByName(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
function AddUsuario() {
var id = getParameterByName("id");
window.open('SecondPage.aspx?type=Usuarios&id=' + id, '_blank');
location.reload();
}
On Page_Load() do following
Button1.Attributes.Add("onclick",
String.Format("window.open('SecondPage.aspx?type=Usuaris&id={0}', '_blank');",
Request.QueryString["SPECIALID"]));
I have to show confirmation dialogue on particular condition.And then proceed according to YES or No clicked.I tried with the following.
In aspx:
<script type="text/javascript">
function ShowConfirmation() {
if (confirm("Employee Introduced already.Continue?") == true) {
document.getElementById("hdn_empname").value = 1;
}
}
</script>
<asp:HiddenField ID="hdn_empname" runat="server" />
in cs:
if (reader2.HasRows)
{
Page.ClientScript.RegisterStartupScript(this.GetType(), "showAl", "ShowConfirmation();", true);
}
else
{
hdn_empname.Value ="1";
}
if ((hdn_empname.Value)=="1")
{
//some code to execute
}
But hdn_empname shows value="" while debuging.
Can anyone help me doing this?
Thanks in advance.
Try it
You need to ClientID
document.getElementById('<%=hdn_empname.ClientID%>').value = 1;
I found out your main problems
The hidden field values will assign after the if condition call.
Edit :
So, You need to call your logic's in javascript side using ajax
if (confirm("Employee Introduced already.Continue?") == true) {
//some code to execute
}
Where is your break point? If reader2.HasRows returns true your javascript will be registered. But it set the value on client and you get the result after postback.
hdn_empname is server controls Id which is different from client sided id, to get client sided id you need to use ClientID
try this:
document.getElementById('<%=hdn_empname.ClientID%>').value = "1";
You dont need to compare
if (confirm("Employee Introduced already.Continue?") == true)
this will work:
if (confirm("Employee Introduced already.Continue?"))
I am working on a donations website. In my page, I have a textbox which accepts a numeric value from the user (that is, money to be donated).
In my code-behind, I have a method which checks whether the value in the textbox is numeric. The method generates an error message if the number is invalid.
I also have a JavaScript which, after checking that the value in the textbox is numeric, opens a new tab to the website confirmation page, thanking the user for his donation. Here is the code of the javascript:
<script type="text/javascript">
function Open_Window()
{
var textbox = document.getElementById('DonationTextBox');
if (textbox.value != "")
{
if (isNan(textbox) == false)
{
window.open("DonationConfirmation.aspx")
}
}
}
</script>
The problem is that the tab is NEVER opened, even if the number is valid. Can you please help me solve this problem? Thank you.
P.S.
Here is the code of the button that initiates the validation:
<asp:ImageButton ID="PayPalButton2" runat="server" ImageAlign="Middle"
ImageUrl="Resources/Icons/PayPalCheckOut.gif"
onclick="PayPalButton2_Click" OnClientClick="Open_Window()"/>
The function name is isNaN. Note: The final 'N' is capital. That should solve your problem.
<script type="text/javascript">
function Open_Window()
{
var textbox = document.getElementById('<%=DonationTextBox.ClientID%>');
if (textbox.value != "" && !isNaN(textbox.value)) {
window.open("DonationConfirmation.aspx");
}
}
</script>
edit
instead of isNan should be isNaN (javascript is casesensitive)
Shouldn't this line...
if (isNan(textbox) == false)
be this instead...
if (isNan(textbox.value) == false)
First, I would recommend explicitly parsing the number, not relying on the implicit ToNumber operation that will be applied when you pass a string into isNaN. Presumably your users are inputting decimal, so if it's meant to be a whole number (e.g., 10), use:
var num = parseInt(textbox.value, 10);
If it's meant to be a number with a fractional component (e.g., 10.5), use:
var num = parseFloat(textbox.value);
You probably want parseFloat for a currency value.
Then your if condition becomes isNaN (note that the final N is capped) on num:
<script type="text/javascript">
function Open_Window()
{
var textbox = document.getElementById('DonationTextBox');
var num = parseInt(textbox.value, 10);
if (!isNaN(num))
{
window.open("DonationConfirmation.aspx")
}
}
</script>
And lastly, are you sure that the client-side ID of the textbox really is 'DonationTextBox'? ASP auto-generates client-side IDs, you may need to use ClientID instead, e.g.:
var textbox = document.getElementById('<%=DonationTextBox.ClientID%>');
Here is a stripped down working jsFiddle example:
http://jsfiddle.net/pjgalbraith/QZeSF/
The html:
Open
<textarea id="donationTextBox">1</textarea>
And the js:
function openWindow() {
if($('#donationTextBox').val() && isNaN($('#donationTextBox').val()) === false)
window.open("http://www.google.com/", "mywindow");
}
$(document).ready(function() {
$('#PayPalButton2').click(function(){
openWindow();
});
});
Hi i have the following pagemethod, however it dues not seem to be working, i tried debugging it and it does not hit the method. Here is what my method looks like;
function InsertStatus() {
var fStatus = document.getElementById('<%=txtStatus.ClientID %>').value;
PageMethods.InsertStatusUpdate(fStatus, onSucess, onError);
function onSucess(result) {
alert(result);
}
function onError(result) {
alert('Cannot process your request at the moment, please try later.');
}
}
And my codebehind;
[WebMethod]
public static string InsertStatusUpdate(string fStatus)
{
string Result = "";
int intUserID = -1;
if (String.IsNullOrEmpty(HttpContext.Current.User.Identity.Name))
HttpContext.Current.Response.Redirect("/login");
else
intUserID = Convert.ToInt32(HttpContext.Current.User.Identity.Name);
if (string.IsNullOrEmpty(fStatus))
return Result = "Please enter a status";
else
{
//send data back to database
return Result = "Done";
}
}
When i click my button it goes straight through the onError Method. Can anyone see what i am doing wrong?
I found the problem i needed a [System.Web.Script.Services.ScriptService] above the method, due to the fact it is being called by a script. Thanks for all the suggestions.
If I were to guess, I would focus on this:
intUserID = Convert.ToInt32(HttpContext.Current.User.Identity.Name);
The best way to solve this is set a breakpoint and start walking through the code. When you run a line and are redirected to the error page, you have found your problem.
The reason I picked that line, is the user is a string. Now, it may be your users are numbers, but it could also be including a domain user == "mydomain/12345", which is not an integer, even if the user part of the string is.
As far as I know, you can't Response.Redirect in a PageMethod.
Return a string of the redirect URL and then use JavaScript document.location.href to handle the redirection.
EDIT: I've just seen that you tried debugging and the method isn't hit: ensure your ScriptManager has EnablePageMethods set to true:
<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true"/>