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;
}
}
Related
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.
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);
...
}
}
I am trying to POST to my ServiceStack service and retrieve the Location header from the response of my CREATED entity. I am not sure whether using IReturn is valid but I am not sure how to access the Response headers from my client.
Can someone help me understand how to interact with the HttpResult properly? There is a test case at the bottom of the code to demonstrate what I want to do.
Here's the codz:
public class ServiceStackSpike
{
public class AppHost : AppHostHttpListenerBase
{
public AppHost() : base("TODOs Tests", typeof(Todo).Assembly) { }
public override void Configure(Container container)
{
//noop
}
}
[Route("/todos", "POST")]
public class Todo:IReturn<HttpResult>
{
public long Id { get; set; }
public string Content { get; set; }
public int Order { get; set; }
public bool Done { get; set; }
}
public class TodosService : Service
{
public object Post(Todo todo)
{
//do stuff here
var result = new HttpResult(todo,HttpStatusCode.Created);
result.Headers[HttpHeaders.Location] = "/tada";
return result;
}
}
public class NewApiTodosTests : IDisposable
{
const string BaseUri = "http://localhost:82/";
AppHost appHost;
public NewApiTodosTests()
{
appHost = new AppHost();
appHost.Init();
appHost.Start(BaseUri);
}
[Fact]
public void Run()
{
var restClient = new JsonServiceClient(BaseUri);
var todo = restClient.Post(new Todo { Content = "New TODO", Order = 1 });
Assert.Equal(todo.Headers[HttpHeaders.Location], "/tada"); //=>fail
}
public void Dispose()
{
appHost.Dispose();
appHost = null;
}
}
}
See the Customizing HTTP Responses ServiceStack wiki page for all the different ways of customizing the HTTP Response.
A HttpResult is just one way customize the HTTP Response. You generally want to include the Absolute Url if you're going to redirect it, e.g:
public object Post(Todo todo)
{
var todo = ...;
return new HttpResult(todo, HttpStatusCode.Created) {
Location = base.Request.AbsoluteUri.CombineWith("/tada")
};
}
Note HTTP Clients will never see a HttpResult DTO. HttpResult is not a DTO itself, it's only purpose is to capture and modify the customized HTTP Response you want.
All ServiceStack Clients will return is the HTTP Body, which in this case is the Todo Response DTO. The Location is indeed added to the HTTP Response headers, and to see the entire HTTP Response returned you should use a HTTP sniffer like Fiddler, WireShark or Chrome's WebInspector.
If you want to access it using ServiceStack's HTTP Clients, you will need to add a Response Filter that gives you access to the HttpWebResponse, e.g:
restClient.ResponseFilter = httpRes => {
Assert.Equal(httpRes.Headers[HttpHeaders.Location], "/tada");
};
Todo todo = restClient.Post(new Todo { Content = "New TODO", Order = 1 });
Inspecting Response Headers using Web Request Extensions
Another lightweight alternative if you just want to inspect the HTTP Response is to use ServiceStack's Convenient WebRequest extension methods, e.g:
var url = "http://path/to/service";
var json = url.GetJsonFromUrl(httpRes => {
Assert.Equal(httpRes.Headers[HttpHeaders.Location], "/tada");
});