How do you debug MVC 4 API routes? - c#

I have a WP7 game that uses RESTsharp to communicate with my MVC4 RESTful server, but I often have issues making requests that work and therefore I want to debug where it fails.
This is an example where the Constructor on my GameController is hit, but the Post method is not hit, and I don't understand why.
Client code:
public void JoinRandomGame()
{
client = new RestClient
{
CookieContainer = new CookieContainer(),
BaseUrl = "http://localhost:21688/api/",
};
client.Authenticator = GetAuth();
RestRequest request = new RestRequest(Method.POST)
{
RequestFormat = DataFormat.Json,
Resource = "game/"
};
client.PostAsync(request, (response, ds) =>
{});
}
Server code:
public void Post(int id)
{
if (ControllerContext.Request.Headers.Authorization == null)
{
//No auth
}
if (!loginManager.VerifyLogin(ControllerContext.Request.Headers.Authorization.Parameter))
{
//Failed login
}
string username;
string password;
LoginManager.DecodeBase64(ControllerContext.Request.Headers.Authorization.Parameter, out username, out password);
gameManager.JoinRandomGame(username);
}
My routes are like this
routes.MapHttpRoute(
name: "gameAPI",
routeTemplate: "api/game/{gameId}",
defaults: new
{
controller = "game",
gameId = RouteParameter.Optional
}
);

Another way is to add an event handler in Global.asax.cs to pick up the incoming request and then look at the route values in the VS debugger. Override the Init method as follows:
public override void Init()
{
base.Init();
this.AcquireRequestState += showRouteValues;
}
protected void showRouteValues(object sender, EventArgs e)
{
var context = HttpContext.Current;
if (context == null)
return;
var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(context));
}
Then set a breakpoint in showRouteValues and look at the contents of routeData.
Keep in mind that in a Web API project, the Http routes are in WebApiConfig.cs, not RouteConfig.cs.

RouteDebugger is good for figuring out which routes will/will not be hit.
http://nuget.org/packages/routedebugger

You can try ASP.NET Web API Route Debugger. It is written for Web Api. https://trocolate.wordpress.com/2013/02/06/introducing-asp-net-web-api-route-debugger/
http://nuget.org/packages/WebApiRouteDebugger/

There are more possibilities for testing the routes. You can try either manual testing or automated as part of unit tests.
Manual testing:
RouteDebugger
MVC API tracing
Automated testing:
MvcRouteTester.

Is GameController deriving from ApiController ?
Are you using WebApi ?
If not then i think the "/api/" is reserved for new WebApi feature. Try changing your routing and controller name to "gameapi"
If however you are using WebApi.
Then remove api from yor BaseUrl
client = new RestClient
{
CookieContainer = new CookieContainer(),
BaseUrl = "http://localhost:21688/",
};

Related

How to enable CORS on C# server?

I am developing a web app that has a cloud server (nodeJS) and a local server (C#) to interface the serial port of the PC. The web page whose origin is the cloud server makes requests to the local server and gets this error
Access to fetch at 'http://localhost:3100/connection' from origin 'http://cloud.tissuelabs.com' has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space `local`
I tried adding headers to the request
'headers': {'Access-Control-Allow-Origin': '*'}
but it doesn`t solve my problem. I solved this problem in python using CORS libraries, but I still want to solve this problem in C# (because of performance). This is my server in C#
private void InitializeServer()
{
myServer = new Thread(Server);
myServer.Start(this);
}
private void Server(Object _obj)
{
Route.Add((req, props) => req.Url.LocalPath.ToLower().EndsWith("connection"),
(req, res, args) =>
{
if (req.HttpMethod == "GET")
{
Regex rx = new Regex(#"port=(?<word>\w+)",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
MatchCollection matches = rx.Matches(req.Url.Query);
if (matches.Count > 0)
{
bool on = true;
bool printing = true;
WriteLog($"GET {req.Url.LocalPath + req.Url.Query}");
WriteLog($"{matches[0].Groups["word"].Value}");
string output = "{" + $"'on':{on}, 'printing':{printing}, 'queue': {10}" + "}";
res.AsText(output);
}
else
{
WriteLog($"GET {req.Url.LocalPath}");
string[] ports = SerialPort.GetPortNames();
string output = "{'ports':[";
foreach (string port in ports)
{
output += "{" + $"'port':'{port}', 'name':'{port}'" + "}";
}
output += "]}";
res.AsText(output);
}
}
else if (req.HttpMethod == "POST")
{
WriteLog("POST /connection");
Stream stream = req.InputStream;
StreamReader sr = new StreamReader(stream);
JsonSerializer serializer = new JsonSerializer();
Connection con = (Connection)serializer.Deserialize(sr, typeof(Connection));
connectSerial(con.port);
res.AsText("{'ok': True}");
}
else if (req.HttpMethod == "DELETE")
{
WriteLog("DELETE /connection");
res.AsText("OK");
}
});
HttpServer.ListenAsync(3100, System.Threading.CancellationToken.None, Route.OnHttpRequestAsync)
.Wait();
}
When I add access control allow origin to header, it works on firefox, but not on Chrome
res.AddHeader("Access-Control-Allow-Origin", "*");
Is there any library I could use with SimpleHttp? I am also using CefSharp to render the webpage. Is there any way to configure chromium web browser to ignore CORS errors?
CORS problem should be solved in server side, there must be a keyword or something that you should set your "project configuration files"
I found
namespace WebService
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// New code
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
and
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;
namespace WebService.Controllers
{
[EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
public class TestController : ApiController
{
// Controller methods not shown...
}
}
just quick googling, please check for further information : https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/enabling-cross-origin-requests-in-web-api
The first one enables cache for all of your controllers whereas the second method is controller based which in this case allows core request that only comes from given origins, if you'd like to allow for all origins you should set "*" as value.

ASP.NET Web Site Not Allowing Axios PUT Access (405 Method Not Allowed)

I've got a controller set up and working for all of my GET requests, but when it comes to the PUT requests my Web Site (not a Web App, if that makes any difference) is returning a 405.
I've got the route defined in my Global.asax Application_Start() with the other routes, and it's the first one, so it should be evaluated first (if my understanding is correct):
void Application_Start(object sender, EventArgs e)
{
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls
| System.Net.SecurityProtocolType.Tls11
| System.Net.SecurityProtocolType.Tls12;
RouteTable.Routes.MapHttpRoute("FilteredSpecialOrders",
routeTemplate: "api/sales/filteredRequests",
defaults: new { controller = "Sales", action = "filteredRequests" });
// subsequent routes are here
}
My SalesController has a method with the right attributes for type of request (Put) and the action name that matches my routeTemplate, as well as the [FromBody] attribute on the parameter:
[HttpPut, ActionName("filteredRequests")]
public IHttpActionResult PutSpecialOrders([FromBody] RequestFilter filter)
{
// do the needful
}
...and my client side code creates the body of the message (filter) as a JavaScript object and sends the .put requests via axios:
getFilteredRequests: async function () {
let filter = {
name: this.name,
age: this.yearsOld,
/* other name/value pairs of course */
};
const response = await axios.put(salesApi + 'filteredRequests', filter);
let data = response.data;
return data;
}
...but I'm always getting back a 405 - Method Not Allowed. What am I forgetting? I'm not sure how the JSON object that gets sent in the body is serialized into my RequestFilter object - does that happen automagically or do I need to define that somewhere? I've made sure that the names are the same on both ends, but other than that...
I edited my web.config to remove the WebDAV bits per this post - but I also had to switch my pipeline from classic mode to integrated to get it to work.

Custom authorize attribute doesn't work after deploying to IIS

I have overridden the HandleUnauthorizedRequest method in my asp.net mvc application to ensure it sends a 401 response to unauthorized ajax calls instead of redirecting to login page. This works perfectly fine when I run it locally, but my overridden method doesn't get called once I deploy to IIS. The debug point doesn't hit my method at all and straight away gets redirected to the login page.
This is my code:
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
filterContext.Result = new JsonResult
{
Data = new
{
success = false,
resultMessage = "Errors"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.HttpContext.Response.End();
base.HandleUnauthorizedRequest(filterContext);
}
else
{
var url = HttpContext.Current.Request.Url.AbsoluteUri;
url = HttpUtility.UrlEncode(url);
filterContext.Result = new RedirectResult(ConfigurationManager.AppSettings["LoginUrl"] + "?ReturnUrl=" + url);
}
}
}
and I have the attribute [AjaxAuthorize] declared on top of my controller. What could be different once it's deployed to IIS?
Update:
Here's how I'm testing, it's very simple, doesn't even matter whether it's an ajax request or a simple page refresh after the login session has expired -
I deploy the site onto my local IIS
Login to the website, go to the home page - "/Home"
Right click on the "Logout" link, "Open in a new tab" - This ensures that the home page is still open on the current tab while
the session is logged out.
Refresh Home page. Now here, the debug point should hit my overridden HandleUnauthorizedRequest method and go through the
if/else condition and then redirect me to login page. But it
doesn't! it just simply redirects to login page straight away. I'm
thinking it's not even considering my custom authorize attribute.
When I run the site from visual studio however, everything works fine, the control enters the debug point in my overridden method and goes through the if/else condition.
When you deploy your web site to IIS, it will run under IIS integrated mode by default. This is usually the best option. But it also means that the HTTP request/response model isn't completely initialized during the authorization check. I suspect this is causing IsAjaxRequest() to always return false when your application is hosted on IIS.
Also, the default HandleUnauthorizedRequest implementation looks like this:
protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
filterContext.Result = new HttpUnauthorizedResult();
}
Effectively, by calling base.HandleUnauthorizedRequest(context) you are overwriting the JsonResult instance that you are setting with the default HttpUnauthorizedResult instance.
There is a reason why these are called filters. They are meant for filtering requests that go into a piece of logic, not for actually executing that piece of logic. The handler (ActionResult derived class) is supposed to do the work.
To accomplish this, you need to build a separate handler so the logic that the filter executes waits until after HttpContext is fully initialized.
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new AjaxHandler();
}
}
public class AjaxHandler : JsonResult
{
public override void ExecuteResult(ControllerContext context)
{
var httpContext = context.HttpContext;
var request = httpContext.Request;
var response = httpContext.Response;
if (request.IsAjaxRequest())
{
response.StatusCode = (int)HttpStatusCode.Unauthorized;
this.Data = new
{
success = false,
resultMessage = "Errors"
};
this.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
base.ExecuteResult(context);
}
else
{
var url = request.Url.AbsoluteUri;
url = HttpUtility.UrlEncode(url);
url = ConfigurationManager.AppSettings["LoginUrl"] + "?ReturnUrl=" + url;
var redirectResult = new RedirectResult(url);
redirectResult.ExecuteResult(context);
}
}
}
NOTE: The above code is untested. But this should get you moving in the right direction.

Can't get Fiddler to capture HttpClient web api calls from MVC with localhost different port

Yet another fiddler can't get it to capture post.
Similar to this SO Post I have spent two hours now reading and trying different solution yet none of them allow me to see my fiddler web api traffic.
As a side note my code is working I am just focused on getting fiddler to show me the api calls.
I will describe my setup then what I have tried.
My Web API is a separate MVC 6, EF 7 project running on port 63381
http://localhost:63381/
My ASP.NET MVC 5 web client project is running on port: 59722
http://localhost:59722/
A typical action controller in the mvc client:
//Controller CTOR
public ClientController()
{
client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:63381/api/MyApi");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
//Action within ClientController
public async Task<JsonResult> AddNewSubCategory()
{
HttpResponseMessage responseMessage = await client.PostAsJsonAsync(url2, content);
if (responseMessage.IsSuccessStatusCode)
{
return Json("[{Update Success}]", JsonRequestBehavior.AllowGet);
}
return Json("[{Error Updating}]", JsonRequestBehavior.AllowGet);
}
}
Added the block to 32 & 62 machine.config. Restarted visual studio did NOT restart machine or any other service. This did not work.
Added the block to the client web.config and this didn't work.
Changed localhost to machinename:port
Specifically I changed http://localhost:63381/api/MyApi to http://gpgvm-pc:63381/api/MyApi
Modified Global.asax with:
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
Fiddler custom rules
Reverse proxy
Set fiddler listening on a different port.
At this point I surrender. It seems to me #1 should work to capture everything but I am obviously still doing something wrong because I can get fiddler to capture one or the other but NOT the client calling off to the client???
Update:
My machine locked and after reboot I started seeing the api calls so this issue was something with my machine. So sorry to bother.
The problem is most likely that you are using localhost which are handled in a special way.
Try using machine name or your ip instead (do not use 127.0.0.1).
The documentation have information about this as well:
http://docs.telerik.com/fiddler/Observe-Traffic/Troubleshooting/NoTrafficToLocalhost
If you try to hit specific action in api then use that code in webapi config
public static void Register(HttpConfiguration config)
{
//config.Routes.MapHttpRoute(
// name: "DefaultApi",
// routeTemplate: "api/{controller}/{id}",
// defaults: new { id = RouteParameter.Optional });
config.Routes.MapHttpRoute("API Default", "api/{controller}/{action}/{id}",
new { id = RouteParameter.Optional });
}
This code where u call your api.
public ActionResult ClientController(model content)
{
try
{
HttpClient client = new HttpClient("http://localhost:63381/");
client.BaseAddress = new Uri();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.PostAsJsonAsync("api/MyApi/url2", content).Result;
if (response.IsSuccessStatusCode)
{
return Json(new { code = 0, message = "Success" });
}
else
{
return Json(new { code = -1, message = "Failed" });
}
}
catch (Exception ex)
{
int code = -2;
return Json(new { code = code, message = "Failed" });
}
}
}

Web api return 404

I'm doing some automated integration test in visual studio on my web api controller. I've got the following code in my test:
var url = serverAddress + "/api/PostalCodes?postalCode=" + postalCodeToFind;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("accept", "application/json");
HttpResponseMessage response = client.GetAsync(url).Result;
response.EnsureSuccessStatusCode();
I get the following error:
System.Net.Http.HttpRequestException: Response status code does not
indicate success: 404 (Not Found).
The selfhost server used:
private const string serverAddress = "http://localhost:8080";
[TestInitialize]
public void Initialize()
{
config = new HttpSelfHostConfiguration(serverAddress);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.MaxReceivedMessageSize = 2024 * 2024;
config.MaxBufferSize = 2024 * 2024;
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
}
The weird part is that if I have my web api controller in IIS, the same URL will work just fine and return a value. I also have the same url doing a post (without the postalCodeToFind query parameter) and it also works well...
Any clues ?
The controller is not being loaded by the test.
Do you have a custom Resolver or are you expecting the baked in resolver to pick it up?
If you're using a custom Resolver (i.e. inheriting from DefaultAssembliesResolver), make sure the library containing the controller is being loaded.
If you're expecting the baked in resolver to pick it up, the library containing your controller needs to be in the directory that the test is running from.

Categories