I'm designing/building a medium-scale ASP.NET web application using WebForms. The main work in the project is building each web form, and I don't know how to design (in a code/data/data flow sense) my controls.
I'll give you a few examples of what I'm dealing with so you can understand what I mean:
A common task in the application is the entry and display of street addresses. So I created a UserControl called "AddressFields" that contains a series of HtmlInputText elements (one for line 1, another for the town/city, country, postal code, etc). My DB entities classes contains a class called "Address" so this control has the methods:
void ShowAddress(Address addr) - which fills the HtmlInputText elements with the appropriate text).
void UpdateAddress(Address addr) - which updates the contents of addr with the current contents of the HtmlInputText elements
Address CreateAddress() - which creates a new instance of Address and then passes it into UpdateAddress and then returns it
So far, so good. This works because the AddressFields control is 'dumb' all it does is display and retrieve data to the user. ViewState is managed by the HtmlInputText fields directly too, so no additional logic is required.
Another entity in my application is the "Client" which is a class with an Address attribute, but this class also has a few more complicated aspects. For example a Client has a series of tags ('categories' in my application) that can be assigned. In this case, I designed a subclass of the ASP.NET CheckboxList control.
I created another UserControl called "ClientFields" which contains all the necessary fields to display a Client's information (including an AddressFields control), but it also includes my CheckboxList subclass called "CategoryList".
The problem here is that the CategoryList control needs to be supplied to the data to display (but not on postbacks, since it uses viewstate). In this case, my question is: whose responsibility is it to connect to the database and retrieve the category listing?
Is it the CategoryList control? (If so, where in the control lifespan does it query the database? It can't do it in Control.Load because that occurs after the ClientFields has been populated. Does it occur in ClientFields itself? (again, then where in the lifespan does it happen, because ClientFields has its ShowClient(Client c) method called within Page.Load. An alternative is to expose the CategoryList from the ClientFields so it can be accessed directly by the Page, but that's a violation of good software design.
In my opinion, CategoryList is responsible to retrieve the category listing data because all of category data should be retrieved no matter if one or more of them are selected by a particular client.
when does CategoryList query the database in its lifespan?
Generally, override OnInit method to populate the CategoryList, but ViewState doesn't begin to work at that time, so you have to retrieve the data from DB and populate itself on each postback. If you'd like to rely on ViewState, define and register the InitComplete event handler of Page on overrided OnInit method and populate the CategoryList in the event handler. Both OnInit method and InitComplete event are called/fired before Load event.
//In CategoryList control
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.InitComplete += new EventHandler(Page_InitComplete);
}
void Page_InitComplete(object sender, EventArgs e)
{
// retrieve the data and populate the control
}
Hope it helps
Related
I want to know about the similarity of the control state and ipostbackdatahandler.
I have disabled view state, then also value of text box in retained in it, why does this happen? Is it because of control state or "i post back data handler" interface.
if it is because of control state then please explain why.
How View State works?
If View State is on for any control, during LoadViewstate, the View State data that got saved last time , gets populated in the control. And in last, the SaveViewState method of every controls that are part of the control hiearchy, gets called and combined View State of all the controls gets base64 enocoded and saved.
So as we know the page is recreated every time page makes a trip to the server, the data persistence is adone with the help of viewstate.
even if we set off the View State of some controls like textbox, checkbox etc.. the data persists during postback. i.e., whenever a page is submitted or posted back to server, the entire form data is posted to the server as a collection with the request. The collection is in the form of NamedValue collection and this collection has the mapping with uniqueid of the control and the value of the control. You can read the data from the form collection by using the following code snippet
//Reading textbox value from the form collection
string textboxvalue = Request.Form[textbox1.UniqueID];
ASP.NET uses this primitive to update the control’s value. ASP.NET uses IPostBackDataHandler for the controls that load the data from the form collection.
Actually all the controls which implement IPostbackdatahandler, implement the method LoadPostData and RaisePostDataChangedEvent. But here the key method is LoadPostData, which returns true if the posted value is changed from earlier value and updates it with posted value, else it returns false. Lets see the sample code here
public virtual bool LoadPostData(string uniqueId,
NameValueCollection postedCollection) {
//Getting the current value of control
String currentValue = this.Text;
//Getting the posted value from the form collection
String postedValue = postedCollection[uniqueId];
//Checks whether the posted value is changed from the current value, if yes updates it with the posted value and return yes
if (currentValue == null || !currentValue.Equals(postedValue)) {
this.Text = postedValue;
return true;
}
//else return false
return false;
}
As from the Page Life Cycle, we can see LoadPostData is called after the LoadViewState, whether viewstate is on or not, it gets populated from the posted data. That’s why the data get persisted even if viewstate is set to off for few controls. Following is the complete list of the controls, those implement IPostBackDataHandler.
CheckBox
CheckBoxList
DropDownList
HtmlInputCheckBox
HtmlInputFile
HtmlInputHidden
HtmlInputImage
HtmlInputRadioButton
HtmlInputText
HtmlSelect
HtmlTextArea
ImageButton
ListBox
RadioButtonList
TextBox
Source
ViewState is a messy hack to persist data between requests. The web is stateless and ViewState tries to make it appear stateful.
I believe the TextBox (and other <input/> controls) keep their value without ViewState because their values are POSTed in a form, while Label (<span/>) values are not.
Many Web Forms developers that care about page size will disable ViewState globally and only enable it for specific controls. If you use standard paging in a GridView, for example, you will need ViewState so that ASP.NET knows what page number the user clicked. You could use custom paging and use actual links for the page numbers and then turn on off ViewState.
On asp.net page, I am using tabs and one of the tab has got user control on it.
On the first tab, data is being displayed from table A and the second tab (which has user control on it) is getting data from table B.
On the user control (second Tab), I need to show the column value of table A. It is just one string value.
I am wondering if there is any best way of displaying the value of a table A column without making a call to database?
The way code has been designed, I can’t access the user control’s textbox from the first tab.
I can only think of using view state or session but don’t know if I should use them instead of making call to DB.
I want value to live for the current page's lifecycle.
If you can save it in viewstate then go for it. But there are plenty of storage options in addition to just viewstate:
querystring (good for Ids, not great for strings)
cookie (pretty straight forward)
local storage (HTML 5 only)
cache (you could still appear to make the call but just have the results cached. you then have to deal with cache expiration as well)
session (as you mentioned, this is basically a per-person cache usage, but is not a bad option)
hidden field (basically what viewstate is)
Even with all of those options, the viewstate is going to be a pretty good one, but it just requires that you post back the viewstate every time you need that value.
How about using js to copy the data contents from tab1? Are you loading the usercontrols in tabs using ajax?
If you have a complex form and need to split into smaller chunks I would use a multiview control with as many views as you need to complete your task. If you design each view with its own controls, validation groups and logics .net will do the rest, you won't have to manually deal with states or saving middle steps
<asp:MultiView ID="MultiView1" runat="server">
<asp:View ID="View1" runat="server">
<asp:TextBox ID="txt1" runat="server" />
<asp:Button ID="Button1" runat="server" Text="Next" OnClick="NextStep_Click" />
</asp:View>
<asp:View ID="View2" runat="server">
<asp:TextBox ID="txt2" runat="server" />
<asp:Button ID="Button2" runat="server" Text="End" OnClick="EndProcess_Click" />
</asp:View>
</asp:MultiView>
<asp:TextBox ID="txt3" runat="server" />
In code behind
protected void NextStep_Click(object sender, EventArgs e)
{
MultiView1.SetActiveView(View2);
txt2.Text = txt1.Text;
}
protected void EndProcess_Click(object sender, EventArgs e)
{
txt3.Text = txt1.Text + " " + txt2.Text;
}
you can go back and forth the times you want and won't have to worry with the values the users entered. Obviously, you have to put buttons to go back and just set the active view you want.
In building custom controls, I've seen two patterns for using the viewstate. One is to use properties to disguise the viewstate access as persistent data.
public bool AllowStuff
{
get
{
return (ViewState[constKeyAllowStuff] != null) ?
(bool)ViewState[constKeyAllowStuff] : false;
}
set { ViewState[constKeyAllowStuff] = value; }
}
The other is to use private member fields and to override the Load/SaveViewState methods on the control and handle it all explicitly:
protected override object SaveViewState()
{
object[] myViewState = new object[2];
myViewState[0] = base.SaveViewState();
myViewState[1] = _allowStuff;
return myViewState;
}
protected override void LoadViewState(object savedState)
{
object[] stateArray = (object[])savedState;
base.LoadViewState(stateArray[0]);
_allowStuff = (bool)stateArray[1];
}
(I cut out a lot of safety checking for clarity, so just ignore that.)
Is there are particular advantage to one method over the other? I can't see how they'd differ much performance wise. Version 1 is lazy, so I guess you save a bit if you don't need that particular value during a pass. Version 1 is also more abstract, hides the details better. Version 2 is clearer about when the data is actually valid and ok to read or modify (between the load and save) because it more clearly works within the ASP.NET lifecycle.
Version 2 does tend to require more boilerplate code though (a property, a backing private field, and viewstate handling in two places) as opposed to Version 1 which combines all that into one place.
Thoughts then?
The private member field approach is often used for objects who do not directly have access to the ViewState state bag. So in a sense, I'd use option one for custom controls, user controls, or pages, or anything that has a ViewState or similar property, but use the other option for an object that does not directly have access to ViewState (like a class you want to be able to "serialize" and store in viewstate). For instance, custom controls would use that approach to store state for child objects that do not directly reference viewstate.
HTH.
Fist of all I would use ControlState and not viewstate so it works correctly if in a container that has view state turned off.
Then i would override init, savecontrolstate, loadcontrolstate and databind.
and make sure to register that the control uses the control state i.e. Page.RegisterRequiresControlState(this)
oh and the advantage is that your control is more robust (user can't screw it up as easily) and will work when dynamically loaded and across postbacks "better"
I am working on an ASP.NET WebForms project, and we need the ability to configure behavior throughout the application based on the current user's "group". This applies to almost all aspects of the application, including site navigation, showing/hiding certain user controls on pages, and executing custom business logic in some cases. However, the vast majority of the application behavior is shared among groups, so we've ruled out the idea of creating entirely separate apps.
Essentially, what I'm looking for is an architectural approach to implementing custom behavior in an ASP.NET WebForms application. Is there a better approach than sprinkling if/else statements throughout the code base in the view layer, the business layer, and the persistence layer?
Edit: Some examples:
If a user in in Group A, their
navigation will consist of all
navigation from Group B plus a few
additional links.
If a user is in Group A, a page will
show user controls c1, c2, and c3.
If the user is in Group B, they will
only see c1 and c3 on the same page.
If a user saves some data on a form
and they are in Group A, send a
notification email. If the user is
in Group B, send a text message
instead.
We can solve all of these specific problems, but are looking for a way to encapsulate this behavior as much as possible so it's not scattered across the code base.
Edit: There are some interesting answers related to dynamically loading user controls. Should the logic to determine which controls to load or which behavior to use based on the user's group be encapsulated in one (non-cohesive) class, e.g.:
GroupManager.GetNavigationControl(int groupId) // loads site nav control based on group
GroupManager.PerformNotification(int groupId) // sends text or email based on group
Or should this logic exist as close as possible to the location in code where it is used, and therefore be spread across the different layers of the code base?
Well there's not a ton of details to go on here, but I would suspect you might benefit from polymorphism (i.e. various interface implementations) to deal with the parts of the application that differ between user groups. An Inversion of Control container like Spring.NET can help you wire up/configure these various implementations together based on the current user role. You might also benefit from Spring's Aspect Oriented Programming API in which you can decorate methods in your business layer/data access layer so that authorization logic can be executed.
By "Groups" do you mean "Roles"? If you're talking about roles, you can set your behavior by doing something like this
If User.IsInRole("SomeRandomRole") Then
'Do some random behavioral crap
ElseIF User.IsInRole("TheCoolRole") Then
'Do some cool behavioral crap
Else
'Do generic crap
End If
Another option might be to use UserControls based on roles. So when you have a page load, it will load a usercontrol based on the role that requested it.
you could have an PlaceHolder sitting empty and call the LoadControl method from the codebehind.
Then all your user controls would match your roles
Role = Admin | UserControl = Admin.ascx
Role = User | UserControl = User.ascx
Without going into too much detail and going on about IoC and all the like, I think I'd keep it pretty simple and have a plain old factory class that you would use to return the appropriate instantiated UI elements [user controls] based on the current user making the request. In doing this, you will have all of your 'if' statements in one single location. To displense with the 'if' statements you could simply create a mapping config file or DB table that contains references to the user controls to use when a user belongs to a particular group.
Note: Both of these options will result in the creation of dynamic controls on the page which is not without its own complications but I have successfully been using dynamic controls in my apps without issue for a while now - it was just a matter of getting down and dirty with the page life-cycle more than I initially felt comfortable with.
You could also inherit from the Principal object to handle this however you would like.
Here's how I have done it in an application that has custom rules like this:
Created my own IPrincipal object that descended from my "Person" object to be able to inherit the ability to look up groups and roles and such:
public class Principal : MyNamespace.Person, IPrincipal {
}
Make the current context use my IPrincipal object:
protected void Application_AuthenticateRequest(Object Sender, EventArgs E) {
if (HttpContext.Current.User != null &&
HttpContext.Current.User.Identity.IsAuthenticated &&
HttpContext.Current.User.Identity is FormsIdentity) {
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
HttpContext.Current.User = new MyNamespace.Principal(id);
}
}
I then made static methods so that I didn't have to cast every time I wanted to get the current user like this:
public class CurrentUser {
/// <summary>
/// Is the current user authenticated
/// </summary>
static public bool IsAuthed {
get { return System.Web.HttpContext.Current.User.Identity.IsAuthenticated; }
}
/// <summary>
/// Returns the Principal object in case it is needed. Also used for other static properties of this class.
/// </summary>
static public MyNamespace.Principal User {
get {
return (MyNamespace.Principal)System.Web.HttpContext.Current.User;
}
}
}
Then you can call things like CurrentUser.User.IsInGroup().
I have a WebPart custom control (Composite) in .Net, which gets created on page load to show a Chart using 'Dundas Charting Controls' (this is created by a user control inside the page). I get the properties for this control from the database.
I have another control, which is a Filter (outside webpart) and based on data of this filter control which the user selects and which I would get on postback after click of button, I have to show the filtered chart results. The problem is CreateChildControls() gets called before the postback data is available (which would be available only after the Page_Load event fires).
I'm unable to get this data in time to pass on the parameters for filtering the Chart Results.
The implementation os like this ...
Webparts
Page > User Control > Webparts > Composite Control/Chart
Filter
Page > User Control > Composite Control [I get this data on Postback]
It sounds like you are running into an event ordering issue. I always try to make my controles relatively dump - so they don't really know how they are being used.
Consider creating a method in your chart control to force an update of its data:
public void UpdateChart(-- arguments as needed --)
then create an event in your composit control (that has your filters) like
public event Eventhandler FiltersChanged;
Assign this to an event hander on parent page:
filterControl.FiltersChanged += new EventHandler(Filter_OnChange)
Then create an event handler that tells your chart control about the change
Filter_OnChange(Object sender, EventArgs e)
{
// get whatever data you need from your filter control
// tell the chart about the new data and have it reload/redraw
myChart.UpdateData( - filter options here -}
}
In doing so, you let the page direct the order of operations and do not rely on the order in which the child controls Load methods are called.
James - Thanks for your answer, but this does not seem to work in my scenario or rather I couldn't make it work, when I tried it. The controls seems to be doing too much and is getting data from every where, it has its own constructor implementation, Load() override etc so a single UpdateChart() function may not have done the trick in this case.
This is what I did, finally.
I fire an Ajax request with Filter Data and set the value in a Session Variable before page does a Postback, this way I get the data at all places/events, and pass on the same as parameter where required. I know it may seem weird way to implement this, but it saved additional Database calls (which in this case are many to create the controls again) even though it comes at the cost of an additional Server HTTP ajax request.
Let me know this implementation can have any negative impact.