Performance ramifications of serving FilePath rather than View - asp.net-mvc

What are the performance ramifications if any of serving a FilePathResult rather than a view (If we have created server cached copies of a website using a headless browser)
public class HomeController : Controller
{
public ActionResult Index()
{
var url = Request.RawUrl.Replace("/", "_");
var path = System.Configuration.ConfigurationManager.AppSettings["PreloadPath"] + "\\" + url + ".html";
if (System.IO.File.Exists(path))
{
return new FilePathResult(path, "text/html");
}
else
{
return View("Index");
}
}
}
We are having to access the AppSettings every request now, use the File System to check if a file exists, and then serve that html file.
What costs are there compared with just
return View("Index");
Will the file access have any cost on the server? Or am I talking nonsense, and IIS would have to perform some similar action?
Note: Please suggest any other tags if I should add them

Looking at the FilePathResult's source code you can see that in the end it goes down to WriteStreamAsText of HttpResponse. It's obvious that there is no magic call to IIS for example to handle the file directly without any .Net code taking place.
Having said that I still expect this to be somewhat faster than running a view, which possibly needs interpretation and execution.

Related

How can I set a global variable in Razor Pages of ASP.NET Core?

I wanna to check if the browser is IE and do something in razor page.
I just made a function in razor page to do that.
However, I think use the function to check if the browser is IE in every razor page is redundant. For independent user, I just need to check this only one time and set a global variable that IsIE=true/false . And other page will easily know that if it is IE.
The question is how can I get/set a global variable in razor page?
Thank you.
————————————————
To #Neville Nazerane ,here is the function which to check if is IE:
#{
Boolean IsIE = false;
string UA = Context.Request.Headers["User-Agent"].ToString();
if (UA.Contains("Trident") || UA.Contains("MSIE"))
{
IsIE = true;
}
else
{
IsIE = false; ;
}
if (IsIE == true)
{
}
else
{
}
}
HTTP requests work by clients sending a request (with header and body) to your server. Your server can then access this info and send a response. This doesn't create any persistent (ongoing) connection between the server and client. This means there is no permanent link between your server and each client. Any global variable you declare will be global for your server's web application and will be common for every client.
What you are trying to do here is create variables isolated from each client's connection. Normally this is done with the help of Session or Cookie variable. But in this case, I don't see how this will improve any performance over the code you have written. In your code, you are trying to access the Http Headers from the request. Cookies and session variables are also accessed in a very similar way. If anything fetching directly from headers must have a slightly better performance. If you are trying to clean up your code so you don't have to write this on every page, services could be quite helpful.
You can create a class for service something like this:
public class AgentChecker
{
public bool IsIE { get; set; }
// makes sure check is done only when object is created
public AgentChecker(IHttpContextAccessor accessor)
{
string UA = accessor.HttpContext.Request.Headers["User-Agent"].ToString();
if (UA.Contains("Trident") || UA.Contains("MSIE"))
{
IsIE = true;
}
else
{
IsIE = false;
}
}
// optional to simplify usage further.
public static implicit operator bool(AgentChecker checker) => checker.IsIE;
}
In your startup class add the following:
// to access http context in a service
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// makes sure object is created once per request
services.AddScoped<AgentChecker>();
Once this is set up, in your view you can use:
#inject AgentChecker checker
#* if you didn't create the implicit operator, you can use if (checker.IsIE) *#
#if (checker)
{
<div>Is ie</div>
}
else
{
<div>not ie</div>
}
The inject goes at the top of any view page you would like to use this in. While this still creates a new object each request, it is cleaner to use and only creates one object no matter how many partial views you are using.

C # MVC view files only for logged in users

how can I set about c # MVC viewing files with absolute path (eg. www.mysite.it/namefile.pdf) only for authenticated users ? for authentication use the method FormsAuthentication.Authenticate(). thanks for the support.
I think that the more properly way to do that is:
www.mysite.it/f={filename}
And in your controller you use the [Authorize] to check if user is authenticated. If the user is authenticated you allow him to view, or download, the file.
The following code can help you to understand:
//Ensure that the user is authenticated
[Authorize]
public class HomeController : Controller
{
string DefaultFileFolder = "C:/Files";
public ActionResult Index(string f)
{
//File request
if (!String.IsNullOrWhiteSpace(f))
{
var filePath = System.IO.Path.Combine(DefaultFileFolder, f);
var mimeType = "text/plain";
return File(filePath, mimeType);
}
return View();
}
}
As far as I know the only way to do this is to route requests for static content through ASP.NET. Normally you don't do this as IIS by itself is far more efficient at serving these types of resources.
From this excellent article, you can force static content requests to go through ASP.NET by:
On IIS 7 this is done either by setting
runAllManagedModulesForAllRequests=”true” or removing the
"managedHandler" preCondition for the UrlRoutingModule.
Depending on your needs, you may wish to do something more in line with what Richard suggested. Something like this perhaps?

Consuming a click-once application from an MVC controller/action

I am using an MVC controller/action located in /myServer/myArea/MyClickOnce/Open that returns a FileResult
public class MyClickOnceController : Controller
{
public FileResult Open()
{
FilePathResult file = new FilePathResult("/Provisioning/4843EA3F-9138-4A0D-9D33-BF4CDDEB7C7E/MyClickOnce.application", "application/x-ms-application");
return file;
}
}
this works fine for the initial loading, but then click-once makes a subsequent request to:
/myServer/myArea/MyClickOnce/9.0.0.132/MyClickOnce.exe.manifest
This path doesn't actually exist because the physical path for the click once is in:
/Provisioning/4843EA3F-9138-4A0D-9D33-BF4CDDEB7C7E/*
so it exists here:
/Provisioning/4843EA3F-9138-4A0D-9D33-BF4CDDEB7C7E/9.0.0.132/MyClickOnce.exe.manifest
Should I be using routing to redirect all these subsequent requests? Is there a better approach to consume a click-once application from and MVC controller/action?
If you use a RedirectResult, the subsequent requests to the ClickOnce manifest and other files get routed to the correct directory.
public ActionResult Open()
{
string path = "/Provisioning/4843EA3F-9138-4A0D-9D33-BF4CDDEB7C7E/MyClickOnce.application";
return new RedirectResult(path);
}

Multi Post for Action with return File in ASP.NET MVC

Assume this code in One of my Actions
[HttpPost]
public ActionResult Generate (Params){
....
InsertOneRawToDB();
return RedirectToAction("Index", new { info = info });
}
So every thing is OK yet but when I change return to:
InsertOneRawToDB();
byte[] myfile = GenerateAZipFile();
return File( myfile , "application/zip" , "MyFileName");
In this case I see a weird behavior: before return, One raw inserted to DB, and after return another raw inserted, it seems the InsertOneRawToDB called again.
Does any one know about this? what happening here?
PS: I use Visual Studio 2012 RTM + ASP.NET MVC4
PS: OK I Use IDM (Internet Download Manager) to download zip file and that cause MultiPost on this Action So How can I handle this?
Based on your conclusion that the problem is related to using a download manager... That's what download mangers do. They create multiple connections to the file.
One thing you could do is store a session variable that says "Already started downloading", and then only insert the record the first time.
however if the user legitimately downloaded the file multiple times then you would only get one record.
Another option would be to examine the Http headers and look for the "Range" header, which is what is used to download a file in multiple pieces (or resume a file). You would then have to take the Range parameters and only return the portion of the file requested.
Here's an example of how to do a Ranged download: http://tpeczek.com/2011/10/range-requests-in-aspnet-mvc.html
I'm actually quite surprised that this hasn't come up before... I guess most people don't do database actions in a download action or notice it.
You can redirect to new action and in this new action return file.
[HttpPost]
public ActionResult Generate (Params){
....
InsertOneRawToDB();
return RedirectToAction("GetFile"};
}
[HttpGet]
public ActionResult GetFile
{
byte[] myfile = GenerateAZipFile();
return File(myfile, "application/zip", "filename.ext");
}

How to get an instance of the current HttpRequestBase outside of a Controller or View's context

What I'm doing:
I am creating an email mailing engine that takes html templates, replaces tokens with data and then sends off the mail to an SMTP server. Eventually we'll want a CMS UI on top of this to allow them to add/remove file templates or update the content in them via a CMS UI. Anyway I am putting the .htm files for the email templates inside my MVC web project and need to read their contents and get the string of HTML back to work with. If I move all this code out of my MVC project into like a utility C# project layer, the problem is then I have to worry about maintaining physical paths ("C:...\theMVCWebSiteFolder...\Email\Templates\SomeTemplate.htm") and that to me would have to be hard coded I think to keep track if you were to move the site to a different server, different hard drive partition, etc. So I'd like to be able to work with the files using the current application's context unless there's a way to do this agnostic to my MVC app and still not have to worry about having to ever change the location of the physical root every time we move folders.
I've got the following utility method I created in a utility class that sits in a folder somewhere in my ASP.NET MVC web project in just a folder (a folder outside of any view folders:
public static string GetFileData(string filePath)
{
if (!File.Exists(HttpContext.Current.Request.PhysicalApplicationPath + filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
string text;
FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
using(StreamReader read = new StreamReader(fileStream))
text = read.ReadToEnd();
return text;
}
I'm trying to figure out why the context is turning up null here. I originally tried HttpContext.Current but current is coming up null so it can't find the current context in my Utility.cs method that sits in my MVC web project.
UPDATE:
Ok so the consensus is to use HttpRequestBase object and not the HttpContext.Current object. I still find it weird that the HttpContext.Current is null. But moving on, if my Utility.cs is outside any context of a controller or view, then how the heck do I get an instance of the current request (HttpRequestBase) to work with and send an instance that implements HttpRequestBase (I do not know what object that would be if I want to do this in the "MVC way" outside a controller or view) to this utility method?
No idea why this returns null but since it is a bad idea to tie your business layers with ASP.NET specifics I'd recommend you the following change to your method:
public static string GetFileData(string filePath)
{
if (!File.Exists(filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
return File.ReadAllText(filePath);
}
and then when you need to consume it from a web application:
public ActionResult Foo()
{
var result = MyClass.GetFileData(Server.MapPath("~/foo/bar.txt"));
...
}
and when you need to consume it in a WinForms application:
protected void Button1_Click(object sender, EventArgs e)
{
var filePath = Path.Combine(Environment.CurrentDirectory, "bar.txt");
var result = MyClass.GetFileData(filePath);
...
}
In a utility class I would remove the dependency on any web-related stuff.
For a path relative to the application root I would use:
Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, fileName)
which will give you what you probably want (a path relative to the web root directory in a web application; relative to the path containing the executable in a WinForms app, ...)
If want to rewrite that method in asp.net mvc way, you could rewrite it this way and remove coupling with HttpContext class
public static string GetFileData(HttpRequestBase request, string filePath)
{
if (!File.Exists(request.PhysicalApplicationPath + filePath))
throw new FileNotFoundException(String.Format("the file {0} was not found", filePath));
string text;
FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None);
using(StreamReader read = new StreamReader(fileStream))
text = read.ReadToEnd();
return text;
}
And generally in MVC HttpContext, HttpRequest and HttpResponse are abstracted into HttpContextBase, HttpRequestBase and HttpResponseBase accordingly

Resources