The MVC Framework

Introduction

The MVC Framework is based on the software design pattern API and its core design has been done taking advantage of Microsoft .NET Framework and C# language.

It implements the instantiation and isolation features and .NET-specific capabilities like LINQ.

Design

Although it follows common MVC design, because C# and .NET are full-object-oriented pieces, some concepts have been implemented in some manner that differs from other solutions, including ones from the same platform.

Some points

Model, view and controller entities exists, but the way that communication beetwen them is performed and other details are quite different:
  • Actions that can performed by a view are entities itself. That is every action is "a class".
    • For example, if a view can register an user, it may be a class like "UserRegistrationAction".
  • Encourages event delegation model instead of observer.
    • Why? Because, in fact, event delegation model can correctly notify the controller that a view did an action.
    • For example, an MVC clock where its controller supports changing the hour may support an action "ChangeHourAction". Then, that view may implement an event "HourChanged" and the controller should handle it.
  • Multi-view support on controllers. A controller instance can handle one or more views of the same type.

Entities on MVC Framework

Model

Models are entities that may implement behaviors which are oriented to change an underlying data store.

They are associated to a controller.

View

Views can be multiple things:
  • A graphical user interface which supports user and/or application-driven actions.
  • A serialized object or data.

They are a presentation layer for some underlying logic.

Their aim is interacting with some user (human or computer) and appropiately answer notifying the view's controller for changing an underlying data store through model's behaviors.

By relying on event delegation model, they can handle actions performed by an user and notify their associated controller.

Controller

A controller is a key entity in the game. It acts as a intermediate between some views and a model.

Since views do actions, a controller may support an specific set of them in order to communicate a model what should it do next and deliver an appropiate answer to the controller which, finally, will notify the associated views that changes have been made in the underlying data behind the model.

Views really "want to do actions" but actually they don't execute them. The execution is delegated to a model selected by its controller.

So then how views notifies a controller that they want to do an action? Easy: each one has a notifying behavior that may be called after handling some user or application event like an "user clicked - that means "close form action" has been done, and this is saving current form in the data store. That is notifying the controller which may invoke some model's behavior saving some data somewhere''.

Controllers support associating multiple views of same type but they can only handle a single model. That is because of optimization needs: each view may do same on the controller while everyone may need same behaviors for the associated model.

Action

A design decision on this framework has been implementing actions done by a view as entities instead of a "key", "name" or "enumeration value".

That is because target environment is fully object-oriented and MVC framework takes advantage of that fact.

An action is a conceptual entity which represents a something-to-do in a view, such as register an user, show contextual menu, search these keywords, _re-draw this form, ...

Case example: an ASP.NET "hello world" using MVC Framework

Recently Microsoft released ASP.NET MVC Framework. This example does not use it.

This example shows how to say "hello world" and "goodbye" based on MVC.


ExampleInstance.cs

Implementing the MVC instance.

There are no implementations for Dispose and WarmUp.
using System;
using Comkarl.Architecture.Patterns.MVC;

public class ExampleInstance : Instance<Guid>
{
    public ExampleInstance()
        : base(new PatternInstanceIdentifier<Guid>(Guid.NewGuid()))
    {
    }

    public override void Dispose()
    {
        // Some complex implementation may release resources here.
    }

    public override void WarmUp()
    {
        // Some complex implementation may load some configuration
    }
}



Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="_Default" %>

<!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>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="scriptManager" runat="server" />
        
        <asp:UpdatePanel ID="updatePanel" runat="server">
            <ContentTemplate>
                <asp:Button ID="btnSayHelloWorld" Text="Say hello world!" runat="server" />
                <asp:Button ID="btnSayGoodbye" Text="Say goodbye!" runat="server" />
                
                <asp:TextBox ID="txtMessage" runat="server" />
            </ContentTemplate>
        </asp:UpdatePanel>
    </div>
    </form>
</body>
</html>
</esc>



Default.aspx.cs

In a correctly-implemented ASP.NET MVC, a Page should be a View, but because of the closed source nature of .NET, there's no chance
to force Page (or their parent classes) inherit View.

Because of this situation, View concept is splitted into two classes. The Page and View class, which are associated.

When a Page fires a control-specific event, raises a counter-part on View. For example, clicking Say hello button, may raise RequestedSayHello on View.

In addition, Page implements a property in order to access the text box's value on Default.aspx which shows "hello world" or "goodbye".

using System;
using Comkarl.Architecture.Patterns.MVC.Entities;

public partial class _Default : System.Web.UI.Page
{
    private ExamplePageView currentView;

    public _Default()
    {
        /*
         * Since this is an example, page's constructor acts as a main entry method
         * of the application.
         * 
         * An instance is created on which a controller, model and view are going to live in.
         */
        ExampleInstance instance = new ExampleInstance();

        ExamplePageController controller = new ExamplePageController();
        controller.AssociateToInstance(instance);


        controller.AssociatedModel = new ExampleModel();
        controller.AssociatedModel.AssociateToInstance(instance);
        controller.AssociatedModel.AssociateToController(controller);

        CurrentView = new ExamplePageView();
        CurrentView.CurrentPage = this;

        controller.ViewManager.Add(CurrentView);
    }

    /// <summary>
    /// Gets or sets an associated view to the page.
    /// </summary>
    public ExamplePageView CurrentView
    {
        get
        {
            return currentView;
        }
        set
        {
            currentView = value;
        }
    }

    /// <summary>
    /// Gets or sets the message that is shown in the page.
    /// </summary>
    public string ResponseMessage
    {
        get
        {
            return txtMessage.Text;
        }
        set
        {
            txtMessage.Text = value;
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        btnSayHelloWorld.Click += new EventHandler(PageControls_Click);
        btnSayGoodbye.Click += new EventHandler(PageControls_Click);
    }

    private void PageControls_Click(object sender, EventArgs e)
    {
        /* When some control fires a click event, 
         * logic fires a view event in order to say "hello" or "goodbye" 
         */
        if (sender.Equals(btnSayHelloWorld))
        {
            CurrentView.PerformSayHelloWorld();
        }
        else if (sender.Equals(btnSayGoodbye))
        {
            CurrentView.PerformSayGoodbye();
        }
    }
}



ExamplePageView.cs

ExamplePageView implements two events: RequestedSayHelloWorld (when a Page event requests saying ''hello world'') and RequestedSayGoodbye (when a Page requests saying ''goodbye'').

Each event has an in-class event handler which processes requests invoking an appropiate action for them.

Action's response may set "ResponseMessage" Page's property.

using System;
using Comkarl.Architecture.Patterns.MVC.Entities;

public class ExamplePageView : View<Guid>
{
    private _Default currentPage;

    /// <summary>
    /// Fired when the associated page requested saying "hello world".
    /// </summary>
    public event EventHandler RequestedSayHelloWorld;

    /// <summary>
    /// Fired when the associated page requested saying "goodbye".
    /// </summary>
    public event EventHandler RequestedSayGoodbye;

    public ExamplePageView()
        : base(new ViewIdentifier<Guid>(Guid.NewGuid()))
    {
        RequestedSayHelloWorld += new EventHandler(ExamplePageView_RequestedSayHelloWorld);
        RequestedSayGoodbye += new EventHandler(ExamplePageView_RequestedSayGoodbye);
    }

    /// <summary>
    /// Gets or sets the associated page to current view.
    /// </summary>
    public _Default CurrentPage { get { return currentPage; } set { currentPage = value; } }

    private void ExamplePageView_RequestedSayGoodbye(object sender, EventArgs e)
    {
        SayGoodbyeAction action = new SayGoodbyeAction();
        action.AssociateToInstance(AssociatedInstance);
        action.AssociateToController(AssociatedController);

        CurrentPage.ResponseMessage = AssociatedController.DoAction<string>(action);
    }

    private void ExamplePageView_RequestedSayHelloWorld(object sender, EventArgs e)
    {
        SayHelloWorldAction action = new SayHelloWorldAction();
        action.AssociateToInstance(AssociatedInstance);
        action.AssociateToController(AssociatedController);

        CurrentPage.ResponseMessage = AssociatedController.DoAction<string>(action);
    }

    public void PerformSayHelloWorld()
    {
        if (RequestedSayHelloWorld != null)
        {
            RequestedSayHelloWorld(this, new EventArgs());
        }
    }

    public void PerformSayGoodbye()
    {
        if (RequestedSayGoodbye != null)
        {
            RequestedSayGoodbye(this, new EventArgs());
        }
    }
}



ExamplePageController.cs

Controller's implementation.

Since this example page controller manages a single view, an AssociatedView property is implemented in order to directly access to this one.

Controller may implement an association to its ''model'', but that's not mandatory.

Finally, SelectAction<> implementation selects which action should be done and, in a complex implementation, may do other things (that's why a controller should invoke Do method on Action instances).

using System;
using Comkarl.Architecture.Patterns.MVC.Entities;

public class ExamplePageController : Controller<Guid>
{
    private ExampleModel associatedModel;

    public ExamplePageController()
        : base(new ControllerIdentifier<Guid>(Guid.NewGuid()))
    {
    }

    public ExamplePageView AssociatedView
    {
        get
        {
            return (ExamplePageView)ViewManager[0];
        }
    }

    public ExampleModel AssociatedModel
    {
        get
        {
            return associatedModel;
        }
        set
        {
            associatedModel = value;
        }
    }

    protected override TReturnValue SelectAction<TReturnValue>(Comkarl.Inno.Kernel.Architecture.Patterns.MVC.Entities.Action<Guid> incomingAction)
    {
        if (incomingAction.GetType() == typeof(SayHelloWorldAction))
        {
            return incomingAction.Do<TReturnValue>();
        }
        else if (incomingAction.GetType() == typeof(SayGoodbyeAction))
        {
            return incomingAction.Do<TReturnValue>();
        }

        return default(TReturnValue);
    }
}


ExampleModel.cs

The ExampleModel class implements two methods.

One says "hello world" and the other one "goodbye".

using System;
using Comkarl.Architecture.Patterns.MVC.Entities;

public class ExampleModel : Model<Guid>
{
    public ExampleModel()
        : base(new ModelIdentifier<Guid>(Guid.NewGuid()))
    {
    }

    public string GetHelloWorldText()
    {
        return "Hello world!!!!";
    }

    public string GetGoodbyeText()
    {
        return "Goodbye!!!!";
    }
}



SayHelloWorldAction.cs

Implements SayHelloWorldAction class which represents the action of "saying hello world".

using System;
using Comkarl.Architecture.Patterns.MVC.Entities;

public class SayHelloWorldAction : Comkarl.Inno.Kernel.Architecture.Patterns.MVC.Entities.Action<Guid>
{
    public SayHelloWorldAction()
        : base(new ActionIdentifier<Guid>(Guid.NewGuid()))
    {
    }

    public override TResult Do<TResult>()
    {
        return (TResult)Convert.ChangeType( ((ExamplePageController)AssociatedController).AssociatedModel.GetHelloWorldText(), typeof(TResult));
    }
}



SayGoodbyeAction.cs

Implements SayGoodbyeAction class which represents the action of "saying goodbye".

using System;
using Comkarl.Architecture.Patterns.MVC.Entities;

public class SayGoodbyeAction : Comkarl.Inno.Kernel.Architecture.Patterns.MVC.Entities.Action<Guid>
{
    public SayGoodbyeAction()
        : base(new ActionIdentifier<Guid>(Guid.NewGuid()))
    {
    }

    public override TResult Do<TResult>()
    {
        return (TResult)Convert.ChangeType( ((ExamplePageController)AssociatedController).AssociatedModel.GetGoodbyeText(), typeof(TResult));
    }
}

Last edited Jul 7, 2009 at 11:58 PM by MFidemraizer, version 7

Comments

No comments yet.