Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added OnAfterExecute() base class filter and Validation refactoring.

  • Loading branch information...
commit 53696aa61f6f3b8780001979e0fec5610db6990e 1 parent f9facad
@mythz mythz authored
Showing with 519 additions and 176 deletions.
  1. +0 −1  src/ServiceStack.Common/ServiceClient.Web/AsyncServiceClient.cs
  2. +1 −1  src/ServiceStack.ServiceInterface/AsyncServiceBase.cs
  3. +14 −2 src/ServiceStack.ServiceInterface/FluentValidation/AbstractValidator.cs
  4. +6 −6 src/ServiceStack.ServiceInterface/RestServiceBase.cs
  5. +15 −107 src/ServiceStack.ServiceInterface/ServiceBase.cs
  6. +2 −2 src/ServiceStack.ServiceInterface/ServiceModel/ResponseStatusTranslator.cs
  7. +3 −1 src/ServiceStack.ServiceInterface/ServiceStack.ServiceInterface.csproj
  8. +116 −0 src/ServiceStack.ServiceInterface/ServiceUtils.cs
  9. +54 −0 src/ServiceStack.ServiceInterface/Validation/ValidationFeature.cs
  10. +35 −36 src/ServiceStack.ServiceInterface/Validation/ValidationFilter.cs
  11. +0 −19 src/ServiceStack.ServiceInterface/Validation/ValidationHandler.cs
  12. +48 −0 src/ServiceStack.ServiceInterface/Validation/ValidatorCache.cs
  13. +140 −0 tests/ServiceStack.WebHost.Endpoints.Tests/CustomerService.cs
  14. +2 −1  tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj
  15. +4 −0 tests/ServiceStack.WebHost.IntegrationTests/Global.asax.cs
  16. +1 −0  tests/ServiceStack.WebHost.IntegrationTests/ServiceStack.WebHost.IntegrationTests.csproj
  17. +73 −0 tests/ServiceStack.WebHost.IntegrationTests/Services/CustomerService.cs
  18. +5 −0 tests/ServiceStack.WebHost.IntegrationTests/Tests/SessionTests.cs
View
1  src/ServiceStack.Common/ServiceClient.Web/AsyncServiceClient.cs
@@ -235,7 +235,6 @@ private void ResponseCallback<T>(IAsyncResult asyncResult)
SendWebRequestAsync(
requestState.HttpMethod, requestState.Request,
requestState, requestState.WebRequest);
-
}
catch (Exception /*subEx*/)
{
View
2  src/ServiceStack.ServiceInterface/AsyncServiceBase.cs
@@ -41,7 +41,7 @@ public virtual object ExecuteAsync(TRequest request)
producer.Publish(request);
}
- return CreateResponseDto(request);
+ return ServiceUtils.CreateResponseDto(request);
}
/// <summary>
View
16 src/ServiceStack.ServiceInterface/FluentValidation/AbstractValidator.cs
@@ -14,8 +14,11 @@
// limitations under the License.
//
// The latest version of this file can be found at http://www.codeplex.com/FluentValidation
-#endregion
-
+#endregion
+
+using ServiceStack.Common;
+using ServiceStack.ServiceInterface;
+
namespace ServiceStack.FluentValidation
{
using System;
@@ -153,6 +156,15 @@ public abstract class AbstractValidator<T> : IValidator<T>, IEnumerable<IValidat
using (nestedValidators.OnItemAdded(r => r.RuleSet = ruleSetName)) {
action();
}
+ }
+
+ public void RuleSet(ApplyTo appliesTo, Action action)
+ {
+ var httpMethods = appliesTo.ToString().Split(',').SafeConvertAll(x => x.Trim());
+ foreach (var httpMethod in httpMethods)
+ {
+ RuleSet(httpMethod, action);
+ }
}
/// <summary>
View
12 src/ServiceStack.ServiceInterface/RestServiceBase.cs
@@ -8,7 +8,7 @@ namespace ServiceStack.ServiceInterface
/// Base class for services that support HTTP verbs.
/// </summary>
/// <typeparam name="TRequest">The request class that the descendent class
- /// is responsible for processing.</typeparam>
+ /// is responsible for processing.</typeparam>
public abstract class RestServiceBase<TRequest>
: ServiceBase<TRequest>,
IRestGetService<TRequest>,
@@ -46,7 +46,7 @@ public object Get(TRequest request)
try
{
OnBeforeExecute(request);
- return OnGet(request);
+ return OnAfterExecute(OnGet(request));
}
catch (Exception ex)
{
@@ -82,7 +82,7 @@ public object Put(TRequest request)
try
{
OnBeforeExecute(request);
- return OnPut(request);
+ return OnAfterExecute(OnPut(request));
}
catch (Exception ex)
{
@@ -118,7 +118,7 @@ public object Post(TRequest request)
try
{
OnBeforeExecute(request);
- return OnPost(request);
+ return OnAfterExecute(OnPost(request));
}
catch (Exception ex)
{
@@ -154,7 +154,7 @@ public object Delete(TRequest request)
try
{
OnBeforeExecute(request);
- return OnDelete(request);
+ return OnAfterExecute(OnDelete(request));
}
catch (Exception ex)
{
@@ -190,7 +190,7 @@ public object Patch(TRequest request)
try
{
OnBeforeExecute(request);
- return OnPatch(request);
+ return OnAfterExecute(OnPatch(request));
}
catch (Exception ex)
{
View
122 src/ServiceStack.ServiceInterface/ServiceBase.cs
@@ -1,10 +1,8 @@
using System;
-using System.Net;
using System.Web;
using ServiceStack.CacheAccess;
using ServiceStack.CacheAccess.Providers;
using ServiceStack.Common;
-using ServiceStack.Common.Utils;
using ServiceStack.Common.Web;
using ServiceStack.Logging;
using ServiceStack.Messaging;
@@ -31,16 +29,6 @@ public abstract class ServiceBase<TRequest>
private static readonly ILog Log = LogManager.GetLogger(typeof(ServiceBase<>));
/// <summary>
- /// Naming convention for the request's Response DTO
- /// </summary>
- public const string ResponseDtoSuffix = "Response";
-
- /// <summary>
- /// Naming convention for the ResponseStatus property name on the response DTO
- /// </summary>
- public const string ResponseStatusPropertyName = "ResponseStatus";
-
- /// <summary>
/// Service error logs are kept in 'urn:ServiceErrors:{ServiceName}'
/// </summary>
public const string UrnServiceErrorType = "ServiceErrors";
@@ -153,8 +141,18 @@ public T TryResolve<T>()
/// Called before the request is Executed. Override to enforce generic validation logic.
/// </summary>
/// <param name="request"></param>
- protected virtual void OnBeforeExecute(TRequest request) { }
-
+ protected virtual void OnBeforeExecute(TRequest request) { }
+
+ /// <summary>
+ /// Called after the request is Executed. Override to decorate the response dto
+ /// </summary>
+ /// <param name="response"></param>
+ /// <returns></returns>
+ protected virtual object OnAfterExecute(object response)
+ {
+ return response;
+ }
+
/// <summary>
/// Execute the request with the protected abstract Run() method in a managed scope by
/// provide default handling of Service Exceptions by serializing exceptions in the response
@@ -167,7 +165,7 @@ public object Execute(TRequest request)
try
{
OnBeforeExecute(request);
- return Run(request);
+ return OnAfterExecute(Run(request));
}
catch (Exception ex)
{
@@ -224,98 +222,9 @@ protected virtual object HandleException(TRequest request, Exception ex)
}
}
- var responseDto = CreateResponseDto(request, responseStatus);
-
- var statusCode = HttpStatusCode.InternalServerError;
- if (responseDto != null)
- {
-
- var httpError = ex as IHttpError;
- if (httpError != null)
- {
- httpError.Response = responseDto;
- return httpError;
- }
-
- if (ex is NotImplementedException) statusCode = HttpStatusCode.MethodNotAllowed;
- else if (ex is ArgumentException) statusCode = HttpStatusCode.BadRequest;
- }
-
- var errorCode = ex.GetType().Name;
- var errorMsg = ex.Message;
- if (responseStatus != null)
- {
- errorCode = responseStatus.ErrorCode ?? errorCode;
- errorMsg = responseStatus.Message ?? errorMsg;
- }
-
- return new HttpError(responseDto, statusCode, errorCode, errorMsg);
+ return ServiceUtils.CreateErrorResponse(request, ex, responseStatus);
}
-
- /// <summary>
- /// Create an instance of the service response dto type and inject it with the supplied responseStatus
- /// </summary>
- /// <param name="request"></param>
- /// <param name="responseStatus"></param>
- /// <returns></returns>
- protected static object CreateResponseDto(TRequest request, ResponseStatus responseStatus)
- {
- // Predict the Response message type name
- // Get the type
- var responseDtoType = AssemblyUtils.FindType(GetResponseDtoName(request));
- var responseDto = CreateResponseDto(request);
-
- if (responseDto == null)
- return null;
-
- // For faster serialization of exceptions, services should implement IHasResponseStatus
- var hasResponseStatus = responseDto as IHasResponseStatus;
- if (hasResponseStatus != null)
- {
- hasResponseStatus.ResponseStatus = responseStatus;
- }
- else
- {
- // Get the ResponseStatus property
- var responseStatusProperty = responseDtoType.GetProperty(ResponseStatusPropertyName);
-
- if (responseStatusProperty != null)
- {
- // Set the ResponseStatus
- ReflectionUtils.SetProperty(responseDto, responseStatusProperty, responseStatus);
- }
- }
-
- // Return an Error DTO with the exception populated
- return responseDto;
- }
-
- /// <summary>
- /// Create an instance of the response dto based on the requestDto type and default naming convention
- /// </summary>
- /// <param name="request"></param>
- /// <returns></returns>
- protected static object CreateResponseDto(TRequest request)
- {
- // Get the type
- var responseDtoType = AssemblyUtils.FindType(GetResponseDtoName(request));
-
- if (responseDtoType == null)
- {
- // We don't support creation of response messages without a predictable type name
- return null;
- }
-
- // Create an instance of the response message for this request
- var responseDto = ReflectionUtils.CreateInstance(responseDtoType);
- return responseDto;
- }
-
- protected static string GetResponseDtoName(TRequest request)
- {
- return typeof(TRequest).FullName + ResponseDtoSuffix;
- }
-
+
protected HttpResult View(string viewName, object response)
{
return new HttpResult(response)
@@ -334,5 +243,4 @@ public virtual object Execute(IMessage<TRequest> request)
return Execute(request.GetBody());
}
}
-
}
View
4 src/ServiceStack.ServiceInterface/ServiceModel/ResponseStatusTranslator.cs
@@ -48,8 +48,8 @@ public ResponseStatus Parse(SerializableValidationException validationException)
public ResponseStatus Parse(SerializableValidationResult validationResult)
{
return validationResult.IsValid
- ? CreateSuccessResponse(validationResult.SuccessMessage)
- : CreateErrorResponse(validationResult.ErrorCode, validationResult.ErrorMessage, validationResult.Errors);
+ ? CreateSuccessResponse(validationResult.SuccessMessage)
+ : CreateErrorResponse(validationResult.ErrorCode, validationResult.ErrorMessage, validationResult.Errors);
}
public static ResponseStatus CreateSuccessResponse(string message)
View
4 src/ServiceStack.ServiceInterface/ServiceStack.ServiceInterface.csproj
@@ -196,6 +196,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServiceResponseException.cs" />
<Compile Include="ServiceRoutesExtensions.cs" />
+ <Compile Include="ServiceUtils.cs" />
<Compile Include="SessionFactory.cs" />
<Compile Include="SessionFeature.cs" />
<Compile Include="Testing\BasicAppHost.cs" />
@@ -208,8 +209,9 @@
</Compile>
<Compile Include="Validation\MultiRuleSetValidatorSelector.cs" />
<Compile Include="Validation\ValidationFilter.cs" />
- <Compile Include="Validation\ValidationHandler.cs" />
+ <Compile Include="Validation\ValidationFeature.cs" />
<Compile Include="Validation\ValidationResultExtensions.cs" />
+ <Compile Include="Validation\ValidatorCache.cs" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
View
116 src/ServiceStack.ServiceInterface/ServiceUtils.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Net;
+using ServiceStack.Common.Utils;
+using ServiceStack.Common.Web;
+using ServiceStack.ServiceHost;
+using ServiceStack.ServiceInterface.ServiceModel;
+using ServiceStack.Text;
+
+namespace ServiceStack.ServiceInterface
+{
+ public static class ServiceUtils
+ {
+ /// <summary>
+ /// Naming convention for the ResponseStatus property name on the response DTO
+ /// </summary>
+ public const string ResponseStatusPropertyName = "ResponseStatus";
+
+ /// <summary>
+ /// Naming convention for the request's Response DTO
+ /// </summary>
+ public const string ResponseDtoSuffix = "Response";
+
+ public static object CreateErrorResponse<TRequest>(TRequest request, Exception ex, ResponseStatus responseStatus)
+ {
+ var responseDto = CreateResponseDto(request, responseStatus);
+
+ var statusCode = HttpStatusCode.InternalServerError;
+ if (responseDto != null)
+ {
+ var httpError = ex as IHttpError;
+ if (httpError != null)
+ {
+ httpError.Response = responseDto;
+ return httpError;
+ }
+
+ if (ex is NotImplementedException) statusCode = HttpStatusCode.MethodNotAllowed;
+ else if (ex is ArgumentException) statusCode = HttpStatusCode.BadRequest;
+ }
+
+ var errorCode = ex.GetType().Name;
+ var errorMsg = ex.Message;
+ if (responseStatus != null)
+ {
+ errorCode = responseStatus.ErrorCode ?? errorCode;
+ errorMsg = responseStatus.Message ?? errorMsg;
+ }
+
+ return new HttpError(responseDto, statusCode, errorCode, errorMsg);
+ }
+
+ /// <summary>
+ /// Create an instance of the service response dto type and inject it with the supplied responseStatus
+ /// </summary>
+ /// <param name="request"></param>
+ /// <param name="responseStatus"></param>
+ /// <returns></returns>
+ public static object CreateResponseDto<TRequest>(TRequest request, ResponseStatus responseStatus)
+ {
+ // Predict the Response message type name
+ // Get the type
+ var responseDtoType = AssemblyUtils.FindType(GetResponseDtoName(request));
+ var responseDto = CreateResponseDto(request);
+
+ if (responseDto == null)
+ return null;
+
+ // For faster serialization of exceptions, services should implement IHasResponseStatus
+ var hasResponseStatus = responseDto as IHasResponseStatus;
+ if (hasResponseStatus != null)
+ {
+ hasResponseStatus.ResponseStatus = responseStatus;
+ }
+ else
+ {
+ // Get the ResponseStatus property
+ var responseStatusProperty = responseDtoType.GetProperty(ResponseStatusPropertyName);
+
+ if (responseStatusProperty != null)
+ {
+ // Set the ResponseStatus
+ ReflectionUtils.SetProperty(responseDto, responseStatusProperty, responseStatus);
+ }
+ }
+
+ // Return an Error DTO with the exception populated
+ return responseDto;
+ }
+
+ /// <summary>
+ /// Create an instance of the response dto based on the requestDto type and default naming convention
+ /// </summary>
+ /// <param name="request"></param>
+ /// <returns></returns>
+ public static object CreateResponseDto<TRequest>(TRequest request)
+ {
+ // Get the type
+ var responseDtoType = AssemblyUtils.FindType(GetResponseDtoName(request));
+
+ if (responseDtoType == null)
+ {
+ // We don't support creation of response messages without a predictable type name
+ return null;
+ }
+
+ // Create an instance of the response message for this request
+ var responseDto = ReflectionUtils.CreateInstance(responseDtoType);
+ return responseDto;
+ }
+
+ public static string GetResponseDtoName<TRequest>(TRequest request)
+ {
+ return typeof(TRequest).FullName + ResponseDtoSuffix;
+ }
+ }
+}
View
54 src/ServiceStack.ServiceInterface/Validation/ValidationFeature.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Funq;
+using ServiceStack.FluentValidation;
+using ServiceStack.ServiceHost;
+using ServiceStack.WebHost.Endpoints;
+using ServiceStack.Text;
+
+namespace ServiceStack.ServiceInterface.Validation
+{
+ public static class ValidationFeature
+ {
+ public static void Init(IAppHost appHost)
+ {
+ var filter = new ValidationFilter();
+ appHost.RequestFilters.Add(filter.ValidateRequest);
+ }
+
+ public static void RegisterValidators(this Container container, params Assembly[] assemblies)
+ {
+ foreach (var assembly in assemblies)
+ {
+ var validators =
+ from t in assembly.GetTypes()
+ where
+ !t.IsAbstract
+ && t.IsGenericType
+ && t.GetGenericTypeDefinition() == typeof(IValidator<>)
+ select t;
+
+ foreach (var validator in validators)
+ {
+ var baseType = validator.BaseType;
+ while (!baseType.IsGenericType)
+ {
+ baseType = baseType.BaseType;
+ }
+
+ var dtoType = baseType.GetGenericArguments()[0];
+ var validatorType = typeof(IValidator<>).MakeGenericType(dtoType);
+
+ var registerFn = typeof(Container).GetMethods(BindingFlags.Public)
+ .First(x => x.Name == "Register"
+ && x.ReturnType == typeof(void) && x.GetParameters().Length == 1)
+ .MakeGenericMethod(validatorType);
+
+ registerFn.Invoke(container, new[] { validator });
+ }
+ }
+ }
+ }
+}
View
71 src/ServiceStack.ServiceInterface/Validation/ValidationFilter.cs
@@ -1,36 +1,35 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using ServiceStack.ServiceHost;
-using ServiceStack.FluentValidation;
-using System.Reflection;
-using ServiceStack.FluentValidation.Internal;
-using ServiceStack.Text;
-using ServiceStack.ServiceInterface.ServiceModel;
-using ServiceStack.Common.Utils;
-using ServiceStack.WebHost.Endpoints.Extensions;
-
-namespace ServiceStack.ServiceInterface.Validation
-{
- public class ValidationFilter
- {
- public void ValidateRequest(IHttpRequest req, IHttpResponse res, object requestDto)
- {
- var validatorType = typeof(IValidator<>).MakeGenericType(requestDto.GetType());
- var resolver = typeof(IHttpRequest).GetMethod("TryResolve")
- .MakeGenericMethod(validatorType);
-
- var validator = (IValidator)resolver.Invoke(req, null);
- if (validator != null)
- {
- string ruleSet = req.HttpMethod;
- var validationResult = validator.Validate(new ValidationContext(requestDto, null, new MultiRuleSetValidatorSelector(ruleSet)));
-
- var translator = new ResponseStatusTranslator();
- ResponseStatus responseStatus = translator.Parse(validationResult.AsSerializable());
- res.WriteToResponse(req, responseStatus);
- }
- }
- }
-}
+using ServiceStack.ServiceHost;
+using ServiceStack.FluentValidation;
+using ServiceStack.ServiceInterface.ServiceModel;
+using ServiceStack.Validation;
+using ServiceStack.WebHost.Endpoints;
+using ServiceStack.WebHost.Endpoints.Extensions;
+
+namespace ServiceStack.ServiceInterface.Validation
+{
+ public class ValidationFilter
+ {
+ public void ValidateRequest(IHttpRequest req, IHttpResponse res, object requestDto)
+ {
+ var validatorType = typeof(IValidator<>).MakeGenericType(requestDto.GetType());
+ var resolver = typeof(IHttpRequest).GetMethod("TryResolve")
+ .MakeGenericMethod(validatorType);
+
+ var validator = (IValidator)resolver.Invoke(req, null);
+ if (validator != null)
+ {
+ string ruleSet = req.HttpMethod;
+ var validationResult = validator.Validate(
+ new ValidationContext(requestDto, null, new MultiRuleSetValidatorSelector(ruleSet)));
+
+ var responseStatus = ResponseStatusTranslator.Instance.Parse(validationResult.AsSerializable());
+
+ var errorResponse = ServiceUtils.CreateErrorResponse(requestDto,
+ new SerializableValidationException(validationResult.AsSerializable()),
+ responseStatus);
+
+ res.WriteToResponse(req, errorResponse);
+ }
+ }
+ }
+}
View
19 src/ServiceStack.ServiceInterface/Validation/ValidationHandler.cs
@@ -1,19 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using ServiceStack.WebHost.Endpoints;
-using ServiceStack.ServiceHost;
-using ServiceStack.FluentValidation;
-
-namespace ServiceStack.ServiceInterface.Validation
-{
- public static class ValidationHandler
- {
- public static void Init(IAppHost appHost)
- {
- var filter = new ValidationFilter();
- appHost.RequestFilters.Add(filter.ValidateRequest);
- }
- }
-}
View
48 src/ServiceStack.ServiceInterface/Validation/ValidatorCache.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Threading;
+using ServiceStack.FluentValidation;
+using ServiceStack.ServiceHost;
+
+namespace ServiceStack.ServiceInterface.Validation
+{
+ public static class ValidatorCache
+ {
+ private static Dictionary<Type, ResolveValidatorDelegate> delegateCache
+ = new Dictionary<Type, ResolveValidatorDelegate>();
+
+ private delegate IValidator ResolveValidatorDelegate(IHttpRequest httpReq);
+
+ public static IValidator GetValidator(IHttpRequest httpReq, Type type)
+ {
+ ResolveValidatorDelegate parseFn;
+ if (delegateCache.TryGetValue(type, out parseFn)) return parseFn.Invoke(httpReq);
+
+ var genericType = typeof(ValidatorCache<>).MakeGenericType(type);
+ var mi = genericType.GetMethod("GetValidator", BindingFlags.Public | BindingFlags.Static);
+ parseFn = (ResolveValidatorDelegate)Delegate.CreateDelegate(typeof(ResolveValidatorDelegate), mi);
+
+ Dictionary<Type, ResolveValidatorDelegate> snapshot, newCache;
+ do
+ {
+ snapshot = delegateCache;
+ newCache = new Dictionary<Type, ResolveValidatorDelegate>(delegateCache);
+ newCache[type] = parseFn;
+
+ } while (!ReferenceEquals(
+ Interlocked.CompareExchange(ref delegateCache, newCache, snapshot), snapshot));
+
+ return parseFn.Invoke(httpReq);
+ }
+ }
+
+ public class ValidatorCache<T>
+ {
+ public static IValidator GetValidator(IHttpRequest httpReq)
+ {
+ return httpReq.TryResolve<IValidator<T>>();
+ }
+ }
+
+}
View
140 tests/ServiceStack.WebHost.Endpoints.Tests/CustomerService.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Runtime.Serialization;
+using System.Text.RegularExpressions;
+using Funq;
+using NUnit.Framework;
+using ServiceStack.ServiceClient.Web;
+using ServiceStack.FluentValidation;
+using ServiceStack.Service;
+using ServiceStack.ServiceHost;
+using ServiceStack.ServiceInterface;
+using ServiceStack.ServiceInterface.Validation;
+using ServiceStack.Text;
+using ServiceStack.WebHost.Endpoints;
+using ServiceStack.WebHost.Endpoints.Support;
+using ServiceStack.WebHost.Endpoints.Tests;
+using ServiceStack.WebHost.Endpoints.Tests.Support;
+using ServiceStack.WebHost.Endpoints.Tests.Support.Host;
+
+namespace ServiceStack.WebHost.IntegrationTests.Services
+{
+ [RestService("/customers")]
+ [RestService("/customers/{Id}")]
+ public class Customers
+ {
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public string Company { get; set; }
+ public decimal Discount { get; set; }
+ public string Address { get; set; }
+ public string Postcode { get; set; }
+ public bool HasDiscount { get; set; }
+ }
+
+ public class CustomersValidator : AbstractValidator<Customers>
+ {
+ public CustomersValidator()
+ {
+ RuleSet(ApplyTo.Post | ApplyTo.Put, () => {
+ RuleFor(x => x.LastName).NotEmpty();
+ RuleFor(x => x.FirstName).NotEmpty().WithMessage("Please specify a first name");
+ RuleFor(x => x.Company).NotNull();
+ RuleFor(x => x.Discount).NotEqual(0).When(x => x.HasDiscount);
+ RuleFor(x => x.Address).Length(20, 250);
+ RuleFor(x => x.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
+ });
+ }
+
+ static readonly Regex UsPostCodeRegEx = new Regex(@"^\d{5}(-\d{4})?$", RegexOptions.Compiled);
+
+ private bool BeAValidPostcode(string postcode)
+ {
+ return UsPostCodeRegEx.IsMatch(postcode);
+ }
+ }
+
+ public class CustomersResponse
+ {
+ public Customers Result { get; set; }
+ }
+
+ public class CustomerService : RestServiceBase<Customers>
+ {
+ public override object OnGet(Customers request)
+ {
+ return new CustomersResponse { Result = request };
+ }
+
+ public override object OnPost(Customers request)
+ {
+ return new CustomersResponse { Result = request };
+ }
+
+ public override object OnPut(Customers request)
+ {
+ return new CustomersResponse { Result = request };
+ }
+
+ public override object OnDelete(Customers request)
+ {
+ return new CustomersResponse { Result = request };
+ }
+ }
+
+ [TestFixture]
+ public class CustomerServiceValidationTests
+ {
+ private const string ListeningOn = "http://localhost:82/";
+
+ public class ValidationAppHostHttpListener
+ : AppHostHttpListenerBase
+ {
+
+ public ValidationAppHostHttpListener()
+ : base("Validation Tests", typeof(CustomerService).Assembly) { }
+
+ public override void Configure(Container container)
+ {
+ ValidationFeature.Init(this);
+ container.RegisterValidators(typeof(CustomersValidator).Assembly);
+ }
+ }
+
+ ValidationAppHostHttpListener appHost;
+
+ [TestFixtureSetUp]
+ public void OnTestFixtureSetUp()
+ {
+ appHost = new ValidationAppHostHttpListener();
+ appHost.Init();
+ appHost.Start(ListeningOn);
+ }
+
+ [TestFixtureTearDown]
+ public void OnTestFixtureTearDown()
+ {
+ appHost.Dispose();
+ }
+
+ protected IServiceClient CreateNewServiceClient()
+ {
+ EndpointHandlerBase.ServiceManager = new ServiceManager(true, typeof(SecureService).Assembly);
+ return new DirectServiceClient(EndpointHandlerBase.ServiceManager);
+ }
+
+ [Test]
+ public void Post_empty_request_throws_validation_exception()
+ {
+ try
+ {
+ var client = CreateNewServiceClient();
+ var response = client.Send<Customers>(new Customers());
+ Assert.Fail("Should throw Validation Exception");
+ }
+ catch (WebServiceException ex)
+ {
+ throw ex;
+ }
+ }
+ }
+}
View
3  tests/ServiceStack.WebHost.Endpoints.Tests/ServiceStack.WebHost.Endpoints.Tests.csproj
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -123,6 +123,7 @@
<Compile Include="AsyncRestClientTests.cs" />
<Compile Include="AsyncServiceClientTests.cs" />
<Compile Include="CompressionTests.cs" />
+ <Compile Include="CustomerService.cs" />
<Compile Include="HttpResultContentTypeTests.cs" />
<Compile Include="RemoteEndDropsConnectionTests.cs" />
<Compile Include="IocServiceTests.cs" />
View
4 tests/ServiceStack.WebHost.IntegrationTests/Global.asax.cs
@@ -14,6 +14,7 @@
using ServiceStack.Redis.Messaging;
using ServiceStack.ServiceHost;
using ServiceStack.ServiceInterface;
+using ServiceStack.ServiceInterface.Validation;
using ServiceStack.WebHost.Endpoints;
using ServiceStack.WebHost.IntegrationTests.Services;
@@ -76,6 +77,9 @@ public override void Configure(Container container)
var resetMovies = this.Container.Resolve<ResetMoviesService>();
resetMovies.Post(null);
+ ValidationFeature.Init(this);
+ container.RegisterValidators(typeof(CustomersValidator).Assembly);
+
//var onlyEnableFeatures = Feature.All.Remove(Feature.Jsv | Feature.Soap);
SetConfig(new EndpointHostConfig {
//EnableFeatures = onlyEnableFeatures,
View
1  tests/ServiceStack.WebHost.IntegrationTests/ServiceStack.WebHost.IntegrationTests.csproj
@@ -117,6 +117,7 @@
</Compile>
<Compile Include="Services\BatchWidgetValidationService.cs" />
<Compile Include="Services\CachedMoviesService.cs" />
+ <Compile Include="Services\CustomerService.cs" />
<Compile Include="Services\SessionService.cs" />
<Compile Include="Services\MqHostStatsService.cs" />
<Compile Include="Services\HttpResultsService.cs" />
View
73 tests/ServiceStack.WebHost.IntegrationTests/Services/CustomerService.cs
@@ -0,0 +1,73 @@
+using System.Runtime.Serialization;
+using System.Text.RegularExpressions;
+using ServiceStack.Common.Web;
+using ServiceStack.FluentValidation;
+using ServiceStack.ServiceHost;
+using ServiceStack.ServiceInterface;
+
+namespace ServiceStack.WebHost.IntegrationTests.Services
+{
+ [RestService("/customers")]
+ [RestService("/customers/{Id}")]
+ public class Customers
+ {
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public string Company { get; set; }
+ public decimal Discount { get; set; }
+ public string Address { get; set; }
+ public string Postcode { get; set; }
+ public bool HasDiscount { get; set; }
+ }
+
+ public class CustomersValidator : AbstractValidator<Customers>
+ {
+ public CustomersValidator()
+ {
+ RuleSet(ApplyTo.Post | ApplyTo.Put, () => {
+ RuleFor(x => x.LastName).NotEmpty();
+ RuleFor(x => x.FirstName).NotEmpty().WithMessage("Please specify a first name");
+ RuleFor(x => x.Company).NotNull();
+ RuleFor(x => x.Discount).NotEqual(0).When(x => x.HasDiscount);
+ RuleFor(x => x.Address).Length(20, 250);
+ RuleFor(x => x.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
+ });
+ }
+
+ static readonly Regex UsPostCodeRegEx = new Regex(@"^\d{5}(-\d{4})?$", RegexOptions.Compiled);
+
+ private bool BeAValidPostcode(string postcode)
+ {
+ return UsPostCodeRegEx.IsMatch(postcode);
+ }
+ }
+
+ public class CustomersResponse
+ {
+ public Customers Result { get; set; }
+ }
+
+ public class CustomerService : RestServiceBase<Customers>
+ {
+ public override object OnGet(Customers request)
+ {
+ return new CustomersResponse { Result = request };
+ }
+
+ public override object OnPost(Customers request)
+ {
+ return new CustomersResponse { Result = request };
+ }
+
+ public override object OnPut(Customers request)
+ {
+ return new CustomersResponse { Result = request };
+ }
+
+ public override object OnDelete(Customers request)
+ {
+ return new CustomersResponse { Result = request };
+ }
+ }
+
+}
View
5 tests/ServiceStack.WebHost.IntegrationTests/Tests/SessionTests.cs
@@ -2,6 +2,7 @@
using NUnit.Framework;
using ServiceStack.Common;
using ServiceStack.Messaging;
+using ServiceStack.ServiceInterface;
namespace ServiceStack.WebHost.IntegrationTests.Tests
{
@@ -11,6 +12,10 @@ public class SessionTests
[Test]
public void Adhoc()
{
+ var appliesTo = ApplyTo.Post | ApplyTo.Put;
+ Console.WriteLine(appliesTo.ToString());
+ Console.WriteLine(appliesTo.ToDescription());
+ Console.WriteLine(string.Join(", ", appliesTo.ToList().ToArray()));
}
}
}

0 comments on commit 53696aa

Please sign in to comment.
Something went wrong with that request. Please try again.