I have an asp.net page with many dozens of controls representing multiple database entities. I would like to track when any of the data for these entities is changed.
If I add a page member such as
public Page1 : System.Web.UI.Page
{
protected bool Entity1HasChanged { get;set; }
protected void RegisterChange(object sender, EventArgs e)
{
Entity1HasChanged = true;
}
}
Then for each control OnTextChanged event or equivalent I call a method that sets this boolean to true.
I would then like to run a method AFTER all control events have completed which does some database updates. What page event can I hook into to do this? Reading the page on the LoadComplete event it only states that all load events will have completed.
Does anyone know how I should achieve this? Am I doing this completely the wrong way?
Thanks,
Ian
Look at INotifyProperyChanged, INotifyPropertyChanging and INotifyCollectionChanged as your starting point.
Try OnPreRender.
ASP.NET Page Life Cycle Overview
This will also still allow you to modify the page output once you've completed the database operations (e.g. if you want to show a status box to say that the operations completed).
This is a really good question. I messed around with something quick that I think will work. You could create your won TextBox that inherits from TextBox:
namespace ServerControl1
{
[DefaultProperty("Text")]
[ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")]
public class TextBoxWithChange : TextBox
{
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public bool HasEntityChanged
{
get
{
bool hasEntityChanged = (bool) ViewState["HasEntityChanged"];
return hasEntityChanged;
}
}
protected override void RenderContents(HtmlTextWriter output)
{
output.Write(Text);
}
}
}
Then you could write a little jQuery script to change that attribute when the client side OnTextChanged event fires. On submit of the form you then could query that HasEntityChanged attribute for any of these TextBoxes.
For this example I put the server control in it's own Library and registered it like this:
<%# Register TagPrefix="MyCompanyName" Namespace="ServerControl1" Assembly="ServerControl1" %>
Then you can declare it on your page like this:
<MyCompanyName:TextBoxWithChange ID="ChangerTextBox" runat="server" HasEntityChanged="false"></MyCompanyName:TextBoxWithChange>
Related
I am working on a WPF application and I have a window with a grid in it and a few user controls. The problem arises when I add these user controls to the grid from the code behind (through C#),
// UserControlA is the user control with all the working content
var ucA = new UserControlA;
Grid.Children.Add(ucA);
There are two things that happen here, one is the user control is initialized, the second is any load events I have in the code behind of the user control also get fired.
The reason I have these load events like is because before the user control is presented to any employee then it should check for employee authorization, so instead of doing this
public partial class UserControlA : UserControl
{
public UserControlA()
{
IntializeComponent();
Authorize();
}
}
I do this:
public partial class UserControlA : UserControl
{
public UserControlA()
{
IntializeComponent();
}
private void UserControlA_Loaded(object sender, RoutedEventArgs e)
{
Authorize();
}
}
I only want to know why the Loaded event is fired without any reason?
I'm implementing a mini program in C# using the MVC Architectural Pattern. The goal of this program is to update the value of a mouse click counter (which is in the Model), by clicking a button (which is in the View) through a Controller that must handle the Button Click Event.
The code I've written so far works (it compiles without errors), but if fails to handle the button click event because I can't figure out what kind of code I must put into the View and what into the Controller. The only solution I tried and found working is to give the View a reference to its Controller. In this way, the Event Handler is registered and implemented in the View and it invokes a method of the Controller (e.g. Controller.DoSomethingOnButtonClick()). But this solution breaks the MVC Pattern, because the View, as far as I understood, should NOT be aware of its Controller.
The Model (implements the Observer Pattern, it is the "observable"):
class Model : Subject
{
private int counter = 0;
public void IncreaseCounterByOne()
{
counter++;
Notify(); // notify the observers
}
public int GetCounter()
{
return counter;
}
}
The View (implements the Observer Pattern, it is the "observer"):
class View : IObserver
{
private Model Model;
private Form MyForm = new Form();
private Label MyLabel = new Label();
private Button MyButton = new Button();
public View(Model model)
{
this.Model = model;
this.Model.Attach(this);
}
public void CreateView()
{
// create and display the view (MyForm, MyLabel, MyButton)
}
public void Update(Subject subject)
{
UpdateLabel();
}
private void UpdateLabel()
{
MyLabel.Text = "Click Counter: " + Model.GetCounter();
}
}
The Controller:
class Controller
{
private Model Model;
private View View;
public Controller(Model model, View view)
{
this.Model = model;
this.View = view;
this.View.CreateView();
}
private void UpdateCounter()
{
this.Model.IncreaseCounterByOne();
}
}
What I want to achieve is that the Controller catch the Button Click Event generated by MyButton and handle it in its Event Handler, which I assume to be something like:
public void OnButtonClicked(object sender, EventArgs e)
{
UpdateCounter();
}
How can I accomplish this without using a reference to the Controller? Is it possible?
PS. I already read a lot of similar questions, but not found the solution I'm looking for.
You're running into a problem where trivial examples for patterns like MVC fall down: the UI often takes on both roles of V and C - it shows the data and offers a way to manipulate it. If your example were more involved (eg a web service is the controller)the concerns would be easier to see separated
The separation is still there logically, but you can't easily put the button in one class and the label that shows the counter in another as a method of separation, when you're essentially trying to combine it al in the same UI - but you shouldn't worry about this
Accept that your UI contains your view and also contains your controller so it's the UI class(es) job to unify all the things. Consider that your label is actually the view, not your class that happens to be called View. The concept appreciation you need to acquire is that you can have 3 different ways of changing the model (a timer, a button, a tcp socket that you send some data to) via the controller and 3 different ways of visualizing it (a label showing a number, a progress bar, a call to a website page that returns a string of 'A' as long as the current counter) and you've separated your concerns - the label/progbar/website are independent of the timer/button/website and they have no knowledge of each other, they don't interact, you don't need a reference to one passing into the other in order for everything to function. You can remove the button and the socket and the timer will carry on causing model manipulations that the label/progbar will carry on showing
I'm using an asp.net user-control in two different aspx forms. How can I customize the events handling of the user-control depending on the calling form ?
void ComboboxCountry_SelectedIndexChanged(object sender, RadComboBoxSelectedIndexChangedEventArgs e)
{
if it is form1 that called the User-control => do process 1
if it is form2 that called the User-control => do process 2
}
Thanks.
Despite the fact if this is a good design you are trying to implement: you could add a property to your control like
public BehaviourEnum Behaviour { get; set; } // You need to implement the enum
Then you could
void ComboboxCountry_SelectedIndexChanged(object sender, RadComboBoxSelectedIndexChangedEventArgs e)
{
if (Behaviour == BehaviourEnum.Behave1) // etc.
}
The pages implementing your control would need to set the Behaviour-Property accordingly.
Edit: If your control needs to interact with the parent page, I would introduce an interface on the parents page. Then you could design something like this:
// The page containing this control needs to implement IMasterpage
public IMasterpage Masterpage { get; set; }
void ComboboxCountry_SelectedIndexChanged(object sender, RadComboBoxSelectedIndexChangedEventArgs e)
{
// Propagate the behavour to your parent page
Masterpage.CallwatheverMethodInYourInterface();
}
The goal is to propagate behavour which depends on the parent page into the parent page itself. That way you can keep your control slim and independent.
I am trying to implement MVVM (Model View ViewModel) pattern for my WinForms application. I am using C# 2005.
My application has a MainForm (View) with 2 multi line textboxes and 3 buttons. The purpose of the 1st textbox is to show a running commentary of what the application is doing, when the button is clicked. I keep on appending lines to the TextBox to update the user what is happening. The purpose of the 2nd textbox is to update the user about any error condition, conflicts, duplicate values; in short, anything which is required by the user to review. It classifies each message as either an INFO or a WARNING or an ERROR. Each of the 3 buttons perform an action, and keeps updating the 2 textboxes.
I have created a MainFormViewModel class.
1st question:
When the user clicks on the button in MainForm, I have to clear the contents of the 2 textboxes, and disable the button so that it cant be clicked again until 1st operation is completed. Should I do this textbox and button updation directly in the MainForm or I should use MainFormViewModel in some way?
2nd question:
The button click calls a method on the MainFormViewModel class. Before calling the method and after calling the method, I want to show a message in the 1st textbox something like "Operation A started / ended". I do this by calling a Common class which has a Log method to log messages to a TextBox or a file or both. Again whether it is ok to do this directly from the MainForm? I call this logging method at the start and end of the event handler.
3rd question:
How do I propagate error messages from ViewModel back to View? I have created a custom Exception class "TbtException". So do I have to write 2 catch blocks in each and every button, one for TbtException and other for genetic Exception class?
Thanks.
You should perform operations in the view only with regard to the state of the ViewModel object. E.g. you shouldn't assume the view model is calculating when you click a button, but you should add a state to the view model that says it's doing something longer and then recognize that state in the view. You shouldn't disable or enable buttons in the view as you please, but only if there's a state that demands these buttons to be changed. This can go as far as to have a property that indicates which item in a list is currently selected, so the UI doesn't call the list control's SelectedItem member, but the viewmodel's. And when the user clicks remove, then the view model will remove the selected member from its list and the view is automatically updated through state changes in the form of events.
Here's what I would call a view model for your view. It exposes messages through an observable collection to which the view can bind (ie. register event handlers, since binding is not well supported in WinForms). The textbox at any time renders only the contents of the collection. It has actions for clearing those collections which your view can call. The view can also call actions of the underlying model, but it should be updated through the viewmodel only! The view should never register any event handlers for events exposed by the underlying model. If you ever want to do that you should hook up that event in the view model and expose it to the view there. Sometimes that may feel like "just another level of indirection" which is why it may be overkill for very simple applications such as yours.
public class MainFormViewModel : INotifyPropertyChanged {
private object syncObject = new object();
private MainFormModel model;
public virtual MainFormModel Model {
get { return model; }
set {
bool changed = (model != value);
if (changed && model != null) DeregisterModelEvents();
model = value;
if (changed) {
OnPropertyChanged("Model");
if (model != null) RegisterModelEvents();
}
}
}
private bool isCalculating;
public bool IsCalculating {
get { return isCalculating; }
protected set {
bool changed = (isCalculating != value);
isCalculating = value;
if (changed) OnPropertyChanged("IsCalculating");
}
}
public ObservableCollection<string> Messages { get; private set; }
public ObservableCollection<Exception> Exceptions { get; private set; }
protected MainFormViewModel() {
this.Messages = new ObservableCollection<string>();
this.Exceptions = new ObservableCollection<string>();
}
public MainFormViewModel(MainFormModel model)
: this() {
Model = model;
}
protected virtual void RegisterModelEvents() {
Model.NewMessage += new EventHandler<SomeEventArg>(Model_NewMessage);
Model.ExceptionThrown += new EventHandler<OtherEventArg>(Model_ExceptionThrown);
}
protected virtual void DeregisterModelEvents() {
Model.NewMessage -= new EventHandler<SomeEventArg>(Model_NewMessage);
Model.ExceptionThrown -= new EventHandler<OtherEventArg>(Model_ExceptionThrown);
}
protected virtual void Model_NewMessage(object sender, SomeEventArg e) {
Messages.Add(e.Message);
}
protected virtual void Model_ExceptionThrown(object sender, OtherEventArg e) {
Exceptions.Add(e.Exception);
}
public virtual void ClearMessages() {
lock (syncObject) {
IsCalculating = true;
try {
Messages.Clear();
} finally { IsCalculating = false; }
}
}
public virtual void ClearExceptions() {
lock (syncObject) {
IsCalculating = true;
try {
Exceptions.Clear();
} finally { IsCalculating = false; }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropetyChanged(string property) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(property));
}
}
EDIT: Regarding exception handling
I would rather catch exceptions in the ViewModel than in the view. The view model is better suited to prepare them for display. I don't know how that works in WPF though. I've yet to program an application in WPF, we're doing a lot of WinForms still.
Opinions may vary, but I think generic try/catch clauses aren't really exception handling. I think you should rather test your UI very well and include exception handling only when necessary. Which is why you unit test your view model and user test the view. However if you really stick to the principle and avoid logic in the view, you can do a lot with unit tests.
I have a button (view state enabled) in Master web page and set it to visible=false in one of the child web pages. If a second child page is opened, the button state (visible=false) is not persisting.
It seems viewstate is only valid for one page and is not transferred to other web pages. Is there some kind of trick to make viewstate global for all web pages?
No, viewstate is page specific. You will need to use something like a session variable or a querystring parameter to pass your state between pages.
No, You cannot make view state global, they are page specific. I would suggest to use cookies if you really want to make it client side otherwise you can use session.
If you need to store on a "global" level, you should be using the Application State. You could also use Cache Object.
You may be wanting to pass values from one page to another, you can achieve this by using the Context object in combination with the Server.Transfer.
1) You need a public property on the source page returning the Value to pass
namespace SomeNameSpace
{
public partial class SourcePage: System.Web.UI.Page
{
public string ValueToPass
{
get
{
if (Context.Items["ValueToPass"] == null)
Context.Items["ValueToPass"] = string.Empty;
return (string)Context.Items["ValueToPass"];
}
set
{
Context.Items["ValueToPass"] = value;
}
}
........
}
}
2) Do a Server.Transfer(DestinationPage.aspx)
3) In the Page_Load event of the destination page
namespace SomeNameSpace
{
public partial class SourcePage: System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
var value = this.Context.Items["ValueToPass"];
}
}
}
Hope this helps