Injecting Dependencies : ASP.net Webforms using an MVP pattern - c#

I'm struggling to implement the injection of dependencies using ASP.net. I have created a simple project in winforms which uses the MVP Passive View pattern. I see lots of variations of this pattern online. Many examples I see have the View responsible for instantiating the Presenter. Others have the Presenter create the View. I chose to let a separate winforms project run first and on the click of a button, create my View, Model and Presenter and have the view passed in to the Presenters constructor. This let me avoid direct references between the three layers such that they only kept a reference to a Library of Interfaces. So far so good and everything worked as hoped. I had heard that if I had managed to keep the View as "dumb" as possible that moving to ASP.net would not be too difficult since the main logic presided in the Presenter.
My next move was to recreate the solution in ASP.net webforms and just replace the UI/View with a webform in whoch the code-behind implemented my IView Interface. All my classes successfully compiled without issue but when I run the program I get a NullReferenceException :object reference not set to an instance of an object. This happens as the Presenter tries to set the view property of a textbox. It seems like the textbox has not been created. If I compare the start sequence of the webforms app with the winforms app then I see that the winforms app runs through the View when it is created and registers each control and property. This does not happen with the webform even though I use the same approach for both.
Here is the separate webform which loads first:
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using POCOClassLibrary;
using InterfaceLibrary;
using AppointmentModel;
using AppointmentsPresenter;
using WebFormMVP;
namespace WebDriver
{
public partial class WebDriver : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnLaunch_Click(object sender, EventArgs e)
{
IAppointmentView view = new frmAppointmentView();
IAppointmentModel<Appointment> model = new AppointmentsModel();
new AppointmentPresenter(view, model);
view.Show();
}
}
}
This loads and the button appears, next on clicking it reaches the Presenter class (not all shown here):
namespace AppointmentsPresenter
{
//The Presenter is the go-between for the View and Model. It sets the properties in the View to be displayed and
//Stores and Gets the data from the Model. It responds to events raised by the View.
public class AppointmentPresenter
{
private readonly IAppointmentView _view; //references the View Interface
private readonly IAppointmentModel<Appointment> _model; //references the Model Interface
/// <summary>
/// The List of Appointments is created and stored in the Model.
/// In reality we would be using a Database and not a List<>.
/// </summary>
private List<Appointment> appointments;
//maintenace of state:
private int currentIndex = 0;
private bool isNew = true;
private string navigationstatus = "next";
///Presenter constuctor which allows us to pass in the View and Model as interfaces - exposing their events,
/// methods and properties.
public AppointmentPresenter(IAppointmentView view, IAppointmentModel<Appointment> model) //dependencies passed into contructor
{
this._view = view;
this._model = model;
Initialize();
}
private void Initialize()
{
appointments = _model.CreateList(); //Creates and returns List of Appointments in the Model
//lets us manipulate the List that the Model creates.
_view.SaveAppointment += Save_Appointment; //Subscribe to the View events.
_view.NewAppointment += New_Appointment;
_view.PreviousAppointment += Previous_Appointment;
_view.NextAppointment += Next_Appointment;
_view.YesButtonClick += YesButtonClicked;
_view.NoButtonClick += NoButtonClicked;
BlankAppointment();
_view.StatusChange = "Ready";
}
private void BlankAppointment()
{
_view.AppointmentName = string.Empty;
_view.Priority = "Low";
_view.StartDate = null;
_view.DueDate = null;
_view.CompletionDate = null;
_view.Completed = false;
}
When it reaches the BlankAppointment() method and tries to set the _view.AppointmentName property is when things go awry and the NullReferenceException is thrown.
Here is some of the View, I have tried to remove the majority of logic outside and just implement properties and events here:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using InterfaceLibrary;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;
namespace WebFormMVP
{
public partial class frmAppointmentView : System.Web.UI.Page, IAppointmentView
{
public string AppointmentName
{
get { return txtAppointment.Text; }
set { txtAppointment.Text = value; }
}
public string Priority
{
get { return cboPriority.Text; }
set { cboPriority.Text = value; }
}
public string StartDate
{
get { return txtStartDate.Text; }
set { txtStartDate.Text = value; }
}
public string DueDate
{
get { return txtDueDate.Text; }
set { txtDueDate.Text = value; }
}
public string CompletionDate
{
get { return txtCompletionDate.Text; }
set { txtCompletionDate.Text = value; }
}
public bool Completed
{
get { return chkCompleted.Checked; }
set { chkCompleted.Checked = value; }
}
public bool isDirty { get; set; }
public bool NewButtonVisible
{
set { btnNew.Visible = value; }
}
public bool SaveButtonVisible
{
set { btnSave.Visible = value; }
}
public bool NextButtonVisible
{
set { btnNext.Visible = value; }
}
public bool PreviousButtonVisible
{
set { btnPrevious.Visible = value; }
}
public bool YesButtonVisible
{
set { btnYes.Visible = value; }
}
public bool NoButtonVisible
{
set { btnNo.Visible = value; }
}
public string StatusChange
{
set { lblStatus.Text = value; }
}
public event EventHandler<EventArgs> NewAppointment;
public event EventHandler<EventArgs> NextAppointment;
public event EventHandler<EventArgs> NoButtonClick;
public event EventHandler<EventArgs> PreviousAppointment;
public event EventHandler<EventArgs> SaveAppointment;
public event EventHandler<EventArgs> YesButtonClick;
public void Show()
{
this.Show();
}
protected void Page_Load(object sender, EventArgs e)
{
this.isDirty = false;
}
private void btnSave_Click(object sender, EventArgs e)
{
if (SaveAppointment != null)
{
SaveAppointment(this, EventArgs.Empty);
}
}
private void btnNew_Click(object sender, EventArgs e)
{
if (NewAppointment != null)
{
NewAppointment(this, EventArgs.Empty);
}
}
private void btnPrevious_Click(object sender, EventArgs e)
{
if (PreviousAppointment != null)
{
PreviousAppointment(this, EventArgs.Empty);
}
}
private void btnNext_Click(object sender, EventArgs e)
{
if (NextAppointment != null)
{
NextAppointment(this, EventArgs.Empty);
}
}
private void btnYes_Click(object sender, EventArgs e)
{
if (YesButtonClick != null)
{
YesButtonClick(this, EventArgs.Empty);
}
}
private void btnNo_Click(object sender, EventArgs e)
{
if (NoButtonClick != null)
{
NoButtonClick(this, EventArgs.Empty);
}
}
private void txtAppointment_TextChanged(object sender, EventArgs e)
{
this.isDirty = true;
}
private void cboPriority_SelectedIndexChanged(object sender, EventArgs e)
{
this.isDirty = true;
}
private void txtStartDate_TextChanged(object sender, EventArgs e)
{
this.isDirty = true;
}
private void txtDueDate_TextChanged(object sender, EventArgs e)
{
this.isDirty = true;
}
private void txtCompletionDate_TextChanged(object sender, EventArgs e)
{
this.isDirty = true;
}
private void chkCompleted_CheckedChanged(object sender, EventArgs e)
{
this.isDirty = true;
}
}
}
Form my searches so far it seems like there may be a problem with implementing the injection into the Presenters constructor in webforms and that I would need to override the PageHandler class to resolve the dependencies however the examples I have looked at so far have the Presenter being created first by the View. I would like to avoid this if at all possible and use a separate webform Page to instantiate the Model, View, Presenter and resolve the dependencies this way.
Apologies if I haven't been clear - I am very wet behind the ears with both MVP and ASP.net Webforms. Any advice would be greatly appreciated as I am pretty confused as to how I might solve this puzzle. ;D

Related

Binding TextBox to a Windows Form not working

My environment is:
Windows 11
Windows Form App created by the Windows Forms App template of Visual Studio 2022.
Problem:
I have a simple Windows Form with only a Textbox and a Button.
I am trying to update the Textbox text with new data whenever the button is pressed.
The binding works when the Windows Form is loaded. The text "12.34" appears in the Textbox.But when I click on the button, the Textbox is not updated with the new data.
Here is my code:
namespace WatchChart;
public partial class WatchForm:Form
{
public class BidAsk
{
public string? BidString { get; set; }
}
public WatchForm()
{
InitializeComponent();
}
private void MyForm_Load(object sender, EventArgs e)
{
var bid = new BidAsk() { BidString = "12.34" };
bidTextBox.DataBindings.Add(nameof(TextBox.Text), bid, nameof(BidAsk.BidString));
}
private void displayButton_Click(object sender, EventArgs e)
{
var bidask = new BidAsk();
bidask.BidString = "23.45";
}
}
Any help will be greatly appreciated,Charles
The first potential issue is that you need the bound variable to be a member of the main form (instead of making a local var inside the methods). The other potential issue is making sure to enable two-way binding. The BidString property should be implemented similar to the BidAsk shown in order to fire a PropertyChange event whenever its value changes.
class BidAsk : INotifyPropertyChanged
{
string _bidString = string.Empty;
public string BidString
{
get => _bidString;
set
{
if (!Equals(_bidString, value))
{
_bidString = value;
OnPropertyChanged();
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Test
Here's the code I used to test this answer::
public partial class MainForm : Form
{
public MainForm() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
BidAsk = new BidAsk();
textBoxBid.DataBindings.Add(
propertyName: nameof(TextBox.Text),
dataSource: BidAsk,
dataMember: nameof(BidAsk.BidString));
buttonDisplay.Click += onClickButtonDisplay;
}
BidAsk BidAsk { get; set; }
private void onClickButtonDisplay(object? sender, EventArgs e)
{
// Test string gobbledegook
var marklar = Guid.NewGuid().ToString().Substring(0, 11);
BidAsk.BidString = marklar;
}
}
You are creating a new BidAsk (let's call it bidAsk1) on load, and binding to that. Then on the button click, you are creating another BidAsk (bidAsk2) and setting the content. Your textbox is still bound to bidAsk1, not bidAsk2 so it will not be updated.
Try keeping a reference to the bound object:
namespace WatchChart;
public partial class WatchForm:Form
{
public class BidAsk
{
public string? BidString { get; set; }
}
private BidAsk bid;
public WatchForm()
{
InitializeComponent();
}
private void MyForm_Load(object sender, EventArgs e)
{
bid = new BidAsk() { BidString = "12.34" };
bidTextBox.DataBindings.Add(nameof(TextBox.Text), bid, nameof(BidAsk.BidString));
}
private void displayButton_Click(object sender, EventArgs e)
{
bid.BidString = "23.45";
}
}

How to share data from one view to another?

I have some problems with simple variable sharing between different views.
I have first main view called MainPage.xaml and second called Page2.xaml.
I want to check which radiobutton on MainPage.xaml is checked and send a variable with that date to Page2.xaml.
MainPage:
namespace IDG
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public string choice;
public MainPage()
{
this.InitializeComponent();
}
private void bt_start_Click(object sender, RoutedEventArgs e)
{
if (rb_choice1.IsChecked == true)
{
choice = "choice1";
}
if (rb_quiz.IsChecked == true)
{
this.Frame.Navigate(typeof(Page2), choice);
}
}
}
}
And Page2:
namespace IDG
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class Page2 : Page
{
private string x;
public Page2()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var param = e.Parameter as string;
x = param;
textBlock1.Text = x;
}
private void button_Click(object sender, RoutedEventArgs e)
{
this.Frame.Navigate(typeof(MainPage));
}
}
}
And I want this parameter to be stored in main class, how to do it?
On Page 2 in the OnNavigatedTo event retreive the value like this: var param = e.Parameter as string
EDIT
Assign the retreived parameter to the textblock in the OnNavigatedTo. At the time the page is constructed the value of x is "".
public sealed partial class Page2 : Page
{
private string x="";
public Page2()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
x = e.Parameter as string;
textBlock1.Text = x;
}
private void button_Click(object sender, RoutedEventArgs e)
{
this.Frame.Navigate(typeof(MainPage));
}
}
Recently, I'm working on WPF project but we use it with DevExpress library, and for your issue, it's very easy to be fixed with Messenger in DevExpress.
We just register the messenger where you want to receive the data,
public class Message {
//...
}
public class Recipient {
public Recipient() {
Messenger.Default.Register<string>(this, OnMessage1);
Messenger.Default.Register<Message>(this, OnMessage2);
}
void SendMessages() {
Messenger.Default.Send("test");
Messenger.Default.Send(new Message());
}
void OnMessage1(string message) {
//...
}
void OnMessage2(Message message) {
//...
}
}
And then you can send it from another view,
public class InheritedMessage : Message {
//...
}
public class Recipient {
public Recipient() {
//Inherited messages are not processed with this subscription
Messenger.Default.Register<Message>(
recipient: this,
action: OnMessage);
//Inherited messages are processed with this subscription
Messenger.Default.Register<Message>(
recipient: this,
receiveInheritedMessagesToo: true,
action: OnMessage);
}
void SendMessages() {
Messenger.Default.Send(new Message());
Messenger.Default.Send(new InheritedMessage());
}
void OnMessage(Message message) {
//...
}
}
With it, you can pass data between modules (or views, but recommend to use MVVM)
If you want to know more about DevExpress, please go through https://documentation.devexpress.com/#WPF/CustomDocument17474
Hope it could help you. :)

Mousebuttonpressed event Issues C#

I have an issue with a custom event i have created. I have made a Usercontrol that looks the following:
public partial class UCListView : UserControl {
public UCListView() {
InitializeComponent();
}
public event EventHandler SubmitClick;
public event EventHandler MouseButtonUpEvent;
private void SubmitButton_OnClick(object sender, RoutedEventArgs e) {
if (SubmitClick != null)
SubmitClick(this, e);
}
private void MouseButtonUp(object sender, RoutedEventArgs e) {
if (MouseButtonUpEvent != null) {
MouseButtonUpEvent(this, e);
}
}
}
Here is the MouseButtonUp event i have.
The following is where i listen to the event:
public partial class RoundsteelWindow : WindowControls {
private UCListView uc;
public RoundsteelWindow() {
InitializeComponent();
uc = new UCListView();
uc.SubmitClick += new EventHandler(ButtonPressed);
uc.MouseButtonUpEvent += new EventHandler(MousePressed);
stkTest.Children.Add(uc);
base.Test<RoundSteel>(uc, "Roundsteel");
}
}
Here is the WindowControls, where the MousePressed method can be seen. This is the same as the code snippet beneath this code. Really don't see the issue:
public abstract class WindowControls : Window {
public IMaterialWith14Elements _ReturnObject { get; set; }
public double amount { get; set; }
private UCListView _uc;
public void Test<T>(UCListView uc, string type) where T: IMaterialWith14Elements, new() {
_uc = uc;
List<T> test = MaterialLogic.GetList(type) as List<T>;
foreach (T material in test) {
uc.listView.Items.Add(material.Name);
}
}
private string str;
public void MousePressed(object sender, EventArgs eventArgs) {
var item = (sender as ListView).SelectedItem;
if (item != null) {
_ReturnObject = _uc.listView.SelectedItems as FlatSteel ;
str = item.ToString();
_uc.amountText.IsEnabled = true;
}
}
public void ButtonPressed(object sender, EventArgs e) {
if (!string.IsNullOrEmpty(_uc.amountText.Text)) {
amount = _uc.amountText.Text.customParseToDouble();
this.Close();
}
else {
MessageBox.Show("Indtast venligst en værdi.");
}
}
}
Now the problem is the following: With the following code it is working, but this class is not using the windowcontrols. It is called by another class which handles all of the buttons.
private void flatsteelListView_PreviewMouseLeftButtonUp(object sender, RoutedEventArgs e) {
var item = (sender as ListView).SelectedItem;
if (item != null) {
_returnObject = flatsteelListView.SelectedItems as FlatSteel;
str = item.ToString();
amountTextbox.IsEnabled = true;
FindObject(str);
}
}
The first picture shows the working window. This is where there is not used a usercontrol. Actually this is a previous issue i have worked with and got help with here on stackoverflow Help for thisissue.
The second picture is showing the next window using the usercontrol that has been created. The button event works and closes the window. Here comes then the issue, when the listview item is pressed. It is doing the same thing as on the first picture(where it works), but it is giving me a null reference, which doesn't make any sense to me. I have also checked the object sender to see if there was a difference between the sender of these two different windows.
I simply can't figure out why this is not working.
greetings darophi
Your sender is an object of UCListView class which is inherited from UserControl and you are trying to use it like ListView. So as result of operation (sender as ListView) you get null because sender is not an instance of ListView class and not inherits it.

Can I prevent ListBox.RefreshItem(object item) from removing and re-adding the object?

The issue I am having is that when I update my object, the ListBox automatically removes and then re-adds the object to the list, thus calling the index and value changed events. I was able to prevent this by creating a custom ListBox control and when the PropertyChangedEvent was called, I would raise a flag that would prevent those events in the base class from being called. What is happening now is that my entire reference is being replace by a new reference and unless I re-select the item in the ListBox, I have the wrong reference.
What I basically want to do, is to change the Display Value in my object and then have it update only the text in the list box. I do not want it to remove and to re-add the object/reference/whatever it does. It's quite annoying.
Here is the example code I am working with...
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.myListBox1.SelectedValueChanged += this.onchange;
}
private void Form1_Load(object sender, EventArgs e)
{
this.myListBox1.Add(new strobj("z"));
this.myListBox1.Add(new strobj("a"));
this.myListBox1.Add(new strobj("b"));
this.myListBox1.Add(new strobj("f"));
this.myListBox1.Add(new strobj("n"));
this.myListBox1.Add(new strobj("h"));
this.myListBox1.Add(new strobj("p"));
this.myListBox1.Add(new strobj("t"));
this.myListBox1.Add(new strobj("c"));
this.myListBox1.Add(new strobj("q"));
}
private void onchange(object sender, EventArgs e)
{
MessageBox.Show("Hello World");
}
int i = 0;
private void button1_Click(object sender, EventArgs e)
{
if (this.myListBox1.SelectedItem != null)
{
strobj item = (strobj)this.myListBox1.SelectedItem;
item.Name1 = i++.ToString();
}
}
}
public partial class MyListBox
{
public MyListBox()
{
InitializeComponent();
}
public void Add(strobj item)
{
item.OnNameChanged += this.MyDispalyMemberChanged;
this.Items.Add(item);
}
bool refreshing = false;
public void MyDispalyMemberChanged(strobj itemChanged)
{
this.refreshing = true;
this.RefreshItem(this.Items.IndexOf(itemChanged));
this.refreshing = false;
}
protected override void OnSelectedValueChanged(EventArgs e)
{
if (!this.refreshing)
{
base.OnSelectedValueChanged(e);
}
}
}
class strobjCollection : List<strobj>
{
NameChangeEventHandler NameChangedEvent;
}
delegate void NameChangeEventHandler(strobj sender);
public class strobj
{
internal NameChangeEventHandler OnNameChanged;
private string _Name1;
public string Name1
{
get { return this._Name1; }
set
{
this._Name1 = value;
if (this.OnNameChanged != null)
{
this.OnNameChanged(this);
}
}
}
public int i = 0;
public string str = "p";
public strobj(string name)
{
this._Name1 = name;
}
public strobj()
{
this._Name1 = "You did not create this object";
}
public override string ToString()
{
return this._Name1;
}
}
This is what the INotifyPropertyChanged interface was made for.
Instead of raising your custom event, you'd raise the PropertyChanged event with the name of the property you changed set in the event args and the listbox would update.
See MSDN.

How to send custom event message just after control instantiation?

I have null exception error on sending ValueChanged() event when creating this custom control and testing it in a client:
Source of custom control:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
namespace customevent
{
[DefaultEvent("ValueChanged")]
public partial class UserControl1 : UserControl
{
private int m_value;
public delegate void ValueChangedHandler();
[Category("Action")]
[Description("Value changed.")]
public event ValueChangedHandler ValueChanged;
public int Value
{
get { return m_value; }
set {
m_value = value;
ValueChanged();
}
}
public UserControl1()
{
InitializeComponent();
}
public UserControl1(int iValue)
{
this.Value = iValue;
InitializeComponent();
}
}
}
Then in test form:
private void Form1_Load(object sender, EventArgs e)
{
userControl11.Value = 100;
}
private void userControl11_ValueChanged()
{
MessageBox.Show(userControl11.Value.ToString());
}
Or instead of form_load, do this in constructor:
private void InitializeComponent()
{
this.userControl11 = new customevent.UserControl1(100);
You should declare the event handling as this:
public event EventHandler ValueChanged;
protected virtual void OnValueChanged(object sender, EventArgs e)
{
if (ValueChanged != null)
{
ValueChanged(sender, e);
}
}
public int Value
{
get { return m_value; }
set {
if (m_value == value) return;
m_value = value;
OnValueChanged(this, EventArgs.Empty);
}
}
PS: there is an interface INotifyPropertyChanged, you should use this instead to comply with standard .NET databinding rules.
You're not checking for nulls:
public int Value
{
get { return m_value; }
set {
m_value = value;
if(ValueChanged != null)
{
ValueChanged();
}
}
}
Furthermore, you're also not hooking into this event in your Form:
private void Form1_Load(object sender, EventArgs e)
{
userControl1.ValueChanged += userControl11_ValueChanged;
userControl11.Value = 100;
}
private void userControl11_ValueChanged()
{
MessageBox.Show(userControl11.Value.ToString());
}
Simon got you almost there:
protected virtual void OnValueChanged(object sender, EventArgs e)
{
var tmp = ValueChanged;
if (tmp != null)
{
tmp(sender, e); // With the tmp, we won't explode if a subscriber changes the collection of delegates underneath us.
}
}

Categories

Resources