Keeping .NET app settings secrets out of source control

Keeping .NET app settings secrets out of source control

Whether your app is a desktop client app or a website, it’s important to keep passwords, connection strings, and API keys out of source control. In .NET, these settings are stored in a app.config or web.config file depending on the type of app you are building and those files would be checked into source control. I’ve done this in the past for many projects. Of course, I never committed production secrets, but it was still a big no-no, but I just never had the time to investigate the proper way to handle this situation…until today.

A quick Google search returned this result: http://www.asp.net/identity/overview/features-api/best-practices-for-deploying-passwords-and-other-sensitive-data-to-aspnet-and-azure. Config files have a simple way of handling this by adding a file attribute to the appSettings section of the web.config file, like so

<appSettings file="secrets.config">
  <add key="testSetting" value="not a secret" />
</appSettings/>

And the secrets.config file looks like this

<appSettings>
  <add key="secretTestSetting" value="very secret" />
  <add key="testSetting" value="I will overwrite" />
</appSettings>

This is the entire file, it is important that the root element is <appSettings> , otherwise you will get a compile or runtime error. Any new keys defined will be added and any existing keys will overwrite the value from the web.config.

Overwriting values in the web.config is very useful for local development. Since the secrets.config file is never checked in, each developer can keep their own local app settings values without worrying about mistakenly checking them in and overriding the web.config default values. No more commenting out values in your web.config!

The same thing works for the connectionStrings section, except in this case the entire section is overwritten. In fact, the web.config can’t have any elements under it or you will get errors. The web.config will look like this

<connectionStrings configSource="connectionStrings.config">
</connectionStrings>

And the connectionStrings.config will look like this

<connectionStrings>
  <clear/>
  <add name="Database" connectionString="very secret" />
</connectionStrings>

As long as your extra files have the .config extension, IIS will never serve them. Also, you should never add these secrets config files to your project to avoid mistakenly deploying them to your servers. Instead, your release process should deploy secrets files separately from code files.

And finally, always add secrets.config and connectionStrings.config to your .gitignore and to get first-time developers setup with secrets, you could include these files somewhere outside of source control, like a file share.

That’s all! Super easy way to keep all your secrets safe in both desktop and web applications. Find the code here: https://github.com/svarcoe/AppSecretsDemoProject

How I met Alexa

On my second week at Interknowlogy they gave me a great task 🙂 work around with Amazon’s Echo, which is a voice command device from Amazon.com that can answer you questions, play music and control smart devices. This device responds to the name “Alexa” (you can configure it to respond to Amazon too). So my main job was to try to find out how Alexa works.

Alexa’s knowledge is given from her skills which you can add at the developer site of amazon . To access this site, you have to register and go to the  Apps&Services section and get started in the Alexa Skill Kit.

 

Capture

Each skill has a Name, Invocation Name (how users will interact with the service), Version and an Endpoint. The last option is the URL service that will provide all the info of your skill to Alexa, you can choose between two options for your endpoint, you can decide between Lambda ARN (Amazon Resouce Name), that has to be developed with Java or Node.js, or your own service, the only thing that amazon requires its that it has to be a HTTPS server.

So I am going to explain how to do an Alexa skill in C# using your own server.

The technologies I used are:

  • ASP.NET Web API
  • Azure Websites

So, to create your Web API you follow the next steps:

Create new project -> ASP.NET Web Application

11-e1438811615167

Choose the Web API option ( from ASP.NET 4.5.2 Templates) and connect it to an Azure Web App.

2

And then you have to Configure all your Azure Website stuff with your credentials to be ready to code 🙂

 

2015-08-12 (1)

So what we want to do is to create a Controller that will have a post method and this method will receive all the requests from Alexa.


[Route( "alexa/hello" )]
[HttpPost]
public async Task<HttpResponseMessage> SampleSession()
{
   var speechlet = new GetMatchesSpeechlet();
   return await speechlet.GetResponse( Request );
}

To understand all these requests I used AlexaSkillsKit that is a NuGet package made by FreeBusy which you can find in this link, it’s pretty great, it helps you understand how Alexa works. Once you install that package you want to create a class that will derive from Speechlet (FreeBusy’s class) and override the following methods 🙂

  • OnSessionStarted

private static Logger Log = NLog.LogManager.GetCurrentClassLogger();

public override void OnSessionStarted(SessionStartedRequest request, Session session)
{
    Log.Info("OnSessionStarted requestId={0}, sessionId={1}", request.RequestId, session.SessionId);
}
  • OnLaunch

This method is called at the start of the application.


public override SpeechletResponse OnLaunch(LaunchRequest request, Session session)

{
    Log.Info("OnLaunch requestId={0}, sessionId={1}", request.RequestId, session.SessionId);
    return GetWelcomeResponse();
 }

 private SpeechletResponse GetWelcomeResponse()
 {
    string speechOutput =
       "Welcome to the Interknowlogy's Yellowjackets app, How may I help you?";
    return BuildSpeechletResponse("Welcome", speechOutput, false);
 }
  • OnIntent

This method will identify the intent that Alexa needs, so the API will receive an Intent with a name and it will be compared to do some action.


private const string NAME_SLOT = "name";

public override async Task<SpeechletResponse> OnIntent(IntentRequest request, Session session)
{
   Log.Info("OnIntent requestId={0}, sessionId={1}", request.RequestId, session.SessionId);

   Intent intent = request.Intent;
   string intentName = (intent != null) ? intent.Name : null;

   if ("LastScore".Equals(intentName))
   {

      //method that does some magic in my backend
      return await GetLastMatchResponse();

   }

   if ("GetPlayerLastScore".Equals(intentName))
   {

      Dictionary<string, Slot> slots = intent.Slots;
      Slot nameSlot = slots[NAME_SLOT];
      //method that does some magic in my backend
      return await GetLastMatchPlayerResponse(nameSlot.Value);

   }

   else
   {
      return BuildSpeechletResponse("Hey", "What can I help you with?", false);
   }

   throw new SpeechletException("Invalid Intent");
}

Intents names are preset at amazon’s developer site with a json, and the example is the following:

Capture

In this page you will preset an Intent Schema which is in a Json format:


{
 "intents": [
 {
 "intent": "LastScore",
 "slots": []
 },
 {
 "intent": "GetPlayerLastScore",
 "slots": [
{
 "name": "name",
 "type": "LITERAL"
 }]
}

This json format text describes that there will be two available intents, one named “LastScore” and the other one “GetPlayerLastScore”, and the last one recieves a “field” that is text.

Now the question is… How do I define the sentences and the parameters that the user of my echo will tell to alexa? In the same page of the developer site is a field that let you provide examples of the usage of Alexa. Which are the following:

 

1

Taking the last samples the interaction with alexa will be the following:

Alexa, ask Yellowjackets..

Welcome to the Interknowlogy’s Yellowjackets app, How may I help you?

What was the last ping pong score for Kevin?

…..

 

Build Responses

To Build responses (in custom methods) you will need to build a Speechlet response that contains a speech response and also can contain a card (optional), that is a graphic representation of a response of alexa that is displayed in the Echo App which is available online and in the following mobile OS:

  • Fire OS 2.0 or higher
  • Android 4.0 or higher
  • iOS 7.0 or higher

The only available cards for devs (right know) are the Simple cards, that only contain plain text. Cards with pictures are not open for non-amazon devs.

gscompanionappcard

 

To create a Speechlet response we will be using the cool Freebusy’s Nuget package class SpeechletResponse like this:


 private SpeechletResponse BuildSpeechletResponse(string title, string output, bool shouldEndSession)
 {
    // Create the Simple card content.
    SimpleCard card = new SimpleCard();
    card.Title = String.Format("YellowJackets - {0}", title);
    card.Subtitle = String.Format("YellowJackets - Sub Title");
    card.Content = String.Format("YellowJackets - {0}", output);

    PlainTextOutputSpeech speech = new PlainTextOutputSpeech();
    speech.Text = output;

    // Create the speechlet response.
    SpeechletResponse response = new SpeechletResponse();
    response.ShouldEndSession = shouldEndSession;
    response.OutputSpeech = speech;
    response.Card = card;
    return response;
 }

The next steps will be to complete the skill configuration at amazon’s developer site.  Which will be the SSL certificate, that will be pointing to my domain, because our WEB Api is a wildcard certificate from Azure.

Also you need to enable test mode to check out your service and Alexa’s interaction in your Amazon Echo.

So, there it is, hope this blog helped you in your adventure with Alexa (Amazon Echo) 🙂

ASP.NET MVC5 + Twitter Bootstrap

Creating an ASP.NET MVC 5 Project

Model-View-Controller (MVC) is a software architectural pattern that divides software application into three parts.
Model consists of the business logic and functions.
View is the representation of the information. These information can be illustrated through graphs or charts or any of the user interface that the user interacts with.
Controller receives input from view and model.

1. Visual Studio -> New -> Project -> Under Web -> ASP.NET Web Application
2. Choose MVC template — this will create a default template with MVC folders.
Another template is Web API which will add API folder for REST HTTP calls.
ChoosingWebApi
3. Run the app to make sure it’s compiling and building properly.

Twitter Bootstrap 3 Discovery

What is Twitter Bootstrap? Twitter Bootstrap is an open-source tool that helps in creating websites or web applications. This tool includes pre-developed HTML and CSS templates for arranging html components (Grid-System), forms, buttons, charts, and other navigation components that we normally see in a responsive web application.

Some PROS of Twitter Bootstrap:

  • CONSISTENCY
    Who would not want to see a consistent font and style within a web page? Both developers and users would want to see a consistent flow in a project, so Twitter Bootstrap was built to avoid a confusing layout flow in every web page that users will interact.
  • RESPONSIVE
    Whether it is a huge desktop, a really small mobile screen or a phone with a size like a tablet, Twitter Bootstrap got it covered! This tool is developed to have a responsive layout that caters to all the different screen sizes that any user may have.
  • LATEST AND GREATEST
    Twitter Bootstrap was made by really great developers who used the latest libraries out there: CSS3, HTML5, JQuery… etc. The CSS was developed using LESS platform that allows using variables, mixins, functions inside the CSS page to make CSS more themable and extendable.

By default, MVC5 is using the Twitter Bootstrap template by default. That means, the bootstrap.min.js and bootstrap.min.css are already inside the project and we do not need to download it from Twitter Bootstrap’s webpage to use it’s styles.

Grid System

  • Twitter Bootstrap makes the page responsive to phone/tablet/desktop by adding the viewport meta tag:
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
  • Twitter Bootstrap’s grid system scales up to 12 columns.
    This means that all the columns with .col-md-* should add up 12. Any columns after the 12th will be stacked underneath the first 12.
  • Rows are placed inside a class .container for proper alignment.
    <div class="container">...</div>
    
  • Use class .row to create horizontal column groups.
  • Columns are created specifying the number that can add up to twelve. For example, four proportionally sized columns would be: four div with class .col-md-3
    XS = Phones, SM = Tablets, MD = Desktops, LG = Large Desktops
  • GridSystemTable

NOTE: As the browser widens, the smaller-sized classes gets overwritten. As the browser contracts, the larger classes are overwritten.

<div class="row">
    <div class="col-xs-6 col-md-6 col-sm-6">col-xs-6 col-md-6 col-sm-6</div>
    <div class="col-xs-6 col-md-6 col-sm-6">col-xs-6 col-md-6 col-sm-6</div>
</div>
<div class="row">
    <div class="col-xs-4 row-grid">col-xs-4</div>
    <div class="col-xs-4 row-grid">col-xs-4</div>
    <div class="col-xs-4 row-grid">col-xs-4</div>
</div>
<div class="row">
    <div class="col-md-3">col-md-3</div>
    <div class="col-md-3">col-md-3</div>
    <div class="col-md-3">col-md-3</div>
    <div class="col-md-3">col-md-3</div>
</div>
<div class="row">
    <div class="col-xs-8 col-md-8 col-sm-8">col-xs-8 col-md-8 col-sm-8</div>
    <div class="col-xs-4 col-md-4 col-sm-4">col-xs-4 col-md-4 col-sm-4</div>
</div>

Other Good Stuff…

Offsetting columns

To move columns to the right, use “.col-*-offset-*”. The first * depends on the screen size which could be written as “.col-xs-offset-, .col-md-offset-, or .col-sm-offset-“. The second * increases the left margin of column by * columns. For example, “.col-md-offset-4” moves that column over four columns.
offset
As we can see on this example, we could combine the class names “.col-md-4” and “.col-md-offset-3” which defines the width of the column then offsets the column to 3 columns (-offset-3).

Like the grid system example, the offsets follow the 12-column way of how the columns are arranged. An example would be doing “.col-md-4” followed by a div with “.col-md-4 .col-md-offset-4”. Where will the second div be?
nested-offset
So, since the position of the second div (without the -offset-4) is beside the first div, the offset made it so it moved another 4 columns over.

<div class="row">
    <div class="col-md-4">.col-md-4</div>
    <div class="col-md-4 col-md-offset-4">.col-md-4 .col-md-offset-4</div>
</div>
<div class="row">
    <div class="col-md-3 col-md-offset-3">.col-md-3 .col-md-offset-3</div>
    <div class="col-md-3">.col-md-3</div>
</div>

Nesting columns

Inside the .row, we can add a set of .col-md-* inside of an exisiting .col-md-*. To visualize it properly…
nested-row

<div class="row">
    <div class="col-md-9">
        1st row .col-md-9 (Parent Row)
        <div class="row">
            <div class="col-md-6">
                Nested 2nd row .col-md-6
            </div>
            <div class="col-md-6">
                Nested 2nd row .col-md-6
            </div>
        </div>
    </div>
</div>

For this example, the parent row (with a class .col-md-9) has a 9 column-width. Since the next row is nested (with 2 .col-md-6) that add up to 12-columns, their TOTAL WIDTH will only extend up to 9 columns (not 12), but they will be evenly divided because both of them add up to 12 columns.
What I mean by that is…
NestedAndNonNestedRows

<div class="row">
    <div class="col-md-9">
        1st row: .col-md-9
        <div class="row">
            <div class="col-md-6">
                2nd row (Nested): .col-md-6
            </div>
            <div class="col-md-6">
                2nd row (Nested): .col-md-6
            </div>
        </div>
    </div>
</div>
<div class="row">
    <div class="col-md-6">
        (Not Nested) .col-md-6
    </div>
    <div class="col-md-6">
        (Not Nested) .col-md-6
    </div>
</div>

IMPORTANT: Nested rows (even when the column-set adds up to 12 columns) follow the max width of the parent row unlike a non-nested row that can span out to 12-column width.

Push and Pull Columns (Column Ordering)

We use the modifiers .col-md-push-* and .col-md-pull-* to change the order of the grid columns.

<div class="row">
    <div class="col-md-9 col-md-push-3">1st column (col-md-push-3)</div>
    <div class="col-md-3 col-md-pull-9">2nd column (col-md-pull-9)</div>
</div>

This code results to:
pushpullTB

For this example, we can see that we pushed the 1st column to 3 columns to the right and pulled the 2nd column 9 columns to the left. Since the columns are also 9 and 3 column-width respectively, they just switched places.
IMPORTANT: In other words, we use -push- and -pull- to re-organize our column positions. Unlike offsets, when we “push” a column, it does not affect the position of any other column beside it. For example, if there are 2 columns, and only the first column has a “-push-*” class, the 2nd column will stay in the same place and will not be pushed to the right. Offset, on the other hand, pushes everything to the right of the column that has the -offset-* class.

References:

Twitter Bootstrap 2.3.2
Twitter Bootstrap 3

The Real-Time Web With SignalR

Today for RECESS I looked into SignalR.  The SignalR site (which currently is just the github repository) describes it as an “async signaling library for ASP.NET to help build real-time, multi-user interactive web applications.”  What does this mean to you and me?  This library allows you, in just a few lines of code, communicate in real-time amongst browsers that are hitting your site.

Scott Hanselman does a good job of summarizing what SignalR does and how we got here, so I won’t repeat it here.

I followed the super quick sample – a browser based chat application as described Scott’s post.  I ran out of time before I could get the lower level connection working, but the higher level “Hub” based connection works great.

It’s super cool to see it working – I have 3 different browsers (IE, Chrome, Firefox) in the chat together.  When I enter a chat message in one, the other two receive the message instantaneously.

    $("#broadcast").click(function () {
      // send() is the method on the ASP.NET Hub class running on the server
      chat.send($('#msg').val());
    });

The use of dynamic objects in the ASP.NET implementation of the Hub allows me to call any method on the Hub, as long as that function exists in the client side JavaScript.

    public class Chat : Hub
    {
        public void Send( string message )
        {
            // addMessage is a client side javascript function
            Clients.addMessage(message);  
        }
    }

Here’s a couple shots of IE Developer Tools (F12) when running the chat client.

Waiting for a response on the first call to signalr/connect:

CropperCapture81

Got the response from the first connect (a message was broadcast to all the clients), now turn right around and connect to wait for the next “reply” from the server:

CropperCapture82

 


 

ASP.NET Universal Providers

Earlier this month, I posted about how the ASP.NET membership providers are creating the required database schema for me automagically when I first hit the site.  Here is a quick update to that statement now that I more thoroughly understand what’s going on.

Scott Hanselman does a great job introducing us to the ASP.NET Universal Providers for Session, Membership, Roles and User Profile so I won’t repeat it here.  What I DON’T get from his article is that the Universal Providers are the default for a new ASP.NET MVC3 project (and that they’re not yet supported on Azure).  I hadn’t touched ASP.NET or MVC for multiple years, so I just went along quietly and created a new project, pointed my connection string at a SQL Server and things were all working.  It wasn’t until I published the site to Azure that things fell apart.

The Universal Providers (“DefaultMembershipProvider”) are referenced throughout the web.config for all the different pieces of membership, and here you see it set as the default provider (the one that membership code in the site will look for and use).

  <membership defaultProvider="DefaultMembershipProvider">
    <providers>
      <clear />
      <add name="AspNetSqlMembershipProvider"
            type="System.Web.Security.SqlMembershipProvider"
            connectionStringName="ApplicationServices"
            enablePasswordRetrieval="false"
            enablePasswordReset="true"
            requiresQuestionAndAnswer="false"
            requiresUniqueEmail="false"
            maxInvalidPasswordAttempts="5"
            minRequiredPasswordLength="6"
            minRequiredNonalphanumericCharacters="0"
            passwordAttemptWindow="10"
            applicationName="/" />
      <add name="DefaultMembershipProvider"
            type="System.Web.Providers.DefaultMembershipProvider, 
                   System.Web.Providers, Version=1.0.0.0, Culture=neutral,
                   PublicKeyToken=31bf3856ad364e35"
            connectionStringName="DefaultConnection"
            enablePasswordRetrieval="false"
            enablePasswordReset="true"
            requiresQuestionAndAnswer="false"
            requiresUniqueEmail="false"
            maxInvalidPasswordAttempts="5"
            minRequiredPasswordLength="6"
            minRequiredNonalphanumericCharacters="0"
            passwordAttemptWindow="10"
            applicationName="/" />
    </providers>
  </membership>

This works fine as long as we were developing on our local machines (where the Universal Providers are installed), and even when we’re hitting the SQL Azure database from our local machine (the provider is local, the database is remote – so the code can create the SQL Azure compatible schema on the fly regardless of the DB location).  When the site is deployed to Azure, where the Universal Providers are not installed, you get an error: Unable to find the requested .Net Framework Data Provider. It may not be installed.

It took forever to figure out that this was the problem, and 1 minute to fix it – just switch web.config to use the SQL Providers that are already configured, just not as the default.

  <membership defaultProvider="AspNetSqlMembershipProvider">

 

The bottom line: the Universal Providers are not yet available on Azure servers, so you have to go with the legacy SQL Providers.  These providers, as always, require you to run the ASPNET_REGSQL tool to create the required database schema before you hit the site.

  • Universal Providers: create their required DB schema on first use, do not have the aspnet_xxx prefix on the tables, and do not use any views or stored procedures.
  • SQL Providers: require you to run ASPNET_REGSQL before first use, the tables are namespaced with “aspnet_” in front, and there are views and stored procedures that go along with the tables (all created by the ASPNET_REGSQL tool)

 
 
 
 

ASP.NET Membership Schema Created Automatically

UPDATE: A few weeks after posting this, I learned more about what’s going on with the membership providers. See the update here.

It’s been a few years since I’ve done any ASP.NET work. Recently, I fired up Visual Studio 2010 and created an ASP.NET MVC project that uses membership.  I remembered that I needed the table schema to support the membership functionality, which you can create by running aspnet_regsql.exe.  This tool shows a wizard that allows you to add or remove the database objects for membership, profiles, role management, and personalization.

aspnet_regsql

Here is the contents of the MembershipTest database after running the tool.  Notice the tables are prefixed with “aspnet_” and there are views and stored procedures to support the functionality.

dbschema-tool

Now we just edit web.config to set where the membership database is located (MembershipTest).

  <connectionStrings>
    <add name="ApplicationServices" connectionString="data source=localhost;Initial Catalog=MembershipTest;
        Integrated Security=SSPI;MultipleActiveResultSets=True"
      providerName="System.Data.SqlClient" />
    <add name="DefaultConnection" connectionString="data source=localhost;Initial Catalog=MembershipTest;
        Integrated Security=SSPI;MultipleActiveResultSets=True"
      providerName="System.Data.SqlClient" />
  </connectionStrings>

We haven’t written any code – we just have the MVC project that was created from the VS project template.  Run it, and register a new user, thus exercising the membership logic to create a new user in the database.  Check out the database schema. There are a handful of new tables listed, those WITHOUT the “aspnet_” prefix.

dbschema-run

When we look in the aspnet_users table (which was created by the aspnet_regsql tool), our user is not there.  Look in the Users table, and it IS there.  What’s going on here?  From what I can tell, the objects created by the aspnet_regsql tool ARE NOT USED by the latest SqlMembershipProvider and DefaultMembershipProvider.   So who is creating the objects required (those without the “aspnet_” prefix)?

So far, I haven’t found any documentation on this, but Reflector is our friend.  Looking through the provider assemblies, I find the code that is creating the database and schema at run time!

In the MVC project AccountController, we call Membership.CreateUser in the Register method.  Let’s find that method using Reflector.  Look up DefaultMembershipProvider.CreateUser, which calls the private Membership_CreateUser.  At the very top of that method, it calls ModelHelper.CreateMembershipEntities( ).

Method1

Follow that call chain down, and eventually you get to see the first part of the schema creation – the actual database.  After that, it goes through a whole bunch of code to generate the objects themselves.

Method2

So just to prove it to myself, I deleted the MembershipTest database, and ran my project again (same connection string in config, pointing to the non-existent MembershipTest database).  Run the project, create a user.  Sure enough, the database is created, the required objects are there (and we never ran the aspnet_regsql tool).  It seems that the newest providers don’t need any of the old Views or Stored Procedures either. None are created.

dbschema-clean

UPDATE: A few weeks after posting this, I learned more about what’s going on with the membership providers. See the update here.

The only thing I can come up with is that the ASPNET_REGSQL tool is obsolete.  Maybe with the advent of the Entity Framework Code-First technology, Microsoft took the attitude that they’ll create the DB objects in code if they’re not already there.

Note: it turns out that you don’t even have to run the web site project and register a new user.  You can use the ASP.NET Configuration tool (Project menu in VS) and create a user there.  It uses the same provider configuration, so that will also create the required database schema.

Even better – I ran this same test against SQL Azure, and it works fine as well.  Creates the database and objects, and membership works just fine (UPDATE: …but running the site IN Azure blows up, since the provider is not installed there yet. See follow-up post.)

 
 

Dynamic Actions in MVC 3

In MVC 3 you get a lot of stuff for free. Convention determines a lot of the basic stuff. This is very awesome and helps alleviate some of the more mundane and boring work involved with setting up an ASP.NET site. However, with magic comes limitations. In my particular case I need to have an action with an arbitrary number of parameters without the need to have an overload in my controller for each parameter. Lets look at what I’m talking about.

My example is a bit of a stretch, but should help explain what I’m doing. If I have a controller named ContactsController with an action named Find which takes one parameter which is a name, then I could call http://site/Contacts/Find/Jon and this would return the contact with the name Jon. However, if I want an action named CreateGroup which will take in a list of names which will then create a group including all of the people whose names were passed in, I’d need to do http://site/Contacts/CreateGroup/Jon/Holly/Drew/Brian/. What I will be addressing in this blog is how to do this without the need for an overload of CreateGroup for 2, 3, 4, etc. names as parameters.

In the Global.asax file we will need to map the correct routes. If we use the MVC 3 Internet Template in VS2010 then we’ll have the following default route:

routes.MapRoute(
	"Default", // Route name
	"{controller}/{action}/{id}", // URL with parameters
	new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);

This default route handles the Find action just fine, however it will not work with CreateGroup. We need a way to specify an unlimited number of parameters. In order to accomplish this we need the following route:

//NOTE: This prevents creating routes after this route that have a static number of parameters
routes.MapRoute(
	"DynamicActionRoute",
	"{controller}/{action}/{*rawParams}"
);

What we did here is say that for any given Controller and any given Action use the no parameter Action regardless of how many parameters there are. So in our controller we will now use:

public class ContactsController : Controller
{
	public ActionResult Find(string id)
	{
		return View();
	}

    public ActionResult CreateGroup(string rawParams)
	{
		var names = rawParams.Split('/');
		//TODO: process names
		return View();
	}
}

As long as the name of the parameter in the route matches the name of the parameter in the Action we will receive our string of names. After we parse it out we’ll be good to go!

Some Words of Caution

When doing this you cannot have an overloaded Action named CreateGroup. If CreateGroup is overloaded you will get the following error of ambiguity:

The current request for action ‘CreateGroup’ on controller type ‘ContactsController’ is ambiguous between the following action methods:
System.Web.Mvc.ActionResult CreateGroup(System.String) on type Mvc3RazorPoC.Controllers.ContactsController
System.Web.Mvc.ActionResult CreateGroup() on type Mvc3RazorPoC.Controllers.ContactsController

This makes sense in our situation, because we always want to handle all parameters in this one Action.

Also, this should be the last route in the Global.asax file. It will prevent overloaded routes from being fired correctly. If you have an Action that takes in 2 parameters and the route is placed after our dynamic route the dynamic route will kick in and the parameters will not be filled correctly even though the Action will be called correctly. If the 2 parameter routed is above the dynamic routed all will be well and work as expected.