Skip to content

Documentation

Artiom Chilaru edited this page Sep 18, 2018 · 4 revisions

R4MVC Documentation page

1. Introduction

R4MVC is a Roslyn based code generator for ASP.NET MVC Core apps that creates strongly typed helpers that eliminate the use of literal strings when referring the controllers, actions and views. It helps make your MVC code much more maintainable, and gives you intellisense where you normally would not have any. It is a re-implementation of T4MVC for ASP.NET Core projects, which was started by David Ebbo as a little blog post thing, and has since gained many new features based on great community feedback.

R4MVC runs in the dotnet cli or in Visual Studio 2017, and supports ASP.NET Core MVC 1 and 2. It consista of a class library and a code generator tool. For installation instructions please view the Installation page.

The code generated by the tool enables a whole range of scenarios, all relating to replacing unwanted literal strings with a strongly typed alternative. The documentation below gives a complete list of those scenarios.

1.1 It's still just standard MVC under the covers

One key concept to understand about R4MVC is that it is just a thin layer over MVC. e.g. while it provides strong typing, in the end it's just putting values in a dictionary, and using standard MVC to generate the routes.

One thing I often suggest when people have questions is to first figure out how they would do things without R4MVC. If you have no idea how it would work, or think it may not be possible at all, then you are probably expecting too much of R4MVC! Generally, it will not do anything that MVC does not support.

1.2 Usage

Unlike T4MVC, R4MVC isn't based on a T4 template, so triggering the generation code is slightly different, and depends on which version of the tool you decided to use in your project.

If you use the dotnet cli tool, open your prompt, and run the following command. You can use the Package Manager Console (Tools Menu -> NuGet Package Manager -> Package Manager Console)

r4mvc generate -p 'project-path'

If you use the legacy PowerShell tool, open the Package Manager Console (Tools Menu -> NuGet Package Manager -> Package Manager Console), select your ASP.NET Core project in the dropdown, and run the following PowerShell command:

> Generate-R4MVC

2. Scenarios

Instead of a formal documentation, we'll just demonstrate by example the various scenarios where R4MVC helps improve your code. The examples are shown in a before/after format to outline the change that is made.

Note: a lot of the examples given below are based on the Nerd Dinner sample MVC application.

2.1 Using View Name constants

Any time you would use a literal string to refer to a view, R4MVC gives you a strongly typed alternative. e.g. suppose you have this code in a view:

@Html.Partial("DinnerForm")

This ugly "DinnerForm" literal string needs to go! Instead, you can now write:

@Html.Partial(MVC.Dinners.Views.DinnerForm)

When you need to use a view name inside a controller, things get even easier. e.g. you can replace:

return View("InvalidOwner");

by

return View(Views.InvalidOwner);

Note how you can omit the MVC.Dinners prefix, since this is implied by the fact that the code lives in the Dinners controller.

2.1.1 Non-Qualified View Name Constants

Non-qualified view names can be used via the ViewNames property underneath the Views property. Non-qualified views are usefull for allowing overloading and are needed by certain setups, e.g. Spark.

Traditional "Magic String" Non-Qualified view:

return View("Index");

R4MVC Non-Qualified view:

return View(Views.ViewNames.Index);

2.2 Referring to controller actions

Many MVC APIs follow a pattern where you need to pass three pieces of information in order to refer to a controller action:

  1. the controller name (often implied when dealing with the current controller)
  2. the action method name
  3. the parameters to be passed to the action method

All of this is typically done by passing literal strings for #1 and #2, and an anonymous object for #3, e.g.

@Html.ActionLink("Dinner Details", "Details", "Dinners", new { id = Model.DinnerID }, null)

or, if using the new tag helpers

<a asp-action="Details" asp-controller="Dinners" asp-route-id="@Model.DinnerID">Dinner Details</a>

Here, "Details" and "Dinners" are clear literal strings that we'd like to get rid of. But that's not all, as the parameter name 'id' is as much of a problem as those other two. Even though it doesn't look like a literal string, it very much is one in disguise. Don't let those anonymous objects fool you!

With R4MVC, you would instead write:

@Html.ActionLink("Dinner Details", MVC.Dinners.Details(Model.DinnerID))

or, if using the new tag helpers

<a mvc-action="MVC.Dinners.Details(Model.DinnerID)">Dinner Details</a>

Basically, we got rid of the three unwanted literal strings ("Details", "Dinners" and "Id"), and replaced them by a very natural looking method call to the controller action. Of course, this is not really calling the controller action, which would be very wrong here. But it's capturing the essence of method call, and turning it into the right route values.

One other great benefit of this approach is that you get full intellisense while you type this line. In fact, you even get full refactoring support if you use a tool like resharper which supports aspx refactoring!

Adding additional route parameters

In some situation, you may need to add extra values to the route that don't exist in the action method. You can do it as follows:

@Html.ActionLink("Dinner Details", MVC.Dinners.Details(Model.DinnerID)
    .AddRouteValue("foo", 17))

You can add multiple values this way using a fluent approach, e.g.

Html.ActionLink("Dinner Details", MVC.Dinners.Details(Model.DinnerID)
    .AddRouteValue("foo", 17)
    .AddRouteValue("bar", "abc"))

Doing this with tag helpers becomes even easier:

<a mvc-action="MVC.Dinners.Details(Model.DinnerID)" asp-route-foo="17" asp-route-bar="abc">Dinner Details</a>

As an alternative for adding multiple values, you can write:

RedirectToAction(MVC.Home.MyAction().AddRouteValues(Request.QueryString));

Note that in some cases, you may want to omit the action method parameters from the route, possibly because they are meant to come from form data and not from the URL. For those cases, you can always use the parameter-less override that R4MVC generates (that part is not new), e.g.

Html.ActionLink("Dinner Details", MVC.Dinners.Details()
    .AddRouteValues(new { foo = 17, bar = "abc"}))

Now suppose you want to add all the current query string values to the route values produced by R4MVC. You can use the AddRouteValues() overload that takes a NameValueCollection. e.g.

Html.ActionLink("Dinner Details", MVC.Dinners.Details()
    .AddRouteValues(new { foo = 17, bar = "abc"}))

This adds to the existing set of AddRouteValue/AddRouteValues fluent APIs to make it easier to deal with all kind different situations.

The nice thing about this fluent approach is that it doesn't require adding overloads to all the R4MVC helpers (e.g. Html.ActionLink is this case) to take in the extra route values. Instead, it automatically applies to any method that uses the R4MVC pattern.

The general pattern of making a pseudo-call to a controller action is used is many places in R4MVC:

2.2.1 Html.ActionLink

Ok, we just covered that one above.

2.2.2 Url.Action

@Url.Action(MVC.Dinners.Details(Model.DinnerID))

2.2.2a Url.JavaScriptReplacableUrl (not yet migrated from T4MVC)

2.2.3 Ajax.ActionLink

There are currently no ajax helpers present in ASP.NET Core MVC. Creating ajax links requires creating regular anchors and adding data-ajax- atributes manually. e.g.

@Ajax.ActionLink("RSVP for this event",
                 "Register", "RSVP",
                 new { id=Model.DinnerID },
                 new AjaxOptions { UpdateTargetId="rsvpmsg", OnSuccess="AnimateRSVPMessage" }) %>

becomes

<a mvc-action="MVC.RSVP.Register(Model.DinnerID)"
   data-ajax="true"
   data-ajax-target="rsvpmsg"
   data-ajax-success="AnimateRSVPMessage">
    RSVP for this event
</a>

2.2.4 RedirectToAction (in controller code)

return RedirectToAction("Details", new { id = dinner.DinnerID });

becomes

return RedirectToAction(MVC.Dinners.Details(dinner.DinnerID));

2.2.5 routes.MapRoute (typically in global.asax) (not yet migrated/considered)

2.2.6 Html.BeginForm

Essentially, it's the same support as ActionLink() but for the BeginForm() method. But note that because form posts typically pass most of the data via the form and not the URL, BeginForm() is trickier to use correctly than the other methods.

Here is how you might use this:

using (Html.BeginForm(MVC.Account.LogOn(), FormMethod.Post)) { ... }

or when using tag helpers

<form mvc-action="MVC.Account.LogOn()" method="post"> ... </form>

Or if the action method takes parameters, you would pass them in the call. However, when you do this you need to make sure that you aren't also trying to pass those same parameters via the form (e.g. using a text box).

Generally, the rule of thumb is that this support works well when you're dealing with forms where the Action method signature exactly matches the form URL.

2.2.7 Html.RenderAction and Html.Action (only in MVC 2 or newer) (review and deprecate. block will be replaced by view components)

2.3. Use constants to refer to area, controller, action and parameter names

2.3.1 Area name constants

MVC.CoolArea.Name

2.3.2 Controller name constants

MVC.Home.Name

2.3.3 Action name constants

MVC.Home.ActionNames.Index

2.3.4 Parameter name constants

Assuming a Home.MyAction action that takes a foo parameter, this evaluates to "foo":

MVC.Home.MyActionParams.foo

2.4. Strongly typed links to script files and static resources

R4MVC generates static helpers for your content files and script files. So instead of writing:

<img src="/Content/nerd.jpg" />

You can write (note the mvc-src attribute):

<img mvc-src="@Links.Content.nerd_jpg" />

Likewise, instead of

<script src="/Scripts/Map.js" type="text/javascript"></script>

You can write (again, note the mvc-src attribute)

<script mvc-src="@Links.Scripts.Map_js" type="text/javascript"></script>

The obvious benefit is that you'll get a compile error if you ever move or rename your static resource, so you'll catch it earlier.

Another benefit is that you get a more versatile reference. When you write src="/Content/nerd.jpg", your app will only work when it's deployed at the root of the site. But when you use the helper, it executes some server side logic that makes sure your reference is correct wherever your site is rooted. It does this by calling VirtualPathUtility.ToAbsolute("~/Content/nerd.jpg").

Support for Minified Javascript files (not implemented, should it be?)

2.5 Using R4MVC with "Areas"

One of MVC 2's major new features is the support for breaking up a large application into "Areas". This works by following a structure that looks like:

  • Root folder
    • Areas
      • NerdDinner
        • Controllers
          • WikiController
        • Models
        • Views
          • Wiki
            • Index.cshtml

R4MVC automatically works with your areas and makes them available in its object model. Here is an example:

Before:

@Html.ActionLink("Wiki", "Index", "Wiki", new { area = "NerdDinner" }, null)

After:

@Html.ActionLink("Wiki", MVC.NerdDinner.Wiki.Index())

Or with tag helpers, before:

<a asp-action="Index" asp-controller="Wiki" asp-area="NerdDinner">Wiki</a>

After:

<a mvc-action="MVC.NerdDinner.Wiki.Index()">Wiki</a>

Notice how we refered to the controller as MVC.NerdDinner.Wiki. Note that if you happen to have a top level controller with the same name as the area, this naming pattern would cause a conflict. In that case, R4MVC appends 'Area' at the end of the area name to avoid the conflict. e.g. If you have both a Home area and a Home controller (top level), you would use MVC.HomeArea.Hello to refer to the area.

Optionally, if you set IncludeAreasToken to true in the settings.xml file, the naming scheme becomes:

@Html.ActionLink("Wiki", MVC.Areas.NerdDinner.Wiki.Index())

Note that in this case, the conflict situation discussed above cannot occur. But this comes with a price as you end up with one more segment all the time. I'm debating whether to even keep this mode given that the default mode works quite well.

2.5.1 Alternative "Feature Folder" project structure (not implemented yet)

3. Tweaking the behavior of R4MVC

When you download R4MVC, you not only get the main R4MVC tools, but you also get a file named r4mvc.json that will be created with some defaults the first time R4MVC gets to run. This file contains various knobs that you can use to tweak the code generated by R4MVC. This section describes the various switches:

3.1 Using Model 'unbinders' (to be implemented)

3.2 Json Settings File

3.3 Miscellaneous tweaks

Force include/exclude some controllers

By default, R4MVC uses the Roslyn compiler to determine which controller it needs to process. This can be overridden using the[R4MVCExclude] attribute.

Set SplitIntoMultipleFiles to false to generate a single file with everything

// If true, the template output will be split into multiple files.
"SplitIntoMultipleFiles": true

Use HelpersPrefix to change the MVC prefix of the generated classes

// The prefix used for things like MVC.Dinners.Name and MVC.Dinners.Details(Model.DinnerID)
"HelpersPrefix": "MVC"

Use ViewsRootFolder to rename the Views folder

// The folder under the project that contains the views
"ViewsRootFolder": "Views"

Use LinksNamespace the generated links' namespace

// The namespace that the links are generated in (e.g. "Links", as in Links.Content.nerd_jpg)
"LinksNamespace": "Links"

Use StaticFilesFolders to tweak the list of folders from which links are generated

// Folders containing static files for which links are generated (e.g. Links.Scripts.Map_js)
"StaticFilesFolders": [ "wwwroot" ]

Use GenerateParamsAsConstantsForActionMethods to generate constants instead of static classes for parameter names. This lets you use them in attributes.

// When true the parameter name class will be generated as constants (allowing use in attributes).  This is the suggested way to do this.  However, if you used R4MVC Params before and referenced them outside the controller, your references will need to change from MVC.User.MyActionParams   to MVC.UserController.MyActionParams.
"GenerateParamsAsConstantsForActionMethods": true

Customize Action values

The generated R4MVC_ControllerClass classes are now partial classes so you can extend and customize the values returned from MVC.Controller.Action() calls. In this class, for each overridden method a partial method call is available, if you implement that partial you have the ability to modify the the action values before they are returned.

partial R4MVC_SomeController
{
    partial void SomeAction(R4Mvc_Microsoft_AspNetCore_Mvc_ActionResult callInfo)
    {
        callInfo.RouteValues["other"] = 1;
    }
}

Also, the parameterless methods are not virtual so they can be overridden in the partial as well.

partial R4MVC_SomeController
{
    public override ActionResult SomeAction()
    {
        var callInfo = base.SomeAction();
        callInfo.GetRouteValueDictionary()["other"] = 1;
        return callInfo;
    }
}

4. Related resources

David Ebbo has written a series of blog posts on T4MVC Scott Hanselman also blogged about it here

Clone this wiki locally
You can’t perform that action at this time.