WebClient must not serve local files - c#

I have a simple reverse proxy to avoid CORS in the browser.
In essence, it works like this:
string url = Request.QueryString["url"];
using (var webClient = new System.Net.WebClient())
{
byte[] buffer = webClient.DownloadData(url);
Response.Clear();
Response.BinaryWrite(buffer);
}
Usage:
/reverseproxy.aspx?url=http%3A%2F%2Fexample.com%2F
However, this has a vulnerability. The following request will return the logs of my IIS server.
/reverseproxy.aspx?url=c%3A%2Finetpub%2Flogs%2FLogFiles%2FW3SVC1%2Fu_ex170712.log
Is there a way to tell WebClient to not serve local files?
Without setting permissions and without using File.Exists(url)

You can create an extra assembly that creates your own implementation for a WebRequest and then configure your web.config to use your custom implementation for the file protocol.
Here is what you need:
Factory
This class decides if the reqest uri is local or not, add your own checks if needed.
public class NoLocalFile: IWebRequestCreate
{
public WebRequest Create(Uri uri)
{
// add your own extra checks here
// for example what Patrick offered in his answer
// I didn't test if I can't create a local UNC path
if (uri.IsFile && !uri.IsUnc)
{
// this is a local file request, we're going to return something safe by
// creating our own custom WebRequest
return (WebRequest) new NoLocalFileRequest();
}
else
{
// this should allow non local file request
// if needed
return FileWebRequest.Create(uri);
}
}
}
NoLocalFileRequest
This is the minimal implementation needed for our custom WebRequest
public class NoLocalFileRequest:WebRequest
{
// return an instance of our custom webResponse
public override WebResponse GetResponse()
{
return new NoLocalFileResponse();
}
}
NoLocalFileResponse
This class implements a WebResponse and returns a memorystream with the ASCII representation of the string "NO!". Here it is required to implement the ContentLength property as well.
public class NoLocalFileResponse : WebResponse {
static byte[] responseBytes = Encoding.ASCII.GetBytes("NO!");
public override long ContentLength
{
get {
return responseBytes.Length;
}
set {
// whatever
}
}
public override Stream GetResponseStream()
{
// what you want to return goes here
return new MemoryStream(responseBytes);
}
}
If you put these classes in a namespace called MyCustomWebRequests and compile this to an assembly called BlockLocalFile.dll, copy that assembly in the bin folder of your webapplication, then all you need is to make or add these changes to your web.config:
<system.net>
<webRequestModules>
<remove prefix="file:"/>
<add prefix="file:" type="MyCustomWebRequests.NoLocalFile, BlockLocalFile"/>
</webRequestModules>
</system.net>
When you testdrive this you should find that your previous code still works unchanged while the browsers that try the local file trick will get "NO!" returned instead of your file content.
Be aware that this config change applies to all code that runs in the appdomain of that webapplication. If you have legit code that also does require the normal use of the file protocol you have to make changes to the factory method that decides which WebRequest to create.

You could check if your provided URI is an URL or a file reference using the Uri class. You could use the code found here:
private static bool IsLocalPath(string p)
{
return new Uri(p).IsFile;
}
Even safer would be to check the scheme of the Uri to match to http or https:
private static bool IsHttpOrHttps(string uri)
{
Uri u = new Uri(uri);
return u.Scheme == Uri.UriSchemeHttp || u.Scheme == Uri.UriSchemeHttps;
}

Related

Creating a proxy to another web api with Asp.net core

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();
}
}
}

How to acquire HttpStatus Codes in ASP.Net Core?

I'm running through a list of both secure and unsecured domains (both http:// and https://) in an array and wish to return their status codes. How is this possible in ASP.Net Core 1.0?
so far, I have
foreach(var item in _context.URLs.ToList())
{
// Do something here with item.Domain to check for status
// For example, if item.Domain was https://example.com..
}
I tried it with regular ASP.Net syntax with this approach:
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(item.Domain);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
The problem is, GetResponse doesn't work in ASP.Net Core
Can anyone help me out with an effecient solution so that the variable returned would be the status?
ex: 200, 500, 404..
EDIT - Here is my full controller AND SOLUTION:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using MyApp.Models;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
namespace MyApp.Controllers.Api
{
public class URLsController : Controller
{
private MyAppDBContext _context;
public class newLink
{
public string Domain { get; set; }
public HttpStatusCode Status { get; set; }
}
public async Task<HttpStatusCode> GetStatusCodes(string url)
{
var client = new HttpClient();
var response = await client.GetAsync(url);
return response.StatusCode;
}
public URLsController(MyAppDBContext context)
{
_context = context;
}
[HttpPost("api/URLs")]
public async Task<IActionResult> Post(string url)
{
if (url != "")
{
// I pass a URL in through this API and add it to _context.URLs..
// I execute a status code check on the URLs after this if statement
}
List<newLink> list = new List<newLink> ();
foreach (var item in _context.URLs.ToList())
{
newLink t = new newLink();
t.Domain = item.Domain;
t.Status = await GetStatusCodes(item.Domain);
list.Add(t);
}
return Ok(list);
}
}
}
This returns an array back in this format:
[{"Domain":"https://example1.com/","Status":200},
{"Domain":"https://example2.com/","Status":200},
{"Domain":"https://example3.com/","Status":200}]
You could use HttpClient as it is easier to work with (you don't need to catch WebExceptions for the non success status codes like you would have to do with the plain HttpWebRequest and then extract the HTTP status code from the exception).
You could write a helper method that given a list of urls will return a list of corresponding status codes. This will make your code a little bit more decoupled. Do not violate the single responsibility principle. A method should not do more than 1 specific thing (in your example you were mixing some DB calls and HTTP calls into a single method which is bad practice).
public async Task<IList<HttpStatusCode>> GetStatusCodes(IList<string> urls)
{
var client = new HttpClient();
var result = new List<HttpStatusCode>();
foreach (var url in urls)
{
var response = await client.GetAsync(url);
result.Add(response.StatusCode);
}
return result;
}
Remark 1: If the url that you are trying to call is not DNS resolvable or your calling application doesn't have network access to the specified address on the target port you will not get any status code for obvious reasons. You will get a nice exception. Think about handling this case. It's up to you to decide what you want to return in the resulting collection in this case.
Remark 2: Making a GET request just for determining the HTTP status code might be a waste as you are throwing away the response body that you have already transported over the wire. If the remote resource responds to HEAD requests that might be more efficient way to determine if the server is alive. But consider this with caution as it will depend on the specifics of the web endpoint that you are calling.
Remark 3: You have undoubtedly noticed that this method is async. Well, if you intend to develop under .NET Core you'd better get accustomed to it. You could of course violate the built-in asynchronous patterns that the framework provides you by blocking the calling thread:
var urls = _context.URLs.ToList();
IList<HttpStatusCode> statusCodes = GetStatusCodes(urls).GetAwaiter().GetResult();
But this is an extremely bad practice. A more idiomatic way of working is to make all your methods asynchronous through the entire chain until you reach the main calling method which is usually provided by the framework itself and which can be asynchronous as well. For example if you are calling this inside a Web API action you could simply make it async:
[HttpGet]
[Route("api/foos")]
public async Task<IActionResult> Get()
{
var urls = _context.URLs.ToList();
IList<HttpStatusCode> statusCodes = await GetStatusCodes(urls);
return this.Ok(statusCodes);
}

unable to configure Web API for content type multipart

I am working on Web APIs - Web API 2. My basic need is to create an API to update the profile of the user. In this, the ios and android will send me the request in multipart/form-data. They will send me a few parameters with an image. But whenever I try to create the API, my model comes to be null every time.
I have also added this line in WebApiConfig:
config.Formatters.JsonFormatter.SupportedMediaTypes
.Add(new MediaTypeHeaderValue("multipart/form-data"));
This is my class:
public class UpdateProfileModel
{
public HttpPostedFileBase ProfileImage { get; set; }
public string Name { get; set; }
}
This is my controller:
[Route("api/Account/UpdateProfile")]
[HttpPost]
public HttpResponseMessage UpdateProfile(UpdateProfileModel model)
{
}
I am even not getting parameter values in my Model. Am I doing something wrong?
None of the answers related to this were helpful for me. It's about 3rd day and I have tried almost everything and every method. but I am unable to achieve it.
Although I can use this but this as shown below but this doesn't seem to be a good approach. so I am avoiding it.
var httpRequest = HttpContext.Current.Request;
if (httpRequest.Form["ParameterName"] != null)
{
var parameterName = httpRequest.Form["ParameterName"];
}
and for files I can do this:
if (httpRequest.Files.Count > 0)
{
//I can access my files here and save them
}
Please help if you have any good approach for it Or Please explain to me why I am unable to get these values in the Model.
Thanks a lot in Advance
The answer provided by JPgrassi is what you would be doing to have MultiPart data. I think there are few more things that needs to be added, so I thought of writing my own answer.
MultiPart form data, as the name suggest, is not single type of data, but specifies that the form will be sent as a MultiPart MIME message, so you cannot have predefined formatter to read all the contents. You need to use ReadAsync function to read byte stream and get your different types of data, identify them and de-serialize them.
There are two ways to read the contents. First one is to read and keep everything in memory and the second way is to use a provider that will stream all the file contents into some randomly name files(with GUID) and providing handle in form of local path to access file (The example provided by jpgrassi is doing the second).
First Method: Keeping everything in-memory
//Async because this is asynchronous process and would read stream data in a buffer.
//If you don't make this async, you would be only reading a few KBs (buffer size)
//and you wont be able to know why it is not working
public async Task<HttpResponseMessage> Post()
{
if (!request.Content.IsMimeMultipartContent()) return null;
Dictionary<string, object> extractedMediaContents = new Dictionary<string, object>();
//Here I am going with assumption that I am sending data in two parts,
//JSON object, which will come to me as string and a file. You need to customize this in the way you want it to.
extractedMediaContents.Add(BASE64_FILE_CONTENTS, null);
extractedMediaContents.Add(SERIALIZED_JSON_CONTENTS, null);
request.Content.ReadAsMultipartAsync()
.ContinueWith(multiPart =>
{
if (multiPart.IsFaulted || multiPart.IsCanceled)
{
Request.CreateErrorResponse(HttpStatusCode.InternalServerError, multiPart.Exception);
}
foreach (var part in multiPart.Result.Contents)
{
using (var stream = part.ReadAsStreamAsync())
{
stream.Wait();
Stream requestStream = stream.Result;
using (var memoryStream = new MemoryStream())
{
requestStream.CopyTo(memoryStream);
//filename attribute is identifier for file vs other contents.
if (part.Headers.ToString().IndexOf("filename") > -1)
{
extractedMediaContents[BASE64_FILE_CONTENTS] = memoryStream.ToArray();
}
else
{
string jsonString = System.Text.Encoding.ASCII.GetString(memoryStream.ToArray());
//If you need just string, this is enough, otherwise you need to de-serialize based on the content type.
//Each content is identified by name in content headers.
extractedMediaContents[SERIALIZED_JSON_CONTENTS] = jsonString;
}
}
}
}
}).Wait();
//extractedMediaContents; This now has the contents of Request in-memory.
}
Second Method: Using a provider (as given by jpgrassi)
Point to note, this is only filename. If you want process file or store at different location, you need to stream read the file again.
public async Task<HttpResponseMessage> Post()
{
HttpResponseMessage response;
//Check if request is MultiPart
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
//This specifies local path on server where file will be created
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
//This write the file in your App_Data with a random name
await Request.Content.ReadAsMultipartAsync(provider);
foreach (MultipartFileData file in provider.FileData)
{
//Here you can get the full file path on the server
//and other data regarding the file
//Point to note, this is only filename. If you want to keep / process file, you need to stream read the file again.
tempFileName = file.LocalFileName;
}
// You values are inside FormData. You can access them in this way
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
Trace.WriteLine(string.Format("{0}: {1}", key, val));
}
}
//Or directly (not safe)
string name = provider.FormData.GetValues("name").FirstOrDefault();
response = Request.CreateResponse(HttpStatusCode.Ok);
return response;
}
By default there is not a media type formatter built into the api that can handle multipart/form-data and perform model binding. The built in media type formatters are :
JsonMediaTypeFormatter: application/json, text/json
XmlMediaTypeFormatter: application/xml, text/xml
FormUrlEncodedMediaTypeFormatter: application/x-www-form-urlencoded
JQueryMvcFormUrlEncodedFormatter: application/x-www-form-urlencoded
This is the reason why most answers involve taking over responsibility to read the data directly from the request inside the controller. However, the Web API 2 formatter collection is meant to be a starting point for developers and not meant to be the solution for all implementations. There are other solutions that have been created to create a MediaFormatter that will handle multipart form data. Once a MediaTypeFormatter class has been created it can be re-used across multiple implementations of Web API.
How create a MultipartFormFormatter for ASP.NET 4.5 Web API
You can download and build the full implementation of the web api 2 source code and see that the default implementations of media formatters do not natively process multi part data.
https://aspnetwebstack.codeplex.com/
You can't have parameters like that in your controller because there's no built-in media type formatter that handles Multipart/Formdata. Unless you create your own formatter, you can access the file and optional fields accessing via a MultipartFormDataStreamProvider :
Post Method
public async Task<HttpResponseMessage> Post()
{
HttpResponseMessage response;
//Check if request is MultiPart
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
//This write the file in your App_Data with a random name
await Request.Content.ReadAsMultipartAsync(provider);
foreach (MultipartFileData file in provider.FileData)
{
//Here you can get the full file path on the server
//and other data regarding the file
tempFileName = file.LocalFileName;
}
// You values are inside FormData. You can access them in this way
foreach (var key in provider.FormData.AllKeys)
{
foreach (var val in provider.FormData.GetValues(key))
{
Trace.WriteLine(string.Format("{0}: {1}", key, val));
}
}
//Or directly (not safe)
string name = provider.FormData.GetValues("name").FirstOrDefault();
response = Request.CreateResponse(HttpStatusCode.Ok);
return response;
}
Here's a more detailed list of examples:
Sending HTML Form Data in ASP.NET Web API: File Upload and Multipart MIME
Not so sure this would be helpful in your case , have a look
mvc upload file with model - second parameter posted file is null
and
ASP.Net MVC - Read File from HttpPostedFileBase without save
So, what worked for me is -
[Route("api/Account/UpdateProfile")]
[HttpPost]
public Task<HttpResponseMessage> UpdateProfile(/* UpdateProfileModel model */)
{
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
await Request.Content.ReadAsMultipartAsync(provider);
foreach (MultipartFileData file in provider.FileData)
{
}
}
Also -
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
isn't required.
I guess the multipart/form-data is internally handled somewhere after the form is submitted.
Very clearly described here -
http://www.asp.net/web-api/overview/advanced/sending-html-form-data-part-2

MVC 4 RedirectToAction does not see Custom Header

If you start a new Web Project, and create a new MVC4 application (with sub-kind as "WebApi", you can paste the below code in (overwriting HomeController.cs) to get the code to work.
I have a MVC4 application (with WebApi).
I am trying to set a custom-header in a MVC controller method and then do a RedirectToAction. The custom-header is not seen in the second mvc-controller-method.
I am able to set a cookie in the first mvc-controller-method and see it in the second mvc-controller-method (after a RedirectToAction).
Is there a way to see the custom-header I set in the second mvc-controller-method after a RedirectToAction ?
Thanks.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
namespace MyMvc4WebApiProjectNamespace.Controllers
{
public class HomeController : Controller
{
private const string CustomCookieName = "CustomCookieName";
private const string CustomHeaderName = "X-CustomHeaderName";
private const string IISExpressRootUrl = "http://localhost:55937/"; /* open up the project properties and go to the web tab and find the iis-express area to get the correct value for your environment */
public ActionResult Index()
{
IEnumerable<string> webApiValues = null;
string value1 = null;
string value2 = null;
HttpClientHandler handler = new HttpClientHandler
{
UseDefaultCredentials = true,
PreAuthenticate = true
};
using (var client = new HttpClient(handler))
{
string valuesUri = IISExpressRootUrl + "api/Values";
webApiValues = client
.GetAsync(valuesUri)
.Result
.Content.ReadAsAsync<IEnumerable<string>>().Result;
if (null != webApiValues)
{
value1 = webApiValues.ElementAt(0);
value2 = webApiValues.ElementAt(1);
}
else
{
throw new ArgumentOutOfRangeException("WebApi call failed");
}
}
HttpCookie customCookie = new HttpCookie(CustomCookieName, "CustomCookieValue_ThisShowsUpIn_MyHomeControllerAlternateActionResult_Method");
Response.Cookies.Add(customCookie);
HttpContext.Response.AppendHeader(CustomHeaderName, "CustomHeaderValue_This_Does_Not_Show_Up_In_MyHomeControllerAlternateActionResult_Method");
//Response.AppendHeader(CustomHeaderName, value2);
return RedirectToAction("MyHomeControllerAlternateActionResult");
}
public ActionResult MyHomeControllerAlternateActionResult()
{
IEnumerable<string> webApiReturnValues = null;
CookieContainer cookieContainer = new CookieContainer();
foreach (string cookiename in Request.Cookies)
{
if (cookiename.Equals(CustomCookieName, StringComparison.OrdinalIgnoreCase))
{
var cookie = Request.Cookies[cookiename];
cookieContainer.Add(new Cookie(cookie.Name, cookie.Value, cookie.Path, "localhost"));
}
}
if (cookieContainer.Count < 1)
{
throw new ArgumentOutOfRangeException("CookieContainer did not find the cookie I was looking for");
}
else
{
Console.WriteLine("This is what actually happens. It finds the cookie.");
}
HttpClientHandler handler = new HttpClientHandler
{
UseCookies = true,
UseDefaultCredentials = true,
PreAuthenticate = true,
CookieContainer = cookieContainer
};
using (var client = new HttpClient(handler))
{
bool customHeaderWasFound = false;
if (null != this.Request.Headers)
{
if (null != this.Request.Headers[CustomHeaderName])
{
IEnumerable<string> headerValues = this.Request.Headers.GetValues(CustomHeaderName);
client.DefaultRequestHeaders.Add(CustomHeaderName, headerValues);
customHeaderWasFound = true;
}
}
/*I wouldn't expect it to be in the below, but I looked for it just in case */
if (null != this.Response.Headers)//
{
if (null != this.Response.Headers[CustomHeaderName])
{
IEnumerable<string> headerValues = this.Response.Headers.GetValues(CustomHeaderName);
client.DefaultRequestHeaders.Add(CustomHeaderName, headerValues);
customHeaderWasFound = true;
}
}
if (!customHeaderWasFound)
{
Console.WriteLine("This is what actually happens. No custom-header found. :( ");
}
string valuesUri = IISExpressRootUrl + "api/Values";
webApiReturnValues = client
.GetAsync(valuesUri)
.Result
.Content.ReadAsAsync<IEnumerable<string>>().Result;
if (null == webApiReturnValues)
{
throw new ArgumentOutOfRangeException("WebApi call failed");
}
}
return View(); /* this will throw a "The view 'MyHomeControllerAlternateActionResult' or its master was not found or no view engine supports the searched locations" error, but that's not the point of this demo. */
}
}
}
Response headers are never copied automatically to requests - so setting any custom headers on response will not impact next request issued to handle 302 redirect.
Note that it is the case even with cookies: response comes with "set this cookie" header, and all subsequent request will get "current cookies" header.
If you have your own client you may be able to handle 302 manually (not possible if you are using browser as client).
As another answer stated, response headers are about this response, not the next one. Redirecting is not a server-side action. A redirect instructs the client to perform a completely new request, and of course in a new request, the response headers for the old request are not present. So return RedirectToAction("MyHomeControllerAlternateActionResult"); is guaranteed to not have this response's headers when the browser initiates the new request.
In trying to solve this problem, one might think of trying to persist the data to the next request server-side, such as through a cookie or in an explicit session variable, or implicitly via use of ViewBag/ViewData/TempData. However, I don't recommend this as using session state heavily has performance implications in large/high-usage web sites, plus there are other negative and subtle side-effects that you may run into down the road. For example, if a person has two browser windows open to the same web site, they can't be doing different actions reliably, as the session data for one window can end up being served to the other one. Avoid session usage as much as possible in your web site design—I promise this will benefit you down the road.
A slightly better way, though still with its problems, is to redirect to a URL with querystring parameters containing a payload. And, instead of the whole set of data, you can provide a key that can be pulled from the session (as long as it's also bound to their IP address and is large like a GUID or two together). However, relying on session state is still not ideal as stated before.
Instead, consider using server-side redirection such as child actions. If you find that hard because what you want to call is a main controller you have a few options:
If you're using dependency injection, add a parameter to the current controller (saving it from the constructor and using it in the request method) that is the desired controller you want to "redirect" to. You can then call that controller directly. This may not be ideal (as all calls to this controller also have to new up a copy of that one), but it does work. Trying to new up the other controller manually can also work, but for reasons I don't fully remember, I think this can give some additional problems. In any case, this method can give issues accessing the HttpRequest context and other context objects correctly, though this can be worked around.
Rearchitect your application so that controllers are not the place where full pages are rendered. Instead, use them as "smart routers" that call child actions to perform the real work. Then, you can call the same child actions from any controller. But this still has problems.
Perhaps the best way is to add custom routing logic through action filters or other means (search the web!) so that the correct controller is hit in the first place! This may not always be possible, but sometimes the need to redirect to another controller mid-procedure actually points to a larger design problem. Focusing on how to cause the knowledge of which controller to hit to be available earlier in the pipeline (such as during routing) can reveal architecture problems, and can reveal likely solutions to them.
There may be other options that I haven't thought of, but at least you have a few alternatives to the simple "no way to do that."
I was able to do something similar like what the user is requesting in the following (rudimentary) way:
In the redirect, add a custom query string parameter
Create a custom Module that checks for that parameter and appends the custom header (read http://dotnetlionet.blogspot.com/2015/06/how-to-add-httpmodule-in-mvc5.html on how to do your own module)
In this way I was able to get my custom headers to be picked up

DotNetOpenAuth OAuth2 Accessing Extra Data

I'm using DotNetOpenAuth's OAuth2 library to handle authorization with another third party system. It all works great, except that the third party system is returning the UserId="testname" in the Response with the AccessToken.
I need that UserId because this third party API requires it as part of their API calls (ex: users/{userId}/account).
Using DotNetOpenAuth, I don't have access to the AccessToken response so I can't get the UserId out.
I'm calling: (_client is a WebServerClient)
var state = _client.ProcessUserAuthorization(request);
state has my AccessToken, but not the extra data sent down. Based on the DotNetOpenAuth source code the UserId came in inside the library and I don't have any access.
Is there anyway to get that UserId out using DotNetOpenAuth? Or do I need to abandon DotNetOpenAuth and try something else?
You can access request and response data by implementing IDirectWebRequestHandler and assigning it to Channel. But with current implementation of DNOA, the only way I got it to work is by applying proxy pattern to an existing UntrustedWebRequestHandlerclass, this is because this particular handler passes a CachedDirectWebResponse, which has a response stream that could be read multiple times - once by your code to retrieve additional data, and later by downstream code to ProcessUserAuthorization().
This is the code for custom IDirectWebRequestHandler :
public class RequestHandlerWithLastResponse : IDirectWebRequestHandler
{
private readonly UntrustedWebRequestHandler _webRequestHandler;
public string LastResponseContent { get; private set; }
public RequestHandlerWithLastResponse(UntrustedWebRequestHandler webRequestHandler)
{
if (webRequestHandler == null) throw new ArgumentNullException( "webRequestHandler" );
_webRequestHandler = webRequestHandler;
}
public bool CanSupport( DirectWebRequestOptions options )
{
return _webRequestHandler.CanSupport( options );
}
public Stream GetRequestStream( HttpWebRequest request )
{
return _webRequestHandler.GetRequestStream( request, DirectWebRequestOptions.None );
}
public Stream GetRequestStream( HttpWebRequest request, DirectWebRequestOptions options )
{
return _webRequestHandler.GetRequestStream( request, options );
}
public IncomingWebResponse GetResponse( HttpWebRequest request )
{
var response = _webRequestHandler.GetResponse( request, DirectWebRequestOptions.None );
//here we actually getting the response content
this.LastResponseContent = GetResponseContent( response );
return response;
}
public IncomingWebResponse GetResponse( HttpWebRequest request, DirectWebRequestOptions options )
{
return _webRequestHandler.GetResponse( request, options );
}
private string GetResponseContent(IncomingWebResponse response)
{
MemoryStream stream = new MemoryStream();
response.ResponseStream.CopyTo(stream);
stream.Position = 0;
response.ResponseStream.Position = 0;
using (var sr = new StreamReader(stream))
{
return sr.ReadToEnd();
}
}
}
And this is how we apply it and get response data:
var h = new RequestHandlerWithLastResponse(new UntrustedWebRequestHandler()); ;
_client.Channel.WebRequestHandler = h;
var auth = _client.ProcessUserAuthorization( request );
//convert response json to POCO
var extraData = JsonConvert.DeserializeObject<MyExtraData>( h.LastResponseContent );
Just read the id straight from the request, line after your call to ProcessUserAuthorization, depending on how it is passed (body, query string). I don't see any reason to stop using the DNOA.
var auth = client.ProcessUserAuthorization();
if ( auth != null )
{
// this is where you could still access the identity provider's request
...
Note that passing additional parameters together with the access token is rather uncommon and could lead to potential security issues. This is because the identity provider's response first gets to the user's browser and is then submitted to your server. The user could possibly alter the identity provider's response by keeping the access token but replacing the userid with any other valid userid.

Categories

Resources