Web API redirect to web page - c#

I have a Web API that I need to convert to API Core 2. I have one issue I cannot seem to resolve. There is a redirect in the controller that uses Request.CreateResponse which does not appear to be available in .Net Core?
public HttpResponseMessage Get() {
var response = Request.CreateResponse(HttpStatusCode.Redirect);
response.Headers.Location = new Uri("https://xxx.xxxx.com");
return response;
}
Any idea how I can modify this to work? Basically if a user does not pass any parameters, we just send them to a web page.

The syntax has changed in ASP.Net-Core. HttpResponseMessage is no longer used in action results. They will serialized like any other model returned object.
Use instead, the Controller.Redirect method which returns a RedirectResult
public IActionResult Get() {
return Redirect("https://xxx.xxxx.com");
}
RedirectResult
RedirectResult will redirect us to the provided URL, it doesn’t matter if the URL is relative or absolute, it just redirect, very simple. Other thing to note is that it can redirect us temporarily which we’ll get 302 status code or redirect us permanently which we’ll get 301 status code. If we call the Redirect method, it redirect us temporarily...
Reference Asp.Net Core Action Results Explained

That's what the Redirect() method exactly does:
return Redirect("https://xxx.xxx.com");
But if you want more control, you can return a ContentResult:
response.Headers.Location = new Uri("https://xxx.xxx.com");
return new ContentResult {
StatusCode = (int)HttpStatusCode.Redirect,
Content = "Check this URL"
};
I don't see any benefit for doing this, though. Well, unless if you use some client that will not follow the redirect and you want to provide some instructions or content in the Content property, which the client will see as the body of the returned page.

You can use HttpResponseMessage instead, something like:
var response = new HttpResponseMessage(HttpStatusCode.Redirect);
response.Headers.Location = new Uri("https://insight.xxx.com");

Related

Simple POST with HttpClient

I have two ASP.NET Core 2.1 apps and I'm trying to make a simple POST call from one app to the other using HttpClient.
For some reason, when I use [FromBody] to get the simple text I'm trying receive, I get a BadRequest error.
Here's my code on the sender. First, this is what's in my ConfigureServices method. I'm using the new HttpClientFactory feature in ASP.NET Core 2.1. I also created a client class named myApiCallerClient that handles my API calls:
services.AddHttpClient("myNamedClient", client =>
{
client.BaseAddress = new Uri("http://localhost:50625/api/");
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
services.AddScoped(x => {
var httpClientFactory = x.GetRequiredService<System.Net.Http.IHttpClientFactory>();
var myApiCallerClient = httpClientFactory.CreateClient("myNamedClient");
return new Clients.ApiCaller.ApiCallerClient(myApiCallerClient);
});
Here's the code in myApiCallerClient:
public async Task SayHello()
{
var response = await _httpClient.PostAsync("test", new StringContent("Saying hello!", System.Text.Encoding.UTF8, "text/plain"));
}
And here's my code on the receiving end which is the POST() API method in TestController:
[HttpPost]
public async Task Post([FromBody]string input)
{
// Some logic
}
My call doesn't hit this method if I use [FromBody] and I get BadRequest error on the sender. If I use [FromHeader] my request hits this API method but I'm getting a null value.
What am I doing wrong here?
ASP.NET Core doesn't support a Content-Type of text/plain out of the box, so the server is rejecting your request as it's not something that it can parse when using the [FromBody] attribute.
In the comments, you said:
Ultimately I won't be sending text. My main concern here is that I'm not hitting my API method. [...] Once I make sure everything is working, I'll be sending JSON data which will get deserialized on the receiving end.
In order to test whether the problem is due to the text/plain Content-Type, you can update your PostAsync line to something like this:
var response = await _httpClient.PostAsync(
"test",
new StringContent("\"Saying hello!\"", System.Text.Encoding.UTF8, "application/json"));
As application/json is a supported Content-Type by default, the code above uses that and wraps the value you were sending with "s in order to make it a valid JSON value.

RedirectToAction is changing the URL

My call to RedirectToAction is acting like RedirectToActionPermanent. That is, the URL is being changed, rather than simply displaying a different view.
Edit: Now that I think about it, RedirectToAction typically acts as a permanent redirect. As in, this is probably the correct behavior. In the below code, if the ModelState is valid, the user is given a 302 redirect back to the index. But then, what's the point of RedirectToActionPermanent?
The redirects are for HTTP errors. I have my Web.config set to point errors to certain action methods in HttpErrorsController. This works perfectly, including showing a temporary redirect, as expected. (https://localhost/ThisPageDoesntExist shows error page but the URL remains the same)
Returning an HttpStatusCodeResult or throwing an HttpException both work as expected.
However, if I try to do a temporary redirect to an error action method by using RedirectToAction, the view is still displayed properly, but the URL changes, e.g. https://localhost/HttpErrors/404.
HttpErrorsController.cs
private ViewResult ErrorView(HttpStatusCode httpStatusCode, string shortDesc, string longDesc)
{
Response.StatusCode = (int)httpStatusCode;
return View("HttpError", new HttpErrorViewModel(httpStatusCode, shortDesc, longDesc));
}
[ActionName("404")]
public ActionResult Error404()
{
return ErrorView(HttpStatusCode.NotFound, "Not Found",
"The requested resource could not be found.");
}
// Other identical methods for each error
ItemController.cs
public ActionResult HttpError(HttpStatusCode status)
{
return RedirectToAction(((int)status).ToString(), "HttpErrors");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ItemViewModel viewModel)
{
if (!Request.IsAjaxRequest())
{
return HttpError(HttpStatusCode.NotAcceptable);
}
if (ModelState.IsValid)
{
db.Items.Add(pm);
db.SaveChanges();
return RedirectToAction("Index");
}
return PartialView("_Create", viewModel);
}
Since writing the above, I've realized I'm probably better off just throwing an HttpException, so that it also gets caught by ELMAH, but I'm still quite confused by the behavior described above.
RedirectToAction method sends a 302 response back to the browser with the location header value which the new url and the browser will make a totally new http GET request to this new url. So what you see is the expected behavior.
If you want not do the redirect, but want to keep the url as it is, do not do return a RedirectResult, return a view result as needed.
RedirectToActionPermanent method sends a 301 Moved Permanently response back to the client. This is usually useful when you are moving one page to another (killing an old page and creating a new one with different url) of your site and wants the client to know it so that they can the calling code to use the new url in future. Think about google search engine changing the links to your new page and showing that in the search result.

Images from http site on https site: mixed mode

My https-based site (site A) uses images from http-based site B. I causes mixed-content error. To fix this, I found solution to swap each external link like http://www.siteB.com/imageX.png with my controller method which do forward to external image. The new link format is:
The code of method /api/misc/forward is following:
[HttpGet]
public async Task<HttpResponseMessage> Forward(string url)
{
HttpResponseMessage httpResponseMessage = new HttpResponseMessage();
try
{
var response = Request.CreateResponse(HttpStatusCode.Found);
response.Headers.Location = new Uri(HttpUtility.UrlDecode(url));
return response;
}
catch (Exception ex)
{
httpResponseMessage.StatusCode = HttpStatusCode.NotFound;
_loggerService.LogException(ex, url);
}
return httpResponseMessage;
}
but the browser still is able to recognize it as mixed mode.... Why?
The original image links sent to browser origins from https-based site.
Any quick tip for it? I dont want to cache all images from site B:).
Because your code sends back a redirect to another location, so, eventually, the browser still go to the HTTP image.
What happens is that your browser calls the controller in HTTPS, then controller action sends back a redirect command to the browser, the browser retrieves the image from the new location that you set in the response.Headers.Location.
If you want to avoid the mixed mode, then you need to retrieve the image from the controller and return a FileResult from the action, this way, the browser will not have to access the HTTP site.
Another approach, would be to just copy the images to you site.

How to clean up existing response in webapi?

There is a authentication library that I have to use that helpfully does things like
Response.Redirect(url, false);
inside of it's method calls. I can't change this libraries code and it's fine for MVC style apps but in angular SPA -> WebApi apps this is just awful.
I really need a 401 otherwise I get into trouble with CORS when my angular scripts, using $http, try to call out to the auth server on another domain in response to the 302, that's if it even could as the Response.Redirect also sends down the object moved html and the angle brackets cause an error to be thrown.
Since I have to make the call to the auth library first the Response.Redirect is already in the response pipeline and so I need to clean it up to remove the body content and convert the 302 into a 401. I thought I could just:
return new HttpWebResponse(StatusCode.UnAuthorized){
Content = new StringContent("data");
}
but this just gets appended to the response and doesn't replace it plus I also need the Location: header which I can't seem to access via WebApi methods.
So instead I've had to do this in my ApiController:
var ctxw = this.Request.Properties["MS_HtpContext"] as HttpContextWrapper;
var ctx = ctxw.ApplicationInstance.Context;
var url = ctx.Response.RedirectLocation;
ctx.Response.ClearContent();
return new HttpWebResponse(StatusCode.UnAuthorized){
Content = new StringContent(url);
}
But this seems terrible and counter to webapi "feel". Plus I'm tied to the controller in doing this. I can't get the wrapper in a MessageHandler for example.
What I'd like to do is monitor the response for a given route in a message handler or in an AuthorizationFilterAttribute, if its a 302, I want to read it's headers, take what I want, wipe it and replace it with my own "fresh" response as a 401. How can I do this?
You might want to write your own ActionFilter and override its OnActionExecuted method where you can access HttpActionExecutedContext. From there, you can check response code, for example, and overwrite response with whatever you want.
Ref: https://msdn.microsoft.com/en-us/library/system.web.http.filters.actionfilterattribute.onactionexecuted%28v=vs.118%29.aspx#M:System.Web.Http.Filters.ActionFilterAttribute.OnActionExecuted%28System.Web.Http.Filters.HttpActionExecutedContext%29

How to redirect to another area in ASP.NET Web API inside ActionFilter attribute

I can't find an obvious way to redirect from Web API FilterAttribute OnActionExecuting to the exact same controller under a different area.
Do you know a Web API alternative or the right way to redirect to an area as we used to, in MVC with RedirectToAction?
I know there's a way to achieve this via resetting the Response object and re-locating the execution context with a Location header like here:
var response = Request.CreateResponse(HttpStatusCode.Found);
response.Headers.Location = new Uri("http://www.google.com");
actionContext.Request = response;
I don't know how to specify the area except for explicitly pointing it in the URL.
Basically, here's the place I want to redirect the request to another area, preferably without a full client redirect.
public override void OnActionExecuting(HttpActionContext actionContext) {}

Categories