DNN Cannot access POST method in DNN Api Controller - asp.net-mvc

My GET method WORKS fine when I use the url logged in as SuperUser like this(I get the name of the first user pulled from the DB):
http://localhost/DesktopModules/AAAA_MyChatServer/API/ChatApi/GetMessage
But I cannot access the POST method in the same controller either using AJAX from view or just by entering the url (post method doesnt get hit/found):
http://localhost/DesktopModules/AAAA_MyChatServer/API/ChatApi/SendMessage
And also this fails as well:
$('#sendChat').click(function (e) {
e.preventDefault();
var user = '#Model.CurrentUserInfo.DisplayName';
var message = $('#chatBoxReplyArea').val();
var url = '/DesktopModules/AAAA_MyChatServer/API/ChatApi/SendMessage';
$.post(url, { user: user, message: message }, function (data) {
}).done(function () {
});
});
The Error message is:
<Error>
<Message>
No HTTP resource was found that matches the request URI 'http://localhost/DesktopModules/AAAA_MyChatServer/API/ChatApi/SendMessage'.
</Message>
<MessageDetail>
No action was found on the controller 'ChatApi' that matches the name 'SendMessage'.
</MessageDetail>
</Error>
And sometimes:
"The controller does not support GET method"
even though I do have both a GET and a POST there and the GET works. What am I missing?
I have made a routing class in my DNN project:
using DotNetNuke.Web.Api;
namespace AAAA.MyChatServer
{
public class RouteMapper : IServiceRouteMapper
{
public void RegisterRoutes(IMapRoute mapRouteManager)
{
mapRouteManager.MapHttpRoute("MyChatServer", "default", "{controller}/{action}", new[] { "AAAA.MyChatServer.Services" });
}
}
}
I added a DNN Api Controller in folder Services of my project named AAAA.MyChatServer:
using DotNetNuke.Web.Api;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
namespace AAAA.MyChatServer.Services
{
[DnnAuthorize(StaticRoles = "SuperUser")]
public class ChatApiController : DnnApiController
{
[HttpGet]
public HttpResponseMessage GetMessage()
{
ChatServerManager csm = new ChatServerManager();
var users = csm.GetAllUsers();
var user = users.FirstOrDefault().Name;
return Request.CreateResponse(System.Net.HttpStatusCode.OK, user);
}
[System.Web.Http.HttpPost]
public HttpResponseMessage SendMessage(string toUser, string message)
{
return Request.CreateResponse(System.Net.HttpStatusCode.OK);
}
}
}

There are two ways to call a POST method in a DNN WebAPI: with parameters and with an object. If you use parameters, as you have in your SendMessage method, those parameter values need to be delivered via the Query String.
On the other hand, creating an object and sending that with your call to the WebAPI method can handle a great many more scenarios and is arguably a better way of handling any POST method (as it hides those values from prying eyes, making the call more difficult to counterfeit). To handle this, you can remove the parameters from your SendMessage method and instead interrogate the HttpContext.Current.Request object within your method. The object you created { user: user, message: message } will be nestled in there somewhere.
As it is written in your example, your object was sailing past your parameters like two ships in the night.
I've only just figured this out myself, and I don't have all the understanding I need yet, but hopefully this will help you along your way. Here are some articles I referenced in my quest to use cURL to upload a file to my DNN WebAPI:
https://www.dnnsoftware.com/community-blog/cid/134676/getting-started-with-dotnetnuke-services-framework
https://www.dnnsoftware.com/community-blog/cid/144400/webapi-tips
How To Accept a File POST
https://forums.asp.net/t/2104884.aspx?Uploading+a+file+using+webapi+C+
https://talkdotnet.wordpress.com/2014/03/18/dotnetnuke-webapi-helloworld-example-part-one/comment-page-1/
http://dnnmodule.com/Article/ArticleDetail/tabid/111/ArticleId/511/Dotnetnuke-7-0-WebAPI-Tips.aspx
How to post file using Curl in WebApi in Asp.Net MVC
Good luck!

Your Web Api for SendMessage contain 2 parameter, so it should POST in query string :
http://localhost/DesktopModules/AAAA_MyChatServer/API/ChatApi/SendMessage?touser=john&message=hello
if you want to POST it using data of object, you need to make the Web Service parameter as object model
Also your javascript parameter is different from the Web Service, as it use "toUser"

Related

How to handle API callbacks in ASP.NET MVC (Helloworks API in my case)

As per their documentation from link https://docs.helloworks.com/v3/reference#callbacks
"With the HelloWorks API you can use callbacks to be notified about certain events. Currently we support a callback to be registered when a step is started, the cancellation of a workflow, and the completion of a workflow.
The callback URL will be called according to the following:
curl -X POST https://your.domain.com/some/path
-H "X-HelloWorks-Signature: {signature}"
-H "Content-Type: application/json"
-d "{payload}
I am not able to figure out how can I handle the callback in ASP.NET MVC 4.0. The callback returns data on JSON format. Once I receive the data, I can format it as per my need and can save to database. But how can I get the data in my controller? Guidance from experts on APIs are highly appreciated. Thanks in advance.
I am not able to figure out how can I handle the callback in ASP.NET MVC 4.0.
You need to have an api controller that accepts POST requests. That api endpoint is then called by the HelloWorks api. The fancy word to describe this mechanism is a Webhook. A nice introduction can be found here.
The very basic would be a controller like
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace MyWebAPI.Controllers
{
public class WebHookController : ApiController
{
// POST: api/webhook
public void Post([FromBody]string value)
{
}
}
}
You will need to register the url https://yourwebsite.domain/api/webhook at the HelloWorks api so it knows where to send the data to.
You probably want to secure this endpoint so others cannot abuse this api. See the docs for some guidance.
For example, in your case you should check that a header named "X-HelloWorks-Signature" is send in the request to the endpoint. The value of that header is a hash that should match the value of a hash of the content you received. To calculate the hash code to match create a hash using the SHA-256 algorithm and base16-encode the result.
There is also documentation from Microsoft on how to create web apis
Peter your guidance worked. I appreciate that. It was straight forward, only the technical jargon are making it intimidating :). Below are the code that worked. I am still to secure it using signature.
[HttpPost]
public ActionResult Callback()
{
string rawBody = GetDocumentContents(Request);
dynamic eventObj = JsonConvert.DeserializeObject(rawBody);
Test newTest = new Test();
newTest.Response = "Bikram-1" + (string)eventObj.type;
var test = db.Tests.Add(newTest);
db.SaveChanges();
return Content("Success!");
}
private string GetDocumentContents(HttpRequestBase Request)
{
string documentContents;
using (Stream receiveStream = Request.InputStream)
{
using (StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8))
{
documentContents = readStream.ReadToEnd();
}
}
return documentContents;
}

Attribute routing is failing for MVC/WebApi combo project

I am trying to create an ASP.NET app that is both MVC and Web Api. The default controller (HomeController) returns a view that is composed of some HTML and jQuery. I would like to use the jQuery to call the API that is part of the same project.
I have the API setup and have been testing it with Postman but I get the following error when trying to reach the endpoints in the API.
{
"Message": "No HTTP resource was found that matches the request URI 'http://localhost:19925/api/encryption/encrypt'.",
"MessageDetail": "No action was found on the controller 'Values' that matches the request."
}
I am attempting to use attribute routing so I am pretty sure that is where I am going wrong.
[RoutePrefix("api/encryption")]
public class ValuesController : ApiController
{
[HttpPost]
[Route("encrypt")]
public IHttpActionResult EncryptText(string plainText, string keyPassPhrase)
{
// Method details here
return Ok(cipherText);
}
}
I have the route prefix set to api/encryption. I also have the method using the route encrypt and marked as a HttpPost. Below is my WebApiConfig which I think is configured properly for attribute routing.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
// Default MVC routing
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
By my understanding a POST to the following URL should reach the method ..
http://localhost:19925/api/encryption/encrypt
yet it isn't. I am posting the two string values to the method via Postman. I have attached a screen capture (and yes the keyPassPhrase is fake).
Here is the global.asax as requested ...
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
One other thing to note ... that when I change from GET to POST in Postman it works .. as long as I am sending the parameters along in the query string. If I send the parameters in the body I get the original error.
The problem was that I was trying to POST two values to an API method that accepted two parameters. This is not possible with the API (well not without some work arounds) as the API method is expecting an object rather than two different primitive types (i.e. String).
This means on the server side I needed to create a simple class that held the values I wanted to pass. For example ...
public class EncryptionPayload
{
public string PlainText { get; set; }
public string PassPhrase { get; set; }
}
I then modified my API method to accept a type of this class
[Route("encrypt")]
[HttpPost]
public IHttpActionResult EncryptText(EncryptionPayload payload)
{
string plainText = payload.PlainText;
string passPhrase = payload.PassPhrase
// Do encryption stuff here
return Ok(cipherText);
}
Then inside that controller I pulled the Strings I needed from the EncryptionPayload class instance. On the client side I needed to send my data as a JSON string like this ..
{"plainText":"this is some plain text","passPhrase":"abcdefghijklmnopqrstuvwxyz"}
After changing these things everything worked in Postman. In the end I wasn't taking into account Model Binding, thinking instead that an API endpoint that accepted POST could accept multiple primitive values.
This post from Rick Strahl helped me figure it out. This page from Microsoft on Parameter Binding also explains it by saying At most one parameter is allowed to read from the message body.
Try the following code. It will work :
[RoutePrefix("api/encryption")]
public class ValuesController : ApiController
{
[Route("encrypt"),HttpPost]
public IHttpActionResult EncryptText(string plainText, string keyPassPhrase)
{
// Method details here
return Ok(cipherText);
}
}
Sorry dear it was really compile time error. I edit my code. Please copy it and paste it in yourcode. Mark as answer If i Helped.

ASP.Net Web API: Regarding web api action calling url or end point url

i am new in asp.net web api. just reading a article on web api from this url http://www.c-sharpcorner.com/article/remote-bind-kendo-grid-using-angular-js-and-Asp-Net-web-api/
now see this code
[RoutePrefix("api/EmployeeList")]
public class EmployeeDetailsController : ApiController
{
[HttpGet]
[Route("List")]
public HttpResponseMessage EmployeeList()
{
try
{
List<Employee> _emp = new List<Employee>();
_emp.Add(new Employee(1, "Bobb", "Ross"));
_emp.Add(new Employee(2, "Pradeep", "Raj"));
_emp.Add(new Employee(3, "Arun", "Kumar"));
return Request.CreateResponse(HttpStatusCode.OK, _emp, Configuration.Formatters.JsonFormatter);
}
catch (Exception ex)
{
return Request.CreateResponse(HttpStatusCode.OK, ex.Message, Configuration.Formatters.JsonFormatter);
}
}
}
as per my understanding the requesting url should be /api/EmployeeList/List but if anyone look the above image then must notice different url api/Employee/GetEmployeeList is being used to call list action method. so i just like to know the reason why different url is getting issued to call List action function ?
also do not understand how this url api/Employee/GetEmployeeList can work in this situation because controller name is EmployeeDetailsController but RoutePrefix has been used to address it api/EmployeeList and action method name is EmployeeList() which has been change to List..........so some one tell me how this url api/Employee/GetEmployeeList can invoke list action of web api ?
please discuss in detail. thanks
Did you activate AttributeRouting? If not, standard routing is in place and your current attributes will be ignored.
You need to do this in the WebApi registration process, like this:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
Then, you can remove any method call like this:
config.Routes.MapHttpRoute
to disable conventional routing.
BTW, the call api/Employee/GetEmployeeList is valid because Employee is the name of your controller, Get is the verb and EmployeeList is the name of the method.
Did you enable attribute routing? You do this by default in webapiconfig.cs by adding this line of code:
config.MapHttpAttributeRoutes();
#Monojit-Sarkar. The URL the user showed in postman did not also match the code. In the code, these are the users we expect to see:
_emp.Add(new Employee(1, "Bobb", "Ross"));
_emp.Add(new Employee(2, "Pradeep", "Raj"));
_emp.Add(new Employee(3, "Arun", "Kumar"));
But the results in postman are different as shown in the image the user posted. So something is disconnected from the article/image and the sample code.

WebAPI not found

Sadly, I cannot get the most basic of things working with WebAPI
$.ajax({
url: "https://192.168.1.100/Api/Authentication/LogIn",
type: "POST",
contentType: "application/json",
data: "{ 'username': 'admin', 'password': 'MyPass' }",
error: function (r, s, e) { alert(e); },
success: function (d, s, r) { alert(s); }
});
I get "Not found"
API controller definition
public class AuthenticationController : ApiController
{
[HttpPost]
public bool LogIn(string username, string password)
{
return true;
}
}
If I remove HttpPost and replace it with HttpGet and then do
$.ajax({
url: "https://192.168.1.100/Api/Authentication/LogIn?username=admin&password=MyPass",
type: "GET",
error: function (r, s, e) { alert(e); },
success: function (d, s, r) { alert(s); }
});
That works fine.
What's wrong with WebAPI?
This article should help answer some of your questions.
http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/
I believe the thinking here is that, especially in a RESTful API, you’ll want to bind data to the single resource that a particular method deals with. So, pushing data into several loose parameters isn’t the sort of usage that Web API caters to.
When dealing with post data, you can tell your action method to bind its parameters correctly like this:
public class LoginDto {
public string Username { get; set; }
public string Password { get; set; }
}
[HttpPost]
public bool LogIn(LoginDto login) {
// authenticate, etc
return true;
}
A couple things. Yahia's change is valid. Also, POSTs need a little direction in WebAPI to know where to look for their data. It's pretty silly in my opinion. If you know it's a POST, look at the message body. At any rate, change your POST to this and things will work. The attribute tells WebAPI to look in the body and the model does binding.
The AuthModel is just a simple model containing your username and password properties. Because of the way WebApi wants to bind to the input, this will make your life easier.
Read here for more details:
http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-1
Should be good to go with those changes.
Binding in WebAPI doesn't work if you use more than 1 parameter.
Though the same works in MVC controller.
In WebAPI use a class to bind two or more parameters. Read useful article:
http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/
You may solve it the following ways:
1. Do the same in MVC action (it works)
2. Stay parameterless Post and read request like this
[HttpPost]
[ActionName("login")]
public async Task<bool> Post()
{
var str= await Request.Content.ReadAsStringAsync();
//
3. Incapsulate parameters to class like gyus prompted
}
Hope it helps ;)
POST action can have only 1 body...
There is no way to send 2 bodies (in your case 2 strings).
Because of this, the WebAPI parser would expect to find it in URL and not in body.
You can solve it by putting attributes and set that one parameter will come from URL and another from body.
In general, When there is only one object parameter in method - there is no need for the attribute [FromBody].
Strings would be expected to be in URL.
So - you can try send them in the URL as parameters (much like you did in GET)
Or - build a class to wrap it.
I would strongly recommend to use POST for login action.

Breeze Web API Controller Method Name Convention

In Official Docs about breeze and the Web API controller, we see some kind of naming convention for the method names on web Api controller. For example, for the Todo entity type, there is a Todos() method.
Suppose I have an entityType "Customer". Then I create a method on apiController:
[HttpGet]
public IQueryable<Customer> GetCustomers() { ... }
In my client javascript app, I run EntityQueries like that:
//api method: GetCustomers
//type name: Customer
var query = EntityQuery.from("GetCustomers");
manager.execute(query); //works
manager.fetchEntityByKey("Customer", 53) //doesn't works!
It fails, I receive the folowwing error:
No HTTP resource was found that matches the request URI
'http://localhost:4119/api/SomeController/Customers?$filter=Id eq 53'
So, Am I forced to rename my GetCustomers method to Customers() or Am I missing something ?

Resources