Highlights of ASP.NET MVC 3 - Bitbucket by zhouwenjuan

VIEWS: 7 PAGES: 15

									Highlights of ASP.NET
MVC 3
As developers are just getting used to ASP.NET MVC 2 and Visual
Studio 2010, Microsoft has already planned and released a
preview of the next version of ASP.NET.
What started out as “just another option” for ASP.NET
developers has become the programming style of choice for
developers writing .Net applications for the web.



The ASP.NET MVC landscape
Before thinking you are behind the technology curve
in any way, remember that the current version of
ASP.NET MVC is version 2. This is the version that
shipped with Visual Studio 2010. It is built against
.Net 3.5 SP1, so it works in Visual Studio 2008 SP1
as well. If you are building web applications right
now, this is the version you should be using.
ASP.NET MVC 2 is built on top of ASP.NET 4, which is
a very powerful web application runtime that works
with IIS (Internet Information Services) and
Windows Servers. Web Forms continues to be a part
of the ASP.NET landscape, and it will for some time.
With ASP.NET 4, there is no need to choose between
Web Forms and MVC. They work well together both
with view engine integration as well as the addition
of routing into the core of ASP.NET 4. Within the
same application, some pages can be Web Form
pages, and some can be MVC-based.
With that being said, there is much excitement in the
industry over the simplicity that ASP.NET MVC brings
to the table.    The code-driven approach is also
favored by many over the XML markup and
template-driven approach to coding.

Agenda
In this article, I am going to lay out the plan for
ASP.NET MVC 3 as disclosed from Microsoft. I will
explain and demonstrate the features that are to
come as well as give some guidance on how to think
about each one.
Next, we will build a Guestbook application from
scratch using ASP.NET MVC 3 Preview 1. We will
apply the new features while building the application,
and you will be able to download the entire source
and try it for yourself.

How to get the preview
On July 27, 2010, Microsoft release ASP.NET MVC 3
Preview 1. You can download it for yourself here:
http://www.microsoft.com/downloads/details.aspx?d
isplaylang=en&FamilyID=cb42f741-8fb1-4f43-a5fa-
812096f8d1e8.
You     will    want     to    download     the    file,
AspNetMVC3Setup.exe and execute it while verifying
that you already have Visual Studio 2010 installed.
This build will not work if you only have Visual Studio
2008.     When the final build is released at an
unknown date, MVC 3 will also work with Visual Web
Developer 2010.
This build works fine beside ASP.NET MVC 1 and 2,
and it also lives in harmony on workstations with
Visual Studios 2008 and 2010 installed side-by-side.
It does this by avoiding the GAC completely. The
new System.Web.Mvc.dll assembly will reside in
C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET
MVC 3\Assemblies\System.Web.Mvc.dll on 64-bit
machines. After you have the preview installed,
head on over to the New Project dialog and notice
the new entries for MVC 3 as shown in figure 1.




Figure 1: ASP.NET MVC 3 adds new project types


Now that we understand how to get started              with
ASP.NET MVC 3 Preview 1, let’s go over the             new
features that are in the works. Keep in mind           that
the build available at this point is full of preview   bits,
so while the happy path works, there are bound to
be holes and bugs.

MVC 3 feature overview
The features being added in version 3 are
complimentary and compatible with previous
versions. There are no revolutionary changes here.
Here is a quick run-down of the features in table 1.
We will examine most of these as we build our
guestbook application.
Feature                  Description
Razor view engine        Provides a new templating
                         syntax & separation from
                         the ASP.NET processing
                         pipeline
CommonServiceLocator     Supports dependency
support                  injection and IoC containers
.Net 4 data annotation   More validations and UI-
support &                building goodness
IValidateObject
interface
Global action filters    Wire up filters for all
                         controllers at once
Dynamic views and        Leverages the DLR
models
JSON model binding        Accepts and binds JSON
                          requests to a regular
                          controller action
More action results       Permanent redirects and
                          other status codes
Table 1: MVC 3 will contain the listed features and more
 In this preview, JSON model binding and
CommonServiceLocator support are not fully baked,
but just knowing that they are in the works is
encouraging.
JSON model binding will be automatic. That is, the
same controller action can service http requests
regardless what form the data comes in as.
When ASP.NET MVC 3 releases, it will take a
dependency on the CommonServiceLocator project
located                                          at
http://commonservicelocator.codeplex.com/.     This
library has been around since 2008, and it is the
work of collaboration by many Inversion of Control
container authors and Microsoft.        It provides
common functions for locating services based on the
interface type. ASP.NET MVC is integrating this to
provide easy factories for:
      Controllers
      Filters
      Views
      Model binders
      Value providers
      And more!
Now that we have seen what the plans look like, let’s
build an application together.

Building the guestbook app
Let’s dive in and build our application. Each step of
the way, we will pause and examine what is new.




Summary
MvcContrib is a self-contained, non-intrusive library
that can be easily used with ASP.NET MVC and
ASP.NET MVC 2 web application. In this article, I
have given an overview of MvcContrib as well as
taught you how to obtain and work with the library.
I have shown you some of the major features
including subcontrollers, input builders, portable
areas, and the test helpers. There are many, many
more features contained in MvcContrib, and I
encourage you to download it to explore further.
We welcome community contributions.        Anything
from documentation to testing to samples, we
welcome any type of contribution. Even if you just
want to keep up with the project, join the email list
at      http://groups.google.com/group/mvccontrib-
discuss, submit a question, or answer questions
submitted by others



       Community Opinions on
       MvcContrib
       “Templated Helpers will deliver
       both increased developer
       productivity and simplified view
       markup. This Convention over
       Configuration (CoC) approach to
       generating input forms just makes
       sense.” – Eric Hexter, MvcContrib
       co-founder

        “MvcContrib includes a number of
       handy additions to the MVC
       Framework. It's the first thing I
       reach for when starting any
       ASP.NET MVC project” – Ben
       Scheirman, co-author of ASP.NET
       MVC in Action
Portable areas allow an entire
area to be distributed as a
single .Net assembly.


MvcContrib has been
downloaded over 70,000 times
and the source contains over
1000 automated tests.

Jeffrey Palermo
jeffrey@headspring.com
512-459-2260
JEFFREY PALERMO is an author of a
recent book, ASP.NET MVC in
Action. You can obtain the book at
http://mvcbook.jeffreypalermo.co
m/. He is the CTO of Headspring
Systems. Jeffrey specializes in
agile management coaching and
helps companies double the
productivity of software teams. He
is instrumental in the Austin
software community as a member
of AgileAustin and a director of the
Austin .NET User Group. Jeffrey
has been recognized by Microsoft
as a “Microsoft Most Valuable
Professional” (MVP) in Solutions
Architecture for five years and
participates in the ASPInsiders
group, which advises the ASP. NET
team on future releases. Jeffrey
has spoken and facilitated at
industry conferences such as
VSLive, DevTeach, the Microsoft
MVP Summit, various ALT.NET
conferences, and Microsoft Tech
Ed. He also speaks to user groups
around the country as part of the
INETA Speakers’ Bureau. His web
sites are headspring.com and
jeffreypalermo.com. He is a
graduate of Texas A&M University,
an Eagle Scout, and an Iraq war
veteran. Jeffrey is the founder of
the CodeCampServer open-source
project and a co-founder of the
MvcContrib project.


Listing 1: The Subcontroller base class packages up execution within a
closure.
using System;
using System.Web.Mvc;
using System.Web.Routing;
using MvcContrib.Filters;

namespace MvcContrib
{
    ///<summary>
    /// Subcontroller base class for subcontrollers.
    /// Subcontrollers can be infinitely nested.
    ///</summary>
    [SubcontrollerActionToViewData]
    public class Subcontroller : Controller, ISubcontroller
    {
        public virtual Action GetResult(
            ControllerBase parentController)
        {
            RequestContext requestContext =
                 GetNewRequestContextFromController(
                     parentController);
            return () => Execute(requestContext);
        }

        ///<summary>
        /// Gets new RequestContext using objects from parent
        /// controller.
        /// This ensures subcontrollers have their own state
        /// that doesn't conflict with the parent.
        ///</summary>
        ///<param name="parentController">Parent controller</param>
        ///<returns>RequestContext</returns>
        public RequestContext
            GetNewRequestContextFromController(
            ControllerBase parentController)
        {
            RouteData parentRouteData =
                parentController.ControllerContext.RouteData;
            var routeData =
                new RouteData(parentRouteData.Route,
                               parentRouteData.
                                   RouteHandler);
            string controllerName = GetControllerName();
            routeData.Values["controller"] = controllerName;
            routeData.Values["action"] = controllerName;
            return
                new RequestContext(
                     parentController.ControllerContext.
                         HttpContext,
                     routeData);
        }

        ///<summary>
        /// Gets the name from the type by trimming
        /// "controller" and "subcontroller"
        /// from the type name. The subcontroller action
        /// must match the controller name.
        ///</summary>
        ///<returns></returns>
        public string GetControllerName()
        {
            string typeNameLowered =
                GetType().Name.ToLowerInvariant();
            int index =
                typeNameLowered.IndexOf("subcontroller");
            if(index == -1)
            {
                  index = typeNameLowered.IndexOf("controller");
              }

              if(index == -1)
              {
                  return typeNameLowered;
              }

              return typeNameLowered.Substring(0, index);
         }
     }

     ///<summary>
     /// Subcontroller with generic Model property.
     ///</summary>
     ///<typeparam name="T"></typeparam>
     public class Subcontroller<T> : Subcontroller,
                                     ISubcontroller<T>
     {
         public virtual T Model { get; set; }
     }
}




Listing 2: The Field.Master defines many different sections for each field.
<%@ Import Namespace="MvcContrib.UI.InputBuilder.Views" %>
<%@ Import Namespace="System.Web.Mvc.Html" %>

<%@ Master Language="C#"
Inherits="System.Web.Mvc.ViewMasterPage<PropertyViewModel>" %>

<%
    Example.Visible = Model.HasExample();
%>
<div style="padding: 5px; clear: both">
    <div style="margin-right: 10px; float:
        left; font-weight: bold; width: 200px; text-align: right">
        <asp:contentplaceholder id="Label" runat="server">
            <label for="<%=Model.Name%>">
            <%=Html.Encode(Model.Label)%></label>
        </asp:contentplaceholder>
        <%=Model.PropertyIsRequired?"*":""%>
        <%=Model.Label.Length>0?":":"" %>
    </div>
    <div style="float: left;">
        <asp:contentplaceholder id="Input" runat="server" />
        <asp:contentplaceholder id="Validation" runat="server">
            <%=Html.ValidationMessage(Model.Name,"*") %>
        </asp:contentplaceholder>
        <asp:contentplaceholder id="Example" runat="Server"
        visible="false">
            <span class="example">ex.:
            <%=Html.Encode(Model.Example)%></span>
        </asp:contentplaceholder>
    </div>
    &nbsp;(custom text)
</div>
Listing 3: The ApplicationBus sends messages from portable areas to the
host application
using System;
using System.Collections.Generic;

namespace MvcContrib.PortableAreas
{
    public class ApplicationBus : List<Type>,
                                   IApplicationBus
    {
        public new void Add(Type type)
        {
            if(
                type.GetInterface(
                    typeof(IMessageHandler).Name) == null)
            {
                throw new InvalidOperationException(
                    string.Format(
                        "Type {0} must implement the
IMessageHandler interface",
                        type.Name));
            }
            base.Add(type);
        }

        private IMessageHandlerFactory _factory;

        public ApplicationBus(IMessageHandlerFactory factory)
        {
            _factory = factory;
        }

        public void Send(IEventMessage eventMessage)
        {
            foreach(
                var handler in
                     GetHandlersForType(
                         eventMessage.GetType()))
            {
                handler.Handle(eventMessage);
            }
        }

        public void SetMessageHandlerFactory(
            IMessageHandlerFactory factory)
        {
            _factory = factory;
        }

        public IEnumerable<IMessageHandler>
            GetHandlersForType(Type type)
        {
            foreach(var handlerType in this)
            {
                IMessageHandler handler =
                    _factory.Create(handlerType);
                if(handler.CanHandle(type))
                {
                    yield return handler;
                }
            }
        }
    }
}




Listing 4: The route testers make testing routes very easy.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
using MvcContrib.TestHelper;
using NUnit.Framework;
using AssertionException =
    MvcContrib.TestHelper.AssertionException;

namespace MvcContrib.UnitTests.TestHelper
{
    [TestFixture]
    public class RouteTestingExtensionsTester
    {
        public class FunkyController : Controller
        {
            public ActionResult Index()
            {
                return null;
            }

             public ActionResult Bar(string id)
             {
                 return null;
             }

             public ActionResult New()
             {
                 return null;
             }

             public ActionResult List(Bar bar)
             {
                 return null;
             }

             public ActionResult Foo(int id)
             {
                 return null;
             }

             [AcceptVerbs(HttpVerbs.Post)]
             public ActionResult Zordo(int id)
             {
                 return null;
             }

             public ActionResult Guid(Guid id)
             {
                 return null;
             }

             public ActionResult Nullable(int? id)
             {
                 return null;
             }
         }
public class Bar {}

public class AwesomeController : Controller {}

[SetUp]
public void Setup()
{
    RouteTable.Routes.Clear();
    RouteTable.Routes.IgnoreRoute(
        "{resource}.gif/{*pathInfo}");
    RouteTable.Routes.MapRoute(
        "default",
        "{controller}/{action}/{id}",
        new
        {
            controller = "Funky",
            Action = "Index",
            id = ""
        });
}

[TearDown]
public void TearDown()
{
    RouteTable.Routes.Clear();
}

[Test]
public void
    should_be_able_to_pull_routedata_from_a_string()
{
    RouteData routeData = "~/charlie/brown".Route();
    Assert.That(routeData, Is.Not.Null);

    Assert.That(
        routeData.Values.ContainsValue("charlie"));
    Assert.That(
        routeData.Values.ContainsValue("brown"));
}

[Test]
public void
    should_be_able_to_match_controller_from_route_data
    ()
{
    "~/".Route().ShouldMapTo<FunkyController>();
}

[Test, ExpectedException(typeof(AssertionException))
]
public void
    should_be_able_to_detect_when_a_controller_doesnt_match
    ()
{
    "~/".Route().ShouldMapTo<AwesomeController>();
}

[Test]
public void
    should_be_able_to_match_action_with_lambda()
{
    "~/".Route().ShouldMapTo<FunkyController>(
                x => x.Index());
       }

       [Test, ExpectedException(typeof(AssertionException))
       ]
       public void
           should_be_able_to_detect_an_incorrect_action()
       {
           "~/".Route().ShouldMapTo<FunkyController>(
               x => x.New());
       }

       [Test]
       public void
           should_be_able_to_match_action_parameters()
       {
           "~/funky/bar/widget".Route().ShouldMapTo
               <FunkyController>(x => x.Bar("widget"));
       }

       [Test, ExpectedException(typeof(AssertionException))
       ]
       public void
           should_be_able_to_detect_invalid_action_parameters
           ()
       {
           "~/funky/bar/widget".Route().ShouldMapTo
               <FunkyController>(
               x => x.Bar("something_else"));
       }

       [Test]
       public void
           should_be_able_to_test_routes_directly_from_a_string
           ()
       {
           "~/funky/bar/widget".ShouldMapTo
               <FunkyController>(x => x.Bar("widget"));
       }

       [Test]
       public void

should_be_able_to_test_routes_with_member_expressions_being_used
            ()
        {
            string widget = "widget";

            "~/funky/bar/widget".ShouldMapTo
                <FunkyController>(x => x.Bar(widget));
       }

       [Test]
       public void

should_be_able_to_test_routes_with_member_expressions_being_used_bu
t_ignore_null_complex_parameteres
            ()
        {
            "~/funky/List".ShouldMapTo<FunkyController>(
                x => x.List(null));
        }
       [Test]
       public void should_be_able_to_ignore_requests()
       {
           "~/someimage.gif".ShouldBeIgnored();
       }

       [Test]
       public void
           should_be_able_to_ignore_requests_with_path_info
           ()
       {
           "~/someimage.gif/with_stuff".ShouldBeIgnored();
       }

       [Test]
       public void
           should_be_able_to_match_non_string_action_parameters
           ()
       {
           "~/funky/foo/1234".Route().ShouldMapTo
               <FunkyController>(x => x.Foo(1234));
       }

       [Test]
       public void

assertion_exception_should_hide_the_test_helper_frames_in_the_call_
stack
            ()
        {
            IEnumerable<string> callstack = new string[0];
            try
            {
                "~/badroute that is not configures/foo/1234"
                    .Route().ShouldMapTo<FunkyController>(
                    x => x.Foo(1234));
            }
            catch(Exception ex)
            {
                callstack =
                    ex.StackTrace.Split(
                        new[] {Environment.NewLine},
                        StringSplitOptions.None);
            }
            callstack.Count().ShouldEqual(1);
        }

       [Test]
       public void
           should_be_able_to_generate_url_from_named_route()
       {
           RouteTable.Routes.Clear();
           RouteTable.Routes.MapRoute(
               "namedRoute",
               "{controller}/{action}/{id}",
               new
               {
                   controller = "Funky",
                   Action = "Index",
                   id = ""
               });

            OutBoundUrl.OfRouteNamed("namedRoute").
                ShouldMapToUrl("/");
       }

       [Test]
       public void

should_be_able_to_generate_url_from_controller_action_where_action_
is_default
            ()
        {
            OutBoundUrl.Of<FunkyController>(x => x.Index()).
                ShouldMapToUrl("/");
        }

       [Test]
       public void
           should_be_able_to_generate_url_from_controller_action
           ()
       {
           OutBoundUrl.Of<FunkyController>(x => x.New()).
               ShouldMapToUrl("/Funky/New");
       }

       [Test]
       public void

should_be_able_to_generate_url_from_controller_action_with_paramete
r
            ()
        {
            OutBoundUrl.Of<FunkyController>(x => x.Foo(1)).
                ShouldMapToUrl("/Funky/Foo/1");
        }

       [Test]
       public void

should_be_able_to_match_action_with_lambda_and_httpmethod
            ()
        {
            RouteTable.Routes.Clear();
            RouteTable.Routes.MapRoute(
                "zordoRoute",
                "{controller}/{action}/{id}",
                new
                {
                    controller = "Funky",
                    Action = "Zordo",
                    id = "0"
                },
                new
                {
                    httpMethod =
                    new HttpMethodConstraint("POST")
                });
            "~/Funky/Zordo/0".WithMethod(HttpVerbs.Post).
                ShouldMapTo<FunkyController>(x => x.Zordo(0));
        }

       [Test]
       public void

should_not_be_able_to_get_routedata_with_wrong_httpmethod
    ()
{
    RouteTable.Routes.Clear();
    RouteTable.Routes.MapRoute(
        "zordoRoute",
        "{controller}/{action}/{id}",
        new
        {
            controller = "Funky",
            Action = "Zordo",
            id = "0"
        },
        new
        {
            httpMethod =
            new HttpMethodConstraint("POST")
        });
    RouteData routeData =
        "~/Funky/Zordo/0".WithMethod(HttpVerbs.Get);
    Assert.IsNull(routeData);
}

[Test]
public void should_match_guid()
{
    "~/funky/guid/80e70232-e660-40ae-af6b-2b2b8e87ee48"
        .Route().ShouldMapTo<FunkyController>(
        c =>
        c.Guid(
             new Guid(
                 "80e70232-e660-40ae-af6b-2b2b8e87ee48")));
}

[Test]
public void should_match_nullable_int()
{
    "~/funky/nullable/24".Route().ShouldMapTo
        <FunkyController>(c => c.Nullable(24));
}

[Test]
public void should_match_nullable_int_when_null()
{
    RouteTable.Routes.Clear();
    RouteTable.Routes.IgnoreRoute(
        "{resource}.gif/{*pathInfo}");
    RouteTable.Routes.MapRoute(
        "default",
        "{controller}/{action}/{id}",
        new
        {
            controller = "Funky",
            Action = "Index",
            id = (int?)null
        });

    "~/funky/nullable".Route().ShouldMapTo
        <FunkyController>(c => c.Nullable(null));
}

[Test]
public void
should_be_able_to_generate_url_with_nullable_int_action_parameter
            ()
        {
            OutBoundUrl.Of<FunkyController>(
                c => c.Nullable(24)).ShouldMapToUrl(
                "/funky/nullable/24");
        }
    }
}

								
To top