AttributeRouting icon indicating copy to clipboard operation
AttributeRouting copied to clipboard

Support for versioning (#91)

Open gregmac opened this issue 12 years ago • 3 comments

Works as described in issue #91.

Note for viewing the diff. 7b9396a and 48c9341 are my upstream merges, they do not have any actual effect.

I cannot figure out why git thinks RouteReflector is entirely changed, the actual diff is:


--- RouteReflector.cs.orig  Mon Jul 23 16:34:22 2012
+++ RouteReflector.cs   Mon Jul 23 16:22:38 2012
@@ -46,6 +46,7 @@
                     let convention = controllerType.GetCustomAttribute<RouteConventionAttributeBase>(false)
                     let routeAreaAttribute = controllerType.GetCustomAttribute<RouteAreaAttribute>(true)
                     let routePrefixAttribute = controllerType.GetCustomAttribute<RoutePrefixAttribute>(true)
+                    let routeVersionedAttribute = controllerType.GetCustomAttribute<RouteVersionedAttribute>(true)
                     from actionMethod in controllerType.GetActionMethods(inheritActionsFromBaseController)
                     from routeAttribute in GetRouteAttributes(actionMethod, convention)
                     // precedence is within a controller
@@ -73,7 +74,10 @@
                         IsAbsoluteUrl = routeAttribute.IsAbsoluteUrl,
                         UseLowercaseRoute = routeAttribute.UseLowercaseRouteFlag,
                         PreserveCaseForUrlParameters = routeAttribute.PreserveCaseForUrlParametersFlag,
-                        AppendTrailingSlash = routeAttribute.AppendTrailingSlashFlag
+                        AppendTrailingSlash = routeAttribute.AppendTrailingSlashFlag,
+                        IsVersioned = routeVersionedAttribute != null && routeVersionedAttribute.IsVersioned,
+                        MinVersion = routeAttribute.MinVersion ?? (routeVersionedAttribute != null ? routeVersionedAttribute.MinVersion : null),
+                        MaxVersion = routeAttribute.MaxVersion ?? (routeVersionedAttribute != null ? routeVersionedAttribute.MaxVersion : null)
                     }).ToList();
         }

gregmac avatar Jul 23 '12 20:07 gregmac

Hi Greg. Thanks for this. I'll be checking it out in the next few days.

mccalltd avatar Aug 02 '12 00:08 mccalltd

@mccalltd Now updated to current code as I'm starting to use this in another project. FWIW I have now been using the version in my original patch for about a year (and several releases) and have done several API changes with it, and this allows all the old versions to continue functioning with identical surface area.


As a somewhat more concrete example of what we've been doing, let's say we have a model called UserPreferences {pageSize:10, timezone:-5, confirmOnSave:false} and it is stored as a table with those same column names. Our controller contains:

public class UsersController : ApiController 
{
  [GET("users/{id}/preferences"), HttpGet]
  public Models.UserPreferences UserPrefs(Guid id) { .. }

In 1.2, we switch to a key-value table, and the new UserPreferences model is now more like a dictionary {prefs: [ {key:"pageSize", value:10}, {key:"timezone":-5} .... ] } (btw, I am not saying this particular change is good, just trying to make a simple example of a breaking change).

What we do in this situation is copy the old Models.UserPreferences into Models.v1_1.UserPreferences, and add the MinVer to the controller action:

  [GET("users/{id}/preferences", MinVer="1.2"), HttpGet]
  public Models.UserPreferences UserPrefs(Guid id) { .. }

Then we make a new controller:

namespace MyProject.Controllers.v1_1
  public class UsersController : ApiController 
  {
    [GET("users/{id}/preferences", MaxVer="1.1"), HttpGet]
    public Models.v1_1.UserPreferences UserPrefs(Guid id) { 
      // call new controller 
      var prefs = (new Controllers.UsersController).UserPrefs(id); 
      // map to old model (AutoMapper can come in handy here, but this is just an example)
      return new Models.v1_1.UserPreferences() {
        PageSize = prefs.First(x => x.Key == "PageSize").Value,
        Timezone = prefs.First(x => x.Key == "Timezone").Value,
      };
    }
  }
}

In this way, anyone calling the 1.0 or 1.1 APIs can continue to do so, and after this initial setup it's basically no extra work to maintain, yet we can still completely change the underlying storage model as well as fix bugs. We could also even add new properties to the old model (since that is a non-breaking change).

gregmac avatar Jan 16 '13 17:01 gregmac

What do your final url requests look like (for new and old version calls)?

waynebrantley avatar Jan 16 '13 23:01 waynebrantley