I am having a problem with return this.View("Index",model) (after the database processing) refreshing the browser when I upload an 80Mb file. The code works fine on smaller files. The code also works when I upload an 80Mb file with the debugger attached to w3wp.exe and manually step through it. I have tried multiple browsers and have the same problem across all of them.
[HttpPost]
public ActionResult Index(ImportDataViewModel model)
{
string importFileLoc = string.Empty;
if (Request.Files.Count != 0)
{
importFileLoc = this.SaveTempFile(Request.Files[0]);
}
if (!this.ValidateImportModel(model))
{
model.SystemNames = this.GetSystemNames().SetSelected(model.SelectedSystem);
return this.View(model);
}
// Do Database and other data processing here
if (!ModelState.IsValid || model.ContainsWarnings)
{
model.SystemNames = this.GetSystemNames(model.SelectedSystem);
return this.View("Index", model);
}
return this.RedirectToAction("Index");
}
Related
I'll start by saying i'm a C# MVC newbie, but I've set up a site with Identity Management and extended the database with some custom tables to store additional info about my users, so I'm not a total neophyte. I've been working on a VB WPF application that I want to deploy from my new website and that is where I'm running into an issue.
I've created a new controller (User) and a couple of views (Download) & (Setup). I created a downloadmodel used by the download view.
In abstract what I am doing is displaying the download view (get) which has three checkboxes to confirm the user has read the Overview, Installation, and Terms of Service. These are boolean values in the model. I also have a string response message in the model, that displays just above the submit button. Here is the model:
public class DownloadModel
{
public bool Overview { get; set; }
public bool Installation { get; set; }
public bool TermsOfService { get; set; }
public string Response { get; set; }
public DownloadModel()
{
Overview = false;
Installation = false;
TermsOfService = false;
Response = "After checking boxes click the button to begin installation";
}
}
My User Controller handles the Get to initially display the download view, and then in the Post it checks to see if all the checkboxes were ticked, if not it updates the response message and returns the view.
If the checkboxes are all checked then it pulls the subscriber (which must exist because it was created when the user verified their e-mail via the account controller - identity management), then proceeds to update the subscriber with the original (if new) or last download date(s). At this point I want to begin downloading the clickonce setup.exe file, before returning the setup view.
[Authorize]
public class UserController : Controller
{
// GET: User/Download
public ActionResult Download()
{
return View(new DownloadModel { });
}
// Post: User/Download
[HttpPost]
public ActionResult Download(DownloadModel downloadcheck)
{
if (!ModelState.IsValid)
{
return View(downloadcheck);
}
//check to see if all the boxes were checked
if (downloadcheck.Overview == true &
downloadcheck.Installation == true &
downloadcheck.TermsOfService == true)
{
//yes - so let's proceed
//first step is to get the subscriber
Subscriber tSubscriber = new Subscriber();
tSubscriber.Email = User.Identity.Name;
bool okLoad = tSubscriber.LoadByEmail();
if (okLoad == false)
{
//we have a real problem. a user has logged in but they are not yet
//a valid subscriber?
throw new Exception("Subscriber not found");
}
// update subscriber with download in process...
if (tSubscriber.OriginalDownload == DateTime.MinValue)
{
tSubscriber.OriginalDownload = DateTime.Now;
tSubscriber.LastDownload = tSubscriber.OriginalDownload;
}
else
{
tSubscriber.LastDownload = DateTime.Now;
}
if (tSubscriber.UpdateDownloaded() == false)
{
//update of download dates failed
//another problem that shouldnt occur.
downloadcheck.Response = "A problem occured downloading your setup."
+ "Try again. If this error continues please contact support.";
return View(downloadcheck);
}
//download dates have been updated for the subscriber so let's start the download!
//THIS IS WHERE I NEED TO BEGIN THE DOWNLOAD
return View("Setup");
}
else
{
// all boxes were not checked - update message
downloadcheck.Response = "Please confirm you have reviewed the above information "
+ "by checking all of the boxes before clicking on the button.";
return View(downloadcheck);
}
}
}
The download view is pretty straight forward, and the setup view simply confirms the download was started and provides a link to the help-setup page.
I'm really a bit lost here. I thought I'd plug in a return new filepathresponse, but I can't do that and return the setup view.
My other thought was to somehow trigger the download of my /xxx/setup.exe from within the setup view as it is returned - but I'm at a loss as to how to accomplish this.
I'll be the first to admit that my mvc c# code is probably overly verbose and my approach to how I've done this may be totally wrong, but I'm just scrambling to get this done so I can deploy my WPF app to select Beta users for testing. It's been a long time living off savings and I can smell go-live from here.
One final note, I'm using setup.exe clickonce deployment of my wpf app for simplicity, as there are .net and localsqldb prerequisites, but I will not be using automated updates - not that this is really relevant.
Appreciate all input and advice.
After more digging and hacking I've found a solution that works. Firstly in my setup view (confirmation page) I added a simple script to initiate a new function in my user controller:
<script type="text/javascript">
window.location.href = "/user/sendfile/"
</script>
The controller change was simple too. For testing I just used a txt file.
// User/SendFile
public ActionResult SendFile()
{
string path = #"~/xxxx/anyfile.txt";
string content = "application/txt";
//string content = "application/x-ms-application";
return new FilePathResult(path, content)
{
FileDownloadName = "mynewtext.txt"
};
}
What is really interesting about this solution is that the FileDownloadName is what the file content is downloaded as. So in this way I can refer to the actual setup.exe file in the path, but then name the downloaded exe anything I want. Bonus :)
I have a very simple file upload method on my MVC controller which works very well when uploading images from the browsers on my PC.
Uploading Code :
[HttpPost]
public ActionResult FileUpload(HttpPostedFileBase file, string encodedId)
However, if I try to upload an image from my iPhone I get error as
No route in the route table matches the supplied values
I tried removing the parameters from the method and instead accessing the data from the Request object:
Modified Code :
[HttpPost]
public ActionResult FileUpload()
{
var file = HttpContext.Request.Files[0];
string encodedId = HttpContext.Request.Form["EncodedId"];
This results in the same error.
I cant't ascertain what the iPhone is sending to the server as I don't have any kind of development tools on my iPhone.
I name my File which i am sending as File and get it via following code.
[HttpPost]
[Route("UploadAttachment")]
public IHttpActionResult UploadAttachment()
{
try
{
HttpPostedFile File = HttpContext.Current.Request.Files.Count > 0 ?
HttpContext.Current.Request.Files.Get("File") : null;
if (File != null)
{
Uploader uploader = new Uploader();
var Result = uploader.uploadToServer(File);
return Ok(Result);
}
else
{
return BadRequest("File is Required");
}
}
catch (Exception ex)
{
return InternalServerError(ex);
}
}
as the question implies I want to store images into the filesystem and save a link to it in the database.
but NHibernate doesn't save the file path in the database. here is the code:
[HttpPost]
public ActionResult Edit(Item item, HttpPostedFileBase image)
{
if (ModelState.IsValid)
{
if (image != null)
{
string imageName = image.FileName;
string location = Path.Combine(Server.MapPath("~/Content/Images/ItemImages/") , imageName);
image.SaveAs(location);
item.Image= imageName;
}
menuItemRepository.SaveOrUpdate(item);
// here the debug show the image path has correctly assigned to the image property
Debug.WriteLine(item.Image);
TempData["message"] = string.Format("{0} has been saved", item.Name);
return RedirectToAction("Index", item.Parent);
}
else
{
// there is something wrong with the data values
return View(Item);
}
}
but after repositor save or update the item, when I look at the database, the image is null. I tried to assign something ele like image name and it did work but the for image path is not working!! I'm confused why this happen. does anyone have any idea?
public class Item
{
public virtual string Image { get; set; }
}
public calss ItemMap : ClassMap<Item>
{
public ItemMap()
{
Map(x => x.Image).Length(100);
}
}
//////////Repository
public T SaveOrUpdate(T entity)
{
session.SaveOrUpdate(entity);
return entity;
}
My best guess - the save is not being flushed to the database. See the documentation:
From time to time the ISession will execute the SQL statements needed to synchronize the ADO.NET connection's state with the state of objects held in memory. This process, flush, occurs by default at the following points
from some invocations of Find() or Enumerable()
from NHibernate.ITransaction.Commit()
from ISession.Flush()
I see nothing in your code that would trigger a flush. Wrap your SaveOrUpdate in a transaction:
using (var trx = menuItemRepository.BeginTransaction())
{
menuItemRepository.SaveOrUpdate(item);
trx.Commit();
}
trx.Commit() will flush that pending update query to the database.
I had implemented a sessionPreRequest module for my MVC app. so I was doing commit() operation there.
I checked and saw my transaction is not committing and is rolling back. and checked the error and the image column in the database was nvarchar(50), but the string which had the path of image was mor than 50 characters. so I changed to nvarchar(200) and now everything works fine.
I have a system whereby users can upload sometimes large(100-200 MB) files from within an MVC3 application. I would like to not block the UI while the file is uploading, and after some research, it looked like the new AsyncController might let me do what I'm trying to do. Problem is - every example I have seen isn't really doing the same thing, so I seem to be missing one crucial piece. After much futzing and fiddling, here's my current code:
public void CreateAsync(int CompanyId, FormCollection fc)
{
UserProfile up = new UserRepository().GetUserProfile(User.Identity.Name);
int companyId = CompanyId;
// make sure we got a file..
if (Request.Files.Count < 1)
{
RedirectToAction("Create");
}
HttpPostedFileBase hpf = Request.Files[0] as HttpPostedFileBase;
if (hpf.ContentLength > 0)
{
AsyncManager.OutstandingOperations.Increment();
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, e) =>
{
string fileName = hpf.FileName;
AsyncManager.Parameters["recipientId"] = up.id;
AsyncManager.Parameters["fileName"] = fileName;
};
worker.RunWorkerCompleted += (o, e) => { AsyncManager.OutstandingOperations.Decrement(); };
worker.RunWorkerAsync();
}
RedirectToAction("Uploading");
}
public void CreateCompleted(int recipientId, string fileName)
{
SystemMessage msg = new SystemMessage();
msg.IsRead = false;
msg.Message = "Your file " + fileName + " has finished uploading.";
msg.MessageTypeId = 1;
msg.RecipientId = recipientId;
msg.SendDate = DateTime.Now;
SystemMessageRepository.AddMessage(msg);
}
public ActionResult Uploading()
{
return View();
}
Now the idea here is to have the user submit the file, call the background process which will do a bunch of things (for testing purposes is just pulling the filename for now), while directing them to the Uploading view which simply says "your file is uploading...carry on and we'll notify you when it's ready". The CreateCompleted method is handling that notification by inserting a message into the users's message queue.
So the problem is, I never get the Uploading view. Instead I get a blank Create view. I can't figure out why. Is it because the CreateCompleted method is getting called which shows the Create view? Why would it do that if it's returning void? I just want it to execute silently in the background, insert a message and stop.
So is this the right approach to take at ALL? my whole reason for doing it is with some network speeds, it can take 30 minutes to upload a file and in its current version, it blocks the entire application until it's complete. I'd rather not use something like a popup window if I can avoid it, since that gets into a bunch of support issues with popup-blocking scripts, etc.
Anyway - I am out of ideas. Suggestions? Help? Alternate methods I might consider?
Thanks in advance.
You are doing it all wrong here. Assume that your action name is Create.
CreateAsync will catch the request and should be a void method and returns nothing. If you have attributes, you should apply them to this method.
CreateCompleted is your method which you should treat as a standard controller action method and you should return your ActionResult inside this method.
Here is a simple example for you:
[HttpPost]
public void CreateAsync(int id) {
AsyncManager.OutstandingOperations.Increment();
var task = Task<double>.Factory.StartNew(() => {
double foo = 0;
for(var i = 0;i < 1000; i++) {
foo += Math.Sqrt(i);
}
return foo;
}).ContinueWith(t => {
if (!t.IsFaulted) {
AsyncManager.Parameters["headers1"] = t.Result;
}
else if (t.IsFaulted && t.Exception != null) {
AsyncManager.Parameters["error"] = t.Exception;
}
AsyncManager.OutstandingOperations.Decrement();
});
}
public ActionResult CreateCompleted(double headers1, Exception error) {
if(error != null)
throw error;
//Do what you need to do here
return RedirectToAction("Index");
}
Also keep in mind that this method will still block the till the operation is completed. This is not a "fire and forget" type async operation.
For more info, have a look:
Using an Asynchronous Controller in ASP.NET MVC
Edit
What you want here is something like the below code. Forget about all the AsyncController stuff and this is your create action post method:
[HttpPost]
public ActionResult About() {
Task.Factory.StartNew(() => {
System.Threading.Thread.Sleep(10000);
if (!System.IO.Directory.Exists(Server.MapPath("~/FooBar")))
System.IO.Directory.CreateDirectory(Server.MapPath("~/FooBar"));
System.IO.File.Create(Server.MapPath("~/FooBar/foo.txt"));
});
return RedirectToAction("Index");
}
Notice that I waited 10 seconds there in order to make it real. After you make the post, you will see the it will return immediately without waiting. Then, open up the root folder of you app and watch. You will notice that a folder and file will be created after 10 seconds.
But (a big one), here, there is no exception handling, a logic how to notify user, etc.
If I were you, I would look at a different approach here or make the user suffer and wait.
I have code that does a simple form post in a new window from our web application that calls out to an Action on a ASP.NET MVC3 Controller. This behavior works just fine on PC Browsers but if a user is on a Mac the form post causes the browser to download a Zero byte file with the name of the Action that is being requested.
The action looks up an item from the database and based on its Format returns a FileContentResult with the correct content type and the item's data.
Can anyone tell me why this is happening and any workarounds they've found when running into this?
if (item.Format == PrintedItemFormat.PDF || item.Format == PrintedItemFormat.XDP)
{
return new FileContentResult(Encoding.UTF8.GetBytes(item.Data), "application/vnd.adobe.xdp+xml");
}
if (item.Format == PrintedItemFormat.XFDL)
{
return new FileContentResult(Encoding.ASCII.GetBytes(item.Data), "application/vnd.xfdl");
}
if (item.Format == PrintedItemFormat.CSV)
{
return new FileContentResult(Encoding.UTF8.GetBytes(item.Data), "application/CSV")
{
FileDownloadName = item.FileName
};
}
if (item.Format == PrintedItemFormat.HTML)
{
return new FileContentResult(Encoding.UTF8.GetBytes(item.Data), "text/html");
}
if (item.Format == PrintedItemFormat.TXT)
{
return new FileContentResult(Encoding.UTF8.GetBytes(item.Data), "text/plain");
}
if (item.Format == PrintedItemFormat.XFDF)
{
return new FileContentResult(Encoding.UTF8.GetBytes(item.Data), "application/vnd.adobe.xfdf");
}
I think the problem is the Mac browsers need to know a FileDownloadName as it cannot be inferred via the request.