eWorld.UI - Matt Hawley

Ramblings of Matt

WikiPlex v1.3 Released

March 11, 2010 17:53 by matthaw

[In addition to blogging, I am also using Twitter. Follow me: @matthawley]

 

It's been a many months since the last release of WikiPlex, but its only because there hasn't been a lot of churn recently.  I've very happy where WikiPlex is at, and it continues to be a very integral part of the CodePlex website!

 

Here's what you'll find in WikiPlex v1.3:

  1. Documentation - This new documentation includes
    1. Full Markup Guide with Examples
    2. Articles on Extending WikiPlex
    3. API Documentation
  2. Video Macro - This macro was updated to support Channel9 Videos.
  3. Syntax Highlight Support - One more language has been included:
    1. {code:powershell} ... Your Powershell Code ... {code:powershell

This time I did what I promised two releases ago, provided some good documentation. I even went so far as creating another open-source project called WikiMaml which will take wiki syntax and convert it to Sandcastle MAML output. The project isn't full proof, and not where I want it to end up, but it is working great within WikiPlex to generate all of the non-API documentation. As always, if you have any ideas for new macros or extensibility points, please post them in the issue tracker and I'll make sure to look at them!

Now, go and download WikiPlex v1.3!


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Extending WikiPlex with Scope Augmenters

March 10, 2010 10:40 by matthaw

[In addition to blogging, I am also using Twitter. Follow me: @matthawley]

 

This post is long overdue, but as I'm preparing the v1.3 release of WikiPlex and working on documentation (yes, I did say documentation) I realized that another extension point with WikiPlex was not covered. This last, important (but rarely used) part is extending with Scope Augmenters. Scope Augmenters allow you to post process the collection of scopes to further augment, or insert/remove, new scopes prior to being rendered. WikiPlex comes with 3 out-of-the-box Scope Augmenters that it uses for indentation, tables, and lists. For reference, I'll be explaining how and why the IndentationScopeAugmenter was created.

 

Why Its Needed

The IndentationMacro allows for an arbitrary indentation level indicated by the number of colons that are placed a the beginning of the line. Let's take a look at the primary macro rule:

new MacroRule(@"(^:+\s)[^\r\n]+((?:\r\n)?)$",
              new Dictionary<int, string>
              {
                 {1, ScopeName.IndentationBegin},
                 {2, ScopeName.IndentationEnd}
              });

As you can see, we're capturing any number of colons at the beginning, but our ending scope knows nothing of the how many defined levels there are. If you can imagine, knowing nothing about your the beginning scope when rendering to correctly render the ending is not a trivial task without context. This is the exact reason a Scope Augmenter is used, it has that context.

 

Ultimately, we would like the input

:: I am content

to be rendered as

<blockquote><blockquote>I am content</blockquote></blockquote>

 

Create a Scope Augmenter

Scope Augmenters can be as easy as you need to make it, but can also be fairly difficult - point of example, the supplied ListScopeAugmenter requires a complex algorithm to correctly identify levels, nested levels, and determining when to start new lists. When creating a Scope Augmenter, it will take in the associated macro, the current list of scopes, and the current content returning a new list of scopes to be rendered. In your solution, create a class called IndentationScopeAugmenter and extend it from WikIPlex.Parsing.IScopeAugmenter. You'll then implement the Augment method.

   1:  public IList<Scope> Augment(IMacro macro, IList<Scope> capturedScopes, string content)
   2:  {
   3:      var augmentedScopes = new List<Scope>(capturedScopes.Count);
   4:   
   5:      int insertAt = 0;
   6:      for (int i = 0; i < capturedScopes.Count; i = i + 2)
   7:      {
   8:          Scope begin = capturedScopes[i];
   9:          Scope end = capturedScopes[i + 1];
  10:   
  11:          string beginContent = content.Substring(begin.Index, begin.Length);
  12:          int depth = Utility.CountChars(':', beginContent);
  13:   
  14:          for (int j = 0; j < depth; j++)
  15:          {
  16:              augmentedScopes.Insert(insertAt, begin);
  17:              augmentedScopes.Add(end);
  18:          }
  19:   
  20:          insertAt = augmentedScopes.Count;
  21:      }
  22:   
  23:      return augmentedScopes;
  24:  }

The Indentation begin / end scopes always come in a sequential pair, which is why I'm able to grab the begin and end scope in lines 8 and 9. Next, you'll see that we need to determine the depth to indent, so we grab the beginning content (which ultimately will be a string containing only colons). In line 12, we count the number of colons there are, which gives us our depth count. Lines 14 - 18 are adding the N-1 listing of IndentationBegin and IndentationEnd scopes. The method then returns this, newly augmented, list of scopes. Basically the augmentation goes from

 

ScopeName.IndentationBegin,
ScopeName.IndentationEnd

to

ScopeName.IndentationBegin,
ScopeName.IndentationBegin,
ScopeName.IndentationEnd,
ScopeName.IndentationEnd

 

Registering a Scope Augmenter

Just as registering macros and renderers, there is (only) a static endpoint. Since augmenters should not rely on runtime dependencies, there is no runtime equivalent of using scope augmenters. When you register a Scope Augmenter, it is always associated with a single macro type, and during parsing, the WikiPlex parser will query for the Scope Augmenter that is associated with the current macro being used. To register your Scope Augmenter, have the following code in your application startup method

ScopeAugmenters.Register<IndentationMacro, IndentationScopeMacro>();

When you call the WikiEngine.Render("content"), it will automatically pick up all registered Scope Augmenters and use them during parsing.

 

Summary

You now have a fully functioning macro / augmenter / renderer that will take an arbitrary depth and have it render correctly. As previously stated, WikiPlex also ships two other Scope Augmenters, ListScopeAugmenter and TableScopeAugmenter, which have a bit more logic associated with them. While Scope Augmenters allow you to manipulate the list of scopes prior to rendering, they should only be used in certain situations in which you cannot capture the correct set of conditions or are unable to contextually determine rendering based on separate scopes.

 

Download WikiPlex now!


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

WikiPlex v1.2 Released

October 8, 2009 13:46 by matthaw

It's been a few months since the last release of WikiPlex, but I honestly have good reasons - paternity leave! This updated version has taken in a lot of user feedback and put it into action - so thank you for contributing ideas. Since the last release, there's been a steady download of the binaries/source code on a day-to-day basis, and am very happy where it is in the ecosystem.

 

Here's what you'll find in WikiPlex v1.2:

  1. Indentation Macro - This new macro adds support for blockquote indentation. Utilize it similar to the ordered/unordered list macros with the colon character. See this documentation for an example on how to use it.
  2. Silverlight Macro - This macro was updated to support:
    1. Any type of height/width unit (px, pc, pt, em, %, etc).
    2. Require Silverlight 3 as the default. You can optionally revert back to Silverlight 2 with the version=2 parameter.
    3. Support for initialization parameters by supplying any additional key/value parameters in the macro
  3. Video Macro - This macro was updated to support:
    1. A height/width supporting any type (px, pc, pt, em %, etc)
    2. Videos will not auto-start by default.
    3. Soapbox support has been removed
  4. Syntax Highlight Support - Two more languages have been included:
    1. {code:c++} ...Your C++ Code... {code:c++}
    2. {code:java} ...Your Java Code... {code:java}
  5. Updated Sample Application - A WebForms variant was added to the sample application. It can be found under the /WebForms directory.

Unfortunately, I didn't get around to documenting the API, but I've not heard of anyone commenting they can't figure it out - so maybe it'll still happen sometime in the future. As always, if you have any ideas for new macros or extensibility points, please post them in the issue tracker and I'll make sure to look at them!

 

Now, go and download WikiPlex v1.2! Also, don't forget to follow me on Twitter @matthawley

 

kick it on DotNetKicks.com


Currently rated 4.2 by 5 people

  • Currently 4.2/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

WikiPlex v1.1 Released

August 4, 2009 18:31 by matthaw

It's only been a few weeks since the v1.0 release, but a lot of work has gone into a new release of CodePlex's embeddable wiki engine, WikiPlex. The time in between has not been without a few highlights, including (detailed stats):

  • Over 500 combined downloads of the v1.0 engine and sample application
  • Roughly 175 downloads of the source code (not including SVN enlistments)
  • Over 8000 page views / 2200 visits of the project site
  • SoftPedia picking up WikiPlex (Download WikiPlex Free Open Source Wiki Engine from Microsoft)
  • WikiPlex being already integrated in several projects (SWiki / Umbraco)

I didn't have a long-term plan for revamping the wiki engine (I actually still don't!), but I did have several focuses for the short term that I wanted to achieve, including Mono compatibility, refactoring the macro parsing to enable more extensibility into the engine, and general "clean-up" tasks that I've been wanting to do. So, what's new in v1.1?

  1. Mono Compatibility - The WikiPlex source has been tested against the 2.4.2.1 release of Mono running on Linux. The source cleanly compiles and runs the sample application (note: you do still have to setup your own database for this). The only remaining issues running the sample application on Mono are ASP.NET MVC / Mono bugs.
  2. Scope Augmenters - Scope Augmenters allow changing the resulting scopes prior to rendering based on a macro mapping. Previously - the Table, Ordered List, and Unordered List scope augmentation were hard-coded into the MacroParser. With this release, you can now add your own augmenters to fully control the rendering of WikiPlex.
  3. Syndicated Feeds - The entire WCF syndication API was removed in lieu of utilizing a simpler, customized syndication framework. The main reasons for this change included: Mono currently not supporting this API and supporting the odd Google Atom specification. Aside from these internal changes, the macro was expanded so that it now supports {rss:url=...}, {feed:url=...}, and {atom:url=...} matching. No matter which format you use, the renderer will still choose the appropriate syndication reader (ie, you can specify rss for an atom feed, and vice versa).
  4. Sample Download - To be honest, I never opened the sample project from the .zip file, and so I never realized the state that it was in. Be it missing files, incorrect references, whatever - everything is fixed now! Along with that, several people have indicated that they didn't have SQL 2008 Express installed, so within the App_Data directory, there's a Wiki.sql file that you can execute on your local SQL server to create the sample tables/data (oh, and don't forget to change your connection string).
  5. Invalid Syntax Highlight Code Blocks - Previously, if someone supplied a {code:xxx} block that didn't match any of the supported languages, their source code would not be formatted as code. In v1.1 this has been changed, as it'll fall back to the non-syntax highlighted display of the code if it cannot find the language.
  6. Namespace Cleanup - This should only affect advanced users of the wiki engine. Below is a list of the changes:
    1. ScopeName was moved from WikiPlex.Common to WikiPlex
    2. IXmlDocumentReader and XmlDocumentReader were moved from WikiPlex.Common to WikiPlex.Syndication

One item that is still on my plate, is actually spending some time documenting the API and producing a help file. Hopefully, the API isn't too difficult to understand, but I realize that it's somewhat necessary when it comes to implementing the engine within your application. Regardless, I feel that the engine is pretty close to where it needs to be regarding usability and extensibility. If you have any ideas for new macros or extensibility points, please post them in the issue tracker and I'll make sure to look at them!

 

With that said, I give you WikiPlex v1.1 (go and download it now!) Also, follow me on Twitter @matthawley

 

kick it on DotNetKicks.com


Currently rated 4.3 by 3 people

  • Currently 4.333333/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Extending WikiPlex with Custom Renderers

July 30, 2009 13:22 by matthaw

Following up from my prior post on Extending WikiPlex with Custom Macros it's now time to talk about creating custom renderers. When we left off, we had created our title link macro and registered it with WikiPlex. If you had attempted to utilize the new macro in wiki content, you'd get the message "Cannot resolve macro, as no renderers were found." instead of a hyperlink. This was the expected behavior, because while you had identified your new scope via parsing, you were missing that critical link of turning the scope into actual content. Let's take a look at our scenario again to refresh what we're trying to achieve:

 

We would like to integrate WikiPlex into an existing application. The idea is to allow a user contributed area specifically for wiki content. The user should be allowed to use all out-of-the-box macros provided, but also have the ability to have inter-wiki links with the format of [Title of Page]. As you probably realized, there is currently no macro/renderer that will take that content and turn it into a inter-wiki link, so we'll have to extend WikiPlex adding this functionality.

Create a Renderer

Creating a renderer is actually the easiest portion of defining new wiki syntaxes, as it's as complicated as you need to make it. Again, a renderer simply takes in a scope (which is a contextual identifier), processes the content, and returns new content. Let's get started - so in your solution, create a class called TitleLinkRenderer and extend it from WikiPlex.Formatting.IRenderer. You'll then implement the members it requires (Id, CanExpand and Expand). The Id value is simply a string that is used as a key for static renderer registration, so it should be unique (follow the same rule of thumb for naming as the macros).

 

Next, you'll implement the CanExpand method. This method simply takes in a scope name and returns a boolean value indicating if this renderer can expand (or render) the scope successfully. As the formatter is processing all scopes, it goes through the list of renderers in the formatter and finds the first match that can expand that particular scope. There is no guarantee of the order of checking renderers, so always unregister a renderer you're overriding its implementation for. As you'll see below, the CanExpand method is fairly trivial, however should your renderer support a number of scopes, you'll need to change this code to include all of them.

public bool CanExpand(string scopeName)
{
   return scopeName == WikiScopeName.WikiLink;
}

Next, you'll implement the Expand method. This method will take in a scope name, the related input from the wiki source, and html / attribute encoding functions. The reason we're passing in html / attribute encoding functions, is so that you can utilize a consistent encoding scheme across all of the renderers. Out of the box, WikiPlex uses HttpUtility.HtmlEncode and HttpUtility.HtmlAttributeEncode, but by creating & supplying your own formatter, you can change these to use another library (like AntiXss). As previously stated, rendering is as hard as you need it to be. In the sample application example, we're just rendering a link utilizing the ASP.NET MVC UrlHelper (which is supplied via the constructor).

private const string LinkFormat = "<a href=\"{0}\">{1}</a>";

public string Expand(string scopeName, string input,
                     Func<string, string> htmlEncode, 
                     Func<string, string> attributeEncode)
{
   string url = urlHelper.RouteUrl("Default", new { slug = SlugHelper.Generate(input) });
   return string.Format(LinkFormat, attributeEncode(url), htmlEncode(input));
}

And now you have created your renderer, however it will still not be picked up when rendering your wiki content as you need to register the renderer with WikiPlex.

 

Registering a Renderer

Just as registering a macro, you have a static and a dynamic way to register your renderers. If your renderer requires only static dependencies (or no external runtime dependencies), you should opt for statically registering your renderer. To do this, have the following code in your application startup method

Renderers.Register<TitleLinkRenderer>();

When you call the WikiEngine.Render("content"), it will automatically pick up all statically defined renderers and use them when formatting your scopes. As previously stated, if you have external runtime dependencies (like in our example), a little bit of extra work is required when calling WikiEngine.Render - as you'll need to pass in a MacroFormatter instead. However, if you utilize the overload to only take in a formatter, you'll need to union the statically defined renderers with yours.

private MacroFormatter GetFormatter()
{
   var siteRenderers = new IRenderer[] {new TitleLinkRenderer(Url)};
   IEnumerable<IRenderer> allRenderers = Renderers.All.Union(siteRenderers);
   return new MacroFormatter(allRenderers);
}

Now, when you call WikiEngine.Render, you'll utilize the overload that takes in an IFormatter as a parameter. After you've set all of this up, try re-loading your page and you should see your syntax of [Title Link] be converted into the html <a href="/title-link">Title Link</a>.

 

Summary

You now have a new fully functioning macro syntax. Obviously, this example is trivial - but I guarantee if you embed WikiPlex into your application and need any cross-page linking, you'll utilize this macro & renderer. Again, the possibilities are endless with what you can do, so long as you have a syntax, regex, and rendering code - you can allow your users to simply include expansive macros.

 

Download WikiPlex now!

 

kick it on DotNetKicks.com


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Extending WikiPlex with Custom Macros

July 24, 2009 18:47 by matthaw

One of the very nice features of WikiPlex is the ability to extend it to your hearts desire. The API is completely open, and the entry points off of WikiEngine are merely wrappers. This means that, if you really wanted to, you could create your own parser and formatter - but the majority of extending WikiPlex will be done via macros and renderers. A summary of the extension points include:

  1. Macros encapsulate rules for matching segments of content.
  2. Macro rules are regular expressions that map matches to scopes.
  3. Scopes are contextual identifiers.
  4. Renderers take scopes and expand them into a HTML formatted representation of the macro.
Scenario

We would like to integrate WikiPlex into an existing application. The idea is to allow a user contributed area specifically for wiki content. The user should be allowed to use all out-of-the-box macros provided, but also have the ability to have inter-wiki links with the format of [Title of Page]. As you probably realized, there is currently no macro/renderer that will take that content and turn it into a inter-wiki link, so we'll have to extend WikiPlex adding this functionality.

 

Create a Macro

When creating a macro, you're going to have to dust off that copy of RegexBuddy you probably don't have installed anymore. Why? Well, as previously stated, macro rules are regular expressions - and unless you're a regex guru, you won't be able to do this ad-hoc without a great tool. Let's get started - so in your solution, create a class called TitleLinkMacro, and extend it from WikiPlex.Compilation.Macros.IMacro. You'll then implement the members it requires (Id and Rules). The Id value is simply a string that is used as a key for static macro registration and macro compilation, so it should be unique (rule of thumb, give it the name of your class but with spaces). Now, it's time to define your macro rules.

 

As you may have noticed, I kept "rule" plural. The reason, is that the majority of macros you will create need to have an initial "escaped" rule. This rule basically stops the regex from matching within code blocks, between curly braces, and possibly between square brackets. Since our macro utilizes square brackets, we'll use the escape rule of CurlyBraceEscape.

 

Next, you'll define your regex (with extreme caution!) utilizing capturing groups to identify scopes. If you take a look at the code, you may not think that the scope identification is zero based - don't be fooled, it really is! Identifying an index 0 scope indicates the full match for that rule. When creating your capturing groups, you can have any number, allowing for fine granularity when rendering. So, let's take a look at the sample project's macro's rules.

public IList<MacroRule> {
   get {
      return new List<MacroRule> {
         new MacroRule(EscapeRegexPatterns.CurlyBraceEscape),
         new MacroRule(@"(?i)(\[)(?!\#|[a-z]+:)((?>[^\]]+))(\])",
                 new Dictionary<int, string> {
                    { 1, ScopeName.Remove },
                    { 2, WikiScopeName.WikiLink },
                    { 3, ScopeName.Remove }
                 })
      };
   }
}

As you can see, the regular expression is indicating that I should match 3 scopes per overall match. The scope "Remove" does just that. It removes the captured content when rendering. The WikiLink scope name is one that was created specifically for the sample. For the non-regex savvy developer, the regex reads:

  1. Use case insensitive matching from this point on
  2. Match the character "["
  3. Match any character until "]" is found
    1. But do not match if the character is preceded
      1. by the "#" character
      2. or by any character between a-z, one or more times, followed by a ":" character
  4. Match the character "]"

Our scope to step matching then is

  1. Remove "["
  2. WikiLink any content
  3. Remove "]"

Defining macro rules is a fairly straight forward process, just keep in mind that the order of the macro rules is important! You should also realize that if you wish to allow nesting of rules (for example, italicize bolded text) the italics and bold macro rules cannot be apart of the same macro. This, again, is because the macro rules are combined to build a large regular expression - and each rule is treated as an "or" statement.

 

Registering a Macro

After you have created your macro, you need to register it with WikiPlex. You have two ways of doing this - statically and dynamically. When statically registering macros, you simply need to have the following code in your application startup method

Macros.Register<TitleLinkMacro>();

When you call the WikiEngine.Render("content"), it will automatically pick up your macro, compile it, and use it when parsing the wiki content. Dynamically loading your macros is useful when you require a different set of macros to be executed than what is normally registered. For example, in CodePlex - we have a different set of allowed macros between editing a project's wiki content and editing your personal statement. Dynamically loading is achieved by utilizing one of the WikiEngine.Render overloads:

WikiEngine.Render("content", new IMacro[] { new WikiTitleLinkMacro() });

The only caveat of this approach, is that it will only use that macro when parsing your wiki content.

 

Summary

This is just the tip of the extensibility for WikiPlex. Creating your own macro is a great. Simply be cautious of your regular expression as it could have negative side effects (catastrophic backtracking) that will bring your site to a screeching halt! In the next installment, we'll take this scenario to the next step by creating a renderer for your macro.

 

Download WikiPlex now!

 

kick it on DotNetKicks.com


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

WikiPlex – An Embedded Wiki Engine

July 16, 2009 11:04 by matthaw

What and Why?

I'd like to introduce you to WikiPlex, which is CodePlex's wiki engine that we have re-written and made open source under the MS-PL license. I'm also happy to announce that our first public release is now available!

 

CodePlex previously had a decent wiki engine that was written eon's ago. On the average, that wiki engine worked relatively well, but had a very problematic performance bug that would cause rendering slowness occasionally. So, instead of attempting to fix the bug, we decided to re-write the entire thing with the intensions of making it available to everyone! This time, we chose a different approach for parsing the wiki markup (utilizing regular expressions) which has proven to give us a performance boost as well as a relatively simpler architecture!

 

The main question you may be asking yourself is - Why use WikiPlex over a different solution? Here's the simple answer: WikiPlex is great if you already have a .NET application you'd like to embed a wiki interface into. Be it as simple as allowing users to host their own homepage content, item descriptions, or comments - the possibilities are endless!

 

Usage

WikiPlex was built in a way that it can easily be added into your infrastructure. Whether your using dependency injection or not, the code is as simple as the following:

var engine = new WikiPlex.WikiEngine();
string output = engine.Render("This is my wiki source!");

If you take a look at the overloads for Render, you'll see that you have a lot of flexibility as far as rendering various wiki segments differently at runtime. (I'll describe the extensibility in the future)

public interface IWikiEngine
{
   string Render(string wikiContent);
   string Render(string wikiContent, IFormatter formatter);
   string Render(string wikiContent, IEnumerable<IMacro> macros);
   string Render(string wikiContent, IEnumerable<IMacro> macros, IFormatter formatter);
}

Supported Macros

The following are the macros supported out of the box for WikiPlex. If you'd like to see the description and usage, please visit the markup guide.

  • Text Formatting
    • Bold
    • Italics
    • Underline
    • Strikethrough
    • Superscript
    • Subscript
  • Headings
  • Images
  • Links
  • Tables
  • Left and Right Aligned Text
  • Ordered and Unordered Lists
  • RSS / Atom Feeds
  • Source Code Blocks (both syntax highlighted and not)
  • Silverlight
  • Videos (Flash, Quicktime, Real, Soapbox, Windows Media, and YouTube)

Enjoy! And don't forget to download WikiPlex now!

 

kick it on DotNetKicks.com


Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

ASP.NET MVC - Living in a Web Forms World

May 9, 2008 08:59 by matthaw

For the life of me, I seem to have Journey stuck in my head when I was thinking about writing this post, and as such the title reflects that! Anyway, onto the post...

 

When developing ASP.NET MVC applications, most examples or sites have shown you starting from complete scratch. This is all and well if you have the time to completely re-write an existing application for 6+ months or have started a v1 product. Right, in the "real-world" the former rarely happens and if your a developer wanting to stay on the bleeding edge, introducing a new architecture into an existing ASP.NET application is fairly tedious. From converting existing pages, supporting legacy routes, and possibly the lack of allowing multiple forms all prove to be a challenge.


Layout

As you know, I'm currently working on the CodePlex team and my biggest task I'm working on is slowly converting the site to use ASP.NET MVC. If you've been to this site, you know how extensive the site is, and how this challenge is very daunting. The one saving grace is that we can do it gradually since we release so often. But, because of this we have to take small steps converting pages (views!) that we can - while maintaining our original master page and other postback functionality. For this post, I'll use CodePlex as a reference point.

 

We have 2 major sections that we're to worry about while working with ASP.NET MVC. Most importantly, we still have a single form on the page! This is a critical piece of information, because this limitation required us to make decisions we did to get around ASP.NET MVC within a Web Forms world. When working on a MVC view, we render everything within a main content section, and do not use ASP.NET server controls at all. The site also contains a project search on each page but this, however, still relies upon that singe form and the normal Postback model.

 

Views

Like any web application, you have content pages and you have forms based pages. CodePlex is no different. When converting each of these types, obviously, the former of the two is the easiest to convert. Why? Well, you don't have to worry about that single form tag and there's no need of posting data back to the server. So, when working with true content pages - converting them is as simple as creating your controller, action, and view. Now, just because you're still in a Web Forms model, still does not give you the right to continue to use ASP.NET server controls!

 

The most challenging portion is working with forms based pages. If you were building a new ASP.NET MVC application from the ground-up, you wouldn't have to think twice about this problem. The reason is that when you create your forms based pages, each logical form has it's own form! Astounding, isn't it. Yes, we've been living in that world of ASP.NET Web Forms world wherein you can have 1 and only 1 form tag (with a runat="server"). Since the application still has this single form tag in place, and we still need to respond to master page events (like project searches) we have to be very mindful of how its done. The biggest challenge is that, if you know the ASP.NET MVC lifecycle, it goes

  1. HTTP Request arrives
  2. Routing HTTP Handler executes, matching based on routes
  3. MVC Route Handler executes controller & action
  4. The ViewPage is rendered (assuming your calling RenderView)
  5. The ASP.NET page life cycle is invoked (assuming your using the default view engine)

Do you see the problem? No? Okay, well its the fact that upon a HTTP Post, your Action method is invoked prior to any event occurring within the ASP.NET lifecycle. That means, at an Action method level - you have absolutely no idea if your MVC form was submitted or was it that pesky Project search. Well, this isn't entirely true because how to get around this is by having a hidden input field in which you set a value when your MVC based form is submitted. I'll show a little later how this is used, but you can probably figure out how.

   1:  <input type="hidden" id="mvcFormSubmitted" name="mvcFormSubmitted" value="" />
   2:  <label for="UserName">User Name:</label><br />
   3:  <%= Html.TextBox("UserName", ViewData["UserName"]) %><br />
   4:  <label for="Password">Password:</label><br />
   5:  <%= Html.Password("Password") %><br />
   6:  <%= ViewData["ErrorMessage"] %><br />
   7:  <%= Html.SubmitButton("submitButton", "Login", new { onclick = "mvcFormSubmit(); " }) %>
   8:   
   9:  <script type="text/javascript">
  10:  function mvcFormSubmit() {
  11:     $get('mvcFormSubmitted').value = 'true';
  12:  }
  13:  </script>

Routing

As you can imagine, routing has become a bit of a nightmare. Why? Well, since we still have to abide by the Postback model, our Views always postback to themselves. For example, /site/login will post to /site/login vs. /site/login posting to /site/processLogin. At first glance, you might think "okay, I'll just check to see if the http method is of type POST within my login action." That would work, but the correct method, which supports later migration away from a Web Forms world, is to have two separate actions. To handle this scenario, you simply use constraints on the HttpMethod within your route definition:

   1:  routes.MapRoute("GET Login", "/site/login", 
   2:                  new { controller = "session", action = "login" }
   3:                  new { httpmethod = "GET" });
   4:   
   5:  routes.MapRoute("POST Login", "/site/login",
   6:                 new { controller = "session", action = "processLogin" }
   7:                 new { httpmethod = "POST" }

Now, when your clients request /site/login, the login action will execute, and upon a POST to /site/login, the processLogin action will execute. Another scenario you should consider at this time, is that if you are converting a previous page, say "/Users/Login.aspx" you would want all subsequent requests to be routed to /site/login. To handle this, you can utilize my Legacy Routing methodology that would send a 301 http status code pointing the client to the new url.

   1:  routes.Add("", new LegacyRoute(
   2:     "Users/Login.aspx",
   3:     "GET Login",
   4:     new LegacyRouteHandler());

Controller Actions

Now it's time to put these two things together. Granted, if you were doing true TDD development, you would have started with your controller & actions, but for the purposes of this post I left it at the very end to show how everything comes together. So a few things to remember, is that we are now assured that our login and processLogin action will execute at the correct time. We also have a hidden input field in which we can determine if it was my form submission that caused the postback, or was it some other control on the master page. So, here's how things should look like:

   1:  public class SessionsController : Controller
   2:  {
   3:     public ActionResult Login()
   4:     {
   5:        if (TempData["ErrorMessage"] != null)
   6:        {
   7:            ViewData["UserName"] = TempData["UserName"];
   8:        }
   9:        return RenderView();
  10:     }
  11:   
  12:     public ActionResult ProcessLogin()
  13:     {
  14:        if (Request.Form["mvcFormSubmitted"] != "true")
  15:           return RenderView("Login");
  16:   
  17:        string userName = Request.Form["UserName"];
  18:        string password = Request.Form["Password"];
  19:        if (string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
  20:        {
  21:           TempData["ErrorMessage"] = "Username and Password not valid.";
  22:           TempData["UserName"] = userName;
  23:           return RedirectToAction("Login");
  24:        }
  25:   
  26:        // process login
  27:        return Redirect("~/home");
  28:     }
  29:  }

As example, I've followed the PRG pattern that I've previously mentioned, but there's one thing here that breaks that pattern. It's the RenderView("Login") if my hidden field does not match. Why do we need to break the pattern here? Well remember the lifecycle I previously mentioned. The only way for events to be fired from your master page, is that it too needs to "postback", thus falling back into the postback model. Once you can safely ditch the singe form, that code can be safely removed.

 

Final Thoughts

As you can tell, having a mixture of ASP.NET WebForms and MVC forms is not a trivial task. There's a lot to consider when tackling this, and if at all possible SPIKE! Yes, spike your master pages so that you can have a Web Forms master page AND a MVC master page. By doing this, you do not have to follow the single form pattern thus negating almost all this post. I would still recommend using the legacy routing to redirect your users to the new page as well as the PRG pattern, but you should always be doing that anyway :) Well, hopefully this post has served its purpose and given you some ideas on how to approach converting existing ASP.NET applications to ASP.NET MVC. If you have any other suggestions or better recommendations, please add a comment!

 

kick it on DotNetKicks.com


Currently rated 4.5 by 2 people

  • Currently 4.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Less Ramp-Up Time with Pair Programming?

April 15, 2008 00:50 by matthaw

Being my first day on the CodePlex team, I figured it'd be filled with "read this", "watch that", and "understand da code". Being that any other job I've started in the past has a significant ramp-up time, I would have guessed CodePlex would be no different. While I think there will be some time before I'm fully comfortable, I'd say that coming on board the first day and already writing some code is a success! Thanks pair programming!


Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

My New Job

March 27, 2008 16:12 by matthaw

Ah, the cat is finally out of the bag at work. I've been holding out recently and didn't want to say anything until the official notice had been sent around to my colleagues. I've been at Microsoft for just about 3 years now, working in the MSIT Partner division and I've finally decided it was time to move on. As of April 14th, I will be working on the CodePlex.com team! It's a very small, very agile team under the direction of Jim Newkirk. I can't wait to get over there and start learning a lot from this team.

With this new adventure, I hope to start blogging a lot more and really get back into the community. I've sorely missed not being involved and it's something I'll get back to doing.

This is just my first of few surprises, stay tuned as the 2nd one is going to take a bit more effort by me!


Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: Personal | CodePlex
Actions: E-mail | Permalink | Comments (5) | Comment RSSRSS comment feed


Copyright © 2000 - 2010 , Excentrics World