Why I Still L.O.V.E. ASP.NET WebForms

Objectives

  • Build Contact Page (a page that interacts with a data-storage)
  • Build a Contact List Page (a page with a gird)
  • Do it using ASP.NET WebForms
  • Create Unit Tests

We will use the MVP pattern and the open-source project WebFormsMVP to accomplish that! Can’t wait? Download the sample!

Ready?

First we open Visual Studio 2010 and create a new ASP.NET Web Application Project like the screenshot.

image

Then we add a new project to our solution which contains our Application Logic like the screenshot.

image

Next we fire-up Package Manager Console, set as Default Project .Web  and type

Install-Package WebFormsMvp

Repeat the above setting as Default Project .Logic

image

Now we are ready to create our Database and our Database Access Layer.

Because in this post this is the less important part our DB is a static List and has a table which is represented by the Contact class. Our DAL is the following:

Contact.cs

public class Contact
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Message { get; set; }
    DateTime? _CreatedAt = null;
    public DateTime CreatedAt
    {
        get
        {
            if (!_CreatedAt.HasValue)
                _CreatedAt = DateTime.Now;
            return _CreatedAt.Value;
        }
    }
}

IContactRepository.cs

public interface IContactRepository
{
    List<Contact> FindAll();
    void Save(Contact entity);
}

ContactRepository.cs

public class ContactRepository : IContactRepository
{
    private static List<Contact> Db;
    static ContactRepository() { Db = new List<Contact>(); }
    public List<Domain.Contact> FindAll() { return Db; }
    public void Save(Domain.Contact entity) { Db.Add(entity); }
}

And a screenshot:

image

Next let’s start designing and implementing our contact page.

The page needs to handle some data (a model). Let’s create a ContactModel class

public class ContactModel
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Message { get; set; }
    public enProcessState ProcessState { get; set; }
}

The enProcessState is the following self-explained enum

public enum enProcessState
{
    Pending,
    Saved
}

Remember we are using the MVP pattern and we have still to implement the View and the Presenter.

Let’s create our view. Because a view is possible to be implemented in various ways (desktop, mobile client etc.) we will first create an interface or a contract that we want to code against. Our interface looks like this:

public interface IContactView : WebFormsMvp.IView<ContactModel>
{
    event EventHandler<EventArgs> Contacting;        
}

The view inherits from WebFormsMvp.IView and defines an event. It also defines a model with the type of ContactModel.

Last but not least let’s implement our Presenter. All the above take place inside .Logic project and not inside Web.

Our presenter looks like this:

public class ContactPresenter : WebFormsMvp.Presenter<IContactView>
{
    private readonly IContactRepository _repository;
    public ContactPresenter(IContactView view)
        : this(view, new ContactRepository()) { }
    public ContactPresenter(IContactView view, IContactRepository repository)
        : base(view)
    {
        _repository = repository;
        View.Contacting += new EventHandler<EventArgs>(Contacting);
    }

    void Contacting(object sender, EventArgs e)
    {
        if (View.Model == null || View.Model.ProcessState == Views.Models.enProcessState.Saved)
            return;
        _repository.Save(View.Model.ToDomainModel());
        View.Model.ProcessState = Views.Models.enProcessState.Saved;
    }
}

The presenter inherits from WebFormsMvp.Presenter and defines the type of view that is handling. It also defines two constructors (for now). To explain in a single sentence the main purpose of the presenter I will say that some times is called also a Controller.

Everything looks right except we don’t have an implementation for our View! Let’s move to our .Web project to build one.

We will create a user control named ContactControl.ascx. The controls code-behind looks like this:

public partial class ContactControl : WebFormsMvp.Web.MvpUserControl<ContactModel>, 
    LoveForWebForms.Logic.Views.IContactView
{
    public event EventHandler<EventArgs> Contacting;
    private void OnContacting()
    {
        if (Contacting != null)
            Contacting(this, EventArgs.Empty);
    }
    protected void btnContactUs_Click(object sender, EventArgs e)
    {
        Model.Name = txtName.Text;
        Model.Email = txtEmail.Text;
        Model.Message = txtMessage.Text;
        OnContacting();
        View activeView = Model.ProcessState == enProcessState.Saved ? vSaved : vContact;
        mv.SetActiveView(activeView);
    }
}

and mark-up looks like this

<asp:MultiView runat="server" ID="mv" ActiveViewIndex="0">
    <asp:View runat="server" ID="vContact">
        <fieldset>
            <legend>Contact Us</legend>
            <table>
                <tr>
                    <td>
                        Name
                    </td>
                    <td>
                        <asp:TextBox runat="server" ID="txtName" />
                        <asp:RequiredFieldValidator ErrorMessage="*" ControlToValidate="txtName" runat="server" />
                    </td>
                </tr>
                <tr>
                    <td>
                        Email
                    </td>
                    <td>
                        <asp:TextBox runat="server" ID="txtEmail" />
                    </td>
                </tr>
                <tr>
                    <td>
                        Message
                    </td>
                    <td>
                        <asp:TextBox runat="server" ID="txtMessage" TextMode="MultiLine" Rows="10" />
                    </td>
                </tr>
                <tr>
                    <td colspan="2">
                        <asp:Button Text="Contact Us" runat="server" OnClick="btnContactUs_Click" />
                    </td>
                </tr>
            </table>
        </fieldset>
    </asp:View>
    <asp:View runat="server" ID="vSaved">
        Contact request was Saved! <a href="ContactList.aspx">View all</a> contact requests.
    </asp:View>
</asp:MultiView>

Our markup is pretty much standard. In our code-behind file when the user clicks the button we prepare our Model and raise the Contacting Event. If everything went OK the ProcessState property of our model becomes Saved and the appropriate view of MultiView control is displayed.

All we wave to do is to create a page and put our UserControl inside. Just a simple page.

But I didn’t declared anywhere the presenter!! How is ContactPresenter is selected? This is where MvpWebForms projects comes!

Pay attention to names. ContactControl.ascx, ContactPresenter,IContactView. When you follow these conventions (just like MVC has it’s own) no further actions are required. If something like this isn’t possible you can use attributes like

[WebFormsMvp.PresenterBinding(typeof(LoveForWebForms.Logic.Presenters.ContactPresenter))]

in the user control to indicate which presenter to use.

Dependency Injection

Let’s say that I am a WebForms developer. When I ‘m around MVC developers I want to be able to say things like: “Yeah Dependency injection rocks!” or “When you say DI god kills a kitten, but still using it in my projects! DI DI DI…”

Can I do it? Is it helpful in any way? Yes and yes!

Go to Package Manager Console target the Web Project and type

Install-Package WebForms.Mvp.Autofac

You might notice that WebFormsMvp has packages for Autofac, Unity and Castle. Let’s use Autofac for now.

After the package is being installed we make the following changes to global.asax

public class Global : System.Web.HttpApplication, IContainerProviderAccessor
{
    protected void Application_Start(object sender, EventArgs e)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<ContactRepository>().As<IContactRepository>();
        _containerProvider = new ContainerProvider(builder.Build());            
        PresenterBinder.Factory = new AutofacPresenterFactory(_containerProvider.ApplicationContainer);
    }
    static IContainerProvider _containerProvider;        
    public IContainerProvider ContainerProvider
    {
        get { return _containerProvider; }
    }
}

Implement IContainerProviderAccessor and add the code above to Application_Start.

Now let’s change our ContactPresenter and remove the first constructor. Now our presenter looks like

public class ContactPresenter : Presenter<IContactView>
{
    private readonly IContactRepository _repository;
    public ContactPresenter(IContactView view, IContactRepository repository)
        : base(view)
    {
        _repository = repository;
        view.Contacting += new EventHandler(view_Contacting);
    }

    void view_Contacting(object sender, EventArgs e)
    {
        if (View.Model.ProcessState == Views.Models.enProcessState.Saved)
            return;

        _repository.Save(View.Model.ToDomainModel());
        View.Model.ProcessState = Views.Models.enProcessState.Saved;
    }
}

The instantiation of IContactRepository is taken care by Autofac.

What about creating a grid page with paging that also uses an ObjectDataSource?

What about validation?

Because this post is really long those scenarios are implemented in the source code! So download and see for your self! But really quick I am posting a view screenshots to get an idea. When you download the project don’t forget to run the Install-Package command to get dependencies from Nuget as they are not included by default.

What about testing?

I 've written a detailed article on DotNetSlackers about testing!

image

image

image

image

image

I think it’s time for refactor!

Download Sample

When you download the project don’t forget to run the Install-Package command to get dependencies from Nuget as they are not included by default. Commands are

  1. Install-Package WebFormsMvp for .Logic project
  2. Install-Package WebFormsMvp.Autofac for .Web project

 

22 Comments

  • 



Blake

    Nice post, thanks for sharing.

  • 



SomeOne

    I was interested to see why one would still "LOVE" ASP.NET WebForm. I was thinking this must be a purist that is going to stick up for WebForms. I thought this maybe something on how you would like to still use WebForms instead of the new MVC platform.

    But then I got the bait and switch. Only two paragraphs into the article I am told to go get a MVP library. So much so for "LOVING" WebForms.

    The funny thing, regardless you never point out why you still love WebForms. Is that to be an acronym?

    You might want to change the title.

  • shahin kiassat on said

    Reply
    



shahin kiassat

    How can I download WebForms MVP project ?
    there is no downloadable links here :
    http://webformsmvp.codeplex.com/releases/view/63239 and also here : http://nuget.org/List/Packages/WebFormsMvp
    --------
    is it better to use T4 to generate Some repeatable codes (for example contact model) ?
    exactly what do this framework (WebForms MVP) do for us ?
    I'm New in MVP , what does Domain(folder) mean in logic class library ?
    i downloaded project but when i want to build Vs returns for me "the namespace WebFormsMvp could not be found"
    regards and thanks.

  • 



djsolid

    @shanin Use nuget to install the references. Nuget is a plugin for Visual Studio. More info at http://nuget.org/

    @SomeOne I still love WebForms because when used in the right way I can have a testable and well-designed Web Application (like MVC) but with tons of UI Controls at my disposal.

  • 



Mikko

    Nice post. A small typo at Install-Package WebFormsMvp.Autotofac should Install-Package WebFormsMvp.Autofac.

  • 



djsolid

    Thank you! Fixed!

  • 



Sean

    Nice post! I'm especially interested in the next post that will explore testing. Will it be posted soon?

  • 



djsolid

    I hope the following week...

  • 



Alex

    20+ files just to display a list with contacts and a simple UI to edit them? C-mon, you can't compete with PHP. Never.

  • 



ponasp

    For a such simple projects as this Web Forms is a best choice. But for more complex one this approach is not applicable at all. Example try to reinvent the wheel : why try to separate view, presenter and model if we have mvc with more elegant way.

  • 



djsolid

    @Alex This can be done also in just two pages, with a sortable, pageble grid, the pages could be AJAX-enabled and not writing a single line of code. If you have 1,2 or 10 pages it's OK. But if you have 300 pages or more in a single project this is neither maintainable nor testable.

    @ponasp The same principles as MVC apply also here. But there are times that you can't use MVC as I said above (for example you have to maintain a legacy app) or you want to use the power of 3rd Party Controls like DevExpress or Telerik. IMHO this is the correct way of writing business Web Applications using WebForms.

  • 



Alex

    @djsolid I've seen a lot of real websites without any MVP or repository layers and abstractions that do work and generate revenue. On the other end if you require 20+ files for a SINGLE entity JUST to display a list with a simple UI, any real-world app with real-world logic becomes a HUGE ENGINEERING PROJECT. Most of them fail because of their complexity or run out of budget very soon.

    Imagine what will happen if someone e.g. decide to merge contacts and, I don't know, friends. I will require changes to hundreds of files. By choosing these very complex model you basically put the code into concrete and discourage developers from any major changes.

  • master-blaster on said

    Reply
    



master-blaster

    @Alex

    "20+ files" your argument is just useless, it is pathetic and means NOTHING.

    This is just a sample, but it is build upon best practices and patterns, if you have a really big project then you "one-file" php solution can't compete with performance, testability and maintainability, NEVER.

  • 



djsolid

    @master-blaster I just gave up!

    I'm glad you understood the point of this article!

  • 



djsolid

    You can find my article about testing a WebApp written in MVP at DotNetSlackers : http://dotnetslackers.com/articles/aspnet/Getting-started-with-testing-an-ASP-NET-Webforms-Application-MVP-Pattern.aspx

  • 



<a href="http://www.voilatech.com" rel="nofollow">Michael</a>

    Thanks for article. It was very helpful for teaching this old dog new tricks!

  • Livingston on said

    Reply
    



Livingston

    Tried this sample with WebformsMVP 1.2.0 and I get a YSOD
    "Unable to resolve type LoveForWebForms.Logic.Presenters.MessagesPresenter using service presenter.messages (WebFormsMvp.IPresenter)"

  • Livingston on said

    Reply
    



Livingston

    Had to add

    builder.RegisterPresenters(typeof(LoveForWebForms.Logic.Presenters.MessagesPresenter).Assembly);

    Now we're good to go

  • 



djsolid

    @Lovingston

    Thank you for your comments! I will update the downloadable sample!

    Cheers!

  • 



djsolid

    @Livingston downloaded latest version of WebFormsMVP and Autofaq, but everything worked fine without any changes.

    Do you have a problem at a specific page?

  • Livingston on said

    Reply
    



Livingston

    The initial issue was coming from the uc:Messages control that lives in the Site.Master page. Once removed, the Default.aspx page loads, but then the other pages have a similar error message.

    Unable to resolve type "X" using service presenter.messages

    For some reason, the DI container wasn't able to resolve the presenters when I ran the sample on my machine without me adding a builder.RegisterPresenters call in the Global.asax

  • Arunshyam on said

    Reply
    



Arunshyam

    Nice post... its really a useful one...

Add a Comment (gravatar-enabled)