I have had a MobileService running on Azure, and have decided to create a new service and migrate the code myself. The new service is of the new type called: Azure Mobile App Service.
Currently I have Authentication working, and can do migrations/update-database. I am following the TodoItem example. I now want to create my own Custom API, which easily worked on MobileService, but I cannot get it working on Azure Mobile App :/
I have followed these two links web-Api-routing and app-service-mobile-backend. And I now have the following:
I have created a new controller:
[MobileAppController]
public class TestController : ApiController
{
// GET api/Test
[Route("api/Test/completeAll")]
[HttpPost]
public async Task<ihttpactionresult> completeAll(string info)
{
return Ok(info + info + info);
}
}
In the mobileApp.cs I have added the below code according to backend:
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
Additionally I have installed the below package according to web-api-routing:
Microsoft.AspNet.WebApi.WebHost
and the call from the client:
string t = await App.MobileService.InvokeApiAsync<string,string>("Test/completeAll", "hej");
Debug shows, that it is the correct URL:
{Method: POST, RequestUri: 'https://xxxxxxx.azurewebsites.net/api/Test/completeAll',
Version: 1.1, Content: System.Net.Http.StringContent, Headers:{ X-ZUMO-FEATURES:
AT X-ZUMO-INSTALLATION-ID: e9b359df-d15e-4119-a4ad-afe3031d8cd5 X-ZUMO-AUTH:
xxxxxxxxxxx Accept: application/json User-Agent:
ZUMO/2.0 User-Agent: (lang=Managed; os=Windows Store; os_version=--; arch=Neutral; version=2.0.31125.0)
X-ZUMO-VERSION: ZUMO/2.0 (lang=Managed; os=Windows Store; os_version=--; arch=Neutral; version=2.0.31125.0)
ZUMO-API-VERSION: 2.0.0 Content-Type: application/json; charset=utf-8 Content-Length: 3}}
But keep getting: 404 (Not Found)
Debug Message "The request could not be completed. (Not Found)"
What am I missing :/ ?
Update
I have tried expanding the code in The mobileApp.cs, with:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration().MapApiControllers()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
based on app-service-backend, however still no access :/
Update
I used fiddler2 to access the endpoint through a browser and got the following results:
Update Again
I have tried to create another minimal solution, but still get the same error. Are there any great tutorials that I can follow to achieve this functionality?
The positive feeling is slowly evaporating . . .
The question is also running now on msdn, I will update here if any information is shown there.
Update
Tested Lindas comment, and I can in fact access the value converter:
// Use the MobileAppController attribute for each ApiController you want to use
// from your mobile clients
[MobileAppController]
public class ValuesController : ApiController
{
// GET api/values
public string Get()
{
MobileAppSettingsDictionary settings = this.Configuration.GetMobileAppSettingsProvider().GetMobileAppSettings();
ITraceWriter traceWriter = this.Configuration.Services.GetTraceWriter();
string host = settings.HostName ?? "localhost";
string greeting = "Hello from " + host;
traceWriter.Info(greeting);
return greeting;
}
// POST api/values
public string Post()
{
return "Hello World!";
}
}
This I access using the both the post and get function:
string t = await App.MobileService.InvokeApiAsync<string, string>("values", null, HttpMethod.Post, null);
or
string t = await App.MobileService.InvokeApiAsync<string, string>("values", null, HttpMethod.Get, null);
But the code I pasted has no route so why can I access it using values? What would the path be to the original controller if did not use the route parameter?
Extra Information
I have now created a support ticket with Microsoft and will update with additional information. . . Hopefully.
Update
Info from MSDN Forum: try MS_SkipVersionCheck
Reading about the attribute here, it does not seem applicable. But I tried it. Still Not Found for my API but the original one is still working. So it did not have an impact on this issue.
Yes !!!
So I finally got it working, I copied the usings from lidydonna - msft git and read about .net backend for mobileservice.
This ended with the following:
using System.Web.Http;
using Microsoft.Azure.Mobile.Server.Config;
using System.Threading.Tasks;
using System.Web.Http.Tracing;
using Microsoft.Azure.Mobile.Server;
namespace BCMobileAppService.Controllers
{
[MobileAppController]
public class TestController : ApiController
{
// GET api/Test
[HttpGet, Route("api/Test/completeAll")]
public string Get()
{
MobileAppSettingsDictionary settings = this.Configuration.GetMobileAppSettingsProvider().GetMobileAppSettings();
ITraceWriter traceWriter = this.Configuration.Services.GetTraceWriter();
string host = settings.HostName ?? "localhost";
string greeting = "Hello from " + host;
traceWriter.Info(greeting);
return greeting;
}
// POST api/values
[HttpPost, Route("api/Test/completeAll")]
public string Post(string hej)
{
string retVal = "Hello World!" + hej;
return retVal;
}
}
}
This is a new controller and not the one that comes with it as lidydonna used. It seemed like it wants both functions get and post. This resulted in the API was registered and could be accessed. This means the client call to the server I used was:
t = await App.MobileService.InvokeApiAsync<string, string>("Test/completeAll", null, HttpMethod.Post, new Dictionary<string, string>() { { "hej", " AWESOME !" }});
dialog = new MessageDialog(t);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
AND I GOT A RESPONSE YAY!!
Extra Information
The controllers that you create, i.e. the class needs to end with Controller, you can have text before but not after. This information was given on a MSDN forum discussion.
If the post and the get has the same input the server returns Not found. Having different inputs solves the issue.
In case of weird Internal Server Error, i.e. weird you can step through the entire server code all variables that you want to return are initialized, but the client receives the error. Then refer to Internal Server Error - Azure App Service Custom Controller where simple fix to the configuration can solve the issue.
You must have something wrong in your project configuration. I have a working sample here: https://gist.github.com/lindydonna/6fca7f689ee72ac9cd20
After creating the HttpConfiguration object, call config.MapHttpAttributeRoutes(). I added the route attribute [Route("api/Test/completeAll")] and I can confirm that the route is registered correctly.
Try adding this attribute to the ValuesController and check the route.
I found another cause for the 404 errors when it came to use attribute routing.
The code above originally had this in mobileApp.cs:
HttpConfiguration config = new HttpConfiguration();
new MobileAppConfiguration()
.UseDefaultConfiguration().MapApiControllers()
.ApplyTo(config);
config.MapHttpAttributeRoutes();
app.UseWebApi(config);
The config.MapHttpAttributeRoutes() needs to be moved above the .ApplyTo:
HttpConfiguration config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
new MobileAppConfiguration()
.UseDefaultConfiguration().MapApiControllers()
.ApplyTo(config);
Try switching inheriting from ApiController to TableController.
It is really strange but simple API request is not working in azure app service
So I have figure out solution which has worked for me. I have tested http requests with c# http post/get, android post/get, and objective C post/get
So first of all you need to update your Startup.MobileApp.cs class :
new MobileAppConfiguration()
.UseDefaultConfiguration()
.MapApiControllers() /* /api endpoints **missing part***/
.ApplyTo(config);
Then create Azure Mobile App Custom Controller. After that modify little bit your controller to get proper json response
public class Mes
{
public string message { get; set; }
}
// GET api/My
public Mes Get()
{
return new Mes { message = "thanks" };
// return "Hello from custom controller!";
}
// POST api/My
public Mes Post(Mes chal)
{
return new Mes { message = chal.message + "asnwer" };
// return "Hello from custom controller!";
}
}
You can simple leave first variant and get response, but OBjective C will say to you that JSON text did not start with array or object and option to allow fragments...and so on.. This happens because you getting simple string not object. So that is why I have modified my response with class Mes
But it is also depends how you make request and what type of object you expect.
So .MapApiControllers() it is the main key for API and WEB API controller is now changed to azure custom controller.
Hope this helps.
Related
I have implemented some front end code which when a user clicks the checkout button they are redirected to a stripe page where they can input their card payment details. the code has a successful URL and failed URL. if the customer enter valid payment details - they are redirected to the successful URL, i need to update my database to ensure that my backend knows that this specific user has paid and can now view subscribed content. I am trying to setup web hooks in order to do this, so I know if the user has paid, cancelled etc.
using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Stripe;
namespace workspace.Controllers
{
[Route("api/[controller]")]
public class StripeWebHook : Controller
{
// You can find your endpoint's secret in your webhook settings
const string secret = "whsec_...";
[HttpPost]
public async Task<IActionResult> Index()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var stripeEvent = EventUtility.ConstructEvent(json,
Request.Headers["Stripe-Signature"], secret);
// Handle the checkout.session.completed event
if (stripeEvent.Type == Events.CheckoutSessionCompleted)
{
var session = stripeEvent.Data.Object as Checkout.Session;
// Fulfill the purchase...
HandleCheckoutSession(session);
}
else
{
return Ok()
}
}
catch (StripeException e)
{
return BadRequest();
}
}
}
}
However when trying to implement this I get errors because I think the custom code provided above uses .NET Core and I am using the full .NET framework.
Is there a way around this or what am I doing wrong?
This may help someone so I'm posting even although it's a bit late to the table as I couldn't find a relevant answer anywhere.
I had this same issue on a dotNet Core MVC web application (so not an exact answer for the question which is .Net Framework) where the Stripe Webhook was constantly giving a 400 Bad Request response. I just couldn't hit it no matter what I tried.
Eventually, and probably obviously the solution for me was to add the [IgnoreAntiforgeryToken] attribute to the Index() method as you have in your question above. As .dotNet Core enables the Validation Token on forms I had to explicitly ignore it. The Webhooks worked as soon as I did that.
So the solution for me was:
[HttpPost]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Index()
This apparently applies to dot Net Core versions: see Microsofts Documentation
Hope this helps someone.
That's works in my Asp.net Framework 4.7, try below code for the webhook
[HttpPost]
[Route("api/[controller]/webhook")]
public async Task<HttpResponseMessage> ProcessRequest()
{
var json = await new StreamReader(HttpContext.Current.Request.InputStream).ReadToEndAsync();
try
{
var stripeEvent = EventUtility.ParseEvent(json);
// Handle the event
if (stripeEvent.Type == Events.PaymentIntentSucceeded)
{
var paymentIntent = stripeEvent.Data.Object as PaymentIntent;
// Then define and call a method to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
}
else if (stripeEvent.Type == Events.PaymentMethodAttached)
{
var paymentMethod = stripeEvent.Data.Object as PaymentMethod;
// Then define and call a method to handle the successful attachment of a PaymentMethod.
// handlePaymentMethodAttached(paymentMethod);
}
// ... handle other event types
else
{
// Unexpected event type
Console.WriteLine("Unhandled event type: {0}", stripeEvent.Type);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
catch (StripeException e)
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
//Modification and Saving Data
}
After adding this webhook , you can test on locally from https://stripe.com/docs/webhooks/test this link
So i have this ASP.NET Core on my local machine, i have installed the prerequisites and after running the application locally, the response was correct from the web browset that it was not found.
Okay, i am trying to invoked this API via Postman and i couldnt determine why i cant access it though i already checked the routings.
Below is the sample template
[HttpGet]
[Route("Details")]
public async Task<IActionResult> GetDetails(string value = null)
{
var response = new ListModelResponse<SomeModel>() as IListModelResponse<SomeModel>;
try
{
response.Model = await GetDetailsRepository
.GetDetailsSS(value)
.Select(item => item.ToViewModel())
.OrderBy(item => item.Name)
.ToListAsync();
}
catch (Exception ex)
{
response.DidError = true;
response.ErrorMessage = ex.Message;
}
return response.ToHttpResponse();
}
And in application insights of visual studio, i can see that it is invoking the API but it seems it can't get through.
Check this insights snippet
Other API's are working fine, it seems that i am missed something that i can't figure out right now.
For the routing i have this.
[Route("api/[controller]")]
I have also checked the Token and as i parsed it, i am getting the required info to access this API.
Thanks Lads!
It doesn't seem that you have the name of the controller in the request url.
api/[controller]/Details?value=CAT...
This error is due to the incorrect url present in the request. The correct URL has to be https://localhost:44309/api/your-controller-name/Details?value=CAT
ie. If the Controller name is ProductsController, then the URL has to be https://localhost:44309/api/Products/Details?value=CAT.
[Route("api/[controller]")]
public class ProductsController : Controller
{
[HttpPost("Details")]
public async Task<IActionResult> GetDetails(string value = null)
{
...
}
}
I am creating a package lib for all the errors in a Webapi service. This library will be used for providing custom responses for BadRequest, BadArgument, ApiVersionsing etc.. related errors. I need help in customizing Apiversion related errors for - ApiVersionUnspecified, UnsupportedApiVersion, InvalidApiVersion, AmbiguousApiVersion. I have follow this article to include api-versioning for my project - https://www.hanselman.com/blog/ASPNETCoreRESTfulWebAPIVersioningMadeEasy.aspx
I have checked the github wiki for the above package and found that "Depending on the desired behavior, you can extend the DefaultErrorResponseProvider or you can implement your own IErrorResponseProvider from stratch.
To wire up an alternate error response behavior, replace the default provider with your own:"
options => options.ErrorResponses = new MyErrorResponseProvider();
However; I am not quite getting how can I customize the default error responses in MyErrorResponseProvider class. Can somebody please provide me with any example so I can get started with this?
Thanks in advance!
Found the way of implementing above as -
class MyErrorResponseProvider : DefaultErrorResponseProvider
{
// note: in Web API the response type is HttpResponseMessage
public override IActionResult CreateResponse( ErrorResponseContext context )
{
switch ( context.ErrorCode )
{
case "UnsupportedApiVersion":
context = new ErrorResponseContext(
context.Request,
context.StatusCode,
context.ErrorCode,
"My custom error message.",
context.MessageDetail );
break;
}
return base.CreateResponse( context );
}
}
Thanks to github issue # - https://github.com/Microsoft/aspnet-api-versioning/issues/233
The answer customizes only the error message returned by the ASP.NET API Versioning.
To customize the whole response you can implement it by returning ObjectResult.
Startup.cs
// Add API Versioning to the service container to your project
services.AddApiVersioning(config =>
{
// Advertise the API versions supported for the particular endpoint
config.ReportApiVersions = true;
config.ErrorResponses = new ApiVersioningErrorResponseProvider();//Send standard error response when API version error.
});
ApiVersioningErrorResponseProvider.cs
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Versioning;
public class ApiVersioningErrorResponseProvider : DefaultErrorResponseProvider
{
public override IActionResult CreateResponse(ErrorResponseContext context)
{
//You can initialize your own class here. Below is just a sample.
var errorResponse = new
{
ResponseCode = 101,
ResponseMessages = "Something went wrong while selecting the api version",
HelpLink = "https://github.com/microsoft/aspnet-api-versioning/wiki/Error-Response-Provider"
};
var response = new ObjectResult(errorResponse);
response.StatusCode = (int)HttpStatusCode.BadRequest;
return response;
}
}
Which produces below output:
{
"ResponseCode": 101,
"ResponseMessages": "Something went wrong while selecting the api version",
"HelpLink": "https://github.com/microsoft/aspnet-api-versioning/wiki/Error-Response-Provider"
}
I'm developing an ASP.Net Core web application where I need to create a kind of "authentication proxy" to another (external) web service.
What I mean by authentication proxy is that I will receive requests through a specific path of my web app and will have to check the headers of those requests for an authentication token that I'll have issued earlier, and then redirect all the requests with the same request string / content to an external web API which my app will authenticate with through HTTP Basic auth.
Here's the whole process in pseudo-code
Client requests a token by making a POST to a unique URL that I sent him earlier
My app sends him a unique token in response to this POST
Client makes a GET request to a specific URL of my app, say /extapi and adds the auth-token in the HTTP header
My app gets the request, checks that the auth-token is present and valid
My app does the same request to the external web API and authenticates the request using BASIC authentication
My app receives the result from the request and sends it back to the client
Here's what I have for now. It seems to be working fine, but I'm wondering if it's really the way this should be done or if there isn't a more elegant or better solution to this? Could that solution create issues in the long run for scaling the application?
[HttpGet]
public async Task GetStatement()
{
//TODO check for token presence and reject if issue
var queryString = Request.QueryString;
var response = await _httpClient.GetAsync(queryString.Value);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
[HttpPost]
public async Task PostStatement()
{
using (var streamContent = new StreamContent(Request.Body))
{
//TODO check for token presence and reject if issue
var response = await _httpClient.PostAsync(string.Empty, streamContent);
var content = await response.Content.ReadAsStringAsync();
Response.StatusCode = (int)response.StatusCode;
Response.ContentType = response.Content.Headers.ContentType?.ToString();
Response.ContentLength = response.Content.Headers.ContentLength;
await Response.WriteAsync(content);
}
}
_httpClient being a HttpClient class instantiated somewhere else and being a singleton and with a BaseAddressof http://someexternalapp.com/api/
Also, is there a simpler approach for the token creation / token check than doing it manually?
If anyone is interested, I took the Microsoft.AspNetCore.Proxy code and made it a little better with middleware.
Check it out here: https://github.com/twitchax/AspNetCore.Proxy. NuGet here: https://www.nuget.org/packages/AspNetCore.Proxy/. Microsoft archived the other one mentioned in this post, and I plan on responding to any issues on this project.
Basically, it makes reverse proxying another web server a lot easier by allowing you to use attributes on methods that take a route with args and compute the proxied address.
[ProxyRoute("api/searchgoogle/{query}")]
public static Task<string> SearchGoogleProxy(string query)
{
// Get the proxied address.
return Task.FromResult($"https://www.google.com/search?q={query}");
}
I ended up implementing a proxy middleware inspired by a project in Asp.Net's GitHub.
It basically implements a middleware that reads the request received, creates a copy from it and sends it back to a configured service, reads the response from the service and sends it back to the caller.
This post talks about writing a simple HTTP proxy logic in C# or ASP.NET Core. And allowing your project to proxy the request to any other URL. It is not about deploying a proxy server for your ASP.NET Core project.
Add the following code anywhere of your project.
public static HttpRequestMessage CreateProxyHttpRequest(this HttpContext context, Uri uri)
{
var request = context.Request;
var requestMessage = new HttpRequestMessage();
var requestMethod = request.Method;
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(request.Body);
requestMessage.Content = streamContent;
}
// Copy the request headers
foreach (var header in request.Headers)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null)
{
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
}
requestMessage.Headers.Host = uri.Authority;
requestMessage.RequestUri = uri;
requestMessage.Method = new HttpMethod(request.Method);
return requestMessage;
}
This method covert user sends HttpContext.Request to a reusable HttpRequestMessage. So you can send this message to the target server.
After your target server response, you need to copy the responded HttpResponseMessage to the HttpContext.Response so the user's browser just gets it.
public static async Task CopyProxyHttpResponse(this HttpContext context, HttpResponseMessage responseMessage)
{
if (responseMessage == null)
{
throw new ArgumentNullException(nameof(responseMessage));
}
var response = context.Response;
response.StatusCode = (int)responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
foreach (var header in responseMessage.Content.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
// SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
response.Headers.Remove("transfer-encoding");
using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
{
await responseStream.CopyToAsync(response.Body, _streamCopyBufferSize, context.RequestAborted);
}
}
And now the preparation is complete. Back to our controller:
private readonly HttpClient _client;
public YourController()
{
_client = new HttpClient(new HttpClientHandler()
{
AllowAutoRedirect = false
});
}
public async Task<IActionResult> Rewrite()
{
var request = HttpContext.CreateProxyHttpRequest(new Uri("https://www.google.com"));
var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
await HttpContext.CopyProxyHttpResponse(response);
return new EmptyResult();
}
And try to access it. It will be proxied to google.com
A nice reverse proxy middleware implementation can also be found here: https://auth0.com/blog/building-a-reverse-proxy-in-dot-net-core/
Note that I replaced this line here
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
with
requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToString());
Original headers (e.g. like an authorization header with a bearer token) would not be added without my modification in my case.
I had luck using twitchax's AspNetCore.Proxy NuGet package, but could not get it to work using the ProxyRoute method shown in twitchax's answer. (Could have easily been a mistake on my end.)
Instead I defined the mapping in Statup.cs Configure() method similar to the code below.
app.UseProxy("api/someexternalapp-proxy/{arg1}", async (args) =>
{
string url = "https://someexternalapp.com/" + args["arg1"];
return await Task.FromResult<string>(url);
});
Piggy-backing on James Lawruk's answer https://stackoverflow.com/a/54149906/6596451 to get the twitchax Proxy attribute to work, I was also getting a 404 error until I specified the full route in the ProxyRoute attribute. I had my static route in a separate controller and the relative path from Controller's route was not working.
This worked:
public class ProxyController : Controller
{
[ProxyRoute("api/Proxy/{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
This does not:
[Route("api/[controller]")]
public class ProxyController : Controller
{
[ProxyRoute("{name}")]
public static Task<string> Get(string name)
{
return Task.FromResult($"http://www.google.com/");
}
}
Hope this helps someone!
Twitchax's answer seems to be the best solution at the moment. In researching this, I found that Microsoft is developing a more robust solution that fits the exact problem the OP was trying to solve.
Repo: https://github.com/microsoft/reverse-proxy
Article for Preview 1 (they actually just released prev 2): https://devblogs.microsoft.com/dotnet/introducing-yarp-preview-1/
From the Article...
YARP is a project to create a reverse proxy server. It started when we noticed a pattern of questions from internal teams at Microsoft who were either building a reverse proxy for their service or had been asking about APIs and technology for building one, so we decided to get them all together to work on a common solution, which has become YARP.
YARP is a reverse proxy toolkit for building fast proxy servers in .NET using the infrastructure from ASP.NET and .NET. The key differentiator for YARP is that it is being designed to be easily customized and tweaked to match the specific needs of each deployment scenario. YARP plugs into the ASP.NET pipeline for handling incoming requests, and then has its own sub-pipeline for performing the steps to proxy the requests to backend servers. Customers can add additional modules, or replace stock modules as needed.
...
YARP works with either .NET Core 3.1 or .NET 5 preview 4 (or later). Download the preview 4 (or greater) of .NET 5 SDK from https://dotnet.microsoft.com/download/dotnet/5.0
More specifically, one of their sample apps implements authentication (as for the OP's original intent)
https://github.com/microsoft/reverse-proxy/blob/master/samples/ReverseProxy.Auth.Sample/Startup.cs
Here is a basic implementation of Proxy library for ASP.NET Core:
This does not implement the authorization but could be useful to someone looking for a simple reverse proxy with ASP.NET Core. We only use this for development stages.
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
namespace Sample.Proxy
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(options =>
{
options.AddDebug();
options.AddConsole(console =>
{
console.IncludeScopes = true;
});
});
services.AddProxy(options =>
{
options.MessageHandler = new HttpClientHandler
{
AllowAutoRedirect = false,
UseCookies = true
};
options.PrepareRequest = (originalRequest, message) =>
{
var host = GetHeaderValue(originalRequest, "X-Forwarded-Host") ?? originalRequest.Host.Host;
var port = GetHeaderValue(originalRequest, "X-Forwarded-Port") ?? originalRequest.Host.Port.Value.ToString(CultureInfo.InvariantCulture);
var prefix = GetHeaderValue(originalRequest, "X-Forwarded-Prefix") ?? originalRequest.PathBase;
message.Headers.Add("X-Forwarded-Host", host);
if (!string.IsNullOrWhiteSpace(port)) message.Headers.Add("X-Forwarded-Port", port);
if (!string.IsNullOrWhiteSpace(prefix)) message.Headers.Add("X-Forwarded-Prefix", prefix);
return Task.FromResult(0);
};
});
}
private static string GetHeaderValue(HttpRequest request, string headerName)
{
return request.Headers.TryGetValue(headerName, out StringValues list) ? list.FirstOrDefault() : null;
}
public void Configure(IApplicationBuilder app)
{
app.UseWebSockets()
.Map("/api", api => api.RunProxy(new Uri("http://localhost:8833")))
.Map("/image", api => api.RunProxy(new Uri("http://localhost:8844")))
.Map("/admin", api => api.RunProxy(new Uri("http://localhost:8822")))
.RunProxy(new Uri("http://localhost:8811"));
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
I'm unable to Post a string to a WebAPI from a Console Application:
Console App:
public void Main()
{
try
{
ProcessData().Wait();
}
catch (Exception e)
{
logger.Log(LogLevel.Info, e);
}
}
private static async Task ProcessData()
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(API_BASE_URL);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// HTTP POST
var response = await client.PostAsJsonAsync("api/teste", "test_api");
}
}
catch (Exception e)
{
logger.Log(LogLevel.Info, e);
}
I try to call a WebAPI in an MVC4 Web Application:
namespace Heelp.Api
{
public class TesteController : ApiController
{
private readonly ICommentService _commentService;
public TesteController(ICommentService commentService)
{
_commentService = commentService;
}
public string Post(string comment)
{
var response = "OK";
try
{
_commentService.Create(new Core.DtoModels.CommentDto { CommentType = "Like", Email = "p#p.pt", Message = "comment" });
}
catch(Exception e)
{
response = e.Message;
}
return response;
}
}
}
[EDIT]
After testing with fiddler, I get the error:
{"Message":"An error has occurred.","ExceptionMessage":"Type 'Heelp.Api.TesteController' does not have a default onstructor",
"ExceptionType":"System.ArgumentException","StackTrace":"
at System.Linq.Expressions.Expression.New(Type type)\r\n at
System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n at
System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request,
HttpControllerDescriptor controllerDescriptor, Type controllerType)"}
The Route is the Default.
I don't know how to debug it, and why it's not working.
What am I doing wrong here?
Here goes my answer, Web API always had this problem with Simple Types. Read Rick Strahls Blog Post on Web API problems with Simple Data Types
What you can do it, have WEB API code in this way -
public HttpResponseMessage Post(FormDataCollection formData)
{
return null;
}
And let HttpClient request in this way -
HttpClient client = new HttpClient();
var nameValues = new Dictionary<string, string>();
nameValues.Add("Name", "hi");
var Name = new FormUrlEncodedContent(nameValues);
client.PostAsync("http://localhost:23133/api/values", Name).ContinueWith(task =>
{
var responseNew = task.Result;
Console.WriteLine(responseNew.Content.ReadAsStringAsync().Result);
});
that will give the output as follows -
[--- EDIT based on question edit ---]
I checked the latest edit on question and the Error because of no Default Constructor. That error is because you got something wrong with Dependency Injection which you are doing for constructor injection. So please check that area. Also use Unity Web API Dependency Injection nuget to get the work done. Here goes complete tutorial in setting up DI using Unity.
Also please check the autofac if you need different versions of it from MVC and Web Api, at least that is the case with Unity though. I think you need to have Autofac.WebApi 3.1.0 for DI to work with Web API controllers.
First of all, thanks for all the help.
The problem was that in the MVC4 project where I add the Api, I was already using Autofac for Dependency Injection, but I was not aware that I also need DI for the ApiControllers.
So I installed using NuGet Autofac.WebApi 3.1.0 and add to the Autofac Config the lines:
In the Begin:
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
In the End:
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
Now everything is working fine ! :)