i have part of Asp.NET 1.1 project.
I work with remote site, which works incorrect in some cases - sometimes it write incorrect Content-Encoding header.
In my code i get HttpResponse from this remote site. And if Content-Encoding header is equals, for example, "gzip", i need to set Content-Encoding header to "deflate".
But there is no properties or methods in HttpResponse class to get Content-Encoding header.
Content-Encoding property returns, in my case, "UTF-8". In Watch window i see _customProperties field, which contain wrong string value. How can i change header value with Asp.NET 1.1?
There is no way to change custom headers in Asp.NET 1.1.
I solve problem only using reflection.
// first of all we need get type ArrayList with custom headers:
Type responseType = Response.GetType();
ArrayList fieldCustomHeaders = ArrayList)responseType.InvokeMember("_customHeaders",BindingFlags.GetField|BindingFlags.Instance|BindingFlags.NonPublic, null, Response,null);
// next we go thru all elements of list and search our header
for(int i=0; i < fieldCustomHeaders.Count; i++)
{
// see all headers
PropertyInfo propHeaderName = fieldCustomHeaders[i].GetType().GetProperty("Name", BindingFlags.Instance|BindingFlags.NonPublic);
String headerName = (String)propHeaderName.GetValue(fieldCustomHeaders[i], null);
// if we find needed header
if(headerName == "Content-Encoding")
{
// get value of header from its field
FieldInfo fieldHeaderValue = _fieldCustomHeaders[i].GetType().GetField("_value", BindingFlags.Instance|BindingFlags.NonPublic);
String headerValue = (String)fieldHeaderValue.GetValue(fieldCustomHeaders[i]);
// if we find needed value
if (headerValue == "gzip")
{
// just set new value to it
fieldHeaderValue.SetValue(_fieldCustomHeaders[i], "deflate");
break;
}
}
}
Related
If one omits the Accept header in a request to an Asp.Net web API the server will return (415) Unsupported Media Type
I am looking for a way to force the API to assume a default return type (in my case, application/json) when the request does not contain an Accept value in its headers.
After a substantial amount of reading and searching, I'm not sure this is even possible?
You can force the framework to use XML formatter when HTTP Accept header is missing, by doing the following trick:
var jsonFormatter = config.Formatters.JsonFormatter;
config.Formatters.Remove(config.Formatters.JsonFormatter);
config.Formatters.Add(jsonFormatter);
This way the JSON formatter will be the second registered formatter in the list, and the XML will be the first one.
This is content negotiator resposibility to choose the right formatter to serialize the response object. But by default WebApi framework gets JsonFormatter if could not find appropriate formatter.
If there are still no matches, the content negotiator simply picks the first formatter that can serialize the type.
So it is strange behavior. Anyway you could set up custom content negotiator to choose explicit JsonFormatter if request does not have Accept header.
public class JsonContentNegotiator : DefaultContentNegotiator
{
protected override MediaTypeFormatterMatch MatchAcceptHeader(IEnumerable<MediaTypeWithQualityHeaderValue> sortedAcceptValues, MediaTypeFormatter formatter)
{
var defaultMatch = base.MatchAcceptHeader(sortedAcceptValues, formatter);
if (defaultMatch == null)
{
//Check to find json formatter
var jsonMediaType = formatter.SupportedMediaTypes.FirstOrDefault(h => h.MediaType == "application/json");
if (jsonMediaType != null)
{
return new MediaTypeFormatterMatch(formatter, jsonMediaType, 1.0, MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderLiteral);
}
}
return defaultMatch;
}
}
And replace in HttpConfiguration object
config.Services.Replace(typeof(IContentNegotiator), new JsonContentNegotiator());
I need to extract uploads from http-trafic. How could do that? First of all, the request-method will be POST. Secondly, there will be a Content-Type header-field. I do not want to extract formular-data, but uploads like mail-attachements.
The content type is per specification multipart/form-data.
This is a special content type which can be visualized as multiple sub-requests in one big request. Each of those sub-requests (one form-data element) has their own set of headers. The content type of the actual data is in there.
Here's an example how it look like with 1 normal field and 1 file field (in HTML terms, when using <input name="textfield"><input type="file" name="filefield">):
Content-Type: multipart/form-data;boundary=SOME_BOUNDARY
--SOME_BOUNDARY
content-disposition: form-data;name="textfield"
content-type: text/plain;charset=UTF-8
value of textfield here
--SOME_BOUNDARY
content-disposition: form-data;name="filefield";filename="some.ext"
content-type: application/octet-stream
binary file content here
--SOME_BOUNDARY--
As to parsing and extracting this data, practically every programming language has builtin/3rd party APIs for this. As you didn't tell anything about which one you're using, it's impossible to give a targeted answer. In case of for example Java, that would be either the 3rd party library Apache Commons FileUpload or when you're using Servlet 3.0, the API-provided request.getPart() method.
If (and I by no means am saying this is the correct way) you just want to save data from a byte array, you should look at how to read the POST body at:
Reading POST body with bottle.py
Reading the data and then creating a new file should do the trick.
Based on #BalusC s solution I made a little extension method for .NET's build in WebClient class which does not support Multipart upload out of the box.
Usage
Just mix string values and files (enclosed in #)
public void UploadMultipart()
{
var fileName = "/some/existing/file.ext";
using (var client = new WebClient())
{
var values = new NameValueCollection();
values.Add("id", Guid.NewGuid().ToString());
values.Add("name", Path.GetFileNameWithoutExtension(fileName));
values.Add("file", $"#{fileName}#");
var result = client.UploadMultipart(address, method, values);
var content = client.Encoding.GetString(result);
}
}
Extension method
public static byte[] UploadMultipart(this WebClient client,
string address, string method, NameValueCollection values)
{
string boundary = DateTime.Now.Ticks.ToString("x");
client.Headers.Add("Content-Type", "multipart/form-data; boundary=" + boundary);
var sb = new StringBuilder()
.AppendLine();
foreach (var key in values.AllKeys)
{
var contentDispositon = $"form-data;name=\"{key}\"";
var contentType = $"text/plain;charset={client.Encoding.WebName}";
var value = values[key];
if (value.StartsWith("#") && value.EndsWith("#"))
{
// if a value is enclosed in hashes we expect this to be a path to a file
// file=#/path/to/file.ext#
var fileName = value.Trim('#');
var file = File.ReadAllBytes(fileName);
value = client.Encoding.GetString(file);
contentType = "application/octet-stream";
contentDispositon = $"form-data;name=\"{key}\"filename=\"{Path.GetFileName(fileName)}\"";
}
sb.AppendLine($"--{boundary}")
.AppendLine($"Content-Disposition: {contentDispositon}")
.AppendLine($"Content-Type: {contentType}")
.AppendLine()
.AppendLine(value);
}
sb.AppendLine($"--{boundary}--");
var data = client.Encoding.GetBytes(sb.ToString());
return client.UploadData(address, method, data);
}
I created a custom dispatcher to handle versioning that uses a customer media type. It looks something like this:
application/vnd.mycompany.myapi-v1+json
The extraction of the version number in order to select the correct controller is all up and working, but being new to MVC, I am not sure how to set the response format. What we want to do is set the response format to match the request. So in this example, the response will be in json. Now I assume I'm going to have to extract that from this content type as well which is fine, but could someone give me an example of how i set the response format of this request in MVC4 assuming I have already created the method which will extract the format as a string?
private string GetResponseFormat(){
//some shennanigans here
}
P.S. the reason for not having the client use the accept header during the request is that there are already clients out there that are using our old service which would set the accept header to match the request.
You can also use Content method to return custom response type:
string responseType = GetResponseFormat();
...
switch(responseType){
case "json":
string json = "yourJSON";
return Content(json, "application/json");
case "xml":
string xml = "yourXML";
return Content(xml, "text/xml");
default:
string plaintxt = "yourPlaintext";
return Content(plaintxt, "text/plain"):
}
I was able to clear the existing Accept header and add to it:
private void SetResponseFormatToRequestFormat(HttpRequestMessage request)
{
// figure out what the request format was
_contentTypeHeader = request.Content.Headers.ContentType.ToString();
if(_contentTypeHeader.Contains("xml")) _contentType = "application/xml";
if (_contentTypeHeader.Contains("json")) _contentType = "application/json";
// set response format to the same as the request format
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(_contentType));
}
In order to support a legacy application that's in the field, I need my ASP.NET MVC app to return an empty response that also has a Content-Type. One of IIS, ASP.NET, or ASP.NET MVC is removing my Content-Type when I send back a null response. Is there any way around this?
(While not requiring an empty response with a set Content-Type would obviously be the ideal solution, the clients are already out there, and many of them cannot be upgraded.)
EDIT: Since there was a request for code: I'm proxying the request from the new web application to the one that older clients rely on. To do this, I have a subclass of ActionResult, called LegacyResult, that you can simply return for those methods that need to be handled by the old software. This is the relevant part of its code:
public override void ExecuteResult(ControllerContext context)
{
using (var legacyResponse = GetLegacyResponse(context))
{
var clientResponse = context.HttpContext.Response;
clientResponse.Buffer = false;
clientResponse.ContentType = legacyResponse.ContentType; /* Yes, I checked that legacyResponse.ContentType is never string.IsNullOrEmpty */
if (legacyResponse.ContentLength >= 0) clientResponse.AddHeader("Content-Length", legacyResponse.ContentLength.ToString());
var legacyInput = legacyResponse.GetResponseStream();
using (var clientOutput = clientResponse.OutputStream)
{
var rgb = new byte[32768];
int cb;
while ((cb = legacyInput.Read(rgb, 0, rgb.Length)) > 0)
{
clientOutput.Write(rgb, 0, cb);
}
clientOutput.Flush();
}
}
}
If legacyInput has data, then Content-Type is set appropriately. Otherwise, it's not. I can actually kluge the old backend to send an empty v. non-empty response for exactly the same request, and observe the difference in Fiddler.
EDIT 2: Poking around with Reflector reveals that, if headers have not been written at the time that HttpResponse.Flush is called, then Flush writes out the headers itself. The problem is that it only writes out a tiny subset of the headers. One of the missing ones is Content-Type. So it seems that, if I can force headers out to the stream, I can avoid this problem.
You have to trick the response into writing the headers, by falsely telling it there's content, then suppressing it:
/// [inside the writing block]
var didWrite = false;
while ((cb = legacyInput.Read(rgb, 0, rgb.Length)) > 0)
{
didWrite = true;
clientOutput.Write(rgb, 0, cb);
}
if (!didWrite)
{
// The stream needs a non-zero content length to write the correct headers, but...
clientResponse.AddHeader("Content-Length", "1");
// ...this actually writes a "Content-Length: 0" header with the other headers.
clientResponse.SuppressContent = true;
}
I need to load an external web (not local) page into my site (some link), but only a part of it.
What are the options for doing so?
That depends on whether or not the external page is local, or on a different domain. If it's local, you can use $.load() in the jQuery library. This has an optional parameter to specify which element in the remote-dom to load it:
$("#links").load("/Main_Page #jq-p-Getting-Started li");
If the page is on another domain, you'll need a proxy script. You can do this with PHP and the phpQuery (php port of jQuery) library. You'll just use file_get_contents() to get the actual remote-dom, and then pull out the elements you want based on jQuery-like selectors.
$f = fopen('http://www.quran.az/2/255', 'r');
and so on...
Once you get the whole page as Michael Todd outlined, you will likely need to either use substring methods for a static means to slice up the content or you can use regex's for a more dynamic way to grab the content. An intro article on Regex's in ASP.Net can be found here. Good luck!
To load a web page in .Net, use the HttpWebRequest class.
Example taken from MSDN, here:
private string StringGetWebPage(String uri)
{
const int bufSizeMax = 65536; // max read buffer size conserves memory
const int bufSizeMin = 8192; // min size prevents numerous small reads
StringBuilder sb;
// A WebException is thrown if HTTP request fails
try
{
// Create an HttpWebRequest using WebRequest.Create (see .NET docs)!
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
// Execute the request and obtain the response stream
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream responseStream = response.GetResponseStream();
// Content-Length header is not trustable, but makes a good hint.
// Responses longer than int size will throw an exception here!
int length = (int)response.ContentLength;
// Use Content-Length if between bufSizeMax and bufSizeMin
int bufSize = bufSizeMin;
if (length > bufSize)
bufSize = length > bufSizeMax ? bufSizeMax : length;
// Allocate buffer and StringBuilder for reading response
byte[] buf = new byte[bufSize];
sb = new StringBuilder(bufSize);
// Read response stream until end
while ((length = responseStream.Read(buf, 0, buf.Length)) != 0)
sb.Append(Encoding.UTF8.GetString(buf, 0, length));
}
catch (Exception ex)
{
sb = new StringBuilder(ex.Message);
}
return sb.ToString();
}
Note that this will return the entire page and not just a portion of it. You'll then need to sift through the page to find the information you're looking for.