Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added new ServiceExceptionHandler to IAppHost, use that to handle exc…

…eptions if it exists.

More Reqstars tests - with Typed + Rest client API examples
  • Loading branch information...
commit 220ad680436f4d42d69c5f92490ae7bf61dfc773 1 parent 727b76a
@mythz mythz authored
View
8 src/ServiceStack.ServiceInterface/ServiceBase.cs
@@ -254,8 +254,14 @@ public object Execute(TRequest request)
/// <returns></returns>
protected virtual object HandleException(TRequest request, Exception ex)
{
- var errorResponse = ErrorHandler.Instance.HandleException(GetAppHost(), request, ex);
+ var useAppHost = GetAppHost();
+
+ var errorResponse = useAppHost != null && useAppHost.ServiceExceptionHandler != null
+ ? useAppHost.ServiceExceptionHandler(request, ex)
+ : ErrorHandler.Instance.HandleException(useAppHost, request, ex);
+
AfterEachRequest(request, errorResponse ?? ex);
+
return errorResponse;
}
View
1  src/ServiceStack.ServiceInterface/ServiceStack.ServiceInterface.csproj
@@ -126,7 +126,6 @@
<Compile Include="Cors\CorsFeature.cs" />
<Compile Include="Cors\CorsSupportAttribute.cs" />
<Compile Include="ErrorHandler.cs" />
- <Compile Include="Express.cs" />
<Compile Include="Providers\InMemoryRollingRequestLogger.cs" />
<Compile Include="Admin\RequestLogsService.cs" />
<Compile Include="RequiredRoleAttribute.cs" />
View
4 src/ServiceStack.ServiceInterface/Testing/BasicAppHost.cs
@@ -56,7 +56,9 @@ public T TryResolve<T>()
public List<IViewEngine> ViewEngines { get; set; }
- public Action<IHttpRequest, IHttpResponse, string, Exception> ExceptionHandler { get; set; }
+ public HandleUncaughtExceptionDelegate ExceptionHandler { get; set; }
+
+ public HandleServiceExceptionDelegate ServiceExceptionHandler { get; set; }
public List<HttpHandlerResolverDelegate> CatchAllHandlers { get; set; }
View
4 src/ServiceStack.ServiceInterface/Testing/TestAppHost.cs
@@ -70,7 +70,9 @@ public T TryResolve<T>()
public List<IViewEngine> ViewEngines { get; private set; }
- public Action<IHttpRequest, IHttpResponse, string, Exception> ExceptionHandler { get; set; }
+ public HandleUncaughtExceptionDelegate ExceptionHandler { get; set; }
+
+ public HandleServiceExceptionDelegate ServiceExceptionHandler { get; set; }
public List<HttpHandlerResolverDelegate> CatchAllHandlers { get; private set; }
View
3  src/ServiceStack/ServiceHost/ServiceController.cs
@@ -6,6 +6,7 @@
using ServiceStack.Common.Web;
using ServiceStack.Configuration;
using ServiceStack.Logging;
+using ServiceStack.ServiceClient.Web;
using ServiceStack.ServiceModel.Serialization;
using ServiceStack.Text;
using ServiceStack.WebHost.Endpoints;
@@ -125,7 +126,7 @@ public void RegisterIService(ITypeFactory serviceFactoryFn, Type serviceType)
continue;
var actionName = mi.Name.ToUpper();
- if (!HttpHeaders.AllVerbs.Contains(actionName) && actionName != ActionContext.AnyAction)
+ if (!HttpMethod.AllVerbs.Contains(actionName) && actionName != ActionContext.AnyAction)
continue;
var requestType = mi.GetParameters()[0].ParameterType;
View
6 src/ServiceStack/ServiceHost/ServiceExec.cs
@@ -3,6 +3,7 @@
using System.Linq.Expressions;
using System.Reflection;
using ServiceStack.Common.Web;
+using ServiceStack.ServiceClient.Web;
using ServiceStack.Text;
using ServiceStack.WebHost.Endpoints;
@@ -139,7 +140,7 @@ static IServiceExec()
var args = mi.GetParameters();
if (args.Length != 1) continue;
var actionName = mi.Name.ToUpper();
- if (!HttpHeaders.AllVerbs.Contains(actionName) && actionName != ActionContext.AnyAction)
+ if (!HttpMethod.AllVerbs.Contains(actionName) && actionName != ActionContext.AnyAction)
continue;
var requestType = args[0].ParameterType;
@@ -251,7 +252,7 @@ public static void CreateServiceRunnersFor<TRequest>()
}
var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLower();
- throw new MissingMethodException(
+ throw new NotImplementedException(
"Could not find method named {1}({0}) or Any({0}) on Service {2}"
.Fmt(request.GetType().Name, expectedMethodName, typeof(TService).Name));
}
@@ -261,7 +262,6 @@ public class IServiceRequestExec<TService, TRequest> : ICanServiceExec
{
static IServiceRequestExec()
{
- "Creating cache for {0},{1}".Print(typeof(TService).Name, typeof(TRequest).Name);
IServiceExec<TService>.CreateServiceRunnersFor<TRequest>();
}
View
8 src/ServiceStack/ServiceHost/ServiceRunner.cs
@@ -137,9 +137,15 @@ public virtual object Execute(IRequestContext requestContext, object instance, I
public virtual object HandleException(IRequestContext requestContext, TRequest request, Exception ex)
{
+ var useAppHost = GetAppHost();
+
//TODO workout validation errors
- var errorResponse = DtoUtils.HandleException(GetAppHost(), request, ex);
+ var errorResponse = useAppHost != null && useAppHost.ServiceExceptionHandler != null
+ ? useAppHost.ServiceExceptionHandler(request, ex)
+ : DtoUtils.HandleException(GetAppHost(), request, ex);
+
AfterEachRequest(requestContext, request, errorResponse ?? ex);
+
return errorResponse;
}
View
7 src/ServiceStack/WebHost.Endpoints/AppDelegates.cs
@@ -1,4 +1,4 @@
-using System.IO;
+using System;
using System.Web;
using ServiceStack.ServiceHost;
@@ -7,4 +7,9 @@ namespace ServiceStack.WebHost.Endpoints
public delegate IHttpHandler HttpHandlerResolverDelegate(string httpMethod, string pathInfo, string filePath);
public delegate bool StreamSerializerResolverDelegate(IRequestContext requestContext, object dto, IHttpResponse httpRes);
+
+ public delegate void HandleUncaughtExceptionDelegate(
+ IHttpRequest httpReq, IHttpResponse httpRes, string operationName, Exception ex);
+
+ public delegate object HandleServiceExceptionDelegate(object request, Exception ex);
}
View
14 src/ServiceStack/WebHost.Endpoints/AppHostBase.cs
@@ -195,10 +195,16 @@ public List<IViewEngine> ViewEngines
}
}
- public Action<IHttpRequest, IHttpResponse, string, Exception> ExceptionHandler
- {
- get { return EndpointHost.ExceptionHandler; }
- set { EndpointHost.ExceptionHandler = value; }
+ public HandleUncaughtExceptionDelegate ExceptionHandler
+ {
+ get { return EndpointHost.ExceptionHandler; }
+ set { EndpointHost.ExceptionHandler = value; }
+ }
+
+ public HandleServiceExceptionDelegate ServiceExceptionHandler
+ {
+ get { return EndpointHost.ServiceExceptionHandler; }
+ set { EndpointHost.ServiceExceptionHandler = value; }
}
public List<HttpHandlerResolverDelegate> CatchAllHandlers
View
5 src/ServiceStack/WebHost.Endpoints/EndpointHost.cs
@@ -6,7 +6,6 @@
using ServiceStack.Common;
using ServiceStack.Common.Web;
using ServiceStack.Html;
-using ServiceStack.Logging;
using ServiceStack.MiniProfiler;
using ServiceStack.ServiceHost;
using ServiceStack.VirtualPath;
@@ -35,7 +34,9 @@ public class EndpointHost
public static List<IViewEngine> ViewEngines { get; set; }
- public static Action<IHttpRequest, IHttpResponse, string, Exception> ExceptionHandler { get; set; }
+ public static HandleUncaughtExceptionDelegate ExceptionHandler { get; set; }
+
+ public static HandleServiceExceptionDelegate ServiceExceptionHandler { get; set; }
public static List<HttpHandlerResolverDelegate> CatchAllHandlers { get; set; }
View
13 src/ServiceStack/WebHost.Endpoints/IAppHost.cs
@@ -67,10 +67,15 @@ public interface IAppHost : IResolver
/// </summary>
List<IViewEngine> ViewEngines { get; }
- /// <summary>
- /// Provide an exception handler
- /// </summary>
- Action<IHttpRequest, IHttpResponse, string, Exception> ExceptionHandler { get; set; }
+ /// <summary>
+ /// Provide an exception handler for un-caught exceptions
+ /// </summary>
+ HandleUncaughtExceptionDelegate ExceptionHandler { get; set; }
+
+ /// <summary>
+ /// Provide an exception handler for unhandled exceptions
+ /// </summary>
+ HandleServiceExceptionDelegate ServiceExceptionHandler { get; set; }
/// <summary>
/// Provide a catch-all handler that doesn't match any routes
View
11 src/ServiceStack/WebHost.Endpoints/Support/HttpListenerBase.cs
@@ -389,14 +389,9 @@ public List<IViewEngine> ViewEngines
}
}
- /// <summary>
- /// Provide an exception handler
- /// </summary>
- public Action<IHttpRequest, IHttpResponse, string, Exception> ExceptionHandler
- {
- get { return EndpointHost.ExceptionHandler; }
- set { EndpointHost.ExceptionHandler = value; }
- }
+ public HandleUncaughtExceptionDelegate ExceptionHandler { get; set; }
+
+ public HandleServiceExceptionDelegate ServiceExceptionHandler { get; set; }
public List<HttpHandlerResolverDelegate> CatchAllHandlers
{
View
1  tests/RazorRockstars.Console.Files/RazorRockstars.Console.Files.csproj
@@ -67,7 +67,6 @@
<Compile Include="DynamicJsonTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RazorRockstars_FilesTests.cs" />
- <Compile Include="ReqstarsController.cs" />
<Compile Include="ReqStarsService.cs" />
</ItemGroup>
<ItemGroup>
View
197 tests/RazorRockstars.Console.Files/ReqStarsService.cs
@@ -1,7 +1,7 @@
-using System.Data;
+using System.Collections.Generic;
+using System.Data;
using System.Diagnostics;
using System.Linq;
-using Alternate.ExpressLike.Controller.Proposal;
using NUnit.Framework;
using ServiceStack.Common;
using ServiceStack.Logging;
@@ -14,13 +14,23 @@
namespace RazorRockstars.Console.Files
{
- //Proposal 2: Keeping ServiceStack's message-based semantics
- //Inspired by Ivan's proposal: http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html
+ /// New Proposal, keeping ServiceStack's message-based semantics:
+ /// Inspired by Ivan's proposal: http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html
+ ///
+ /// To align with ServiceStack's message-based design, an "Action":
+ /// - is public and only supports a single argument the typed Request DTO
+ /// - method name matches a HTTP Method or "Any" which is used as a fallback (for all methods) if it exists
+ /// - only returns object or void
+ ///
+ /// Notes:
+ /// Content Negotiation built-in, i.e. by default each method/route is automatically available in every registered Content-Type (HTTP Only).
+ /// New API are also available in ServiceStack's typed service clients (they're actually even more succinct :)
+ /// Any Views rendered is based on Returned DTO type, see: http://razor.servicestack.net/#unified-stack
+
[Route("/reqstars", "GET")]
[Route("/reqstars/aged/{Age}")]
public class SearchReqstars : IReturn<ReqstarsResponse>
{
- public int Id { get; set; }
public int? Age { get; set; }
}
@@ -39,13 +49,45 @@ public class DeleteReqstar : IReturnVoid
public int Id { get; set; }
}
- //[Authenticate]
- public class ReqStarsService : Service
+ [Route("/reqstars")]
+ public class Reqstar : IReturn<ReqstarsResponse>
+ {
+ public int Id { get; set; }
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public int? Age { get; set; }
+
+ public Reqstar() { }
+ public Reqstar(int id, string firstName, string lastName, int age)
+ {
+ Id = id;
+ FirstName = firstName;
+ LastName = lastName;
+ Age = age;
+ }
+ }
+
+ [Csv(CsvBehavior.FirstEnumerable)]
+ public class ReqstarsResponse
{
+ public int Total { get; set; }
+ public int? Aged { get; set; }
+ public List<Reqstar> Results { get; set; }
+ }
+
+
+ public class ReqstarsService : Service
+ {
+ public static Reqstar[] SeedData = new[] {
+ new Reqstar(1, "Foo", "Bar", 20),
+ new Reqstar(2, "Something", "Else", 30),
+ new Reqstar(3, "Foo2", "Bar2", 20),
+ };
+
public void Any(ResetReqstar request)
{
Db.DeleteAll<Reqstar>();
- Db.Insert(Reqstar.SeedData);
+ Db.Insert(SeedData);
}
public object Get(SearchReqstars request)
@@ -60,7 +102,7 @@ public object Get(SearchReqstars request)
};
}
- [ClientCanSwapTemplates] //aka action-level filters
+ [ClientCanSwapTemplates] //allow action-level filters
public object Get(GetReqstar request)
{
return Db.Id<Reqstar>(request.Id);
@@ -85,9 +127,6 @@ public class ReqStarsServiceTests
private const string ListeningOn = "http://*:1337/";
public const string Host = "http://localhost:1337";
- //private const string ListeningOn = "http://*:1337/subdir/subdir2/";
- //private const string Host = "http://localhost:1337/subdir/subdir2";
-
private const string BaseUri = Host + "/";
JsonServiceClient client;
@@ -115,7 +154,7 @@ public void SetUp()
{
db = appHost.TryResolve<IDbConnectionFactory>().OpenDbConnection();
db.DropAndCreateTable<Reqstar>();
- db.Insert(Reqstar.SeedData);
+ db.Insert(ReqstarsService.SeedData);
}
[TearDown]
@@ -130,32 +169,79 @@ public void TestFixtureTearDown()
"Time Taken {0}ms".Fmt(startedAt.ElapsedMilliseconds).Print();
appHost.Dispose();
}
+
public class EmptyResponse { }
+
[Test]
public void Can_GET_SearchReqstars()
{
+ var response = client.Get<ReqstarsResponse>("/reqstars");
+ Assert.That(response.Results.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
+ }
+
+ [Test]
+ public void Disallows_GET_SearchReqstars_PrettyTypedApi()
+ {
try
{
- var response = client.Get<ReqstarsResponse>("/reqstars");
- Assert.That(response.Results.Count, Is.EqualTo(Reqstar.SeedData.Length));
+ var response = client.Send(new SearchReqstars());
+ Assert.Fail("POST's to SearchReqstars should not be allowed");
}
- catch (System.Exception ex)
+ catch (WebServiceException webEx)
{
- ex.Message.Print();
- throw;
+ Assert.That(webEx.StatusCode, Is.EqualTo(405));
+ Assert.That(webEx.StatusDescription, Is.EqualTo("Method Not Allowed"));
}
}
[Test]
+ public void Can_GET_SearchReqstars_PrettyRestApi()
+ {
+ var request = new SearchReqstars();
+ var response = client.Get(request);
+
+ Assert.That(request.ToUrl("GET"), Is.EqualTo("/reqstars"));
+ Assert.That(response.Results.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
+ }
+
+
+ [Test]
public void Can_GET_SearchReqstars_aged_20()
{
var response = client.Get<ReqstarsResponse>("/reqstars/aged/20");
Assert.That(response.Results.Count,
- Is.EqualTo(Reqstar.SeedData.Count(x => x.Age == 20)));
+ Is.EqualTo(ReqstarsService.SeedData.Count(x => x.Age == 20)));
}
[Test]
+ public void Disallows_GET_SearchReqstars_aged_20_PrettyTypedApi()
+ {
+ try
+ {
+ var response = client.Send(new SearchReqstars { Age = 20 });
+ Assert.Fail("POST's to SearchReqstars should not be allowed");
+ }
+ catch (WebServiceException webEx)
+ {
+ Assert.That(webEx.StatusCode, Is.EqualTo(405));
+ Assert.That(webEx.StatusDescription, Is.EqualTo("Method Not Allowed"));
+ }
+ }
+
+ [Test]
+ public void Can_GET_SearchReqstars_aged_20_PrettyRestApi()
+ {
+ var request = new SearchReqstars { Age = 20 };
+ var response = client.Get(request);
+
+ Assert.That(request.ToUrl("GET"), Is.EqualTo("/reqstars/aged/20"));
+ Assert.That(response.Results.Count,
+ Is.EqualTo(ReqstarsService.SeedData.Count(x => x.Age == 20)));
+ }
+
+
+ [Test]
public void Can_DELETE_Reqstar()
{
var response = client.Delete<EmptyResponse>("/reqstars/1/delete");
@@ -163,20 +249,61 @@ public void Can_DELETE_Reqstar()
var reqstarsLeft = db.Select<Reqstar>();
Assert.That(reqstarsLeft.Count,
- Is.EqualTo(Reqstar.SeedData.Length - 1));
+ Is.EqualTo(ReqstarsService.SeedData.Length - 1));
}
[Test]
+ public void Can_DELETE_Reqstar_PrettyTypedApi()
+ {
+ client.Send(new DeleteReqstar { Id = 1 });
+
+ var reqstarsLeft = db.Select<Reqstar>();
+
+ Assert.That(reqstarsLeft.Count,
+ Is.EqualTo(ReqstarsService.SeedData.Length - 1));
+ }
+
+ [Test]
+ public void Can_DELETE_Reqstar_PrettyRestApi()
+ {
+ client.Delete(new DeleteReqstar { Id = 1 });
+
+ var reqstarsLeft = db.Select<Reqstar>();
+
+ Assert.That(reqstarsLeft.Count,
+ Is.EqualTo(ReqstarsService.SeedData.Length - 1));
+ }
+
+
+ [Test]
public void Can_CREATE_Reqstar()
{
var response = client.Post<ReqstarsResponse>("/reqstars",
new Reqstar(4, "Just", "Created", 25));
Assert.That(response.Results.Count,
- Is.EqualTo(Reqstar.SeedData.Length + 1));
+ Is.EqualTo(ReqstarsService.SeedData.Length + 1));
}
- //TODO: FIX
+ [Test]
+ public void Can_CREATE_Reqstar_PrettyTypedApi()
+ {
+ var response = client.Send(new Reqstar(4, "Just", "Created", 25));
+
+ Assert.That(response.Results.Count,
+ Is.EqualTo(ReqstarsService.SeedData.Length + 1));
+ }
+
+ [Test]
+ public void Can_CREATE_Reqstar_PrettyRestApi()
+ {
+ var response = client.Post(new Reqstar(4, "Just", "Created", 25));
+
+ Assert.That(response.Results.Count,
+ Is.EqualTo(ReqstarsService.SeedData.Length + 1));
+ }
+
+
[Test]
public void Can_GET_ResetReqstars()
{
@@ -186,7 +313,31 @@ public void Can_GET_ResetReqstars()
var reqstarsLeft = db.Select<Reqstar>();
- Assert.That(reqstarsLeft.Count, Is.EqualTo(Reqstar.SeedData.Length));
+ Assert.That(reqstarsLeft.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
+ }
+
+ [Test]
+ public void Can_GET_ResetReqstars_PrettyTypedApi()
+ {
+ db.DeleteAll<Reqstar>();
+
+ client.Send(new ResetReqstar());
+
+ var reqstarsLeft = db.Select<Reqstar>();
+
+ Assert.That(reqstarsLeft.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
+ }
+
+ [Test]
+ public void Can_GET_ResetReqstars_PrettyRestApi()
+ {
+ db.DeleteAll<Reqstar>();
+
+ client.Get(new ResetReqstar());
+
+ var reqstarsLeft = db.Select<Reqstar>();
+
+ Assert.That(reqstarsLeft.Count, Is.EqualTo(ReqstarsService.SeedData.Length));
}
}
}
View
4 tests/ServiceStack.ServiceHost.Tests/Formats/ViewTests.cs
@@ -87,8 +87,10 @@ public T TryResolve<T>()
public List<Action<IHttpRequest, IHttpResponse, object>> ResponseFilters { get; set; }
public List<IViewEngine> ViewEngines { get; set; }
+
+ public HandleUncaughtExceptionDelegate ExceptionHandler { get; set; }
- public Action<IHttpRequest, IHttpResponse, string, Exception> ExceptionHandler { get; set; }
+ public HandleServiceExceptionDelegate ServiceExceptionHandler { get; set; }
public List<HttpHandlerResolverDelegate> CatchAllHandlers { get; set; }
Please sign in to comment.
Something went wrong with that request. Please try again.