I am trying to make a simple api post call using servicestack and it keeps throwing an exception "not found". When the same post call is made directly to the api using a web browser rest api e.g. postman, the api call works.
I have decorated my request object with the route attributes
[Route("/register", "POST")]
public class Register : IReturn<RegistrationResponse>
{
public DateTime? BirthDate { get; set; }
public string Continue { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Gender { get; set; }
public string Password { get; set; }
}
The JsonServiceClient is initialised with the base uri but the following call fails
_client = new JsonServiceClient(_apiUri);
_client.HttpMethod = HttpMethods.Post;
var response = _client.Send(body);
The exception that I catch is:
$exception {"Not Found"} System.Exception
{ServiceStack.ServiceClient.Web.WebServiceException} at
ServiceStack.ServiceClient.Web.ServiceClientBase.ThrowWebServiceException[TResponse](Exception
ex, String requestUri) at
ServiceStack.ServiceClient.Web.ServiceClientBase.ThrowResponseTypeException[TResponse](Object
request, Exception ex, String requestUri) at
ServiceStack.ServiceClient.Web.ServiceClientBase.HandleResponseException[TResponse](Exception
ex, Object request, String requestUri, Func1 createWebRequest, Func2
getResponse, TResponse& response) at
ServiceStack.ServiceClient.Web.ServiceClientBase.Send[TResponse](Object
request) at
ApiService`2.Post(String
path, TParams body) in
ApiService.cs:line 81
The documentation on the new API at servicestack mentions the use of the Route attributes decorating the request DTO and the use of the IReturn but from looking at the code behind the Send method, it is working out the rest api url from the name of the request, which implies that your request dto cannot be named anything different.
public virtual TResponse Send<TResponse>(object request)
{
var requestUri = this.SyncReplyBaseUri.WithTrailingSlash() + request.GetType().Name;
var client = SendRequest(requestUri, request);
try
{
var webResponse = client.GetResponse();
return HandleResponse<TResponse>(webResponse);
}
catch (Exception ex)
{
TResponse response;
if (!HandleResponseException(ex,
request,
requestUri,
() => SendRequest(HttpMethods.Post, requestUri, request),
c => c.GetResponse(),
out response))
{
throw;
}
return response;
}
}
What is causing the Not Found exception?
Everthing in your Register class looks correct.
For your client call I would change it to
_client = new JsonServiceClient(_apiUri);
_client.Post(new Register()); //assuming you can map your 'body' variable to a Register class
Just to lose the extra line of code.
it is working out the rest api url from the name of the request, which implies that your request dto cannot be named anything different.
It is working out the endpoint that the rest api will hit. Once it hits the endpoint, the internals of ServiceStack should handle the routing based on the Operation (in this case Register) and Http method. Basically it will try to find a Service class (any class inheriting the Service marker interface) that has the request object (Register) as a parameter and it will use the Http method as the 'function' to call.
What is causing the Not Found exception?
Not exactly sure about this. If you could provide your 'Service' class it may help.
If you have a Service class like
public class RegisterService : Service
{
public RegistrationResponse Post(Register request)
{
//service code
return new RegistrationResponse();
}
}
the routing should work.
The fix for this was to ensure that the servicestack feature for predefined routes was enabled on the api. Once this is done, you don't need to bother with the Route attribute on the request objects.
The end point host config now looks like this:
new EndpointHostConfig
{
DefaultContentType = ContentType.Json,
EnableFeatures = Feature.None
.Add(Feature.Json)
.Add(Feature.PredefinedRoutes),
GlobalResponseHeaders = new Dictionary<string, string>(),
DefaultRedirectPath = "/documentation"
}
Related
My goal is to call api (via post), accept payload as base type and later cast it to concrete type. If I do that from main solution (where my api stands), everything works well. But I can't understand why same code doesn't work from other solutions.
So I have my request (declared in different solutions)
namespace Nb
{
public class NbRequestBase
{
public string BaseProp { get; set; }
}
public class NbRequestConcrete : NbRequestBase
{
public string ConcreteProp { get; set; }
}
}
And this is my endpoint:
[HttpPost]
[Route("payments/nb")]
public IHttpActionResult Prepare(NbRequestBase request)
{
if(request is NbRequestConcrete)
{
}
try
{
// <<< INSERT CODE HERE >>>
NbRequestConcrete nbRequestConcrete = (NbRequestConcrete)request;
return Ok();
}
catch (Exception ex)
{
_logger.Error(ex);
return InternalServerError();
}
}
and this is my calling code:
NbRequestConcrete requestTwo = new NbRequestConcrete()
{
BaseProp = "BaseProp",
ConcreteProp = "ConcreteProp"
};
using (var client = new HttpClient())
{
var _clientId = "_clientId";
var _clientSecret = "_clientSecret";
client.BaseAddress = new Uri("http://localhost:50228");
#region Formatter
JsonMediaTypeFormatter formatter = new JsonMediaTypeFormatter();
formatter.SerializerSettings.TypeNameHandling = TypeNameHandling.All;
List<MediaTypeFormatter> formatters = new List<MediaTypeFormatter>();
formatters.Add(formatter);
#endregion
var responseMessage = client.PostAsync($"payments/nb?clientId={_clientId}&clientSecret={_clientSecret}", requestTwo, formatter).Result;
responseMessage.EnsureSuccessStatusCode();
}
If I put my calling code into other project/solution (for example just new console app), API endpoint is hit, but payload is null.
payload when called form console app
If I put exacly same calling code into project where my api is (for example in same API endpoint method, at try/catch block start and call it again), API endpoint is hit, payload is NOT null and casting works. Why is it? And how to fix it?
payload when called from same solution try/catch start
And BTW. How to make this call via postman?
Regards
This line tells the model binder to set the values of any matching properties in request to the value that was passed to the API:
public IHttpActionResult Prepare(NbRequestBase request)
The model binder does not attach all the other properties to the request, because it has no idea what they would be.
Problem was Assemblies name where NbRequestConcrete in console app lived in one assembly and on API lived in other. So request was different.
{
"$type": "Nb.NbRequestConcrete, Tester",
"ConcreteProp": "ConcreteProp",
"BaseProp": "BaseProp"
}
VS
{
"$type": "Nb.NbRequestConcrete, MYApi",
"ConcreteProp": "ConcreteProp",
"BaseProp": "BaseProp"
}
I am working on a custom middlware that's going to set the response Http Status Code based on the response itself.
I have a class:
public class Response<T>
{
public T Data { get; set; }
public IEnumerable<CustomError> Errors { get; set; }
}
that is returned by every controller in my .Net Core API.
I want to create a custom middleware that's going to access the response after it is returned from the controller and it will assign a correct Http Status Code in the Response based on the Errors field.
I can see some solutions for accessing the Response.Body field of the HttpContext, but it would provide a serialized string that I would have to deserialize again and that's running around in circles.
Is it possible in .Net Core?
Best regards,
Marcin
Instead of middleware, you could create an ActionFilter, specifically your own implementation IAsyncResultFilter. It's going to be easier to cast to Response in the MVC context rather than in the middleware because you may access there ObjectResult.
It could look like this.
public class Response
{
public IEnumerable<string> Errors { get; set; }
}
public class Response<T> : Response
{
public T Data { get; set; }
}
Note that I changed the Response<T> class to make casting easier.
public class ErrorResultFilter : IAsyncResultFilter
{
public Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var result = context.Result as ObjectResult;
var response = result?.Value as Response;
if (response != null)
context.HttpContext.Response.StatusCode
= response.Errors.Any(x => x.Equals("SomeError")) ? 400 : 200;
return next();
}
}
This sample sets status code based on the presence of "SomeError". There's no serialization/deserialization involved, just casting.
services.AddControllers(o =>
{
o.Filters.Add(typeof(ErrorResultFilter));
});
This way, I registered my filter in the startup.cs
I have a simple C# Aws Lambda function which succeeds to a test from the Lambda console test but fails with a 502 (Bad Gateway) if called from the API Gateway (which i generated from the Lambda trigger option) and also if I use postman.(this initial function has open access (no security))
// request header
Content-Type: application/json
// request body
{
"userid":22,
"files":["File1","File2","File3","File4"]
}
The error I get in the logs is:
Wed Feb 08 14:14:54 UTC 2017 : Endpoint response body before transformations: {
"errorType": "NullReferenceException",
"errorMessage": "Object reference not set to an instance of an object.",
"stackTrace": [
"at blahblahmynamespace.Function.FunctionHandler(ZipRequest input, ILambdaContext context)",
"at lambda_method(Closure , Stream , Stream , ContextInfo )"
]
}
It seems like the posted object is not being passed to the lambda input argument.
Code below
// Lambda function
public LambdaResponse FunctionHandler(ZipRequest input, ILambdaContext context)
{
try
{
var logger = context.Logger;
var headers = new Dictionary<string, string>();
if (input == null || input.files.Count == 0)
{
logger.LogLine($"input was null");
headers.Add("testheader", "ohdear");
return new LambdaResponse { body = "fail", headers = headers, statusCode = HttpStatusCode.BadRequest };
}
else
{
logger.LogLine($"recieved request from user{input?.userid}");
logger.LogLine($"recieved {input?.files?.Count} items to zip");
headers.Add("testheader", "yeah");
return new LambdaResponse { body = "hurrah", headers = headers, statusCode = HttpStatusCode.OK };
}
}
catch (Exception ex)
{
throw ex;
}
}
//Lambda response/ZipRequest class
public class LambdaResponse
{
public HttpStatusCode statusCode { get; set; }
public Dictionary<string, string> headers { get; set; }
public string body { get; set; }
}
public class ZipRequest
{
public int userid { get; set; }
public IList<string> files { get; set; }
}
This might not have been available when the OP asked the question, but when invoking a Lambda function using the API Gateway, specific response objects are provided.
As previously noted in the documentation Api Gateway Simple Proxy for Lambda Input Format, the API Gateway wraps the input arguments in a fairly verbose wrapper. It also expects a similarly verbose response object.
However, it is not necessary to create custom request and response objects. The AWS team provides the Amazon.Lambda.APIGatewayEvents library, which is also available on NuGet. This library includes APIGatewayProxyRequest and APIGatewayProxyResponse objects ready-made.
It is still necessary to manually deserialize the Body of the request, as it is a string, not a JSON object. I assume this was done for flexibility?
An example function could look like this. It's a modification of the default function provided by the AWS tools:
public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest request, ILambdaContext context)
{
var bodyString = request?.Body;
if (!string.IsNullOrEmpty(bodyString))
{
dynamic body = JsonConvert.DeserializeObject(bodyString);
if (body.input != null)
{
body.input = body.input?.ToString().ToUpper();
return new APIGatewayProxyResponse
{
StatusCode = 200,
Body = JsonConvert.SerializeObject(body)
};
}
}
return new APIGatewayProxyResponse
{
StatusCode = 200
};
}
When using Lambda Proxy Integration in API Gateway, the first parameter to your FunctionHandler is not the body of your POST, but is another API Gateway-created object, which let's call LambdaRequest. Try these changes to your sample code. Add:
public class LambdaRequest
{
public string body { get; set; }
}
Change your handler prototype to:
public LambdaResponse FunctionHandler(LambdaRequest req, ILambdaContext context)
And inside FunctionHandler add:
ZipRequest input = JsonConvert.DeserializeObject<ZipRequest>(req.Body);
The full LambdaRequest object is documented under Input Format of a Lambda Function for Proxy Integration in the AWS docs, and contains HTTP headers, the HTTP method, the query string, the body, and a few other things.
I also lost a lot of time trying to get a "Path Parameter" passed in on a Get method. For example, if you had a path such as
/appsetting/123
... then you'd have something configured like
By specifying the resource "appid" as {appid} it tells the API Gateway to capture this as a path variable.
One key discovery I found was that by posting in the body of a POST type action, my Lambda would work. Reading around on some other threads, I then discovered I can transform my path variable into a body of the GET action by:
Selecting the GET value (as pictured)
Clicking Integration Request
Creating a mapping template as shown below
Now when I test, I'm able to plug in my appid value in only and get the proper result. Hope this helps someone.
I have a web API which takes a class object as an input parameter and posts it to a WCF service operation contract. The WCF service is designed such that it takes any type of request and does internal mapping of what type of request has come and what code needs to be executed. The logic for that is given below.
public class PdfPrinterService : IPdfPrinter
{
public PdfPrinterResponse Print(PdfPrinterRequestBase request)
{
if (request is Request1)
{
//Process user report request and send back the response
}
if (request is Request2)
{
//Process request 2 and send back the response
}
return PdfPrinterFacade.PrintPdf();
}
}
//IPdfPrinter
public interface IPdfPrinter
{
[OperationContract]
PdfPrinterResponse Print(PdfPrinterRequestBase request);
}
//PdfPrinterRequestBase
[DataContract]
[KnownType(typeof(Request1))]
[KnownType(typeof(Request2))]
public class PdfPrinterRequestBase : IRequest
{
[DataMember]
public RequestHeader ReqHdr { get; set; }
}
//Web API Request1
public PdfPrinterResponse Print(Request1 _request1)
{
PdfPrinterService.PdfPrinterClient
_Client = new PdfPrinterService.PdfPrinterClient();
return _Client.Print(_request1);
}
//Web API Request2
public PdfPrinterResponse Print(Request2 _request2)
{
PdfPrinterService.PdfPrinterClient _Client =
new PdfPrinterService.PdfPrinterClient();
return _Client.Print(_request2);
}
The above approach is okay for me, But planning to have only 1 API which takes any type of object and passes it to the WCF service and the mapping that i did (if request 1, process and send back response else if request 2 process and sendback etc) should work with 1 API call. Any help is greatly appreciated to achieve this.
Maybe this could work?
Pseudo code
[HttpPost]
public PdfPrinterResponse Print()
{
var json = Request.Content.ReadAsByteArrayAsync().Result;
Requeste1 request1;
if(TrySerialzieTo<Request1>(json, out request1))
// send request to wcf service
Requeste2 request2;
if(TrySerializeTo<Request2>(json, out requeste2))
// send request to wcf service
}
private bool TrySerializeTo<T>(string json, out T request)
{
// use JsonConvert.Deserialize<T>(json);
throw new NotImplementedException();
}
I have a WCF Restful service and I would like the methods to return HttpResponseMessage because it seems structured rather than just returning the data or the exception or whatever else might make its way there.
I am assuming this is a correct, if not let me know, but my problem is what happens when I try to set HttpResponseMessage.Content. When I do this, the client in which I made the RESTful call request authentication.
Here is my code:
In the interface:
[WebGet(UriTemplate = "/GetDetailsForName?name={name}"
, ResponseFormat = WebMessageFormat.Json)]
HttpResponseMessage GetDetailsForName(string name);
In the class:
public HttpResponseMessage GetDetailsForName(string name)
{
HttpResponseMessage hrm = new HttpResponseMessage(HttpStatusCode.OK)
{
//If I leave this line out, I get the response, albeit empty
Content = new StringContent("Hi")
};
return hrm;
}
I wanted to try to use Request.CreateResponse but I can't seem to get to Request from my WCF Restful method. OperationContext.Current.RequestContext does not have CreateResponse.
Any pointers?
Unfortunately this will not work. The demonstrated code says:
Construct an HttpResponseMessage object, serialize it with a JSON serializer and pass the result over the wire.
The problem is HttpResponseMessage is disposable and not meant to be serialized, while StringContent cannot be serialized at all.
As to why you are redirected to an authentication form -
the service throws an exception when it cannot serialize StringContent
and returns a 400 HTTP status code which gets interpreted as an authentication issue.
I had a similar error, but not quite the same. I was trying to serialize a plain object and was getting an net::ERR_Conection_Reset message. The wcf method executed 7 times and never threw an exception.
I discovered I had to annotate the class I was returning so that my JSON serializer would understand how to serialize the class. Here is my wcf method:
[OperationContract]
[WebGet(
UriTemplate = "timeexpensemap",
ResponseFormat = WebMessageFormat.Json)]
public TimeexpenseMap timeexpensemap() {
string sql = "select * from blah"
DbDataReader reader = this.GetReader(sql);
TimeexpenseMap tem = null;
if (reader.Read()) {
tem = new TimeexpenseMap();
// Set properties on tem object here
}
return tem;
}
My original class which failed to serialize had no annotations:
public class TimeexpenseMap {
public long? clientid { get; set; }
public int? expenses { get; set; }
}
The annotated class serialized without issues:
[DataContract]
public class TimeexpenseMap {
[DataMember]
public long? clientid { get; set; }
[DataMember]
public int? expenses { get; set; }
}
If I am calling, for example a public string getDetails(int ID) and an error is thrown, this works ...
catch(Exception ex)
{
OutgoingWebResponseContext response = WebOperationContext.Current.OutgoingResponse;
response.StatusCode = System.Net.HttpStatusCode.OK; //this returns whatever Status Code you want to set here
response.StatusDescription = ex.Message.ToString(); //this can be accessed in the client
return "returnValue:-998,message:\"Database error retrieving customer details\""; //this is returned in the body and can be read from the stream
}