Iam new to service stack and have been strugling for hours, trying to make servicestak work for me. For now the major show stopper is that i cann't make the exception part work. I registered all plugins by the book and services work for both REST, Soap, CSV, XML and JSV. The project contains 4 basic test methods for crud operations on a customer object. When an error is thrown i do not get the expected error: ResponseStatus is not set and a generel error is generated. Can some one please help me find out why?
https://dl.dropbox.com/u/101619220/TestingServiceStack.zip
EDIT: Thanks for comment :)
I created a simple AppHost file:
namespace TestingServiceStack
{
public class AppHost : AppHostBase
{
public AppHost()
: base("StarterTemplate ASP.NET Host", typeof(CustomersService).Assembly)
{
}
public override void Configure(Container container)
{
Plugins.Add(new ValidationFeature());
Plugins.Add(new RequestLogsFeature());
SetConfig(new EndpointHostConfig
{
DebugMode = true, //Enable StackTraces in development
});
LogManager.LogFactory = new Log4NetFactory(true);
JsConfig.EmitCamelCaseNames = true;
JsConfig.DateHandler = JsonDateHandler.ISO8601;
Routes.Add<GetCustomers>("/customers", "GET")
.Add<GetCustomers>("/customers/{Id}", "GET")
.Add<AddCustomer>("/customers", "POST")
.Add<UpdateCustomer>("/customers/{Id}", "PUT")
.Add<DeleteCustomer>("/customers/{Id}", "DELETE");
}
public static void Start()
{
new AppHost().Init();
}
}
}
And a service:
namespace TestingServiceStack
{
public class CustomersService : Service
{
#region Logging
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
#endregion
public object Any(GetCustomers request)
{
GetCustomersResponse response = null;
try
{
if (request.Id != "0")
throw HttpError.NotFound("Id {0} throws error".Fmt(request.Id));
response = new GetCustomersResponse {Id = request.Id ?? "notset", Name = "GetCustomers"};
}
catch (Exception ex)
{
Log.Error(base.RequestContext.Get<IHttpRequest>(), ex);
throw;
}
return response;
}
public object Any(AddCustomer request)
{
return new AddCustomerResponse {Id = request.Id, Name = "AddCustomer"};
}
public object Any(UpdateCustomer request)
{
return new UpdateCustomerResponse {Id = request.Id, Name = request.Name};
}
public object Any(DeleteCustomer request)
{
return new DeleteCustomerResponse {Id = request.Id, Name = "DeleteCustomer"};
}
}
}
And the exchanged objects are:
using System.Runtime.Serialization;
using ServiceStack.ServiceInterface.ServiceModel;
namespace TestingServiceStack
{
[DataContract]
public class GetCustomers
{
[DataMember]
public string Id { get; set; }
}
[DataContract]
public class UpdateCustomer
{
[DataMember]
public string Id { get; set; }
[DataMember]
public string Name { get; set; }
}
[DataContract]
public class AddCustomer
{
[DataMember]
public string Id { get; set; }
[DataMember]
public string Name { get; set; }
}
[DataContract]
public class DeleteCustomer
{
[DataMember]
public string Id { get; set; }
}
[DataContract]
public class GetCustomersResponse : IHasResponseStatus
{
[DataMember]
public string Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public ResponseStatus ResponseStatus { get; set; }
}
[DataContract]
public class UpdateCustomerResponse : IHasResponseStatus
{
[DataMember]
public string Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public ResponseStatus ResponseStatus { get; set; }
}
[DataContract]
public class AddCustomerResponse : IHasResponseStatus
{
[DataMember]
public string Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public ResponseStatus ResponseStatus { get; set; }
}
[DataContract]
public class DeleteCustomerResponse : IHasResponseStatus
{
[DataMember]
public string Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public ResponseStatus ResponseStatus { get; set; }
}
}
I use SoapUi to call the method GetCustomers that throws an error if id equals 0, and i would expect the ResponseStatus to be set, but it isn't. When calling from SoapUi i get the following error:
I have no clue how to get reponsestatus set proberly, any hints are appreciated.
I registered all plugins by the book and services work for both REST, Soap, CSV, XML and JSV
To echo #mythz it's much easier to answer direct questions with clearly stated problems with examples of errors or exceptions. My issue with statements/generalizations like above is that I don't know what 'by the book' means nor do I know your concept of working is (could be build succeeds, metadata page is displayed, etc)
ResponseStatus is not set and a generel error is generated.
In your CustomersService class it looks you are throwing an error (HttpError) and catching/logging it. The code will then proceed to return a null response. ServiceStack has native support for throwing of exceptions. If you add a throw into your catch (assuming you want to keep the catch for logging purposes) you should get a populated ResponseStatus.
GetCustomersResponse response = null;
try
{
if (request.Id != "0")
throw HttpError.NotFound("Id {0} throws error".Fmt(request.Id));
response = new GetCustomersResponse {Id = request.Id ?? "notset", Name = "GetCustomers"};
}
catch (Exception ex)
{
Log.Error(base.RequestContext.Get<IHttpRequest>(), ex);
throw; //Let ServiceStack do its thing.
}
return response;
SoapUI
This change may fix the issue with soapUI but I'm unclear as what 'general error' you are receiving. I'm guessing the issue could be due to 'deserializing' a null response.
ServiceStack doesn't support troubleshooting with 3rd party SOAP libraries or client proxies.
See WebServicesTests.cs for examples of exceptions in integration tests. For SOAP you also want to read up on ServiceStack's SOAP Support and its limitations
We had an issue where ResponseStatus wasn't being populated - we'd decorated the Response DTO with DataContract/DataMember, but not the ResponseStatus property. Once we added that decoration, all was joyful.
Related
Good day,
We are experiencing an issue with serialization where a request object set with a value for one property ends up being received by the service with the value assigned to a different property. Please see below for more information.
We are using the 3.9.71 version of ServiceStack NuGet packages. The solution is made up of the following projects:
Project.Host: Used for self-hosting ServiceStack and contains Service classes.
Project.DTO: All services DTOs and surrounding classes.
Project.Tests: Contains unit tests.
The problems has been identified to only one class/service, namely MinimalUser and MinimalUserService, which you can see code for both below:
MinimalUser.cs
namespace Project.DTO
{
[Route("/User/{Identity}", "GET")]
[Route("/User/{Username}", "GET")]
[Route("/User/{DisplayName}", "GET")]
public class MinimalUser : IReturn<MinimalUser>
{
#region Properties
public int? Identity { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Language { get; set; }
public string TimeZone { get; set; }
public string Culture { get; set; }
public List<string> Roles { get; set; }
public List<string> Permissions { get; set; }
public DiscUserDetails DiscUserDetails { get; set; }
#endregion
#region Constructors
public MinimalUser() { }
public MinimalUser(UserAuth auth)
{
if (auth != null)
{
this.Identity = auth.Id;
this.Username = auth.UserName;
this.DisplayName = auth.DisplayName;
this.Email = auth.Email;
this.FirstName = auth.FirstName;
this.LastName = auth.LastName;
this.Language = auth.Language;
this.TimeZone = auth.TimeZone;
this.Culture = auth.Culture;
this.Roles = auth.Roles;
this.Permissions = auth.Permissions;
this.DiscUserDetails = auth.Get<DiscUserDetails>();
}
}
#endregion
#region Methods
public static MinimalUser FromUserAuth(UserAuth auth)
{
return auth == null ? new MinimalUser() : new MinimalUser
{
Identity = auth.Id,
Username = auth.UserName,
DisplayName = auth.DisplayName,
Email = auth.Email,
FirstName = auth.FirstName,
LastName = auth.LastName,
Language = auth.Language,
TimeZone = auth.TimeZone,
Culture = auth.Culture,
Roles = auth.Roles,
Permissions = auth.Permissions,
DiscUserDetails = auth.Get<DiscUserDetails>()
};
}
#endregion
}
}
DiscUserDetails.cs
namespace Project.DTO
{
public class DiscUserDetails
{
public int? LocationId { get; set; }
public bool IsActive { get; set; }
public byte NumberOfFailedLoginAttempts { get; set; }
public bool MustChangePasswordAtNextLogon { get; set; }
public int? LastAcceptedPolicyId { get; set; }
}
}
MinimalUserService.cs
namespace Project.Services
{
[Authenticate]
[RequiredRole(new string[] { RoleNames.Admin })]
public class MinimalUserService : Service
{
IUserAuthRepository authRepo = AppHost.Resolve<IUserAuthRepository>() as OrmLiteAuthRepository;
/// <summary>
/// Return a minimalist structure of user insensitive information.
/// </summary>
/// <param name="request">The request containing the ID of the user.</param>
/// <returns>A minimalist structure of user insensitive information.</returns>
public object Get(MinimalUser request)
{
if (request.Identity != null)
return new MinimalUser(authRepo.GetUserAuth(request.Identity.ToString()));
else if (request.Username != null)
return new MinimalUser(authRepo.GetUserAuthByUserName(request.Username));
else
return null;
}
}
}
From my test project, I run the following test:
[TestMethod]
public void GetMinimalUserByUsername()
{
AuthResponse authResponse = client.Post<AuthResponse>("/auth", new Auth
{
UserName = "accountwithadminrole",
Password = "blablabla",
RememberMe = true,
provider = CredentialsAuthProvider.Name
});
MinimalUser request = new MinimalUser
{
DisplayName = BaseAccounts.System,
};
MinimalUser user = client.Get<MinimalUser>(request);
Assert.IsNotNull(user);
}
I clearly see, before issuing the client.Get method, that the request object have all its properties set to null except for the DisplayName which has the value "system". When this request is received by the MinimalUserService Get method, the value "system" is now assigned to the property UserName and DisplayName is null.
Also, I've tried to comment properties one by one in the MinimalUser class, suspecting one of its field could be causing serialization problem and I would end up having random 'Bad Request' when commenting a certain number of properties. Although, I could comment a properties randomly and one property that previously caused a 'Bad Request' would not do it depending on others properties commented out.
I'm really confused about how this can possibly happens. I feel the service and DTO are simple compared to others from this same project but I'm sure I'm doing something really stupid here.
Don't hesitate to ask for more details, it will be my pleasure to give all information you need.
Thank you.
The reason for Username to be populated instead of DisplayName is because of the routes you have defined for MinimalUser. In MinimalUser.cs you defined 2 identical routes:
[Route("/User/{Identity}", "GET")]
[Route("/User/{Username}", "GET")]
[Route("/User/{DisplayName}", "GET")]
Username and Displayname are both strings. This makes it impossible for ServiceStack to determine the appropriate route direct the request to as it cannot differentiate between the routes. You can fix this by either removing a route, or by adding additional text to one of the routes; eg /User/ByDisplayName/{Username}.
I'm having an issue returning a custom AutenticateResponse in the new version of ServiceStack. This code worked in the previous version of ServiceStack, but after the upgrade it is no longer functioning as expected.
The AuthenticateResponse
public class CustomAuthResponse : AuthenticateResponse
{
public List<CustomCompanyDTO> Companies { get; set; }
public List<string> Roles { get; set; }
public List<string> Permissions { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
}
The Service
public class CurrentUserService : AppServiceBase
{
public object Any(CurrentUser cu)
{
CustomAuthResponse response = new CustomAuthResponse();
response.DisplayName = UserSession.DisplayName;
response.Email = UserSession.Email;
response.Companies = UserSession.Companies;
response.UserName = UserSession.UserName;
response.Roles = UserSession.Roles;
response.Permissions = UserSession.Permissions;
return response;
}
}
In v3 I can call the CurrentUserService and it returns all the data as expected. In v4 when I call CurrentUserService none of the custom fields are included in the response.
I can work around this particular call by changing the code as follows:
public class CurrentUserService : AppServiceBase
{
public object Any(CurrentUser cu)
{
CustomAuthResponse response = new CustomAuthResponse();
var x = new
{
DisplayName = UserSession.DisplayName,
Email = UserSession.Email,
Companies = UserSession.Companies,
UserName = UserSession.UserName,
Roles = UserSession.Roles,
Permissions = UserSession.Permissions,
};
return x;
}
}
The above code works as expected. I can certainly change my code to work this way, I'm mostly wondering what has changed as I'm curious if it will impact my code in other places. I'm seeing the same issue when trying to return ny CustomAuthResponse from the Authenticate call my custom CredentialsAuthProvider.
The issue is likely that DataContract attributes are now inherited and if a DTO is marked as a [DataContract] it's opt-in and only the properties marked with DataMember are serialized.
As AuthenticateResponse is a DataContract, if you want to re-use the DTO you should mark the properties you want serialized with a [DataMember] attribute, e.g:
[DataContract]
public class CustomAuthResponse : AuthenticateResponse
{
[DataMember]
public List<CustomCompanyDTO> Companies { get; set; }
[DataMember]
public List<string> Roles { get; set; }
[DataMember]
public List<string> Permissions { get; set; }
[DataMember]
public string DisplayName { get; set; }
[DataMember]
public string Email { get; set; }
}
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 must be overlooking something around getting the fluent-validation to fire within basic Service-Stack application I created.
I have been following the example found here. For the life of me I can't seem to get my validators fire????
Crumbs, there must be something stupid that I'm missing....???
I'm issuing a user request against the User-Service (http://my.service/users), the request goes straight through without invoking the appropriate validator registered.
Request is :
{"Name":"","Company":"Co","Age":10,"Count":110,"Address":"123 brown str."}
Response :
"user saved..."
Here is the code :
1.DTO
[Route("/users")]
public class User
{
public string Name { get; set; }
public string Company { get; set; }
public int Age { get; set; }
public int Count { get; set; }
public string Address { get; set; }
}
2.Validator
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(r => r.Name).NotEmpty();
RuleFor(r => r.Age).GreaterThan(0);
}
}
3.AppHostBase
public class ValidationAppHost : AppHostBase
{
public ValidationAppHost()
: base("Validation Test", typeof(UserService).Assembly)
{
}
public override void Configure(Funq.Container container)
{
Plugins.Add(new ValidationFeature());
//This method scans the assembly for validators
container.RegisterValidators(typeof(UserValidator).Assembly);
}
}
4.Service
public class UserService : Service
{
public object Any(User user)
{
return "user saved...";
}
}
5.Global.asax.cs
protected void Application_Start(object sender, EventArgs e)
{
new ValidationAppHost().Init();
}
Ok....found the issue....I (in error) installed (via nuget) and referenced within my project the FluentValidation.dll with Service-Stack's FluentValidation implementation (see namespace ServiceStack.FluentValidation).
Once I removed this the sole incorrect FluentValidation reference and ensured that my validator extended from the service-stack implementation of the AbstractValidator the validators fired correctly...
I created a custom RegistrationFeature:
public class CustomRegistrationFeature: IPlugin
{
private string AtRestPath {get; set;}
public CustomRegistrationFeature ()
{
AtRestPath = "/register";
}
public void Register (IAppHost apphost)
{
appHost.RegisterService <CustomRegistrationService>(AtRestPath);
appHost.RegisterAs <CustomRegistrationValidator, IValidator <CustomRegistration>>();
}
}
I configured in AppHost:
Plugins.Add (new CustomRegistrationFeature ());
but in the metadata page there are CustomRegistration and Registration.
Why?
Thanks.
Update
The CustomRegistrationService:
[DefaultRequest(typeof(CustomRegistration))]
public class CustomRegistrationService : RegistrationService
{
public object Post(CustomRegistration request)
{
//base.Post( request);
return new CustomRegistrationResponse();
}
}
The CustomRegistration (Request dto):
[DataContract]
public class CustomRegistration : IReturn<CustomRegistrationResponse>
{
[DataMember]
public string Name{ get; set; }
}
The CustomRegistrationResponse (Response dto):
[DataContract]
public class CustomRegistrationResponse
{
[DataMember]
public string Test { get; set; }
}
The CustomRegistration service should appear although as we can't see the implementation of it, I can't tell if the service has been written correctly or not.
But there's no reason why Registration would appear in the /metadata pages since you haven't registered the RegistrationFeature.