c# How to utilize Page_init, ViewState and Page_Load properly? - c#

I have been working with Web Forms for a short while now and I have read most of what I have found about this on google. However, I am still unclear on how to work with this properly. I like the picture in this answer, but find it a bit too generic. I have not found one decent, concrete example on how to work with these events.
I am currently creating several controls dynamically in code behind in the Page_Load event:
foreach (Service service in Services)
{
// service div
HtmlGenericControl serviceDiv = new HtmlGenericControl("div");
serviceDiv.ID = service.ID;
serviceDiv.Style.Add(HtmlTextWriterStyle.TextAlign, "center");
outerDiv.Controls.Add(serviceDiv); //outerDiv exists in the aspx page
// service updatepanel
UpdatePanel uPanel = new UpdatePanel()
{
ID = service.ID + "_uPanel",
UpdateMode = UpdatePanelUpdateMode.Conditional
};
serviceDiv.Controls.Add(uPanel);
// status span
HtmlGenericControl statusSpan = new HtmlGenericControl("span");
statusSpan.ID = service.ID + "_statusSpan";
statusSpan.InnerHtml = service.Status;
uPanel.ContentTemplateContainer.Controls.Add(statusSpan);
// show specific content
if (service.Status.Equals(ServiceControllerStatus.Running.ToString()))
{
// status color
statusSpan.Attributes.Add("class", "status-run");
// stop button
HtmlButton stopButton = new HtmlButton();
stopButton.ID = service.ID + "_btnStop";
stopButton.InnerHtml = "<i class=\"fa fa-stop btn-red\"/></i>";
stopButton.Attributes.Add("type", "button");
stopButton.Attributes.Add("runat", "server");
stopButton.Attributes.Add("class", "btn btn-link btn-xs");
stopButton.Attributes.Add("title", "Stop");
stopButton.ServerClick += new EventHandler(BtnStop_Click);
ScriptManager.GetCurrent(this).RegisterAsyncPostBackControl(stopButton);
uPanel.ContentTemplateContainer.Controls.Add(stopButton);
// restart button
HtmlButton restartButton = new HtmlButton();
restartButton.ID = service.ID + "_btnRestart";
restartButton.InnerHtml = "<i class=\"fa fa-refresh btn-blue\"/></i>";
restartButton.Attributes.Add("type", "button");
restartButton.Attributes.Add("runat", "server");
restartButton.Attributes.Add("class", "btn btn-link btn-xs");
restartButton.Attributes.Add("title", "Restart");
restartButton.ServerClick += new EventHandler(BtnRestart_Click);
ScriptManager.GetCurrent(this).RegisterAsyncPostBackControl(restartButton);
uPanel.ContentTemplateContainer.Controls.Add(restartButton);
}
else
{
// status color
statusSpan.Attributes.Add("class", "status-stop");
// start button
HtmlButton startButton = new HtmlButton();
startButton.ID = service.ID + "_btnStart";
startButton.InnerHtml = "<i class=\"fa fa-play btn-green\"/></i>";
startButton.Attributes.Add("type", "button");
startButton.Attributes.Add("runat", "server");
startButton.Attributes.Add("class", "btn btn-link btn-xs");
startButton.Attributes.Add("title", "Start");
startButton.ServerClick += new EventHandler(BtnStart_Click);
ScriptManager.GetCurrent(this).RegisterAsyncPostBackControl(startButton);
uPanel.ContentTemplateContainer.Controls.Add(startButton);
}
// version span
HtmlGenericControl versionSpan = new HtmlGenericControl("span");
versionSpan.ID = service.ID + "_version";
versionSpan.InnerHtml = service.Version;
versionSpan.Attributes.Add("class", "version-text");
serviceDiv.Controls.Add(versionSpan);
What would I gain from creating these in Page_Init? If I create them in Page_Init, how do I access them in Page_Load? Private global lists of UpdatePanels and HtmlButtons feels so unclean.
I know that the ViewState loads between Page_Init and Page_Load, but what does that really mean? Since I don't do full postbacks, but instead use RegisterAsyncPostBackControl to only update the UpdatePanel's on postback, don't I need to re-populate in Page_Load?

If you don't need the ViewState, then you can also create the controls in Page_Load. The ViewState is used to store the values of the controls (which ones depends on the control) and to use them when the PostBack is sent to the server.
For instance, if you have a textbox, the PostBack contains the new value of the textbox and also the old value in the ViewState. The ASP.NET framework now compares those two and raises the TextChanged event if necessary. Without ViewState, this would not be possible.
The best advice you can give regarding dynamically created controls in ASP.NET WebForms is to avoid using them. They increase complexity very fast und are usually not necessary (even if it seems so at first).
In the majority of the cases, there is a much simpler approach, e.g. by using a Repeater. In your case, you have a list of services. You can bind this list to a Repeater and by that avoid to create the controls manually. See this link on how to use a Repeater.
Another upside of using a Repeater is that you can define the UI in the ASPX markup instead of in the code behind file. In your example, you change the UI based upon the status of the service. In this case, using the ItemDataBound-event of the Repeater might be a good option.

Related

Dynamic ModalPopupExtender not firing the OK Click event

ASP.NET 4.7.2 Web Forms c# VS 2019
I am trying to use a modalpopupextender to prompt for new data for foreign key fields. Like the form itself, the MPE is built on the fly in code -- in this case the click handler for the hidden button that the Javascript fires off to build and show the MPE.
I read every single article on SO and the ASP forums and tried everything I saw there. No joy. I get the popup perfectly. Hitting OK closes the popup, but never fires the OK Event.
Here is the code:
//Building the form, we do this in OnInit:
// AJAX Update Panel
UpdatePanel PUP = new UpdatePanel()
{
ID = "PUP",
};
PlaceHolder.Controls.Add(PUP);
// HiddenField containing the field name to permit
// creating the correct modalpopup.
HiddenField HFPopupField = new HiddenField()
{
ID = "HF_POPUP"
};
PUP.ContentTemplateContainer.Controls.Add(HFPopupField);
// Create Hidden button to track the popup
Button BPopup = new Button()
{
ID = "BPOPUP",
UseSubmitBehavior = false
};
BPopup.Click += BPopup_Click;
BPopup.Attributes.Add("style", "display: none;");
PUP.ContentTemplateContainer.Controls.Add(BPopup);
// And create the background panel for the popup.
Panel PnlPopup = new Panel()
{
ID = "PNLPOPUP",
CssClass = "MpeBackground"
};
PnlPopup.Attributes.Add("style", "display: none;");
PUP.ContentTemplateContainer.Controls.Add(PnlPopup);
/// Event handler for hidden button.
protected void BPopup_Click(object sender, EventArgs e)
{
[snip -- code to get the dataset that is being filled]
UpdatePanel PUP = Placeholder.FindControlRecursive("PUP");
Table T = new Table()
{
CssClass = "PopupTbl"
};
TableRow TRTitle = new TableRow();
TableCell TCTitle = new TableCell()
{
CssClass = "PopupTitle",
ColumnSpan = 2
};
Label LPopTitle = new Label()
{
Text = [title of the popup]
};
TCTitle.Controls.Add(LPopTitle);
TRTitle.Cells.Add(TCTitle);
DataRow drData = null;
// Add Fields, and also the cancel and Add buttons
foreach (DataColumn DC in dsColumns.Tables[0].Columns)
{
TableRow TRColumn = [create a tablerow with 2 columns, a prompt and the input field]
if (TRColumn != null)
{
T.Rows.Add(TRColumn);
[snip]
}
} // end of foreach(DataColumn DC in dsColumns.Tables[0].Columns)
PnlWindow.Controls.Add(T);
TableRow TRButtons = new TableRow();
TableCell TCButtons = new TableCell()
{
ColumnSpan = 2,
CssClass="PopupButtons"
};
Button MPEBOK = new Button()
{
ID = "MPE" + sFieldName + "_MPEBOK",
Text = "OK",
CausesValidation = false,
UseSubmitBehavior = false
};
MPEBOK.Click += MPEBOK_Clicked;
TCButtons.Controls.Add(MPEBOK);
LiteralControl LCB = new LiteralControl()
{
Text = " "
};
TCButtons.Controls.Add(LCB);
//************************************************************
//*** Postback Trigger ***
//************************************************************
AsyncPostBackTrigger trigger = new AsyncPostBackTrigger()
{
ControlID = MPEBOK.ID,
EventName = "click"
};
PUP.Triggers.Add(trigger);
//************************************************************
//*** Cancel Button ***
//************************************************************
Button MPEBuhBye = new Button()
{
ID = "MPE" + sFieldName + "_BUHBYE",
Text = "Cancel",
UseSubmitBehavior = false
};
TCButtons.Controls.Add(MPEBuhBye);
TRButtons.Cells.Add(TCButtons);
T.Rows.Add(TRButtons);
PnlPopup.Controls.Add(PnlWindow);
AjaxControlToolkit.ModalPopupExtender MPE = new AjaxControlToolkit.ModalPopupExtender()
{
ID = "MPE" + sFieldName,
PopupControlID = "PNLPOPUP",
TargetControlID = "BPOPUP",
BackgroundCssClass = "MpeBackground"
};
// Add the MPE to the UpdatePanel.
PUP.ContentTemplateContainer.Controls.Add(MPE);
// Show the modal popup extender.
MPE.Show();
}
protected void MPEBOK_Clicked(object sender, EventArgs e)
{
[snip - this never fires]
}
I cannot find out what is happening here. Can anyone see something hinky?
Thanks
John.
You can't add a server side button or inject a server side button into the page DOM.
When you drag a asp.net button onto the form, BOTH the "mypage.cs" and mypage.desinger.cs ARE updated. The wire up of the button occurs at design time, and you would have to modify mypage.desinger.cs ALSO and ADD a button event stub.
So you can't do this.
A compromise would be to also add some js and have that HTML button execute a .click() method of a hidden asp.net button you drop into that page (that would give you the post back, and the running behind of a separate button event code stub.
This event resolution occurs at compile time - not at page render time. You have to drop that button onto the page.
I suppose you could adopt a standard that you always place right below that "div" on the page the button (hidden with style=none. And then as noted, have your injected code along with some js execute a click on the hidden button. Or just have the js button code execute a __doPostback("some value") and pick this up in the page on-load event, and then call the routine (function) from on-page load event.
I think better would be to use a jQuery.UI dialog, as that dialog CAN say load + use another different web page into a “div” on the existing page. So you layout, make, and create the nice looking popup form as a separate web page. jQuery is able to remove the “form” and additonal tags out of that page load, and then inject it into the existing page. (that code would be rather hard to re-produce). so jQuery.UI is able to pop up that separate page. however, the buttons on that loaded page (into that div) of course can't really run any code behind in the current page. However, the buttons CAN run local js in the current page. Thus the actions of this injected page would be local to each page. But the popup would not be directly calling a code behind stub.
Now, to adopt jQuery.UI, then you also have to of course adopt jQuery. So that is two extra libraries you need. (but, jQuery you likely already have).
However, I suppose the whole point of using the ajax toolkit is to avoid jQuery.ui in the first place. To be fair, before jQuery.ui came along, that tool kit was REALLY impressive, and gave asp.net folks a REAL leg up on the competition. (and it tends to be MUCH less wiring up then say using jQuery.UI
So the AjaxToolkit in its heyday was impressive. Now, it of course showing its age, but I still use the kit, and this is especially the case for the AjaxFileUploader. And yes I do use the popups – even to this day. However, I find now that jQuery.UI dialogs are more flexible, and would be better in this case (because you want a on-the fly setup).
Also, having code behind buttons in even the jQuery.UI dialog, or in this case the ajax popup? Well, only the action button can run code behind. The cancel button of course will just dismiss the dialog. However, any button in the dialog that WILL run code behind? Well, that's ok, since you have a page post back, and it actually the page postback that BLOWS out the dialog anyway.

Asp.net Custom Table Cell ClientID not set

I have a class derived from WebControls.TableCell.
When the Text property is set, I call a method that dynamically adds asp:Panels and asp:LiteralControls to the Cell. I want to reference these controls in Javascript, so naturally I tried using the ClientId of the panels in my JS functions. However, these controls have no ClientId set (the string is empty). Why is this? How do I force the ClientIds to be set?
As a temporary solution, I set the ClientIDMode to "static" and created the IDs on my own, but this is not satisfactory because it's hard to reference those IDs in JS. Why? If you assign, for example, "12345" to one control, it gets changed on client side to something like "MainContent_123456". This is bad because the "MainContent" part is not fixed; thus I never know for sure what the real Id on the client side will be. Currently, I can get the control with jQuery using $ctrl = $('[id$='12345']');, but this is dirty because it would get any control that has '123456' in its id.
So, back to the original question: how do I get my ClientIds set automatically for my panels in my custom TableCells?
Edit: Code added
protected void Page_Load(object sender, EventArgs e)
{
this.ClientIDMode = System.Web.UI.ClientIDMode.Static;
}
Code in the method that adds the controls to the custom TableCell:
Panel remainingTextPanel = new Panel();
remainingTextPanel.ID = Guid.NewGuid().ToString();
remainingTextPanel.Style["display"] = "none";
LiteralControl remainingText = new LiteralControl(myText.Substring(initialStringLength, myText.Length - initialStringLength));
remainingTextPanel.Controls.Add(remainingText);
this.Controls.Add(remainingTextPanel);
Panel linkBtnPanel = new Panel();
LinkButton lnkBtn = new LinkButton() {Text = "...", OnClientClick = "toggleDynamicText('" + remainingTextPanel.ID + "'); return false;" };
lnkBtn.Font.Bold = true;
linkBtnPanel.Controls.Add(lnkBtn);
this.Controls.Add(linkBtnPanel);
And the JS Code:
function toggleDynamicText(id) {
$ctrl = $('[id$=' + id + ']');
$(document).ready(function () {
$ctrl.toggle(1000);
});
}
Without seeing any code it's difficult to say what's going on but to access your controls using jQuery you can do the following:
$("#<%=myElement.ClientID%>")
This way it doesn't matter what .NET assigns as the ID.

Adding EventHandler for dynamically added control

I've got a problem with adding some controls into a Panel(which gets "PopUpped" by a ModalPopupExtender) and add a CheckedChanged-EventHandler.
First of all, when user clicks on a button, this happens inside the CreatePanelChoose() function:
foreach (ListItem item in lbSupplier.Items)
{
string cbid = "cb" + i;
CheckBox cb = new CheckBox();
cb.ID = cbid;
cb.Text = item.Text;
cb.AutoPostBack = true;
AjaxControlToolkit.MutuallyExclusiveCheckBoxExtender mecbe = new AjaxControlToolkit.MutuallyExclusiveCheckBoxExtender();
mecbe.ID = "mecbe" + cbid;
mecbe.TargetControlID = cbid;
mecbe.Key = "SupplierKEY";
mecbe.BehaviorID = mecbe.ID + i;
//Also adding a Label
phModalPopupExtender.Controls.Add(new LiteralControl("</br>")); //phModalPopupExtender is a PlaceHolder
phModalPopupExtender.Controls.Add(cb);
phModalPopupExtender.Controls.Add(mecbe);
phModalPopupExtender.Controls.Add(lbl);
AsyncPostBackTrigger trigger = new AsyncPostBackTrigger();
trigger.ControlID = cbid;
trigger.EventName = "CheckedChanged";
UpdatePanelMatrix.Triggers.Add(trigger);
i++;
ButtonOK.Enabled = false;
}
lblText.Text = "Select one Supplier";
ModalPopupExtender1.Show();
Then i add the EventHandler in the Page_LoadComplete:
As you can see it also gets asigned to the control (I think).
The ModalPopup shows up correctly, but if I click one of the CheckBox, then it just closes it without going into cb_CheckedChanged, but it makes a Async postback ...
If I check Request.Form["__ASYNCPOST"] its true and Request.Form["__EVENTTARGET"] is also correct. (It gives me the unique id!)
Request.Form["__EVENTARGUMENT"] is empty.
I think I also need to say that I use a masterpage.
The problem shouldn't be the lifecycle of the page, because msdn says:
LoadComplete
Raised at the end of the event-handling stage.
Use this event for tasks that require that all other controls on the page be loaded.
Its the onliest place it makes me think it would be right.
Btw: yes i looked trough the topics here allready, but nothing helped me ... (google fo sure also)
Edit 1:
if (IsPostBack)
{
if (recreating == true)
{
CreatePanelChoose();
}
}
In CreatePanelChoose i do the foreach now everytime when its a postback! But it still doesnt fire cb_ChangedChecked ...
Edit 2:
MSDN-Page-Lifecycle also says:
PreInit
Raised after the start stage is complete and before the initialization
stage begins.
Use this event for the following:
Create or re-create dynamic controls.
So i tried to recreate the Panel there. But i dont have the ListItems there to get the values ... ?!
Okay, gave up ...
If someone would still have an answer, it would be great!
Right now I dont use the OnCheckedChanged-Event of the CheckBoxes anymore.
I just let them select a CheckBox and on the OnClick of the ButtonOk I loop through the CheckBoxes and check which one is selected.

FindControl returns null

I am working on a solution in C# and ASP.NET 4.0 I am trying to get the value of a radiobutton from my page that was dynamically created based on some database information.
Here is what gets generated in the page source:
<td>
<input id="masterMain_3Answer_0" type="radio" name="ctl00$masterMain$3Answer"
value="Y" onclick="return answeredyes(3);" />
<label for="masterMain_3Answer_0">Y</label>
</td>
<td>
<input id="masterMain_3Answer_1" type="radio" name="ctl00$masterMain$3Answer"
value="N" onclick="return answeredno(3,'desc');" />
<label for="masterMain_3Answer_1">N</label>
</td>
Inside the OnClick function of my submit button I want to gather wether Y or N has been selected based on the user's input.
Here is what I have written so far:
RadioButton _rbAnswer = new RadioButton();
RadioButtonList _rbList = new RadioButtonList();
ContentPlaceHolder cp = (ContentPlaceHolder)Master.FindControl("masterMain");
_rbAnswer = (RadioButton)Master.FindControl("masterMain_3Answer_0");
HtmlInputRadioButton rb = (HtmlInputRadioButton)Master.FindControl("masterMain_3Answer_0");
_rbAnswer = (RadioButton)cp.FindControl("masterMain_3Answer_0");
_rbList = (RadioButtonList)cp.FindControl("masterMain_3Answer_0");
I am able to get the ContentPlaceHolder without any issues but the rest of the objects are null after it attempts to get the . I have also attempted removing the "masterMain_" but still doesn't want to find the controls.
Here is the code in which the individual radiobuttonlists are added
TableRow _tempRow = new TableRow();
TableCell _cellOK = new TableCell();
RadioButtonList _rbList = new RadioButtonList();
_rbList.ID = r[0].ToString()+"Answer";
_rbList.RepeatDirection = RepeatDirection.Horizontal;
//add options for yes or no
ListItem _liOk = new ListItem();
_liOk.Value = "Y";
ListItem _linotOk = new ListItem();
_linotOk.Value = "N";
_rbList.Items.Add(_linotOk);
//add cell to row
_rbList.Items.Add(_liOk);
_cellOK.Controls.Add(_rbList);
_tempRow.Cells.Add(_cellOK);
//add the row to the table
stdtable.Rows.Add(_tempRow);
To be able to quickly find dynamically created controls, add a dictionary to your page class:
private Dictionary<string, Control> fDynamicControls = new Dictionary<string, Control>();
then when a new control is created in code and its ID is assigned:
fDynamicControls.Add(newControl.ID, newControl);
and when you need control's reference:
Control c = fDynamicControls["controlIdThatYouKnow"];
When using FindControl don't use the id that's generated by the page. Use the ID that you specified inthe aspx.
If this is inside a Repeateror another DataBound control, you have to first find the current record. (GridViewRow or RepeaterItem) first, an use that item's .FindControl function.
See this (different - not duplicate) question to see a code example of how to do it: How to find control with in repeater on button click event and repeater is placed with in gridview in asp.net C#
When you create dynamic controller give specific ids for them. This facilitate to generate controls with our own id. therefore then we can access the controls with this id.
And also use OnInit life cycle event to generate dynamic controllers, this is the best place to generate them.
RadioButton _rbAnswer = new RadioButton();
_rbAnswer.ID="ranswerid";
Given your update, you'll find that your control heirarchy is fairly deep. You have a RadioButtonList inside a cell inside a row inside a table ...
FindControl is a method that needs to be called on a specific object and can only find objects that are actual children of that object. In this case, you either need to build a recursive method or go directly to the control in question. Since so many of these controls are generated dynamically, you'll have no real way of accessing them directly so building the recursive function may be simplest. However, on very large pages this method can be very resource consuming:
public static WebUserControl FindControlRecursive(this WebUserControl source, string name)
{
if (source.ID.Equals(name, StringComparison.Ordinal))
return source;
if (!source.Controls.Any()) return null;
if (source.Controls.Any(x => x.ID.Equals(name, StringComparison.Ordinal))
return source.FindControl(name);
WebUserControl result = null;
// If it falls through to this point then it
// didn't find it at the current level
foreach(WebUserControl ctrl in source.Controls)
{
result = ctrl.FindControlRecursive(name);
if (result != null)
return result;
}
// If it falls through to this point it didn't find it
return null;
}
This is an extension method that would allow you to call this on your ContentPlaceHolder control:
var _cp = (ContentPlaceHolder)Master.FindControl("masterMain");
RadioButtonList _rbList = _cp.FindControlRecursive("3Answer");
if (_rbList != null)
// ... Found it
Note: Treat the above as psuedo-code. It has not be implemented by me anywhere so may (likely) require tweaking to behave exactly right.

ASP.NET:adding controls Dynamicly afther post back with diffrent data

i got a page where the admin can edit some photos i pull from flickr so all the controls are added Dynamiclly.
i have few controls i add to a panel and then add the panel to Form.Controls.
all the data thay are holding come from flickr.
my problem is that, afther a post back the list change and the controls are loading, BUT the Text Box's keep that same data befor the post back even though i get the data from flickr every time.
what wierd for my that if i do this with less textbox every thing work fine.
the list are about 10+ pictures and this numbers can change in the future.
(sorry for the messy code im pretty new to programming)
here is the code:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
RadioButtonList1.AutoPostBack = true;
selectedSetID = getSelectedSetId(RadioButtonList1.SelectedItem.Text);
photoSet = new MyPhotoSet(selectedSetID);
photo = new FlickerImages[photoSet.Count];
for (int j = 0; j < photo.Length; j++)
{
photo[j] = new FlickerImages(photoSet.MediumURLS[j], photoSet.ThumbnailURLS[j], photoSet.Titles[j], photoSet.Descreption[j], photoSet.PhotosID[j]);
panel = photo[j].GetPanelWithEditControls(j);
button = new Button();
button.ID = "sendDataButton" + j;
button.Text = "send data";
button.Click += button_Click;
panel.Controls.Add(button);
Form.Controls.Add(panel);
}
}
else
{
//here is the post back,
every thing load fine.
adding and removing the controls but if the textbox's load in the same place, it hold the same data befor the post back.
RadioButtonList1.AutoPostBack = true;
selectedSetID = getSelectedSetId(RadioButtonList1.SelectedItem.Text);
photoSet = new MyPhotoSet(selectedSetID);
photo = new FlickerImages[photoSet.Count];
for (int i = 0; i < photo.Length; i++)
{
photo[i] = new FlickerImages(photoSet.MediumURLS[i], photoSet.ThumbnailURLS[i], photoSet.Titles[i], photoSet.Descreption[i], photoSet.PhotosID[i]);
panel = photo[i].GetPanelWithEditControls(i);
button = new Button();
button.ID = "sendDataButton" + i;
button.Text = "send data";
button.Click += button_Click;
panel.Controls.Add(button);
Form.Controls.Add(panel);
}
}
}
(sorry for my english)
Instead of putting your control generation in Page_Load, put it in Page_Init.
Since you mentioned that you're pretty new to ASP.NET, I strongly recommend reading up on the Page Lifecycle.
As an ASP.NET developer, it's very important that you understand how and when certain things happen on the server in between the time the server receives a request for an aspx page, and the time the page is output to be rendered by the browser.
The short explanation for why I suggest putting the code in Page_Init is how the runtime deals with dynamically created controls and Viewstate. The Viewstate contains any changes made by the user of your web app. In the page lifecycle, you'll notice that the application of Viewstate values happen before Page_Load, but after Page_Init.
This means that if your code in in Page_Load, then the controls are generated from Viewstate, updated with what the user put in, but then your own code in page_Load is re-generating those controls with the default values. There's nothing that happens after this event that can re-load the user's input.
(Hopefully that makes sense. If not, the page I linked to may explain it better.)
There's another article on here that addresses the subject from a different angle, but still should be very helpful for you.
And there's the ultra-short version here.
And finally, (saving the best for last) one of the most comprehensive articles on the subject that explains things in plain English can be found here.

Categories

Resources