I am beginner in MVC coding.
When the application starts, the ViewBag.Message is: Choose a file to upload.
After a successful upload, it changes to: File uploaded successfully!
Is there a way I can make it to return and show the "Choose a file to upload" message again after around 5 seconds, without using any javascript ?
I thought if mvc had some built in time function I could use maybe ?
https://github.com/xoxotw/mvc_fileUploader
My view:
#{
ViewBag.Title = "FileUpload";
}
<h2>FileUpload</h2>
<h3>Upload a File:</h3>
#using (Html.BeginForm("FileUpload", "Home", FormMethod.Post, new {enctype = "multipart/form-data"}))
{
#Html.ValidationSummary();
<input type="file" name="fileToUpload" /><br />
<input type="submit" name="Submit" value="upload" />
#ViewBag.Message
}
My controller:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Mvc_fileUploader.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Choose a file to upload!";
return View("FileUpload");
}
[HttpPost]
public ActionResult FileUpload(HttpPostedFileBase fileToUpload)
{
if (ModelState.IsValid)
{
if (fileToUpload != null && fileToUpload.ContentLength > (1024 * 1024 * 1)) // 1MB limit
{
ModelState.AddModelError("fileToUpload", "Your file is to large. Maximum size allowed is 1MB !");
}
else
{
string fileName = Path.GetFileName(fileToUpload.FileName);
string directory = Server.MapPath("~/fileUploads/");
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
string path = Path.Combine(directory, fileName);
fileToUpload.SaveAs(path);
ModelState.Clear();
ViewBag.Message = "File uploaded successfully!";
}
}
return View("FileUpload");
}
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
}
The short answer is No. I am guessing because you are "new" you want to focus on the MVC part but MVC and JavaScript are very much interlinked, think client (JavaScript) and server (MVC) and you should really master both to make good websites.
Normally the server doesn't fire events to the browser and instead the browser would make the requests. There are ways to get the server to raise events on the client using things like SignalR but that would be overkill in this scenario.
Finally... what you are trying to achieve is very much a client-side action, i.e. To inform the user to do something. If you did it in MVC you would waste network bandwidth and add delays (think of server calls as expensive) when really it is a client action and so should be done in JavaScript.
Don't shy away from JavaScript. Embrace it. Look into JQuery that takes a lot of the heavy lifting away for you.
Related
Have gone through the first 3 pages of Google and still can't get to the bottom of this. I have a controller which I am using to upload images:
[HttpPost]
[Authorize(Roles = "Admin,Tradesman,Customer")]
public ActionResult UploadFile(HttpPostedFileBase file)
{
// to do: ensure only valid file types are sent
try
{
if (file.ContentLength > 0)
{
using (var ctx = new ApplicationDbContext())
{
if (ModelState.IsValid)
{
// Need to check we have a current UserId and JobId before we go any furthur
var profileData = Session["UserProfile"] as UserProfileSessionData;
if (profileData.JobIdGuid.ToString().Length != 36)
{
// to do: something went horribly wrong! Redirect back to main view
}
if (profileData.UserIdGuid.ToString().Length != 36)
{
// to do: something went horribly wrong! Redirect back to main view
}
var photo = new Photos();
photo.Guid = Guid.NewGuid();
photo.Url = Server.MapPath("~/Images/2017");
photo.Extension = Path.GetExtension(file.FileName);
photo.JobGuid = profileData.JobIdGuid;
photo.UserIdGuid = profileData.UserIdGuid;
photo.Timestamp = DateTime.Now;
ctx.Photo.Add(photo);
ctx.SaveChanges();
string _path = Path.Combine(photo.Url, photo.Guid.ToString() + photo.Extension);
file.SaveAs(_path);
}
}
}
ViewBag.Message = "File Uploaded Successfully.";
return View();
}
catch
{
ViewBag.Message = "File upload failed.";
return View();
}
}
Each image is saved to a given location, the location saved to the db, happy days. Want I want though is for my images to be displayed on the same page after each upload. The model is as you'd expect just Id, Guid, Url, Extension, UserId, Timestamp.
Here is the view that uploads the images:
#{
ViewBag.Title = "UploadFile";
}
<h2>Upload File</h2>
#using (Html.BeginForm("UploadFile", "Job", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div>
#Html.TextBox("file", "", new { type = "file" })
<br />
<input type="submit" value="Next" />
#ViewBag.Message
</div>
// to do display the images uploaded
}
Is it possible to just have some kind of for...each and have each displayed at the bottom? Anyone know how to do this! Btw this is my first C# MVC app so if this is daft question I apologise. Thanks in advance :)
You should be following the P-R-G pattern. After successfully saving the data in your HttpPost action method, you should do a redirect to your GET action method, where you will read the data you need and pass it to the view where you will display it.
I would create a view model to represent each image and use that
public class ProfileImageVm
{
public string FileName { set;get;}
public DateTime CreatedTime { set;get;}
}
Now, for your save partin your http post action method, i would advise you to not save the physical location of the file in the table. The Server.MapPath returns the physical path. Storing that is unnecessary. What if you decide to move the location to some other directory in the server tomorrow? You could simply store the unique fileName. Let's assume that you want to store all the files in the Images/2017 in app root ,you can use Server.MapPath to get the physical location so that you can store the file in disk, but do not use that to store your table record.
var fileName = Path.GetFileNameWithoutExtension(file.FileName);
photo.Url = fileName ;
photo.Extension = Path.GetExtension(file.FileName);
With this code, it is simply storing the file name(without extension) as it is, not a unique name. That means, if you are uploading a second file with same name, it will overwrite the first one in disk. If you want to generate a unique file name, use the GetUniqueName method from this post.
Now in the GET action method, you read the Photos collection and create a list of our view model from that.
public ActionResult UploadFile()
{
var list= ctx.Photos
.Select(x=>new ProfileImageVm { FileName=x.Url + x.Extension ,
CreatedTime = x.Timestamp })
.ToList();
return View(list);
}
Now in your UploadFile view will be strongly typed to a list of ProfileImageVm, you can loop through the model data and render the images.
#model List<ProfileImageVm>
#using (Html.BeginForm("UploadFile", "Job", FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
#Html.TextBox("file", "", new { type = "file" })
<input type="submit" value="Next" />
}
<h3>Images</h3>
#foreach(var item in Model)
{
<img src="~/Images/2017/#item.FileName" />
<p>Uploaded at #item.CreatedTime </p>
}
Now, after successfully saving the photo and the record in table, you will return a redirect response to the GET action.
file.SaveAs(_path);
return RedirectToAction("Upload","Job");
You can also keep the base path ~/Images/2017 in a config settings/constant and use that across your app so if you ever decide to change it to ~/Images/profilepics, there is only one place you have to change.
I have a view where user chooses a photo from his/her computer and uploads it to Flickr. The point is that once the button is clicked, it redirects to Flickr which asks for authorization, and once authorization process is finished it redirects back to that action method. Below you can see some code to make it more clear.
Test.cshtml:
#using (Html.BeginForm("UploadToFlickr", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<fieldset>
<input type="file" name="file" />
<input type="submit" value="Upload!" />
</fieldset>
}
HomeController.cs:
public class HomeController : Controller
{
public static string tmpFilePath, filename, path;
// some other methods...
public ActionResult UploadToFlickr(HttpPostedFileBase file, FormCollection form)
{
tmpFilePath = Server.MapPath("~/App_Data/Uploads/Pictures");
if (file == null || file.ContentLength == 0)
{
return RedirectToAction("Index");
}
filename = Path.GetFileName(file.FileName);
path = Path.Combine(tmpFilePath, filename);
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
}
file.SaveAs(path);
if (Request.QueryString["oauth_verifier"] != null && Session["RequestToken"] != null)
{
// Flickr relevant code...
string photoId = flickr.UploadPicture(path, "Test picture");
}
else
{
// Flickr relevant code...
string url = flickr.OAuthCalculateAuthorizationUrl(token.Token, AuthLevel.Write);
Response.Redirect(url);
}
return View("Test");
}
So the point is that, I have already defined tmpFilePath, filename and path variables as static, as you can see. When I click the upload button, at at first it calls theUploadToFlickr method, which executes the initial lines of code, and then enters into else, which redirects the app to Flickr authorization, then when I click authorize, it again generates a URL that includes the UploadToFlickr method, which call that method again, but this time the file parameter is null, and it enters into the part return RedirectToAction("Index");. Is there any way, how can I solve this? I need the part until the if case to be executed just once, only when the button is clicked. Not the second time when I'm redirected from Flickr.
The callback is most likely using an HTTP GET rather than an HTTP POST. Split your actions into two methods and decorate the one you post to with [HttpPost] and the callback method with [HttpGet]. The [HttpPost] method should only be called when the user hits the upload button (since the FormMethod is set to Post), so that method should only be responsible for validating the file they uploaded and passing it along to Flickr. After Flickr has done it's thing, if it is calling back to your app, it should call the [HttpGet] method, where you redirect or do whatever else you want to do. I'm not familiar with the Flickr API but this should get you close.
Keep in mind that uploading your image and getting a callback from Flickr are two completely separate requests to your application. You need to determine what to do based on the HTTP request method, provided parameters, etc. for both unique requests.
public class HomeController : Controller
{
public static string tmpFilePath, filename, path;
// some other methods...
[HttpGet]
public ActionResult UploadToFlickr()
{
// This method will probably get called back by Flickr
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult UploadToFlickr(HttpPostedFileBase file, FormCollection form)
{
// This method will only be called when the user clicks the upload button
tmpFilePath = Server.MapPath("~/App_Data/Uploads/Pictures");
if (file == null || file.ContentLength == 0)
{
// No file was provided...show validation errors or something
}
filename = Path.GetFileName(file.FileName);
path = Path.Combine(tmpFilePath, filename);
if (System.IO.File.Exists(path))
{
System.IO.File.Delete(path);
}
file.SaveAs(path);
if (Request.QueryString["oauth_verifier"] != null && Session["RequestToken"] != null)
{
// Flickr relevant code...
string photoId = flickr.UploadPicture(path, "Test picture");
}
else
{
// Flickr relevant code...
string url = flickr.OAuthCalculateAuthorizationUrl(token.Token, AuthLevel.Write);
Response.Redirect(url);
}
return View("Test");
}
}
I am using kendo mobile to build a mobile application in which the user will be able to click and upload a photo. When they first enter the page, it will show their current photo, I want to be able to click and open file explorer on their device and have the ability to show a preview of their photo in place of the old one. Then when the click done it will send it to my MVC controller where I can then send it to where I want. I cant figure out how to send my file to the controller.
HTML
<div id="NewAccountUploadContainer">
<img id="NewAccountUpload" src="~/Images/btnCamera.png" data-bind="click: uploadPhoto" />
#using (Html.BeginForm("SendNewPhoto", "MobilePlatform", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<input id="ImageUploadBtn" style="display: none" type="file" accept="image/*" />
<input type="submit" value="OK" style="display: none" />
}
<div id="ImgUploadTxt" data-bind="click: uploadPhoto">
Upload a<br />
different photo.
</div>
The #ImageUploadBtn will be triggered by the #NewAccountUpload or #ImgUploadTxt clicks in jquery which works, but I cant get it to display a file or send to my controller when I trigger the submit.
C# Controller
[HttpPost]
public ActionResult SendNewPhoto(HttpPostedFileBase file)
{
// Verify that the user selected a file
if (file != null && file.ContentLength > 0)
{
// extract only the fielname
var fileName = Path.GetFileName(file.FileName);
// store the file inside ~/App_Data/uploads folder
var path = Path.Combine(Server.MapPath("~/App_Data/uploads"), fileName);
file.SaveAs(path);
}
// redirect back to the index action to show the form once again
return RedirectToAction("Index");
}
The file is always null at this point.
I'm using the Kendo for mvc4, and a mobile implementation, and I'm using the follow code, works for me:
View:
#(Html.Kendo().Upload()
.Name("files")
)
Controller
public ActionResult Submit(IEnumerable<HttpPostedFileBase> files)
{
if (files != null)
{
TempData["UploadedFiles"] = GetFileInfo(files);
}
return RedirectToAction("Result");
}
public ActionResult Result()
{
return View();
}
private IEnumerable<string> GetFileInfo(IEnumerable<HttpPostedFileBase> files)
{
return
from a in files
where a != null
select string.Format("{0} ({1} bytes)", Path.GetFileName(a.FileName), a.ContentLength);
}
I am having few problems with displaying a success message after a file has been uploaded.
I first tried with using the ViewBag.Message , and it works good and display the Success message after the file has been uploaded, which is what I want. But then I cant find a way on how to , after a few seconds change that message back to: "Choose a file to upload !" , so that the user understand he can now upload a new file.
I tried to implement a javascript feature to handle the success message instead. The problem with that is that the success message then shows up before the file upload is completed, which is no good, and if its a very small file, the message will only show for a millisecond.
Do you have any suggestion on how I can fine tune this ? Im not sure if I should try work further using javascript or viewbag, or something different ?
What I am looking for is a success message that are display for around 5 seconds after a successful upload, it then changes back to the "Choose a file to upload message" again.
https://github.com/xoxotw/mvc_fileUploader
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Mvc;
namespace Mvc_fileUploader.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
//ViewBag.Message = "Choose a file to upload !";
return View("FileUpload");
}
[HttpPost]
public ActionResult FileUpload(HttpPostedFileBase fileToUpload)
{
if (ModelState.IsValid)
{
if (fileToUpload != null && fileToUpload.ContentLength > (1024 * 1024 * 2000)) // 1MB limit
{
ModelState.AddModelError("fileToUpload", "Your file is to large. Maximum size allowed is 1MB !");
}
else
{
string fileName = Path.GetFileName(fileToUpload.FileName);
string directory = Server.MapPath("~/fileUploads/");
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
string path = Path.Combine(directory, fileName);
fileToUpload.SaveAs(path);
ModelState.Clear();
//ViewBag.Message = "File uploaded successfully !";
}
}
return View("FileUpload");
}
public ActionResult About()
{
ViewBag.Message = "Your app description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
}
FileUpload view:
#{
ViewBag.Title = "FileUpload";
}
<h2>FileUpload</h2>
<h3>Upload a File:</h3>
#using (Html.BeginForm("FileUpload", "Home", FormMethod.Post, new {enctype = "multipart/form-data"}))
{
#Html.ValidationSummary();
<input type="file" name="fileToUpload" /><br />
<input type="submit" onclick="successMessage()" name="Submit" value="upload" />
//#ViewBag.Message
<span id="sM">Choose a file to upload !</span>
}
<script>
function successMessage()
{
x = document.getElementById("sM");
x.innerHTML = "File upload successful !";
}
</script>
Few things,
First, you need a Model to indicate a successful upload, we can just use a bool in your instance to indicate it.
Add this at the top of your view:
#model bool
Then you can do (keeping your view as is):
#{
ViewBag.Title = "FileUpload";
}
<h2>FileUpload</h2>
<h3>Upload a File:</h3>
#using (Html.BeginForm("FileUpload", "Home", FormMethod.Post, new {enctype = "multipart/form-data"}))
{
#Html.ValidationSummary();
<input type="file" name="fileToUpload" /><br />
<input type="submit" onclick="successMessage()" name="Submit" value="upload" />
<span id="sM">Choose a file to upload !</span>
}
We can manipulate the sM in JS dependent upon the model value
<script>
#if(Model)
{
var x = document.getElementById("sM");
x.innerHTML = "File upload successful !";
setTimeout("revertSuccessMessage()", 5000);
}
function revertSuccessMessage()
{
var x = document.getElementById("sM");
x.innerHTML = "Choose a file to upload !";
}
</script>
Then in your else statement in your action method, just make sure you return true on success, otherwise false. Like so
else
{
string fileName = Path.GetFileName(fileToUpload.FileName);
string directory = Server.MapPath("~/fileUploads/");
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
string path = Path.Combine(directory, fileName);
fileToUpload.SaveAs(path);
ModelState.Clear();
return View("FileUpload", true);
}
return View("FileUpload", false);
You could do the following:
$('form').submit(function(e) {
var form = $(this);
if (form.valid()) {
e.preventDefault();
$.ajax(form.attr('action'), {
data: new FormData(form[0]),
xhr: function() {
var myXhr = $.ajaxSettings.xhr();
var progress = $('progress', form);
if (myXhr.upload && progress.length > 0) {
progress.show();
myXhr.upload.addEventListener('progress', function(e) {
if (e.lengthComputable)
progress.attr({ value: e.loaded, max: e.total });
}, false);
}
return myXhr;
},
success: function(e) {
alert('Upload complete!');
},
// Options to tell JQuery not to process data or worry about content-type
contentType: false,
processData: false
});
}
});
However it will only work in modern browsers. You could use Modernizr to detect this. For example, if you wrap the code within the form's submit event handler with the following, it will fall back to a regular submit if it is not supported.
if (Modernizr.input.multiple) {
...
}
This also supports progress indication. Simply put a progress tag within the form.
The above code simply alerts the the user when the upload is complete. I use a nice little library called toastr instead.
Perhaps you could just use alert() on it's success? Not the most elegant solution but it sounds like it may suffice. Otherwise, you should look into JQuery
I'm using TempData to pass additional messages to show a notification accross requests:
public ActionResult Address()
TempData["NotificationType"] = "error";
TempData["NotificationMessage"] = "There was an error updating the address.";
return RedirectToAction("Index", "Home");
}
public ActionResult Index()
{
if (TempData["NotificationType"] != null && TempData["NotificationMessage"] != null)
{
model.NotificationMessage = TempData["NotificationMessage"].ToString();
model.NotificationType = TempData["NotificationType"].ToString();
}
return View();
}
Index View:
<div id="NotificationType" data-notification_type="#Model.NotificationType"/>
<div id="NotificationMessage" data-notification_message="#Model.NotificationMessage" />
<script type=text/javascript>
if($('#NotificationType').data('notification_type') == 'error'){
Notify('error', "Error!", $('#NotificationMessage').data('notification_message'));
}
</script>
I then display the error notification in the view and it works great.
My problem comes in after that if I click another link and then press the back button in the browser the notification displays again.
Is there a way to prevent the notification from redisplaying?
EDIT: Looks like its because its caching the index view as it doesn't hit a breakpoint in the action when I hit the back button.
Fixed this by preventing caching on the index view:
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
public ActionResult Index()
For .net core 3.0 or higher you can make use of this on your index view
[ResponseCache(Duration = 30, NoStore = true)]
public ActionResult Index()
{
//Your code here..
}
Happy coding.