I am trying to implement service gateway pattern according to Service Gateway tutorial to support in-process handing via InProcessServiceGateway and external calling via JsonServiceClient in case ServiceStack service is deployed standalone. I use ServiceStack 4.5.8 version.
Validation feature works fine, but with InProcessServiceGateway, the failed validation throws ValidationException which directly results in a ServiceStack.FluentValidation.ValidationException in the client rather than populating ResponseStatus property of MyResponseDto. And I also tried GlobalRequestFilters and ServiceExceptionHandlers, both of them seem to work fine to capture ValidationException only with JsonHttpClient but InProcessServiceGateway.
Is there any way to make ValidationException thrown by InProcessServiceGateway captured and translated into Dto's ResponseStatus? Thanks.
My AppHost:
//Register CustomServiceGatewayFactory
container.Register<IServiceGatewayFactory>(x => new CustomServiceGatewayFactory()).ReusedWithin(ReuseScope.None);
//Validation feature
Plugins.Add(new ValidationFeature());
//Note: InProcessServiceGateway cannot reach here.
GlobalRequestFilters.Add((httpReq, httpRes, requestDto) =>
{
...
});
//Note: InProcessServiceGateway cannot reach here.
ServiceExceptionHandlers.Add((httpReq, request, ex) =>
{
...
});
My CustomServiceGatewayFactory:
public class CustomServiceGatewayFactory : ServiceGatewayFactoryBase
{
private IRequest _req;
public override IServiceGateway GetServiceGateway(IRequest request)
{
_req = request;
return base.GetServiceGateway(request);
}
public override IServiceGateway GetGateway(Type requestType)
{
var standaloneHosted = false;
var apiBaseUrl = string.Empty;
var apiSettings = _req.TryResolve<ApiSettings>();
if (apiSettings != null)
{
apiBaseUrl = apiSettings.ApiBaseUrl;
standaloneHosted = apiSettings.StandaloneHosted;
}
var gateway = !standaloneHosted
? (IServiceGateway)base.localGateway
: new JsonServiceClient(apiBaseUrl)
{
BearerToken = _req.GetBearerToken()
};
return gateway;
}
}
My client base controller (ASP.NET Web API):
public virtual IServiceGateway ApiGateway
{
get
{
var serviceGatewayFactory = HostContext.AppHost.TryResolve<IServiceGatewayFactory>();
var serviceGateway = serviceGatewayFactory.GetServiceGateway(HttpContext.Request.ToRequest());
return serviceGateway;
}
}
My client controller action (ASP.NET Web API):
var response = ApiGateway.Send<UpdateCustomerResponse>(new UpdateCustomer
{
CustomerGuid = customerGuid,
MobilePhoneNumber = mobilePhoneNumber
ValidationCode = validationCode
});
if (!response.Success)
{
return this.Error(response, response.ResponseStatus.Message);
}
My UpdateCustomer request DTO:
[Route("/customers/{CustomerGuid}", "PUT")]
public class UpdateCustomer : IPut, IReturn<UpdateCustomerResponse>
{
public Guid CustomerGuid { get; set; }
public string MobilePhoneNumber { get; set; }
public string ValidationCode { get; set; }
}
My UpdateCustomerValidator:
public class UpdateCustomerValidator : AbstractValidator<UpdateCustomer>
{
public UpdateCustomerValidator(ILocalizationService localizationService)
{
ValidatorOptions.CascadeMode = CascadeMode.StopOnFirstFailure;
RuleFor(x => x.ValidationCode)
.NotEmpty()
.When(x => !string.IsNullOrWhiteSpace(x.MobilePhoneNumber))
.WithErrorCode(((int)ErrorCode.CUSTOMER_VALIDATIONCODE_EMPTY).ToString())
.WithMessage(ErrorCode.CUSTOMER_VALIDATIONCODE_EMPTY.GetLocalizedEnum(localizationService, Constants.LANGUAGE_ID));
}
}
My UpdateCustomerResponse DTO:
public class UpdateCustomerResponse
{
/// <summary>
/// Return true if successful; return false, if any error occurs.
/// </summary>
public bool Success { get; set; }
/// <summary>
/// Represents error details, populated only when any error occurs.
/// </summary>
public ResponseStatus ResponseStatus { get; set; }
}
ServiceStack 4.5.8's InProcessServiceGateway source code:
private TResponse ExecSync<TResponse>(object request)
{
foreach (var filter in HostContext.AppHost.GatewayRequestFilters)
{
filter(req, request);
if (req.Response.IsClosed)
return default(TResponse);
}
if (HostContext.HasPlugin<ValidationFeature>())
{
var validator = ValidatorCache.GetValidator(req, request.GetType());
if (validator != null)
{
var ruleSet = (string)(req.GetItem(Keywords.InvokeVerb) ?? req.Verb);
var result = validator.Validate(new ValidationContext(
request, null, new MultiRuleSetValidatorSelector(ruleSet)) {
Request = req
});
if (!result.IsValid)
throw new ValidationException(result.Errors);
}
}
var response = HostContext.ServiceController.Execute(request, req);
var responseTask = response as Task;
if (responseTask != null)
response = responseTask.GetResult();
return ConvertToResponse<TResponse>(response);
}
ServiceStack's Service Gateways now convert validation exceptions into WebServiceExceptions from this commit which is available from v4.5.13 that's now available on MyGet.
Related
I have an implementation, where I am calling an autoquery operation via the service gateway. The service gateway will successfully call both internal and external operations. However, any autoquery operation fails because it is not getting the connection string set. These same autoquery operations work just fine when called directly and not through the gateway.
Here is the stack trace.
at ServiceStack.OrmLite.OrmLiteConnectionFactory.CreateDbConnection() in C:\\BuildAgent\\work\\27e4cc16641be8c0\\src\\ServiceStack.OrmLite\\OrmLiteConnectionFactory.cs:line 70\r\n at ServiceStack.OrmLite.OrmLiteConnectionFactory.OpenDbConnection() in C:\\BuildAgent\\work\\27e4cc16641be8c0\\src\\ServiceStack.OrmLite\\OrmLiteConnectionFactory.cs:line 95\r\n at ServiceStack.ServiceStackHost.GetDbConnection(IRequest req) in C:\\BuildAgent\\work\\3481147c480f4a2f\\src\\ServiceStack\\ServiceStackHost.Runtime.cs:line 691\r\n at ServiceStack.AutoQuery.GetDb(Type type, IRequest req) in C:\\BuildAgent\\work\\3481147c480f4a2f\\src\\ServiceStack.Server\\AutoQueryFeature.cs:line 598\r\n at ServiceStack.AutoQuery.CreateQuery[From](IQueryDb`1 dto, Dictionary`2 dynamicParams, IRequest req) in C:\\BuildAgent\\work\\3481147c480f4a2f\\src\\ServiceStack.Server\\AutoQueryFeature.cs:line 608\r\n at IDOE.SecurityPortal.Api.ServiceInterface.OrganizationUserStaffTypeService.Get(QueryOrganizationUserStaffTypes query) in E:\\source\\repos\\Azure - Security Portal\\src\\IDOE.SecurityPortal\\IDOE.SecurityPortal.Api.ServiceInterface\\OrganizationUserStaffTypeService.cs:line 47\r\n at ServiceStack.Host.ServiceRunner`1.<ExecuteAsync>d__15.MoveNext() in C:\\BuildAgent\\work\\3481147c480f4a2f\\src\\ServiceStack\\Host\\ServiceRunner.cs:line 133
Database Connection Registration in startup.cs
var dbFacotry = container.Resolve<IDbConnectionFactory>();
dbFacotry.RegisterConnection("SecPortal", AppSettings.Get<string>("SQLSERVER-SECPORTAL-CONNECTIONSTRING"), SqlServer2017Dialect.Provider);
dbFacotry.RegisterConnection("EdfiMdm", AppSettings.Get<string>("SQLSERVER-EDFIMDM-CONNECTIONSTRING"), SqlServer2017Dialect.Provider);
Plugins.Add(new AutoQueryFeature { IncludeTotal = true });
AutoQuery Definition
[Authenticate]
[RequiredClaim("scope", "secprtl-read")]
[Route("/files", Verbs = "GET")]
[ConnectionInfo(NamedConnection = "SecPortal")]
public class QueryFiles : QueryDb<Types.File>
{
[QueryDbField(Field = "Id", Template = "({Value} IS NULL OR {Field} = {Value})")]
public int? Id { get; set; }
[QueryDbField(Field = "FileName", Template = "({Value} IS NULL OR UPPER({Field}) LIKE UPPER({Value}))", ValueFormat = "%{0}%")]
public string FileName { get; set; }
[QueryDbField(Field = "UserId", Template = "({Value} IS NULL OR UPPER({Field}) LIKE UPPER({Value}))", ValueFormat = "%{0}%")]
public string UserId { get; set; }
[QueryDbField(Field = "StateOrganizationId", Template = "({Value} IS NULL OR UPPER({Field}) LIKE UPPER({Value}))", ValueFormat = "%{0}%")]
public string StateOrganizationId { get; set; }
[QueryDbField(Field = "Notes", Template = "({Value} IS NULL OR UPPER({Field}) LIKE UPPER({Value}))", ValueFormat = "%{0}%")]
public string Notes { get; set; }
}
Code calling the service
public class ContactService : Service
{
public ContactService()
{
}
public async Task<object> Post(PostContact request)
{
try
{
var files = base.Gateway.Send(new QueryFiles() { });
return new Contact() { Name = request.Name };
}
catch (Exception ex)
{
throw ex;
}
}
}
Custom Service Gateway
public class CustomServiceGatewayFactory : ServiceGatewayFactoryBase
{
private IRequest request;
public override IServiceGateway GetServiceGateway(IRequest request)
{
this.request = request;
return base.GetServiceGateway(request);
}
public override IServiceGateway GetGateway(Type requestType)
{
var isLocal = HostContext.Metadata.RequestTypes.Contains(requestType);
if (isLocal)
{
return base.localGateway;
}
else
{
return new JsonServiceClient("https://localhost:6001")
{
BearerToken = request.GetBearerToken()
};
}
}
}
Custom service gateway registration in startup.cs
container.Register<IServiceGatewayFactory>(x => new CustomServiceGatewayFactory()).ReusedWithin(ReuseScope.None);
The call being made in the service class is a local call. Calling an external service that uses autoquery works just fine. I can also call the local service directly with no problem.
I created a custom autoquery method in the service interface, I noticed that the db connection info was not populated on the request.items array. So I manually added that information to the request, and it worked as expected. So somehow, in my setup, the autoquery operations that are called locally, the db connection info is not getting added to the request object.
Request Filter Attributes like [ConnectionInfo] is only applied on HTTP Requests, not internal Service Gateway requests.
The Connection info isn't configured because it's not annotated on your PostContact Service Request DTO that calls the in-procces Service Gateway.
You can have the [ConnectionInfo] on the QueryFiles AutoQuery Request DTO attached to the current Request with:
public async Task<object> Post(PostContact request)
{
try
{
typeof(QueryFiles).FirstAttribute<ConnectionInfoAttribute>()
.Execute(Request,Response,request);
var files = base.Gateway.Send(new QueryFiles() { });
return new Contact() { Name = request.Name };
}
catch (Exception ex)
{
throw ex;
}
}
I am getting an error on SendAll in a unittest
This works fine...
using (var service = HostContext.ResolveService<DeviceService>(authenticatedRequest))
{
service.Put(new AddConfig { ConfigName = key.KeyName, ConfigValue = key.Value, DeviceId = 0 });
}}
ServiceStack.WebServiceException: 'The operation 'AddConfig[]' does not exist for this service'
//DeviceConfig
/// <summary>
/// To insert new Config
/// </summary>
/// <returns> New row Id or -1 on error</returns>
public long Any(AddConfig request)
{
try
{
//convert request to model
var perm = request.ConvertTo<DeviceConfig>();
//log user
perm.AuditUserId = UserAuth.Id;
//insert data
var insert = Db.Insert(perm, selectIdentity:true);
//log inserted data
LogInfo(typeof(DeviceConfig), perm, LogAction.Insert);
return insert;
}
//on error log error and data
catch (Exception e)
{
Log.Error(e);
}
return -1;
}
[Route("/Config", "PUT")]
public class AddConfig : IReturn<long>
{
public int DeviceId { get; set; }
public string ConfigName { get; set; }
public string ConfigValue { get; set; }
}
public const string TestingUrl = "http://localhost:5123/";
public void DeviceX400Test(string deviceTemaplateFile)
{
//Resolve auto-wired service
WireUpService<DeviceService>();
var requests = new[]
{
new AddConfig { ConfigName = "Foo" },
new AddConfig { ConfigName = "Bar" },
new AddConfig { ConfigName = "Baz" },
};
var client = new JsonServiceClient(TestingUrl);
var deviceConfigs = client.SendAll(requests);
}
MY ServiceBase for Unit Testting that builds from my .netcore appsettings.Json file
public abstract class ServiceTestBase: IDisposable
{
//private readonly ServiceStackHost appHost;
public BasicRequest authenticatedRequest;
public const string TestingUrl = "http://localhost:5123/";
public SeflHostedAppHost apphost;
public ServiceTestBase()
{
var licenseKeyText = "********************************";
Licensing.RegisterLicense(licenseKeyText);
apphost = (SeflHostedAppHost) new SeflHostedAppHost()
.Init()
.Start(TestingUrl);
//regsiter a test user
apphost.Container.Register<IAuthSession>(c => new AuthUserSession { FirstName = "test", IsAuthenticated = true });
}
public void WireUpService<T>() where T : class
{
//var service = apphost.Container.Resolve<T>(); //Resolve auto-wired service
apphost.Container.AddTransient<T>();
authenticatedRequest = new BasicRequest
{
Items = {
[Keywords.Session] = new AuthUserSession { FirstName = "test" , UserAuthId="1", IsAuthenticated = true}
}
};
}
public virtual void Dispose()
{
apphost.Dispose();
}
}
//Create your ServiceStack AppHost with only the dependencies your tests need
/// <summary>
/// This class may need updates to match what is in the mvc.service apphost.cs
/// </summary>
public class SeflHostedAppHost : AppSelfHostBase
{
public IConfigurationRoot Configuration { get; set; }
public SeflHostedAppHost() : base("Customer REST Example", typeof(StartupService).Assembly) { }
public override void Configure(Container container)
{
var file = Path.GetFullPath(#"../../../../cbw.services");
var builder = new ConfigurationBuilder().SetBasePath(file).AddJsonFile("appsettings.json").AddJsonFile("appsettings.LocalSQLServer.json", optional: true);
Configuration = builder.Build();
var sqlString = Configuration["ConnectionString"];
RegisterServiceStack();
//container.Register<ServiceStack.Data.IDbConnectionFactory>(new OrmLiteConnectionFactory(sqlString,SqlServerDialect.Provider));
container.Register<IDbConnectionFactory>(new OrmLiteConnectionFactory(":memory:", SqliteDialect.Provider));
container.RegisterAutoWired<DatabaseInitService>();
var service = container.Resolve<DatabaseInitService>();
container.Register<IAuthRepository>(c =>
new MyOrmLiteAuthRepository(c.Resolve<IDbConnectionFactory>())
{
UseDistinctRoleTables = true,
});
container.Resolve<IAuthRepository>().InitSchema();
var authRepo = (OrmLiteAuthRepository)container.Resolve<IAuthRepository>();
service.ResetDatabase();
SessionService.ResetUsers(authRepo);
service.InitializeTablesAndData();
//Logging
LogManager.LogFactory = new SerilogFactory(new LoggerConfiguration()
.ReadFrom.Configuration(Configuration)
.Destructure.UsingAttributes()
.CreateLogger());
Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
Serilog.Debugging.SelfLog.Enable(Console.Error);
ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
//ILog Log = LogManager.GetLogger(typeof(StartupService));
log.InfoFormat("Applicaiton Starting {Date}", DateTime.Now);
}
public void RegisterServiceStack()
{
var licenseKeyText = "****************************";
Licensing.RegisterLicense(licenseKeyText);
}
}
My Xunit Test
public class DeviceTemplateTest : ServiceTestBase
{
//Post Data
//Device Sends State.XML
[Theory]
[InlineData("C:\\DeviceTemplate.txt")]
public void DeviceX400Test(string deviceTemaplateFile)
{
//Resolve auto-wired service
WireUpService<DeviceService>();
var parser = new FileIniDataParser();
IniData data = parser.ReadFile(deviceTemaplateFile);
List<AddConfig> batch = new List<AddConfig>();
//Iterate through all the sections
foreach (SectionData section in data.Sections)
{
Console.WriteLine("[" + section.SectionName + "]");
//Iterate through all the keys in the current section
//printing the values
foreach (KeyData key in section.Keys)
{
batch.Add(new AddConfig { ConfigName = key.KeyName, ConfigValue = key.Value, DeviceId = 0 });
// using (var service = HostContext.ResolveService<DeviceService>(authenticatedRequest))
//{
// service.Any(new AddConfig { ConfigName = key.KeyName, ConfigValue = key.Value, DeviceId = 0 });
//}
}
}
var client = new JsonServiceClient(TestingUrl);
var deviceConfigs = client.SendAll(batch.ToArray());
}
}
Firstly, you should never return value types in Services, your Request DTO says it returns a DeviceConfig Response Type DTO:
public class AddConfig : IReturn<DeviceConfig> { ... }
Which your Service should be returning instead.
I'm unclear how this can work or compile:
using (var service = HostContext.ResolveService<DeviceService>(authenticatedRequest))
{
service.SendAll(new AddConfig {
ConfigName = key.KeyName, ConfigValue = key.Value, DeviceId = 0
});
}
Since it's calling methods on the DeviceService Service class directly and there is no SendAll() method on the Service class (or in your example), were you using the Service Gateway instead?
I can't tell what the issue is from here without seeing the full source code and being able to repro the issue but it sounds like AddConfig is not recognized as a Service, is it appearing in the /metadata page? If not do you have it a class that inherits Service?
Otherwise if you can post a minimal repro on GitHub, I'll be able to identify the issue.
Can I retrieve the operation DTO from url route inside a service stack service ?
Example :
public class HelloService : IService
{
public object Any(HelloRequest request)
{
//Here I want to retrieve operation Dto.
//In this case if request.AnotherApiRoute is "/another?Age=33"
//then result could be operation AnotherRequest
return new HelloResponse { Result = "Hello, " + name };
}
}
public class AnotherApiService : IService
{
public object Another(AnotherRequest request)
{
return new AnotherResponse { Result = "Your Age : " + Age };
}
}
//OPERATIONS
[Route("/hello/{Name}")]
public class Hello : IReturn<HelloResponse>
{
public string Name { get; set; }
public string AnotherApiRoute {get; set;}
}
public class HelloResponse
{
public string Result { get; set; }
}
[Route("/another/{Age}")]
public class AnotherRequest : IReturn<AnotherResponse>
{
public string Age { get; set; }
}
public class AnotherResponse
{
public string Result { get; set; }
}
Thanks for your suggests
If you want access to the HTTP Request Context the Service was executed in you should inherit from the convenience Service base class (or have your service also implement IRequiresRequestContext so Request is injected), e.g:
public class HelloService : Service
{
public object Any(Hello request)
{
var pathInfo = base.Request.PathInfo;
return new HelloResponse { Result = "Hello, " + name };
}
}
But what you're after is unclear since the Request DTO is the Operation DTO for that request. If instead you wanted to call another Service from within your Service you can do it with Resolving the Service from the IOC (which also injects the current HTTP Request) with:
public class HelloService : Service
{
public object Any(Hello request)
{
using (var service = base.ResolveService<AnotherService>())
{
var anotherDto = request.ConvertTo<Another>();
return service.Any(anotherDto);
}
}
}
Alternatively you can just execute the Service by passing in the Request DTO, and let ServiceStack call the appropriate Service, e.g:
public class HelloService : Service
{
public object Any(Hello request)
{
var anotherDto = request.ConvertTo<Another>();
return base.ExecuteRequest(anotherDto);
}
}
I know I can manage the routes for the REST-ful interface operations by attributing the DTOs
[Route("/widgets", "GET, POST")]
[DataContract()]
public class GetWidgetsRequest
{
[DataMember]
public string OutletCode { get; set; }
[DataMember]
public IList<Specification> WidgetsCaptured { get; set; }
}
but I have searched and experimented unsuccessfully at trying to affect the default /soap11 appendage to the endpoint for a given SOAP operation.
**POST /soap11 HTTP/1.1**
Host: localhost
Content-Type: text/xml; charset=utf-8
Content-Length: nnn
SOAPAction: GetItemsRequest
A broader question within the question is, what are my options and how to configure the different endpoint settings?
Thanks!
Please read the SOAP Support docs for guidelines and limitations of using SOAP in ServiceStack.
For a different SOAP path, eg ~/services, you can add your own servicestack plugin , that returns your own servicestack soap handler.
public class MySoapFeature : IPlugin
{
private static IHttpHandler GetHandlerForPathParts(string[] pathParts)
{
string str2 = string.Intern(pathParts[0].ToLower());
if (pathParts.Length != 1) return null;
if (str2 == "services")
{
return new MySoapHttpHandler();
}
return null;
}
public IHttpHandler ProcessRequest(string httpMethod, string pathInfo, string filePath)
{
char[] chrArray = new char[] { '/' };
string[] strArrays = pathInfo.TrimStart(chrArray).Split(new char[] { '/' });
if ((int)strArrays.Length == 0)
{
return null;
}
return MySoapFeature.GetHandlerForPathParts(strArrays);
}
public void Register(IAppHost appHost)
{
appHost.CatchAllHandlers.Add(this.ProcessRequest);
}
}
Then implement this handler based on Soap11Handler or Soap12Handler
public class MySoapHttpHandler : Soap11Handler, IHttpHandler
{
public MySoapHttpHandler()
: base((EndpointAttributes)((long)32768))
{
}
public new void ProcessRequest(HttpContext context)
{
if (context.Request.HttpMethod == "GET")
{
(new Soap11WsdlMetadataHandler()).Execute(context);
return;
}
Message message = base.Send(null);
context.Response.ContentType = base.GetSoapContentType(context.Request.ContentType);
using (XmlWriter xmlWriter = XmlWriter.Create(context.Response.OutputStream))
{
message.WriteMessage(xmlWriter);
}
}
public override void ProcessRequest(IHttpRequest httpReq, IHttpResponse httpRes, string operationName)
{
if (httpReq.HttpMethod == "GET")
{
(new Soap11WsdlMetadataHandler()).Execute(httpReq, httpRes);
return;
}
Message message = base.Send(null, httpReq, httpRes);
httpRes.ContentType = base.GetSoapContentType(httpReq.ContentType);
using (XmlWriter xmlWriter = XmlWriter.Create(httpRes.OutputStream))
{
message.WriteMessage(xmlWriter);
}
}
Then register your plugin in the servicestack apphost Configure()
Plugins.Add(new MySoapFeature());
Then create your Dto classes for the request and response. Have "Response" added to the response dto class name. Do NOT put a Route attribute on the request Dto, as it gets routed by the Soap method name in the Xml.
[DataContract(Namespace = "http://mynamespace/schemas/blah/1.0")]
public class MySoapMethod
{}
DataContract(Namespace = "http://mynamespace/schemas/blah/1.0")]
public class MySoapMethodResponse
{
[DataMember]
public string SomeProperty { get; set; }
}
Then have a Service to implement the Soap Dto's
public class SOAPService : Service
{
public MySoapMethodResponse Post(MySoapMethod request)
{
var response = new MySoapMethodResponse();
response.SomeProperty = "blah";
return response;
}
}
I am trying to implement validation feature in ServiceStack to validate my RequestDTO's before calling db operations.
When i try to validate request dto like
ValidationResult result = this.AddBookingLimitValidator.Validate(request);
the code automatically throws a validation error automatically.
I can not even debug service what is happening behind the scenes ? Can i change that behaviour or am i doing something wrong here.
Thanks.
My Request DTO :
[Route("/bookinglimit", "POST")]
[Authenticate]
public class AddBookingLimit : IReturn<AddBookingLimitResponse>
{
public int ShiftId { get; set; }
public DateTime Date { get; set; }
public int Limit { get; set; }
}
My Response DTO :
public class AddBookingLimitResponse
{
public int Id { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}
Validation class :
public class AddBookingLimitValidator : AbstractValidator<AddBookingLimit>
{
public AddBookingLimitValidator()
{
RuleFor(r => r.Limit).GreaterThan(0).WithMessage("Limit 0 dan büyük olmalıdır");
}
}
Service Implementation :
public AddBookingLimitResponse Post(AddBookingLimit request)
{
ValidationResult result = this.AddBookingLimitValidator.Validate(request);
Shift shift = new ShiftRepository().Get(request.ShiftId);
BookingLimit bookingLimit = new BookingLimit
{
RestaurantId = base.UserSession.RestaurantId,
ShiftId = request.ShiftId,
StartDate = request.Date.AddHours(shift.StartHour.Hour).AddMinutes(shift.StartHour.Minute),
EndDate = request.Date.AddHours(shift.EndHour.Hour).AddMinutes(shift.EndHour.Minute),
Limit = request.Limit,
CreateDate = DateTime.Now,
CreatedBy = base.UserSession.UserId,
Status = (byte)Status.Active
};
return new AddBookingLimitResponse
{
Id = new BookingLimitRepository().Add(bookingLimit)
};
}
AppHost code :
container.RegisterValidators(typeof(AddBookingLimitValidator).Assembly);
Plugins.Add(new ValidationFeature());
And i consume the service in c# code:
try
{
AddBookingLimitResponse response = ClientHelper.JsonClient.Post(new AddBookingLimit
{
Date = DateTime.Parse(DailyBookingLimitDateTextBox.Text),
Limit = Convert.ToInt32(DailyBookingLimitTextBox.Text),
ShiftId = Convert.ToInt32(DailyDayTypeSelection.SelectedValue)
});
WebManager.ShowMessage(UserMessages.SaveSuccessful.FormatString(Fields.BookingLimit));
}
catch (WebServiceException ex)
{
WebManager.ShowMessage(ex.ResponseStatus.Message);
}
Right, ServiceStack validates the request DTO before the service gets called if the ValidationFeature is enabled.
To manually invoke the validator in the service, you have to remove this line from your AppHost first:
Plugins.Add(new ValidationFeature());
Please make sure that the validator property in your service has the type IValidator<>, otherwise it won't be injected by the IoC container if you register your validators with container.RegisterValidators(typeof(AddBookingLimitValidator).Assembly).
public class TestService : Service
{
public IValidator<Request> Validator { get; set; }
public RequestResponse Post(Request request)
{
Validator.Validate(request);
...
}
}