I'm building an Angular2 service to log certain events, stored in ILog objects, and send them to an API to be stored in a database.
My log service is pretty straightforward:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { EnvironmentModule } from "../environment";
import { ILog } from "./log";
#Injectable()
export class LogService {
constructor(private _http: Http, private environment: EnvironmentModule) { }
postLog(log: ILog): void {
this.json = this.convertToJSON(log);
this._http.post(this.environment.getWebApiUri() + 'api/Log/PostLog/', log, {})
.subscribe(
() => console.log('Success')
); //goes to localhost:3304/WebAPI/Log/PostLog/, returns 404 not found
}
}
And it calls a WebAPI Controller that passes the data off to a service to be processed:
[RoutePrefix("api/Log")]
public class LogController : ApiController
{
private ILogService _LogService;
public LogController() : this(new LogService())
{
} //constructor
public LogController(ILogService LogService)
{
_LogService = LogService;
} //constructor
[HttpPost()]
[Route("PostLog")]
public void PostLog(Log log)
{
_LogService.PostLog(log);
} //PostLog
} //class
Yet, when my service calls the API it throws a 404 Not Found Error.
Navigating to the path in the browser I see this:
<Error>
<Message>
No HTTP resource was found that matches the request URI
'http://localhost:3304/WebAPI/api/Log/PostLog/'.
</Message>
<MessageDetail>
No action was found on the controller 'Log' that matches the request.
</MessageDetail>
</Error>
Can anyone help me with this? I don't understand why it's behaving this way.
Its because you cant post an atomic value directly to your method as json. You could turn it into an object and then post it as a corresponding object or post it as form uri encoded which also works. This is a limitation of asp.net's web api.
There are some other similar questions all with similar answers. Here is a quick example of how you could change it to work.
c# code
[HttpPost()]
[Route("PostLog")]
public void PostLog(LogContainerModel logModel)
{
_LogService.PostLog(logModel.log);
}
// model
public sealed class LogContainerModel {
public string log { get; set; }
}
javascript code
private convertToJSON(log: ILog): string {
return JSON.stringify({log: log});
}
Option 2
Stringify it as an object according to this previous SO answer.
c# code
[HttpPost()]
[Route("PostLog")]
public void PostLog([FromBody] string jsonString)
javascript code
private convertToJSON(log: ILog): string {
return JSON.stringify({'': log}); // if that does not work try the snippet below
// return JSON.stringify({'': JSON.stringify(log)});
}
Option 3
Here are some options from bizcoder.com
Use HttpResponseMessage
[HttpPost()]
[Route("PostLog")]
public async Task PostLog(HttpRequestMessage request)
{
var jsonString = await request.Content.ReadAsStringAsync();
_LogService.PostLog(jsonString);
}
Or use json.net
[HttpPost()]
[Route("PostLog")]
public void PostLog([FromBody]JToken jsonbody)
{
var jsonString = jsonbody.ToString();
_LogService.PostLog(jsonString);
}
SO - How to post a single string (using form encoding)
SO - send single string parameter - this might be the better option, good answer.
Related
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();
}
Running ServiceStack 4.0.44 I have the following on my test client:
return client.Send(new GetVendor { VenCode = vencode });
vs what I had
// return client.Get(new GetVendor { VenCode = vencode });
and then on the server I have/had
public class VendorsService : Service {
public object Any(GetVendor request) {
var vendor = Db.SingleWhere<Vendors>("VenCode", request.VenCode);
return vendor;
}
//public object Get(GetVendor request) {
// var vendor = Db.SingleWhere<Vendors>("VenCode", request.VenCode);
// return vendor;
//}
}
//[Route("/vendor/{VenCode}", "GET")]
[Route("/vendor/{VenCode}")]
public class GetVendor : IReturn<Vendors> {
public string VenCode { get; set; }
}
public class Vendors {
:
:
}
My question is why when I pass "B&T" for VenCode -- and I understand that IIS is interpreting the & as part of the URL -- why does the Send work and return Vendors -- but the Get blows up with Bad Request unless I put
<httpRuntime requestPathInvalidCharacters="" />
into my web.config
Bottom line what is the difference? How would I implement CRUD routines with all the html special characters without modifying the registry etc? Or do I need to urlEncode them somehow?
Using a Get() API sends the request using the ?QueryString which is what requestPathInvalidCharacters is validating against.
When you use Send() you're sending a JSV serialized Request DTO via a HTTP POST which isn't validated by requestPathInvalidCharacters.
It's unlikely there's any way to disable ASP.NET's default behavior other than using Web.config, note this validation happens in ASP.NET before the request reaches ServiceStack.
Is it possible to have multiple IReturn<> on a request DTO?
For example following route:
[Route("/api/whatever", "GET,POST,PUT,DELETE")]
public class WhateverRequest : IReturn<bool>, IReturn<List<Whatever>>
{
public string WhateverId { get; set; }
}
Depending on the request method I want to have another IReturn.
Post-Put-Delete Request should only return a acknowledge if the request was successful:
IReturn<bool>
but on a GET request I want to have a:
IReturn<List<Whatever>>
It would also be good if there is a way to reflect this in Swagger Api/ Metadata Page.
Currently only the first IReturn is shown.
Is this possible or would it be better to create a route for each different IReturn?
You definitely want to be creating different routes to handle the multiple return types. Only one IReturn<T> or IReturnVoid is expected, or consuming clients wouldn't know how to type the returned data correctly.
[Route("/api/whatever", "GET")]
public class ListWhateverRequest : IReturn<List<Whatever>>
{
public string WhateverId { get; set; }
}
// Action
public List<Whatever> Get(ListWhateverRequest request)
{
...
}
[Route("/api/whatever", "POST,PUT,DELETE")]
public class UpdateWhateverRequest : IReturn<bool>
{
public string WhateverId { get; set; }
}
// Action
public bool Post(UpdateWhateverRequest request)
{
...
}
public bool Put(UpdateWhateverRequest request)
{
...
}
public bool Delete(UpdateWhateverRequest request)
{
...
}
I presume you are returning true from these methods to show they completed successfully. Do the methods ever return false when something goes wrong, or is an exception thrown instead? If you are only throwing exception in the method, and never returning false then instead of returning bool consider using void methods with IReturnVoid. The request is therefore successful if it doesn't throw an exception.
I have a ServiceStack web service that requires support for the X-HTTP-Method-Override header.
I tried simulating the Delete request to through a Get request with the X-HTTP-Method-Override header set but I get a:-
404 - Handler for Request not found
Here's what the request format:
Get - http://localhost/test/1
Headers
User-Agent: Fiddler
Host: localhost
X-HTTP-Method-Override: Delete
And the Service and its DTO implementation looks like:
[Route("/test/{id}", HttpMethods.Delete)]
public class TestRequest {
public int id { get; set; }
}
public class TestService : Service {
public object Delete(TestRequest request){
return request.id;
}
}
I found a snippet in the ServiceStack source that says X-HTTP-Method-Override feature is supported.
Is there something else I need to configure in the project to get this to work? Help please...
I figured it out, I have the add the Get verb to the request dto like this:
[Route("/test/{id}", "Delete,Get")]
public class TestRequest {
public int id { get; set; }
}
Now the Delete method will be called when simulated via the Get request via the X-HTTP-Method-Override
public class TestService : Service {
public object Delete(TestRequest request){
return request.id;
}
}
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"
}