Allowing the user downloading a file located in a specific IIS folder - asp.net

I have the following issue: ASP-MVC
I want to put a file in a folder in IIS and allow users surfing my site to download it.
In my site, I will have a link that points to an action method in my controller, and within this method I want to put the needed code. Never dealt with this issue before, will appriciate a code sample. Thanks!

This code , taken from this question, will accomplish what you want.
public FileResult Download()
{
byte[] fileBytes = System.IO.File.ReadAllBytes("c:\folder\myfile.ext");
string fileName = "myfile.ext";
return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

Assuming you want to get a specific file based on some passed-in ID, you can use the Controller.File function as described here: http://msdn.microsoft.com/en-us/library/dd492492(v=vs.100).aspx
Here's an example controller function from that page:
public ActionResult ShowFileFN(string id) {
string mp = Server.MapPath("~/Content/" + id);
return File(mp, "text/html");
}
This will return a binary stream of the named file with the specified MIME content type, in this case "text/html". You'll need to know the MIME type for each file you're returning.
Here's a function to get the MIME type of a file based on its extension:
public static string GetMimeType(string fileName)
{
string mimeType = "application/unknown";
string ext = System.IO.Path.GetExtension(fileName).ToLower();
Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
if (regKey != null && regKey.GetValue("Content Type") != null)
mimeType = regKey.GetValue("Content Type").ToString();
return mimeType;
}

Related

.Net Core - Send image through api = Resource interpreted as Document but transferred with MIME type image/jpeg

In the wwwroot folder of my .Net Core MVC application, I have some images. I need to serve these pictures to TopDesk, where I can put in a url which gets embedded. I have no influence on Topdesk. I can only change the way the image is served.
When I use a direct link to the image, it works. The image gets embedded
Example of direct url:
https://my.web.site/images/image001.jpeg
But there is a limited embedded size (600px) so i need to resize the images. For that purpose I wrote a very simple api controller:
[HttpGet]
[Route("api/Images/GetImage/{id}")]
public IActionResult GetImage(string id)
{
try
{
var pad = $"c:\\Images\\{id}";
if(System.IO.File.Exists(path))
{
var fileBytes = System.IO.File.ReadAllBytes(path);
var smallImage = ..... doing resizing;
new FileExtensionContentTypeProvider().TryGetContentType(Path.GetFileName(path), out var contentType);
return File(smallImage , contentType ?? "application/octet-stream", $"{id}");
}
return NotFound();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
but the url
https://my.web.site/api/images/GetImage/image001.jpeg
results in
Resource interpreted as Document but transferred with MIME type
image/jpeg
The image doesn't show.
When i test the url in Postman, it returns the image without warning.
What am i missing here?
Instead of returning a File, try using FileContentResult instead:
[HttpGet]
[Route("api/Images/GetImage/{id}")]
public IActionResult GetImage(string id)
{
try
{
var path = $"c:\\Images\\{id}";
if(System.IO.File.Exists(path))
{
var fileBytes = System.IO.File.ReadAllBytes(path);
var smallImage = ..... doing resizing;
new FileExtensionContentTypeProvider().TryGetContentType(Path.GetFileName(path), out var contentType);
return new FileContentResult(fileBytes, contentType ?? "application/octet-stream");
}
return NotFound();
}
catch(Exception ex)
{
return BadRequest(ex.Message);
}
}
When navigating to /GetImage/{id} with a browser, you will see that with File the browser tends to download the file, but with FileContentResult it displays the image in the browser tab directly, which is the same behavior as using static files. This is probably happening because of the Response Headers being added when using File/FileContentResult (probably a Content-Disposition header). Not sure how TopDesk is using these images though.
Off-topic: It's also a good practice to not instantiate a FileExtensionContentTypeProvider with every request. Instead, you can register it as a singleton in your Startup.cs like:
services.AddSingleton(new FileExtensionContentTypeProvider());
and inject it in your controller's constructor.

How to use "Azure storage blobs" for POST method in controller

I am creating an app where user can upload their text file and find out about its most used word.
I have tried to follow this doc to get used to the idea of using AZURE STORAGE BLOBS - https://learn.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-dotnet
But I am super newbie and having a hard time figuring it out how to adapt those blobs methods for my POST method.
This my sudo - what I think I need in my controller and what needs to happen when POST method is triggered.
a.No need for DELETE or PUT, not replacing the data nor deleting in this app
b.Maybe need a GET method, but as soon as POST method is triggered, it should pass the text context to the FE component
POST method
connect with azure storage account
if it is a first time of POST, create a container to store the text file
a. how can I connect with the existing container if the new container has already been made? I found this, but this is for the old CloudBlobContainer. Not the new SDK 12 version.
.GetContainerReference($"{containerName}");
upload the text file to the container
get the chosen file's text content and return
And here is my controller.
public class HomeController : Controller
{
private IConfiguration _configuration;
public HomeController(IConfiguration Configuration)
{
_configuration = Configuration;
}
public IActionResult Index()
{
return View();
}
[HttpPost("UploadText")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
if (files != null)
{
try
{
string connectionString = Environment.GetEnvironmentVariable("AZURE_STORAGE_CONNECTION_STRING");
BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
string containerName = "textdata" + Guid.NewGuid().ToString();
BlobContainerClient containerClient = await blobServiceClient.CreateBlobContainerAsync(containerName);
//Q. How to write a if condition here so if the POST method has already triggered and container already created, just upload the data. Do not create a new container?
string fileName = //Q. how to get the chosen file name and replace with newly assignmed name?
string localFilePath = //Q. how to get the local file path so I can pass on to the FileStream?
BlobClient blobClient = containerClient.GetBlobClient(fileName);
using FileStream uploadFileStream = System.IO.File.OpenRead(localFilePath);
await blobClient.UploadAsync(uploadFileStream, true);
uploadFileStream.Close();
string data = System.IO.File.ReadAllText(localFilePath, Encoding.UTF8);
//Q. If I use fetch('Home').then... from FE component, will it receive this data? in which form will it receive? JSON?
return Content(data);
}
catch
{
//Q. how to use storageExeption for the error messages
}
finally
{
//Q. what is suitable to execute in finally? return the Content(data) here?
if (files != null)
{
//files.Close();
}
}
}
//Q. what to pass on inside of the Ok() in this scenario?
return Ok();
}
}
Q1. How can I check if the POST method has been already triggered, and created the Container? If so how can I get the container name and connect to it?
Q2. Should I give a new assigned name to the chosen file? How can I do so?
Q3. How can I get the chosen file's name so I can pass in order to process Q2?
Q4. How to get the local file path so I can pass on to the FileStream?
Q5. How to return the Content data and pass to the FE? by using fetch('Home').then... like this?
Q6. How can I use storageExeption for the error messages
Q7. What is suitable to execute in finally? return the Content(data) here?
Q8. What to pass on inside of the Ok() in this scenario?
Any help is welcomed! I know I asked a lot of Qs here. Thanks a lot!
Update: add a sample code, you can modify it as per your need.
[HttpPost]
public async Task<IActionResult> SaveFile(List<IFormFile> files)
{
if (files == null || files.Count == 0) return Content("file not selected");
string connectionString = "xxxxxxxx";
BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
string containerName = "textdata" + Guid.NewGuid().ToString();;
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
containerClient.CreateIfNotExists();
foreach (var file in files)
{
//use this line of code to get file name
string fileName = Path.GetFileName(file.FileName);
BlobClient blobClient = containerClient.GetBlobClient(fileName);
//directly read file content
using (var stream = file.OpenReadStream())
{
await blobClient.UploadAsync(stream);
}
}
//other code
return View();
}
Original answer:
When using List<IFormFile>, you should use foreach code block to iterate each file in the list.
Q2. Should I give a new assigned name to the chosen file? How can I do
so?
If you want to keep the file original name, in the foreach statement like below:
foreach (var file in myfiles)
{
Path.GetFileName(file.FileName)
//other code
}
And if you want to assign a new file name when uploaded to blob storage, you should define the new name in this line of code: BlobClient blobClient = containerClient.GetBlobClient("the new file name").
Q3. How can I get the chosen file's name so I can pass in order to
process Q2?
refer to Q2.
Q4. How to get the local file path so I can pass on to the FileStream?
You can use code like this: string localFilePath = file.FileName; to get the path, and then combine with the file name. But there is a better way, you can directly use this line of code Stream uploadFileStream = file.OpenReadStream().
Q5. How to return the Content data and pass to the FE? by using
fetch('Home').then... like this?
Not clear what's it meaning. Can you provide more details?
Q6. How can I use storageExeption for the error messages
The storageExeption does not exist in the latest version, you should install the older one.
You can refer to this link for more details.
#Ivan's answer is what the documentation seems the recommend; however, I was having a strange issue where my stream was always prematurely closed before the upload had time to complete. To anyone else who might run into this problem, going the BinaryData route helped me. Here's what that looks like:
await using var ms = new MemoryStream();
await file.CopyToAsync(ms);
var data = new BinaryData(ms.ToArray());
await blobClient.UploadAsync(data);

NSUrl Not Initializing properly

I've placed 25 mp3 files in the "Music folder" in my Xamarin.ios project.
I called the Play function and passed in the name of a mp3 file I want to play. I created a string object which contains the filePath.
Then I create a another string object called "withFileName" containing the path and song name passed into the function. The withFileName variable looks correct.
When I execute the instruction ...
songURL = new NSUrl(withFileName);
I get the following exception...
System.Exception: Could not initialize an instance of the type 'Foundation.NSUrl': the native 'initWithString:' method returned nil. It is possible to ignore this condition by setting MonoTouch.ObjCRuntime.Class.ThrowOnInitFailure to false. at Foundation.NSO…
I'm lost, need some help.
code snippet:
public void Play(String song)
{
String dir = Directory.GetCurrentDirectory();
String filePath = Path.Combine(dir, "Music");
String withFileName = String.Format("{0}/{1}", filePath, song);
NSUrl songURL = null;
try
{
songURL = new NSUrl(withFileName);
}
catch (Exception e)
{
string msg = e.Message;
}
NSError err;
_audioPlayer = new AVAudioPlayer( songURL,"Song", out err );
_audioPlayer.Play();
}
It seems You can get the correct path of file embeded in the project. Then if you want to get the file's url you should use:
new NSUrl(filePath, false);
//or
NSUrl.FromFilename(filePath)

How to create a directory on user login for .NET Core 2

I have a requirement, at least for now, to create a subdirectory based on a username for a .NET Core website. Where is the best place to do this?
I tried adding in ApplicationUser and I am not sure how to add it correctly. What I have, which I know is completely wrong, is the following.
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Hosting;
using System.IO;
namespace BRSCRM.Models
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
private IHostingEnvironment hostingEnvironment;
public string HomeDir { get; set; }
HomeDir=HostingEnvironment.WebRootPath + UserName;
string path = this.hostingEnvironment.WebRootPath + "\\uploads\\" + UserName;
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
}
I wish the documentation was better. It seems they have plenty of getting-started material out there, but when you go to try and do something that is not covered it gets pretty tough to find help.
What I am trying to do is supportfileuploading for members.
I think I am getting closer, but I get this error now:
> 'Microsoft.AspNetCore.Hosting.IHostingEnvironment'. Model bound complex types must not be abstract or value types and must have a parameterless constructor Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder.CreateModel(ModelBindingContext
I cannot seem to read the IHostingEnvironment webrootpath. It is so frustrating!!
I moved my code into the Register action in file AccountController.cs...
This is what I have so far..
if (result.Succeeded)
{
await _userManager.AddToRoleAsync(user, "Member");
_logger.LogInformation("User created a new account with password.");
// Add code here to create a directory...
string webRootPath = _hostingEnvironment.WebRootPath;
string contentRootPath = _hostingEnvironment.ContentRootPath;
var userId = User.FindFirstValue(ClaimTypes.Email);
string path = webRootPath + "\\uploads\\" + userId;
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
I removed the code for the environment since it didn’t work anyway. I tried to just add a directory on my local system, but I discovered that I am not getting anything in the claims field. I am not sure how to get the username, email or anything else out of it. What should I do?
The code is 1) syntactically and 2) ideologically incorrect.
The following code must be in some method, not in the model class definition
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
The main idea of MVC is to separate the model definition (M), business logic (controller C), and presentation (view V). So a part of the code should be in some controller where the folder is first required (for example, AccountController) and called from (for example) [HttpPost]Register action.
private void SetUserFolder(ApplicationUser user)
{
IHostingEnvironment hostingEnvironment = /*getEnv()*/;
user.HomeDir = HostingEnvironment.WebRootPath + user.UserName;
string path = this.hostingEnvironment.WebRootPath + "\\uploads\\" + UserName;
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
}
Would it meet your requirements to just check if the folder exists when the user uploads a file, then create it before saving the file if it doesn't?
As an example, if your action method (assuming MVC) is like so:
Upload files in ASP.NET Core
[HttpPost("UploadFiles")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
long size = files.Sum(f => f.Length);
// Full path to file in temp location
var filePath = Path.GetTempFileName();
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await formFile.CopyToAsync(stream);
}
}
}
// Process uploaded files
// Don't rely on or trust the FileName property without validation.
return Ok(new { count = files.Count, size, filePath});
}
You could simply use your own path in place of
var filePath = Path.GetTempFileName();
and check for its existence/create it if needed before saving.

Why is ASP.NET View Engine checking for .Mobile.chstml view path?

For my ASP.NET MVC 4 project, I'm trying to implement a custom view engine to find an "Index.cshtml" view file if one exists within a folder. Additionally, I'm throwing a 404 for all view paths that are not found.
The 404 works when a view file doesn't exist. When a view file does exist, the view engine will then try looking for a .Mobile.cshtml file using the FileExists() function. There is no .mobile.cshtml file, so it throws an exception. Why does the view engine still look for a .mobile.cshtml file when it has found the non-mobile file already?
For example, when the view engine is able to find a view path at "~/Views/About/History/Index.cshtml", it will then try finding the file "~/Views/About/History/Index.Mobile.cshtml". Below is my full code for the custom view engine.
namespace System.Web.Mvc
{
// Extend where RazorViewEngine looks for view files.
// This looks for path/index.ext file if no path.ext file is found
// Ex: looks for "about/history/index.chstml" if "about/history.cshtml" is not found.
public class CustomViewEngine : RazorViewEngine
{
public BeckmanViewEngine()
{
AreaViewLocationFormats = new[]
{
"~/Areas/{2}/Views/{1}/{0}/Index.cshtml",
};
ViewLocationFormats = new[]
{
"~/Views/{1}/{0}/Index.cshtml",
};
}
// Return 404 Exception if viewpath file in existing path is not found
protected override bool FileExists(ControllerContext context, string path)
{
if (!base.FileExists(context, path))
{
throw new HttpException(404, "HTTP/1.1 404 Not Found");
}
return true;
}
}
}
I have found the answer after digging a bit in the MVC 4 source code.
The RazorViewEngine derives from BuildManagerViewEngine, and this one in turns derives from VirtualPathProviderViewEngine.
It is VirtualPathProviderViewEngine the one that implements the method FindView:
public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(viewName))
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
}
string[] viewLocationsSearched;
string[] masterLocationsSearched;
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string viewPath = GetPath(controllerContext, ViewLocationFormats, AreaViewLocationFormats, "ViewLocationFormats", viewName, controllerName, CacheKeyPrefixView, useCache, out viewLocationsSearched);
string masterPath = GetPath(controllerContext, MasterLocationFormats, AreaMasterLocationFormats, "MasterLocationFormats", masterName, controllerName, CacheKeyPrefixMaster, useCache, out masterLocationsSearched);
if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched));
}
return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this);
}
That GetPath method used there will do something like this when the view path has not been cached yet:
return nameRepresentsPath
? GetPathFromSpecificName(controllerContext, name, cacheKey, ref searchedLocations)
: GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, cacheKey, ref searchedLocations);
Getting there! The interesting method is GetPathFromGeneralName, which is the one trying to build the whole path for the view and checking if that path exists. The method is looping through each of the view locations that were registered in the View Engine, updating the view path with the display mode valid for current HttpContext and then checking if the resolved path exists. If so, the view has been found, is assigned to the result, cached and the result path returned.
private string GetPathFromGeneralName(ControllerContext controllerContext, List<ViewLocation> locations, string name, string controllerName, string areaName, string cacheKey, ref string[] searchedLocations)
{
string result = String.Empty;
searchedLocations = new string[locations.Count];
for (int i = 0; i < locations.Count; i++)
{
ViewLocation location = locations[i];
string virtualPath = location.Format(name, controllerName, areaName);
DisplayInfo virtualPathDisplayInfo = DisplayModeProvider.GetDisplayInfoForVirtualPath(virtualPath, controllerContext.HttpContext, path => FileExists(controllerContext, path), controllerContext.DisplayMode);
if (virtualPathDisplayInfo != null)
{
string resolvedVirtualPath = virtualPathDisplayInfo.FilePath;
searchedLocations = _emptyLocations;
result = resolvedVirtualPath;
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, virtualPathDisplayInfo.DisplayMode.DisplayModeId), result);
if (controllerContext.DisplayMode == null)
{
controllerContext.DisplayMode = virtualPathDisplayInfo.DisplayMode;
}
// Populate the cache for all other display modes. We want to cache both file system hits and misses so that we can distinguish
// in future requests whether a file's status was evicted from the cache (null value) or if the file doesn't exist (empty string).
IEnumerable<IDisplayMode> allDisplayModes = DisplayModeProvider.Modes;
foreach (IDisplayMode displayMode in allDisplayModes)
{
if (displayMode.DisplayModeId != virtualPathDisplayInfo.DisplayMode.DisplayModeId)
{
DisplayInfo displayInfoToCache = displayMode.GetDisplayInfo(controllerContext.HttpContext, virtualPath, virtualPathExists: path => FileExists(controllerContext, path));
string cacheValue = String.Empty;
if (displayInfoToCache != null && displayInfoToCache.FilePath != null)
{
cacheValue = displayInfoToCache.FilePath;
}
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, AppendDisplayModeToCacheKey(cacheKey, displayMode.DisplayModeId), cacheValue);
}
}
break;
}
searchedLocations[i] = virtualPath;
}
return result;
}
You may have noticed that I haven´t talked about a piece of code with the following comment (reformatted for clarity):
// Populate the cache for all other display modes.
// We want to cache both file system hits and misses so that we can distinguish
// in future requests whether a file's status was evicted from the cache
// (null value) or if the file doesn't exist (empty string).
That (and the piece of code below the comment :)) means that once MVC 4 has found the first valid path from the View Locations registered in the View Engine, it will also check if the view file for all of the additional display modes that were not tested exist, so that information can be included in the cache (although just for that view location and not all of the locations available in the view engine).
Notice also, how it is passing a lambda to each of the tested display modes for checking if the file for that mode exists:
DisplayInfo displayInfoToCache = displayMode.GetDisplayInfo(
controllerContext.HttpContext,
virtualPath,
virtualPathExists: path => FileExists(controllerContext, path));
So that explains why when you override FileExists it is also being called for the mobile view, even when it has already found the non-mobile view.
In any case, display modes can be removed the same way they can be added: by updating the DisplayModes collection when the application starts. For example, removing the Mobile display mode and leaving just the default and unspecific one (You cannot clear the collection or no view will ever be found):
...
using System.Web.WebPages;
...
protected void Application_Start()
{
DisplayModeProvider.Instance.Modes.Remove(
DisplayModeProvider.Instance.Modes
.Single(m => m.DisplayModeId == "Mobile"));
Quite a long answer but hopefully it makes sense!
Have you tried removing Mobile DisplayModeProvider. You can achieve this by running the following in Application_Start:
var mobileDisplayMode = DisplayModeProvider.Instance.Modes.FirstOrDefault(a => a.DisplayModeId == "Mobile");
if (mobileDisplayMode != null)
{
DisplayModeProvider.Instance.Modes.Remove(mobileDisplayMode);
}
THe problem that you are getting is an expected behavior because FindView method queries DisplayModeProvider.

Resources