Skip to content

Commit

Permalink
Update Routes.AddFromAssembly Ext method to also auto-register NewAPI…
Browse files Browse the repository at this point in the history
… conventional routes
  • Loading branch information
mythz committed Jan 24, 2013
1 parent 822d8e3 commit caefcec
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 106 deletions.
130 changes: 85 additions & 45 deletions src/ServiceStack.ServiceInterface/ServiceRoutesExtensions.cs
Expand Up @@ -6,7 +6,9 @@
using ServiceStack.Common;
using ServiceStack.Common.Utils;
using ServiceStack.Common.Web;
using ServiceStack.Text;
using ServiceStack.ServiceHost;
using ServiceStack.WebHost.Endpoints;

namespace ServiceStack.ServiceInterface
{
Expand All @@ -26,71 +28,109 @@ public static class ServiceRoutesExtensions
{
foreach (Assembly assembly in assembliesWithServices)
{
IEnumerable<Type> services =
from t in assembly.GetExportedTypes()
where
!t.IsAbstract &&
t.IsSubclassOfRawGeneric(typeof(ServiceBase<>))
select t;

foreach (Type service in services)
{
Type baseType = service.BaseType;
//go up the hierarchy to the first generic base type
while (!baseType.IsGenericType)
{
baseType = baseType.BaseType;
}
AddOldApiRoutes(routes, assembly);
AddNewApiRoutes(routes, assembly);
}

Type requestType = baseType.GetGenericArguments()[0];
return routes;
}

string allowedVerbs = null; //null == All Routes
private static void AddNewApiRoutes(IServiceRoutes routes, Assembly assembly)
{
var services = assembly.GetExportedTypes()
.Where(t => !t.IsAbstract
&& t.HasInterface(typeof(IService)));

if (service.IsSubclassOfRawGeneric(typeof(RestServiceBase<>)))
foreach (Type service in services)
{
var allServiceActions = service.GetActions();
foreach (var requestDtoActions in allServiceActions.GroupBy(x => x.GetParameters()[0].ParameterType))
{
var requestType = requestDtoActions.Key;
var hasWildcard = requestDtoActions.Any(x => x.Name.EqualsIgnoreCase(ActionContext.AnyAction));
string allowedVerbs = null; //null == All Routes
if (!hasWildcard)
{
//find overriden REST methods
var allowedMethods = new List<string>();
if (service.GetMethod("OnGet").DeclaringType == service)
foreach (var action in requestDtoActions)
{
allowedMethods.Add(HttpMethods.Get);
allowedMethods.Add(action.Name.ToUpper());
}

if (service.GetMethod("OnPost").DeclaringType == service)
{
allowedMethods.Add(HttpMethods.Post);
}
if (allowedMethods.Count == 0) continue;
allowedVerbs = string.Join(" ", allowedMethods.ToArray());
}

if (service.GetMethod("OnPut").DeclaringType == service)
{
allowedMethods.Add(HttpMethods.Put);
}
routes.AddRoute(requestType, allowedVerbs);
}
}
}

if (service.GetMethod("OnDelete").DeclaringType == service)
{
allowedMethods.Add(HttpMethods.Delete);
}
private static void AddOldApiRoutes(IServiceRoutes routes, Assembly assembly)
{
var services = assembly.GetExportedTypes()
.Where(t => !t.IsAbstract
&& t.IsSubclassOfRawGeneric(typeof(ServiceBase<>)));

if (service.GetMethod("OnPatch").DeclaringType == service)
{
allowedMethods.Add(HttpMethods.Patch);
}
foreach (Type service in services)
{
Type baseType = service.BaseType;
//go up the hierarchy to the first generic base type
while (!baseType.IsGenericType)
{
baseType = baseType.BaseType;
}

if (allowedMethods.Count == 0) continue;
allowedVerbs = string.Join(" ", allowedMethods.ToArray());
Type requestType = baseType.GetGenericArguments()[0];

string allowedVerbs = null; //null == All Routes

if (service.IsSubclassOfRawGeneric(typeof(RestServiceBase<>)))
{
//find overriden REST methods
var allowedMethods = new List<string>();
if (service.GetMethod("OnGet").DeclaringType == service)
{
allowedMethods.Add(HttpMethods.Get);
}

routes.Add(requestType, requestType.Name, allowedVerbs);
if (service.GetMethod("OnPost").DeclaringType == service)
{
allowedMethods.Add(HttpMethods.Post);
}

var hasIdField = requestType.GetProperty(IdUtils.IdField) != null;
if (hasIdField)
if (service.GetMethod("OnPut").DeclaringType == service)
{
var routePath = requestType.Name + "/{" + IdUtils.IdField + "}";
routes.Add(requestType, routePath, allowedVerbs);
allowedMethods.Add(HttpMethods.Put);
}

if (service.GetMethod("OnDelete").DeclaringType == service)
{
allowedMethods.Add(HttpMethods.Delete);
}

if (service.GetMethod("OnPatch").DeclaringType == service)
{
allowedMethods.Add(HttpMethods.Patch);
}

if (allowedMethods.Count == 0) continue;
allowedVerbs = string.Join(" ", allowedMethods.ToArray());
}

routes.AddRoute(requestType, allowedVerbs);
}
}

return routes;
private static void AddRoute(this IServiceRoutes routes, Type requestType, string allowedVerbs)
{
routes.Add(requestType, requestType.Name, allowedVerbs);

var hasIdField = requestType.GetProperty(IdUtils.IdField) != null;
if (!hasIdField) return;

var routePath = requestType.Name + "/{" + IdUtils.IdField + "}";
routes.Add(requestType, routePath, allowedVerbs);
}

public static IServiceRoutes Add<TRequest>(this IServiceRoutes routes, string restPath, ApplyTo verbs)
Expand Down
2 changes: 1 addition & 1 deletion src/ServiceStack/ServiceHost/ServiceMetadata.cs
Expand Up @@ -248,7 +248,7 @@ public bool HasImplementation(Operation operation, Format format)
if (operation.Actions == null) return false;

return operation.Actions.Contains("POST")
|| operation.Actions.Contains("ANY");
|| operation.Actions.Contains(ActionContext.AnyAction);
}
return true;
}
Expand Down
151 changes: 102 additions & 49 deletions tests/ServiceStack.ServiceHost.Tests/Routes/ServiceRoutesTests.cs
@@ -1,52 +1,105 @@
using System.Linq;
using NUnit.Framework;
using ServiceStack.ServiceClient.Web;
using ServiceStack.ServiceInterface;

namespace ServiceStack.ServiceHost.Tests.Routes
{
[TestFixture]
public class ServiceRoutesTests
{
[Test]
public void Can_Register_Routes_From_Assembly()
{
var routes = new ServiceRoutes();
routes.AddFromAssembly(typeof(RestServiceWithAllVerbsImplemented).Assembly);

RestPath restWithAllMethodsRoute =
(from r in routes.RestPaths
where r.Path == "RequestDto2"
select r).FirstOrDefault();

Assert.That(restWithAllMethodsRoute, Is.Not.Null);

Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("GET"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("POST"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("PUT"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("DELETE"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("PATCH"));
}

[Test]
public void Can_Register_Routes_With_Partially_Implemented_REST_Verbs()
{
var routes = new ServiceRoutes();
routes.AddFromAssembly(typeof(RestServiceWithSomeVerbsImplemented).Assembly);

RestPath restWithAFewMethodsRoute =
(from r in routes.RestPaths
where r.Path == "RequestDto"
select r).FirstOrDefault();

Assert.That(restWithAFewMethodsRoute, Is.Not.Null);

Assert.That(restWithAFewMethodsRoute.AllowedVerbs.Contains("GET"), Is.True);
Assert.That(restWithAFewMethodsRoute.AllowedVerbs.Contains("POST"), Is.False);
Assert.That(restWithAFewMethodsRoute.AllowedVerbs.Contains("PUT"), Is.True);
Assert.That(restWithAFewMethodsRoute.AllowedVerbs.Contains("DELETE"), Is.False);
Assert.That(restWithAFewMethodsRoute.AllowedVerbs.Contains("PATCH"), Is.False);
}
[TestFixture]
public class ServiceRoutesTests
{
[Test]
public void Can_Register_NewApi_Routes_From_Assembly()
{
var routes = new ServiceRoutes();
routes.AddFromAssembly(typeof(NewApiRestServiceWithAllVerbsImplemented).Assembly);

RestPath restWithAllMethodsRoute =
(from r in routes.RestPaths
where r.Path == "NewApiRequestDto"
select r).FirstOrDefault();

Assert.That(restWithAllMethodsRoute, Is.Not.Null);

Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("GET"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("POST"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("PUT"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("DELETE"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("PATCH"));

RestPath restWithAllMethodsRoute2 =
(from r in routes.RestPaths
where r.Path == "NewApiRequestDto2"
select r).FirstOrDefault();

Assert.That(restWithAllMethodsRoute2, Is.Not.Null);

Assert.That(restWithAllMethodsRoute2.AllowedVerbs.Contains("GET"));
Assert.That(restWithAllMethodsRoute2.AllowedVerbs.Contains("POST"));
Assert.That(restWithAllMethodsRoute2.AllowedVerbs.Contains("PUT"));
Assert.That(restWithAllMethodsRoute2.AllowedVerbs.Contains("DELETE"));
Assert.That(restWithAllMethodsRoute2.AllowedVerbs.Contains("PATCH"));
}

[Test]
public void Can_Register_NewApi_Routes_With_Id_and_Any_Fallback_From_Assembly()
{
var routes = new ServiceRoutes();
routes.AddFromAssembly(typeof(NewApiRequestDtoWithIdService).Assembly);

var route = (from r in routes.RestPaths
where r.Path == "NewApiRequestDtoWithId"
select r).FirstOrDefault();

Assert.That(route, Is.Not.Null);
Assert.That(route.AllowedVerbs, Is.Null);

route = (from r in routes.RestPaths
where r.Path == "NewApiRequestDtoWithId/{Id}"
select r).FirstOrDefault();

Assert.That(route, Is.Not.Null);
Assert.That(route.AllowedVerbs, Is.Null);
}

[Test]
public void Can_Register_OldApi_Routes_From_Assembly()
{
var routes = new ServiceRoutes();
routes.AddFromAssembly(typeof(OldApiRestServiceWithAllVerbsImplemented).Assembly);

RestPath restWithAllMethodsRoute =
(from r in routes.RestPaths
where r.Path == "OldApiRequestDto2"
select r).FirstOrDefault();

Assert.That(restWithAllMethodsRoute, Is.Not.Null);

Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("GET"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("POST"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("PUT"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("DELETE"));
Assert.That(restWithAllMethodsRoute.AllowedVerbs.Contains("PATCH"));
}

[Test]
public void Can_Register_OldApi_Routes_With_Partially_Implemented_REST_Verbs()
{
var routes = new ServiceRoutes();
routes.AddFromAssembly(typeof(OldApiRestServiceWithSomeVerbsImplemented).Assembly);

RestPath restWithAFewMethodsRoute =
(from r in routes.RestPaths
where r.Path == "OldApiRequestDto"
select r).FirstOrDefault();

Assert.That(restWithAFewMethodsRoute, Is.Not.Null);

Assert.That(restWithAFewMethodsRoute.AllowedVerbs.Contains("GET"), Is.True);
Assert.That(restWithAFewMethodsRoute.AllowedVerbs.Contains("POST"), Is.False);
Assert.That(restWithAFewMethodsRoute.AllowedVerbs.Contains("PUT"), Is.True);
Assert.That(restWithAFewMethodsRoute.AllowedVerbs.Contains("DELETE"), Is.False);
Assert.That(restWithAFewMethodsRoute.AllowedVerbs.Contains("PATCH"), Is.False);
}

[Test]
public void Can_Register_Routes_Using_Add_Extension()
Expand All @@ -56,11 +109,11 @@ public void Can_Register_Routes_Using_Add_Extension()
var route = routes.RestPaths[0];
Assert.That(route.Path == "/Users/{Name}/Orders/{OrderId}");
}
}
}

public class Customer
{
public string Name { get; set; }
public int OrderId { get; set; }
}
public class Customer
{
public string Name { get; set; }
public int OrderId { get; set; }
}
}

0 comments on commit caefcec

Please sign in to comment.