Storing local files with ASP.NET Core and MVC - asp.net-mvc

With Asp.NET Core, The handy path-finding functions in Environment are gone. HttpContext and HttpServerUtility have been stripped. And the Application store within the Cache framework is gone. I can no longer assume (in code) that my server is using IIS or that it's even running on a Windows box.
And I don't have a database; I have a set of JSON files. Which, for reasons outside the scope of this question, cannot be stored in a database.
How do I read and write to files on the server?

In the new ASP.NET Core world when we deploy we have 2 folders appRoot and wwwroot
we generally only put files below the wwwroot folder that we intend to serve directly with http requests. So if your json files are to be served directly ie consumed by client side js then maybe you would put them there, otherwise you would use a different folder below appRoot.
I will show below how to resolve paths for both scenarios, ie sample code how to save a json string to a folder below either appRoot or wwwroot. In both cases think of your location as a virtual path relative to one of those folders, ie /some/folder/path where the first / represents either appRoot or wwwroot
public class MyFileProcessor
{
public MyFileProcessor(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
hostingEnvironment = env;
appEnvironment = appEnv;
appRootFolder = appEnv.ApplicationBasePath;
}
private IHostingEnvironment hostingEnvironment;
private IApplicationEnvironment appEnvironment;
private string appRootFolder;
public void SaveJsonToAppFolder(string appVirtualFolderPath, string fileName string jsonContent)
{
var pathToFile = appRootFolder + appVirtualFolderPath.Replace("/", Path.DirectorySeparatorChar.ToString())
+ fileName;
using (StreamWriter s = File.CreateText(pathToFile))
{
await s.WriteAsync(jsonContent);
}
}
public void SaveJsonToWwwFolder(string virtualFolderPath, string fileName string jsonContent)
{
var pathToFile = hostingEnvironment.MapPath(virtualFolderPath) + fileName;
using (StreamWriter s = File.CreateText(pathToFile))
{
await s.WriteAsync(jsonContent);
}
}
}

Related

Save file to path desktop for current user

I have a project ASP.NET Core 2.0 MVC running on IIS.
Want to Export some information from data grid to Excel and save it from web page to the desktop of current user.
string fileName = "SN-export-" + DateTime.Now + ".xlsx";
Regex rgx = new Regex("[^a-zA-Z0-9 -]");
fileName = rgx.Replace(fileName, ".");
string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string fileName2 = Path.Combine(path, fileName);
FileInfo excelFile = new FileInfo(fileName2);
excel.SaveAs(excelFile);
This works perfect local at Visual Studio, but not after publishing at IIS.
Using simple path string path = #"C:\WINDOWS\TEMP"; It will save this export file at the server temp folder, but not current web page user.
How to get this?
ASP.NET MVC is framework for a web application. So you have fronted and backend parts. This code will executed on the server side of your application. Even if you use Razor pages, they also generated at the backend. So there are several ways to save data on the computer:
use js to iterate data and save it, but I'm not sure that saving to excel with js is easy;
send desired data to backend, save it to excel and then return to the client.
For a second way you can use next code:
[Route("api/[controller]")]
public class DownloadController : Controller {
//GET api/download/12345abc
[HttpGet("{id}"]
public async Task<IActionResult> Download(YourData data) {
Stream stream = await {{__get_stream_based_on_your_data__}}
if(stream == null)
return NotFound();
return File(stream, "application/octet-stream"); // returns a FileStreamResult
}
}
And because of security reasons you can save data only to downloads directory.

MVC 5 bundling and Azure CDN (query string)

I have been following this tutorial: https://azure.microsoft.com/en-us/documentation/articles/cdn-serve-content-from-cdn-in-your-web-application/
Everything had been great until I noticed that bundled scripts and CSS files return with the cache: no-cache,expires: -1 and pragma: no-cache headers.
Of course, this has nothing to do with Azure. To prove this, I tested the bundles by accessing them directly from my site, instead of CDN - ie. mysite.com/bundles/mybundle?v={myassemblyversion}. The result was the same. When I disabled the CDN, and accessed the bundled file with the v query string generated by MVC, the headers were as expected: public caching, with the expiry time of one year.
I've tried to implement the IBundleTransform interface, but the context.BundleVirtualPath is read-only (even though it says gets or sets the virtual path...). I've also tried to modify the response headers at the Application_EndRequest(), but it didn't work, either. My last bet was writing IIS outbound rules, but since my bundles (used with "custom" v query string) don't return Last-Modified header, it was a futile attempt, too.
My question is: how can I use MVC bundling with Azure CDN if I want my bundled files to be cached on the client - that is, until the v query string changes?
I know I'm a little late to the game, but I did find a workaround. I'm making use of the code by Frison B Alexander here.
The issue is that once you rewrite the querystring for StyleBundles or ScriptBundles, the default caching behavior of one year resets to no-cache. This is solved by regenerating the exact same querystring per bundle that the MVC framework uses when specifying each Bundle's CDNPath.
Here's how it's done using the MVC Web App template. Here's the BundleConfig class:
public class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
//we have to go ahead and add our Bundles as if there is no CDN involved.
//this is because the bundle has to already exist in the BundleCollection
//in order to get the hash that the MVC framework will generate for the
//querystring.
Bundle jsBundle = new ScriptBundle("~/scripts/js3").Include(
"~/Scripts/jquery-{version}.js",
"~/Scripts/modernizr-*",
"~/Scripts/bootstrap.js",
"~/Scripts/respond.js");
bundles.Add(jsBundle);
Bundle cssBundle = new StyleBundle("~/content/css3").Include(
"~/Content/bootstrap.css",
"~/Content/site.css");
bundles.Add(cssBundle);
#if Debug
bundles.UseCdn = false;
#else
bundles.UseCdn = true;
//grab our base CDN hostname from web.config...
string cdnHost = ConfigurationManager.AppSettings["CDNHostName"];
//get the hashes that the MVC framework will use per bundle for
//the querystring.
string jsHash = GetBundleHash(bundles, "~/scripts/js3");
string cssHash = GetBundleHash(bundles, "~/content/css3");
//set up our querystring per bundle for the CDN path.
jsBundle.CdnPath = cdnHost + "/scripts/js3?v=" + jsHash;
cssBundle.CdnPath = cdnHost + "/content/css3?v=" + cssHash;
#endif
}
//Frison B Alexander's code:
private static string GetBundleHash(BundleCollection bundles, string bundlePath)
{
//Need the context to generate response
var bundleContext = new BundleContext(new HttpContextWrapper(HttpContext.Current), BundleTable.Bundles, bundlePath);
//Bundle class has the method we need to get a BundleResponse
Bundle bundle = BundleTable.Bundles.GetBundleFor(bundlePath);
var bundleResponse = bundle.GenerateBundleResponse(bundleContext);
//BundleResponse has the method we need to call, but its marked as
//internal and therefor is not available for public consumption.
//To bypass this, reflect on it and manually invoke the method
var bundleReflection = bundleResponse.GetType();
var method = bundleReflection.GetMethod("GetContentHashCode", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
//contentHash is whats appended to your url (url?###-###...)
var contentHash = method.Invoke(bundleResponse, null);
return contentHash.ToString();
}
}

PDF JS, from file location

I have successfully set up using the viewer with the following code:
protected void btnShowPDFS_OnClick(object sender, EventArgs e)
{
// Display all files.
string[] files = Directory.GetFiles(#"D:\Reports\2014\July\", "*.PDF");
var pdfNames = new List<string>();
foreach (string file in files)
{
string fileName = Path.GetFileName(file);
string queryString = "/web/viewer.html?file=" + System.Web.HttpUtility.UrlEncode("../July/" + fileName);
pdfNames.Add(queryString);
}
listView.DataSource = pdfNames;
listView.DataBind();
}
Now, this all works fine if all my PDF's are in a folder within the website (i.e localhost). However, how do i point the view to either a network share, or just another folder on the same machine, but outside of IIS?
A browser's XMLHttpRequest might have a restrictions for local files access (Firefox has more relaxed policy for local file than other browsers).
PDF.js is using XHR; and PDF.js also allows "load" files from a typed array (Uint8Array). You can use the latter in your solution. Notice the Internet Explorer (WebBrowser control) has window.external that can be used to transmit the data from the host application, see http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.objectforscripting(v=vs.110).aspx

File.Move on client machine Asp.net MVC

Silly question but here goes...
Is it possible to write an intranet windows auth asp.net mvc app that uses File.Move to rename a file on a users machine? Or will the File.Move and using Path.GetDirectory and other System.IO functions look on the IIS server directory structure instead of the client machine?
[HttpPost]
public ActionResult Index(HttpPostedFileBase file, string append)
{
try
{
if (file != null && file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
DirectoryInfo filepath = new DirectoryInfo(file.FileName);
string parentpath = Path.GetDirectoryName(filepath.FullName);
DirectoryInfo searchablePath = new DirectoryInfo(parentpath);
var directories = searchablePath.GetFiles("*", SearchOption.AllDirectories);
foreach (FileInfo d in directories)
{
if (!string.IsNullOrEmpty(append) && !d.Name.Contains(append))
{
string fName = Path.GetFileNameWithoutExtension(d.Name);
string fExt = Path.GetExtension(d.Name);
System.IO.File.Move(d.FullName, Path.Combine(d.DirectoryName, fName + append + fExt));
}
}
}
}
catch (Exception ex)
{
}
return View();
}
I have tried this but am getting a filenotfoundexception.
Any ideas?
The ASP.NET code runs on the server, so it will look at the files on the server.
You can't rename a file on the client machine, however it would be possible to rename a file on the computer that is used as client, if:
the server and computer are on the same network
the server knows the name of the computer
the server knows which folder to look for in the computer
the folder is shared with the user account running the ASP.NET code on the server with enough privileges to change the name of a file
In that sense the computer is not a client to the server, but the server communicates directly with the computer via the file system, not via IIS.
These will indeed work only on the server.
You may look at the various file and filesystem related specifications for client-side javascript APIs provided by the user's browser:
http://www.w3.org/TR/FileAPI/
http://www.w3.org/TR/file-system-api/
http://www.w3.org/TR/file-writer-api/

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