views:

412

answers:

4

What is the best way to call a Response.Redirect in the Model-View-Presenter pattern while adhering to correct tier separation?

+1  A: 

The way we do it works nicely once some ground work is laid. I'm sure there are several ways to skin a cat though. (Who skins cats anyway. Cats are cute and cuddly!)

First, this will only work on the ASP.Net compiled Web Projects, not Websites.

Each page should inherit from a custom abstract base class which looks something like this:

public abstract class PageBase : Page
{
  private static string _baseUrl = "/";

  public static string BaseUrl
  {
    get { return _baseUrl; }
    set { _baseUrl = value; }
  }

  protected static string BuildUrl(string basePath)
  {
    if( !string.IsNullOrEmpty(basePath) && basePath.StartsWith("~/"))
    {
      basePath = basePath.replace("~/", BaseUrl);
    }
    return basePath;
  }

  protected static string LoadView(string path)
  {
    Response.Redirect(path);
  }
}

Each page also implements a page-specific interface. Each page-specific interface also inherits from a base interface:

public interface IPageBase()
{
  void LoadView(string path);
}

Then it's a matter of each page defining it's own version of BaseUrl. You might want to account for querystrings/path encryption/etc.

Finally, any of your presenters (which should be referencing the page-specific interfaces) can grab the static BuildUrl() on a desired page to view and then call LoadView() with the returned path.

ddc0660
A: 

It depends how generic your presenters are. If your presenters are totally UI agnostic (can be reused between WinForms and WebForms) then you'll have to abstract the redirection operation. In WebForms, the redirection operation would be implemented in the view by a Response.Redirect. In WinForms, (I disclaim lots of experience with WinForms) my guess is that it would be implemented by SomeForm.Show.

One simple, off-the-top-of-my-head option would be to include in the view's interface a ShowViewX() method. You can have one for each form the view could logically redirect to. Alternatively, the view can implement an interface method like Show(ConnectedViews) where ConnectedForms is an enum that includes a value for each of the views that can be "redirected" to from a particular view. This enum would live at the presenter level.

The above approaches are specific to view-presenter pairs. You could instead implement it as a system-wide thing. The logic would be similar to above, implemented in the base view and presenter. There would be a ShowView__() for each form, or a Show(Views) method where Views is an enum of all forms.

It's a toss-up between encapsulation and DRY-ness.

gWiz
+4  A: 

One way I handled this is for the presenter to raise an event (like Succeeded or something) that the view would subscribe to. When the presenter finished it's processing, it would raise the event, which would get handled by the View. In that handler, the view would redirect to the next page.

This way, the presenter doesn't need to know anything about pages or URLs or anything. It just knows when it has completed its task and lets the view know by raising an event. You can raise different events if the presenter succeeded or failed, in case you need to redirect to different places.

KevnRoberts
That's an interesting idea.
Chris Marisic
I've been thinking about this recently again is there really any specific advantage to using an event as opposed to a method?
Chris Marisic
+2  A: 

I do not know whether it is the most correct way, conceptually. But what I did in my last MVP-applications, is create a wrapper around HttpContext.Current which I called HttpRedirector. I also created a dummy redirector for testing purposes. Both keep track of the last redirected url, so that I can check in my unit tests that the redirect actually happened when I call a method on my controller/presenter. With an IOC-container I am able to switch the implementation of IRedirector based on the environment (production/test).

Ruben
Abstracting the `HttpContext` I also really like
Chris Marisic