Downloading file with webapi - asp.net-web-api

I'm trying to write an action in a webapi controllers to allow downloading a file.
But for some strange reason, the code doesn't works.
Here is my code:
<RoutePrefix("api/files")>
Public Class PermitFilesController
Inherits ApiController
<Route("download")>
public function GetFile() As HttpResponseMessage
Dim fStream as FileStream = File.Open("C:\Projects\1234.pdf", FileMode.Open, FileAccess.Read )
Dim response = Request.CreateResponse(HttpStatusCode.OK)
response.Content = new StreamContent(fStream)
'response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
'response.Content.Headers.ContentDisposition.FileName = Path.GetFileName(fStream.Name)
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf")
return response
End Function
I try to download simply using the url in browser:
localhost:<myport>/api/files/download
The error (in Chrome ) is Error code: ERR_CONNECTION_RESET
In FF, it is even stranger: it redirects me to www.localhost.com:/... with the same error - connection reset by host
I put a breakpoint in my code, and I noticed the code gets called twice (as soon as I exit from trace from last line, it gets called again to the first line).
I have several other actions in this controller, and they all work ok.
Anyone having any idea what am I doing wrong?
EDIT
I started Fiddler, and now my browser shown this error:
[Fiddler] ReadResponse() failed: The server did not return a response
for this request. Server returned 0 bytes.
EDIT
I want to mention that webapi is integrated into a legacy classic asp.net application
The initialization code is as follows:
In global.asax.Application_Start
WebApiHelper.Initialize
....
....
Public Class WebApiHelper
Public Shared Sub Initialize()
GlobalConfiguration.Configuration.MessageHandlers.Add(New BasicAuthMessageHandler() With { _
.PrincipalProvider = New MPNAuthProvider() _
})
AreaRegistration.RegisterAllAreas()
WebApiConfig.Register(GlobalConfiguration.Configuration)
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters)
RouteConfig.RegisterRoutes(RouteTable.Routes)
GlobalConfiguration.Configuration.EnsureInitialized()
End Sub
....
MPNAuthProvider is used to ensure authenticated access to some webapi controllers
Public Class MPNAuthProvider
Implements IProviderPrincipal
Public Function CreatePrincipal(username As String, password As String) As IPrincipal Implements IProviderPrincipal.CreatePrincipal
Dim userID As Integer = 0
If Not UserData.ValidateUser(username, password, userID) Then Return Nothing
Dim identity = New GenericIdentity(userID)
Dim principal = New GenericPrincipal(identity, {"User"})
Return principal
End Function
End Class
Anything else I should check to see what happens?
Thank you

Initial Solution
After suggestion from Julien Jacobs, I tested my code into a separate, stand alone webapi project, and indeed the code proved to be correct.
So I started to investigate the web.config.
And I found the following settings that I had to comment out:
<system.web>
....
<httpModules>
<add name="RadUploadModule" type="Telerik.Web.UI.RadUploadHttpModule" />
<add name="RadCompression" type="Telerik.Web.UI.RadCompression" />
</httpModules>
and
<modules runAllManagedModulesForAllRequests="true">
<remove name="RadUploadModule" />
<remove name="RadCompression" />
<add name="RadUploadModule" type="Telerik.Web.UI.RadUploadHttpModule" preCondition="integratedMode" />
<add name="RadCompression" type="Telerik.Web.UI.RadCompression" preCondition="integratedMode" />
</modules>
After I commented them, the code started to work ok.
But this proved to not be the ideal solution, so please read on...
Updated solution
After more tests with the application, I realized that RadCompression, while not absolutely required, is very useful to web applications with Telerik Ajax, because it provides transparent, on the fly compression for all ajax traffic (plus viewstate, is configured).
Because I disabled it, the application started to be slower.
So I had to find a way to re-enable RadCompression, but disable it for certain requests (like webapi endpoint for files download).
And the solution is:
Add special config section for RadCompression configuration
<configSections>
<sectionGroup name="telerik.web.ui">
<section name="radCompression" type="Telerik.Web.UI.RadCompressionConfigurationSection, Telerik.Web.UI, PublicKeyToken=121fae78165ba3d4" allowDefinition="MachineToApplication" requirePermission="false"/>
</sectionGroup>
....
</configSections>
Add handlers in system.web\httpModules
<system.web>
....
<httpModules>
<add name="RadCompression" type="Telerik.Web.UI.RadCompression" />
</httpModules>
Add handlers in system.webServer\modules
<system.webServer>
<modules runAllManagedModulesForAllRequests="false">
<add name="RadCompression" type="Telerik.Web.UI.RadCompression" preCondition="managedHandler" />
</modules>
</system.webServer>
And the critical part, to disable RadCompression for specific requests (URIs), add a new config section as below
<telerik.web.ui>
<radCompression enablePostbackCompression="true">
<excludeHandlers>
<!--This will match every api/permitfiles/download file regardless of its location in the web site--> <add handlerPath="api/permitfiles/download" matchExact="false"/>
</excludeHandlers>
</radCompression>
</telerik.web.ui>
With those changes, RadCompression is empowered globally in the app for all requests, but restricted for specific requests (like webapi files download)

Related

How to config HTTPPlatformHandler of IIS for Server Sent Event (SSE, EventStream)

Currently I have program that provide SSE as a service, and I have to deploy on IIS. But its does not work correctly,
Here is the result when I run .exe without IIS.
data: Hello, world
But when its run behind IIS, Browser was stuck on loading.
I have to flush event Hello, world thousand times to make IIS flush result to browser and it's flush instantly instead of incremental update like SSE use to be.
Here is my web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="httpplatformhandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" />
</handlers>
<httpPlatform processPath=".\sse_server.exe"
arguments="-port=%HTTP_PLATFORM_PORT% -environment development"
stdoutLogEnabled="false"
requestTimeout="00:05:00"
stdoutLogFile=".\sse_server_log">
</httpPlatform>
<urlCompression doStaticCompression="true" doDynamicCompression="false" />
<caching enabled="false" enableKernelCache="false" />
</system.webServer>
</configuration>
Here is my go code
func SSEHello(rw http.ResponseWriter, flusher http.Flusher) {
rw.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
rw.Header().Set("Cache-Control", "no-cache")
rw.Header().Set("Connection", "keep-alive")
rw.WriteHeader(http.StatusOK)
for i := 0; i < 1000; i++ {
rw.Write([]byte("data:Hello, world\n\n"))
flusher.Flush()
time.Sleep(time.Millisecond * 100)
}
}
Actually HttpPlatformHandler has 8kb output buffer , so my message is not sent out immediately.
I have to change HttpPlatformHandler to ASP.NET Core Module,
so web.config must update to this.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath=".\sse_server.exe" />
</system.webServer>
</configuration>
And to start go 's application as aspNetCore on iis, the application need to get environment variable name ASPNETCORE_PORT then start http service on that port.
port := os.Getenv("ASPNETCORE_PORT")
http.ListenAndServe(":"+port, nil)
That's all!

Passing DateTimeOffset to WebAPI

My WebAPI method looks like this:
[Route("api/v1.0/Profile/Test/{offset}")]
public async Task<ServiceResult> GetTest(DateTimeOffset offset)
{
...
}
Calling it like this works:
http://localhost:54295/api/v1.0/Profile/Test/2016-04-05T13:30:44-11:00
However, this call won't work:
http://localhost:54295/api/v1.0/Profile/Test/2016-04-05T13:30:44+11:00
What's the trick here?
In my Web.config in the system.web section I have this entry:
<httpRuntime targetFramework="4.5" requestPathInvalidCharacters="<,>,%,&,*,\,?" />
I found a solution:
In my web.config in the system.webServer section I added this block:
<security>
<requestFiltering allowDoubleEscaping="true" />
</security>
Now the plus sign works as desired.

ASP.NET MVC Custom Error Pages with Magical Unicorn

my question is regarding Pure.Kromes answer to this post. I tried implementing my pages' custom error messages using his method, yet there are some problems I can't quite explain.
a)
When I provoke a 404 Error by entering in invalid URL such as localhost:3001/NonexistantPage, it defaults to the ServerError() Action of my error controller even though it should go to NotFound(). Here is my ErrorController:
public class ErrorController : Controller
{
public ActionResult NotFound()
{
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = (int)HttpStatusCode.NotFound;
var viewModel = new ErrorViewModel()
{
ServerException = Server.GetLastError(),
HTTPStatusCode = Response.StatusCode
};
return View(viewModel);
}
public ActionResult ServerError()
{
Response.TrySkipIisCustomErrors = true;
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var viewModel = new ErrorViewModel()
{
ServerException = Server.GetLastError(),
HTTPStatusCode = Response.StatusCode
};
return View(viewModel);
}
}
My error routes in Global.asax.cs:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{*favicon}", new { favicon = #"(.*/)?favicon.ico(/.*)?" });
routes.MapRoute(
name: "Error - 404",
url: "NotFound",
defaults: new { controller = "Error", action = "NotFound" }
);
routes.MapRoute(
name: "Error - 500",
url: "ServerError",
defaults: new { controller = "Error", action = "ServerError" }
);
And my web.config settings:
<system.web>
<customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="/ServerError">
<error statusCode="404" redirect="/NotFound" />
</customErrors>
...
</system.web>
<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
</httpErrors>
...
The Error views are located in /Views/Error/ as NotFound.cshtml and ServerError.cshtml.
b)
One funny thing is, When a server error occurs, it does in fact display the Server Error view I defined, however it also outputs a default error message as well saying that the Error page could not be found.
Here's how it looks like:
Do you have any advice how I could fix these two problems? I really like Pure.Kromes approach to implementing these error messages, but if there are better ways of achieving this don't hestitate to tell me.
Thanks!
**EDIT : **
I can directly navigate to my views through the ErrorController by accessing /Error/NotFound or Error/ServerError.
The views themselves only contain some text, no markup or anything.
As I said, it actually works in some way, just not the way I intended it to work. There seems to be an issue with the redirect in the web.config, but I haven't been able to figure it out.
there is one more issue with that setup, when you have more complex routes and have several segments ex.
http://localhost:2902/dsad/dadasdmasda/ddadad/dadads/ddadad/dadadadad/
I got server error ->
Sorry, an error occurred while processing your request.
Exception: An error occured while trying to Render the custom error view which you provided, for this HttpStatusCode. ViewPath: ~/Views/Error/NotFound.cshtml; Message: The RouteData must contain an item named 'controller' with a non-empty string value.
Source:
my solution for that was to add additional route at the end after default route
routes.MapRoute(
"Default Catch all 404",
"{controller}/{action}/{*catchall}",
new { controller = "Error", action = "NotFound" }
);
hope it could help someone:-)
I got it to work. It seems my understanding of the problem was somewhat wrong to begin with.
In the web.config, I changed the following:
<customErrors mode="On" redirectMode="ResponseRewrite" defaultRedirect="~/Views/Error/ServerError.cshtml">
<error statusCode="404" redirect="~/Views/Error/NotFound.cshtml" />
</customErrors>
...
and
...
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" path="/NotFound" responseMode="ExecuteURL" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="500" path="/ServerError" responseMode="ExecuteURL" />
</httpErrors>
This directly redirects to the views. My understanding was that I had to redirect to the error controller which in turn would redirect to the views, but apparently this was not the case. I'd like to thank you for your comments as they have made me analyze the problem again when I was already about to just ditch the custom error stuff and simply be lazy and display YSOD's. :)

FormsAuthentication.SetAuthCookie problem

In my MVC 3 application I have a very basic controller
[HttpPost]
public ActionResult SignIn(LogonModel logonModel)
{
string logonMessage;
if(_authenticationService.Login(logonModel.UserName, logonModel.Password,
out logonMessage))
{
FormsAuthentication.SetAuthCookie(logonModel.UserName,true);
return RedirectToAction("Index", "Home");
}
return View();
}
I see the cookie getting set in the browser however when I close the browser and come back to the site, it is not logging me in automatically. It is almost like the cookie is not being processed, which it should be.
The first and most obvious question is: are cookies enabled in your browser?
What you could check as well is, whether in your web.config, you have configured the <authentication> section to have a timeout:
<authentication mode="Forms">
<forms loginUrl="~/login" timeout="60" />
</authentication>
if you don't specify a timeout it'll use the default value of 30 minutes, but maybe you set it to some other (invalid) value?
timeout attribute:
Specifies the time, in integer minutes, after which the cookie expires.
You could also check the CookiesSupported property (boolean) to see that that returns.
Be sure to remove the following <modules> tag from the web.config if using MVC 5+
<system.webServer>
<modules>
<remove name="FormsAuthentication" />
</modules>
</system.webServer>
I had a situation where the domain was wrong in the authenticaion section:
<authentication mode="Forms">
<forms name="yourAuthCookie" loginUrl="/user/login" protection="All" path="/" enableCrossAppRedirects="true" timeout="90" domain="WRONGDOMAIN.COM" />
</authentication>
Removing the domain did the trick:
<authentication mode="Forms">
<forms name="yourAuthCookie" loginUrl="/user/login" protection="All" path="/" enableCrossAppRedirects="true" timeout="90" />
</authentication>

asp.net mvc 3 and elmah.axd - yet another 404

Hi all I know that this has been posted as a prior question several times, but I've gone through each question and their proposed solutions and I'm still not able to surmount my 404 issue. I'm running Elmah 1.1 32-bit. I've referred to ASP.NET MVC - Elmah not working and returning 404 page for elmah.axd but I haven't had any luck after applying the suggestions.
I'm running ASP.NET MVC 3. Here's my web.config:
...
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
<add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
</httpModules>
...
<errorLog type="Elmah.SqlErrorLog, Elmah"
connectionStringName="dbconn" />
<errorFilter>
<test>
<jscript>
<expression>
<![CDATA[
// #assembly mscorlib
// #assembly System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// #import System.IO
// #import System.Web
HttpStatusCode == 404
|| BaseException instanceof FileNotFoundException
|| BaseException instanceof HttpRequestValidationException
/* Using RegExp below (see http://msdn.microsoft.com/en-us/library/h6e2eb7w.aspx) */
|| Context.Request.UserAgent.match(/crawler/i)
|| Context.Request.ServerVariables['REMOTE_ADDR'] == '127.0.0.1' // IPv4 only
]]>
</expression>
</jscript>
</test>
</errorFilter>
I have my .axd routes ignored using:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
I'm running the site on IIS7, 32 bit mode enabled. I've tried many different configuration options but all to no avail. Any ideas?
Thanks
Shan
My bad. My .axd ignore route rule was ordered after the default route mapping. The default route mapping rule was matching the URL elmah.axd. I guess I didn't realize that the ignore rules had to be listed above this route. Thanks everyone for your help!
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new
{
controller = "Home",
action = "Index",
id = UrlParameter.Optional
} // Parameter defaults
);
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
Simply moving routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); before the Default route mapping resolved this issue.
Copy the .dll to your bin and reference... add the elmah defaults to configSections
Don't put the handler inside the system.webServer as mentioned above, try system.web section like this instead in your web.config.
<system.web>
<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>
</system.web>
just leave your global.asax as default:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
browse to the axd locally
then if working, lock it down with the config section Gedas mentioned above.
Have you tried this?
<configuration>
<system.webServer>
<handlers>
<add name="elmah" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</handlers>
</system.webServer>
</configuration>
also make sure to secure elmah.axd location from regular users:
<location path="elmah.axd">
<system.web>
<authorization>
<allow roles="Admin" />
<deny users="*" />
</authorization>
</system.web>
</location>
I was getting a 404 error due to the SQLServer Compact database being over the default max file size. Just deleted the SDF data file and 404 went away.
In asp.net mvc 3 global.asax.cs file
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
//filters.Add(new HandleErrorAttribute());
}
HandleErrorAttribute will swallow all exceptions, leaving nothing for ELMAH to handle.
See Joe's blog http://joel.net/wordpress/index.php/2011/02/logging-errors-with-elmah-in-asp-net-mvc3-part1/

Resources