Event Bubbling and MVP: ASP.NET
Asked Answered
C

3

2

I am trying to learn MVP

It is using web forms in ASP.NET. I have two user controls CurrentTimeView.ascx and MonthViewControl.ascx. The CurrentTimeView displayes time. There is a textbox to add days in the same control. The newly got date is called “resultant date”. When the button is clicked for add days, an event is raised “myBtnAddDaysClickedEvent“.

On the MonthViewControl, there is a label that shows the month of the “resultant date”. At present I am setting a sample value for the variable “monthValueToPass” (since I don’t know how to do it properly). How do I set the value for monthValueToPass variable to make it comply with MVP model?

string monthValueToPass = "TEST";
monthPresenter.SetMonth(monthValueToPass);

The expectation is to create MVP that is easy to do Unit Testing and does not violate MVP architecure.

Note: Though this is a simple example, I am expecting an answer scalablt to databinding in GridView control using MVP and validation mechanisms.

Note: Can view be totally independant of presenter?

Note: Each user control is separate views here

Note: Can there be multiple views for same presenter (like different controls for various users based on thier permisssion?)

GUIDELINES

  1. Model View Presenter - Guidelines

--COMPLETE CODE--

using System;
public interface ICurrentTimeView
{
    //Property of View
    DateTime CurrentTime 
    {
        set; 
    }
    //Method of View
    void AttachPresenter(CurrentTimePresenter presenter);
}

using System;
public interface IMonthView
{
    //Property of View
    string MonthName 
    {
        set; 
    }

    //Method of View
    //View interface knows the presenter
    void AttachPresenter(MonthPresenter presenter);     
}

using System;
public class CurrentTimePresenter 
{
    private ICurrentTimeView view;

    //Constructor for prsenter
    public CurrentTimePresenter(ICurrentTimeView inputView) 
    {
        if (inputView == null)
        {
            throw new ArgumentNullException("view may not be null");
        }
    }
    this.view = inputView;
}

//Method defined in Presenter
public void SetCurrentTime(bool isPostBack) 
{
    if (!isPostBack) 
    {
        view.CurrentTime = DateTime.Now;
    }
}

//Method defined in Presenter
public void AddDays(string daysUnparsed, bool isPageValid) 
{
    if (isPageValid) 
    {
        view.CurrentTime = DateTime.Now.AddDays(double.Parse(daysUnparsed));           
    }
}

using System;
public class MonthPresenter
{
    private IMonthView monthView;

    //Constructor for prsenter
    public MonthPresenter(IMonthView inputView)
    {
        if (inputView == null)
        {
           throw new ArgumentNullException("view may not be null");
        }
        this.monthView = inputView;
    }


    //Method defined in Presenter
    //How does presenter decides the required value.
    public void SetMonth(string monthValueInput) 
    {
       if (!String.IsNullOrEmpty(monthValueInput))
       {
          monthView.MonthName = monthValueInput;
       }
       else
       {

       }        
    }   
}

User Control 1

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="CurrentTimeView.ascx.cs" Inherits="Views_CurrentTimeView" %>

<asp:Label id="lblMessage" runat="server" /><br />
<asp:Label id="lblCurrentTime" runat="server" /><br />
<br />

<asp:TextBox id="txtNumberOfDays" runat="server" />
<asp:Button id="btnAddDays" Text="Add Days" runat="server" OnClick="btnAddDays_OnClick" ValidationGroup="AddDays" />

using System;
using System.Web.UI;
public partial class Views_CurrentTimeView : UserControl, ICurrentTimeView
{
   //1. User control has no method other than view defined method for attaching presenter
   //2. Properties has only set method

   private CurrentTimePresenter presenter;

   // Delegate 
   public delegate void OnAddDaysClickedDelegate(string strValue);

   // Event 
   public event OnAddDaysClickedDelegate myBtnAddDaysClickedEvent;

   //Provision for getting the presenter in User Control from aspx page.
   public void AttachPresenter(CurrentTimePresenter presenter)
   {
       if (presenter == null)
       {
         throw new ArgumentNullException("presenter may not be null");
       }
       this.presenter = presenter;
   }

   //Implement View's Property
   public DateTime CurrentTime
   {
      set
      {
        //During set of the property, set the control's value
        lblCurrentTime.Text = value.ToString();
      }
   }

   //Event Handler in User Control
   protected void btnAddDays_OnClick(object sender, EventArgs e)
   {
      if (presenter == null)
      {
         throw new FieldAccessException("presenter null");
      }

      //Ask presenter to do its functionality
      presenter.AddDays(txtNumberOfDays.Text, Page.IsValid);

      //Raise event
      if (myBtnAddDaysClickedEvent != null)
      {
        myBtnAddDaysClickedEvent(string.Empty);
      }
   }     
}

User Control 2

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="MonthViewControl.ascx.cs" Inherits="Views_MonthViewControl" %>

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class Views_MonthViewControl : System.Web.UI.UserControl, IMonthView
{
   //1. User control has no method other than view defined method for attaching presenter
   //2. Properties has only set method

   private MonthPresenter presenter;

   //Provision for gettng the presenter in User Control from aspx page.
   public void AttachPresenter(MonthPresenter presenter)
   {
      if (presenter == null)
      {
         throw new ArgumentNullException("presenter may not be null");
      }
      this.presenter = presenter;
   }

   //Implement View's Property
   public string MonthName
   {
      set
      {
        //During set of the popert, set the control's value
        lblMonth.Text = value.ToString();
      }
   }

   protected void Page_Load(object sender, EventArgs e)
   {

   }    
}

ASPX Page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ShowMeTheTime.aspx.cs"      Inherits="ShowTime" %>

<%@ Register TagPrefix="mvpProject" TagName="CurrentTimeView" Src="Views/CurrentTimeView.ascx" %>

<%@ Register TagPrefix="month" TagName="MonthView" Src="Views/MonthViewControl.ascx" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>PAGE TITLE </title>
</head>
<body>
<form id="form1" runat="server">

    <mvpProject:CurrentTimeView id="ucCtrlcurrentTimeView" runat="server" 
    />
    <br />
    <br />
    <month:MonthView id="ucCtrlMonthView" runat="server" />

</form>
</body>
</html>

using System;
using System.Web.UI;

public partial class ShowTime : Page
{
    CurrentTimePresenter currentTimePresenter;
    MonthPresenter monthPresenter;

    protected void Page_Load(object sender, EventArgs e) 
    {
       HelperInitCurrentTimeView();
       HelperInitMonth();
    }

    private void HelperInitMonth()
    {
       //Create presenter
       monthPresenter = new MonthPresenter(ucCtrlMonthView);

       //Pass the presenter object to user control
       ucCtrlMonthView.AttachPresenter(monthPresenter);
    }

    private void HelperInitCurrentTimeView() 
    { 
       //Cretes presenter by passing view(user control) to presenter.
       //User control has implemented IView
       currentTimePresenter = new CurrentTimePresenter(ucCtrlcurrentTimeView);

        //Pass the presenter object to user control
        ucCtrlcurrentTimeView.AttachPresenter(currentTimePresenter);

        //Call the presenter action to load time in user control.
        currentTimePresenter.SetCurrentTime(Page.IsPostBack);

        //Event described in User Control ???? Subsribe for it.
        ucCtrlcurrentTimeView.myBtnAddDaysClickedEvent += new Views_CurrentTimeView.OnAddDaysClickedDelegate(CurrentTimeViewControl_AddButtonClicked_MainPageHandler);        
    }

    void CurrentTimeViewControl_AddButtonClicked_MainPageHandler(string strValue)
    {
       string monthValue = "l";
       monthPresenter.SetMonth("SAMPLE VALUE");
       //myGridCntrl.CurentCharacter = theLetterCtrl.SelectedLetter;
       //myGridCntrl.LoadGridValues();
    }
}

Some MVP discussions:

Model View Presenter - Guidelines

In MVP where to write validations

MVP - Should views be able to call presenter methods directly or should they always raise events?

MVP events or property

The Model in MVP - Events

MVP - Should the Presenter use Session?

Why do Presenters attach to View events instead of View calling Presenter Methods in most ASP.NET MVP implementations?

Public Methods or subscribe to View events

MVP pattern, how many views to a presenter?

MVP and UserControls and invocation

ASP.NET Web Forms - Model View Presenter and user controls controls

Restrict violation of architecture - asp.net MVP

Control modification in presentation layer

Decoupling the view, presentation and ASP.NET Web Forms web-forms

Chlor answered 13/1, 2012 at 14:18 Comment(1)
tl;dr: How do you create MVP that is easy to do Unit Testing on, and does not violate MVP architecure. Additionally, can view be totally independant of presenter?Arteriovenous
C
2

Thanks for the inputs. I referred MVP Quickstarts http://msdn.microsoft.com/en-us/library/ff650240.aspx. Model can raise events. I think, I should go with that approach. Any thoughts are welcome.

Also, I have posted http://forums.asp.net/t/1760921.aspx/1?Model+View+Presenter+Guidelines to collect general rules on MVP.

Quote

Develop Presenter which can communicate with both View and Model. Presenter may only have knowledge of view interfaces. Even if the concrete view changes, it does not affect presenter.

In the concrete view, control’s event handlers will simply call presenter methods or raise events to which presenter would have subscribed. There should be no presentation rule/logic written in concrete view.

Presenter should have only interface object of model; not concrete model. This is for the ease of Unit Testing

View can refer business entities. However there should no logic written associated with the entity objects. It may just pass the entity object to presenter.

View interface should be an abstraction. It should NOT have any control or System.Web reference. In concrete view, there should be no method other than interface defined methods.

The "Model" never knows about the concrete view as well as the interface view

"Model" can define and raise events. Presenter can subscribe these events raised by model.

Public methods in presenter should be parameterless. View object should access only parameterless methods of presenter. Another option is view can define events to which the presenter can subscribe. Either way, there should be no parameter passing.

Since the model has all the required values (to be stored back in database), there is no need to pass any value to model from view (most of the time). E.g. when an item is selected in dropdown list only the controls’ current index need to be passed to model. Then model knows how to get the corresponding domain values. In this case, the view need not pass anything to presenter. Presenter knows how to get value from view.

View may make use of model directly (without using presenter). E.g. ObjectDataSource's SelectMethod. But controller never knows about the concrete view as well as the interface view.

The presenter references the view interface instead of the view's concrete implementation. This allows you to replace the actual view with a mock view when running unit tests.

Chlor answered 23/1, 2012 at 5:39 Comment(0)
P
2

TLDR the code.

Here's how I would do it. You say there are 2 controls on the same page. So that can be served by a ContainerVM with references (members) of TimeVM and MonthVM.

  1. TimeVM updates a backing property ResultantDate whenever you do your thing.
  2. ContainerVM has subscribed to property-changed notifications for TimeVM.ResultantDate. Whenever it receives a change notification, it calls MonthVM.SetMonth()

This can now be tested without using any views - purely at the presenter level.

Potency answered 16/1, 2012 at 8:49 Comment(2)
new ContainerVM (TimeVM, MonthVM). The ContainerVM corresponds to the page, the others are the VMs for the inner controls. In the ctor, ContainerVM subscribes to TimeVM.PropertyChanged. TimeVM has a property ResultantDate, which is databound . When you change the Time, ResultantDate is modified raising the PropertyChanged event (data-binding). ContainerVM gets notified.. in its handler, ContainerVM does `_monthVM.SetMonth(new-value)'. Once again through-data binding the Month control updates to show the new-value. Writing a code sample.. might take some time :)Potency
@Lijo - Search for Josh Smith's MSDN article on MVVM for an explanation of the pattern itself. I was assuming that you were already familiar with it. If you have one view with 2 child views, you can mirror at the ViewModel level with one view-model holding references to 2 child viewmodels.Potency
C
2

Thanks for the inputs. I referred MVP Quickstarts http://msdn.microsoft.com/en-us/library/ff650240.aspx. Model can raise events. I think, I should go with that approach. Any thoughts are welcome.

Also, I have posted http://forums.asp.net/t/1760921.aspx/1?Model+View+Presenter+Guidelines to collect general rules on MVP.

Quote

Develop Presenter which can communicate with both View and Model. Presenter may only have knowledge of view interfaces. Even if the concrete view changes, it does not affect presenter.

In the concrete view, control’s event handlers will simply call presenter methods or raise events to which presenter would have subscribed. There should be no presentation rule/logic written in concrete view.

Presenter should have only interface object of model; not concrete model. This is for the ease of Unit Testing

View can refer business entities. However there should no logic written associated with the entity objects. It may just pass the entity object to presenter.

View interface should be an abstraction. It should NOT have any control or System.Web reference. In concrete view, there should be no method other than interface defined methods.

The "Model" never knows about the concrete view as well as the interface view

"Model" can define and raise events. Presenter can subscribe these events raised by model.

Public methods in presenter should be parameterless. View object should access only parameterless methods of presenter. Another option is view can define events to which the presenter can subscribe. Either way, there should be no parameter passing.

Since the model has all the required values (to be stored back in database), there is no need to pass any value to model from view (most of the time). E.g. when an item is selected in dropdown list only the controls’ current index need to be passed to model. Then model knows how to get the corresponding domain values. In this case, the view need not pass anything to presenter. Presenter knows how to get value from view.

View may make use of model directly (without using presenter). E.g. ObjectDataSource's SelectMethod. But controller never knows about the concrete view as well as the interface view.

The presenter references the view interface instead of the view's concrete implementation. This allows you to replace the actual view with a mock view when running unit tests.

Chlor answered 23/1, 2012 at 5:39 Comment(0)
A
1

I am not experienced with ASP.net but I think I follow the gist of what you are trying to do.

It appears that you are going down to fine a level with your presenter by making presenters for the individual UI elements. In this case the Month and the Time. I would think of it more as ShowTime period. ShowTime has the capability of showing the Month and Time.

To use this with MVP. Then you will need a IShowTimeView that the page will implement. (Not the controls). And then write a ShowTimePresenter that uses IShowTimeView to send and retrieve values.

You will have ShowTime implement the IShowTimeView interface. It will route items like the Time, AddDay events, and the Month to and from the actual controls on the page.

So if I understand your writeup. The sequence of event would be something like this.

The user types in the days to add. The user clicks add days Add Days fires the event which calls a method on the Present to add days. The method in the presenter that add days will make it's calculation and other needed steps. The add days method will then use the View pointer in the Presenter to tell the view to update the Month with the calculated value. The View will then take the calculated value set the correct property on the control.

To do unit testing you need to make a mock object implementing IShowTimeView and use that in place of the actual page object.

Antisthenes answered 17/1, 2012 at 18:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.