I have a web app in which has the following features:
Extract data from an MS Excel file
Process the data and store it in data structures based on certain criteria.
Pass the data structures from the controller to the view where they can be rendered.
The issue I'm having occurs with the first few steps. On my view page, I have a button where the user can upload the excel file. Once they click submit, a POST request is sent to transmit the file to the controller action (I'm using the index action for this which I'm not sure is correct) where the data is extracted from the file. Once the file is processed, I want to display the extracted data back on the same page as the upload button.
I've tried to implement this first by creating a class in the controller which is instantiated for each excel row and then each row is stored in one of three different lists of objects.
I then stored each of these lists in the ViewBag object:
//Handle POST request and determine in the file uploaded was of correct type
List<Dictionary<string, string>> dictionary = new List<Dictionary<string, string>>();
bool isSuccess = true;
int colID = 0;
int colTier = 0;
if (Request != null)
{
HttpPostedFileBase file = Request.Files["UploadedFile"];
if ((file != null) && (file.ContentLength > 0) && !string.IsNullOrEmpty(file.FileName))
{
string fileName = "";
//string fileinitPath = "//app235wnd1t/equityfrontoffice/INGDS/TierBulkUpload/";
string fileinitPath = "C:/Users/chawtho/Desktop/";
Regex regex = new Regex("WTDA.+xlsx"); //find correct filename
if (match.Success)
{
fileName = (match.Value);
}
if (fileName != "")
{
Match match = regex.Match(file.FileName);
//Extract data from excel file and store in collections
ViewBag.inactive_subscriptions = inactiveSubscriptions;
ViewBag.active_subscriptions = activeSubscriptions;
}
return View();
}
In the view I have the following:
#{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title></title>
</head>
<body>
random text
#using (Html.BeginForm("Index", "Subscription", FormMethod.Post, new { enctype = "multipart/form-data" })) {
<fieldset class="form">
<legend>Upload Document</legend>
<input type="file" name="UploadedFile" id="FileUpload" required />
<input type="submit" name="Submit" value="Upload" />
<label id="saveStatus" style="color: red">
</label>
</fieldset>
}
#{
<li>#ViewBag.inactive_subscriptions[0].ID</li>
}
Here, I'm simply trying to read the ID field of the first object in the list of Subscriptions but I get the error:
Cannot perform runtime binding on a null reference
I'm not sure where this error is coming from because when I debug the controller code, the Viewbag is populated with the two lists before the View() is returned. I also tried moving the Subscription Class from the controller to a model class and created a container to hold the list of subscriptions but that didn't resolve the issue.
I think the problem might have something to do with the fact that the code the print the viewbag data is present when the page initially loads but I'm not sure if/how it should be kept from running until the file is processed.
How should I go about structuring this mvc setup to implement what I have outlined?
I put the next example, this is the way that I use to manipulate the excel files, notice that I don't use viewbag variables, this could be an option, like I've said I return the processed data into a json object and then I manipullate it via javascript.
-- Razor ViewPage
<!--Upload File-->
#using (Html.BeginForm("ProcessExcelFile", "ControllerName", FormMethod.Post,
new { id = "formUploadExcel", enctype = "multipart/form-data" }))
{
<div class="row">
<div class="col-md-4">
<label for="file">Excel File (.xls, .xlsx)</label>
<input type="file" name="file" id="file" required="required">
</div>
</div>
<br>
<button type="submit" class="btn btn-primary">Upload File</button>
}
-- JS Script
<script type="text/javascript">
$('form#formUploadExcel').unbind('submit').bind('submit', function () {
formdata = new FormData($('form#formUploadExcel').get(0));
$.ajax({
url: this.action,
type: this.method,
cache: false,
processData: false,
contentType: false,
data: formdata,
success: function (data, status) {
console.log(data);
},
complete: function () {
//code...
}
});
return false;
});
</script>
-- Controller
[HttpPost]
public JsonResult ProcessExcelFile(HttpPostedFileBase file)
{
// Process the excel...
var business = new BusinessLayer();
var data = business.ValidateExcel(file);
// Return the Json with the procced excel data.
return Json(data, JsonRequestBehavior.AllowGet);
}
Related
Is it possible to upload a file using ASP.NET MVC4 with Razor without using forms (either BeginForm or <form>) in the view.
My problem is I have a partial view on the main view to show infomation (a log), if I use forms I can get the information about the file being uploaded either via the HttpPostFileBase or Request.Files, however my partial view refresh, refreshes the entire page and I end up only seeing the partial view. If I don't use forms the partial view updates correctly, but I'm missing all information about the file.
I've tried preventDefault() in the ajax (which updates the partial view). But I can't seem to get it to work.
Here is my code:
Controller:
[HttpPost]
public PartialViewResult FileUpload(MyViewModel vm)
{
vm.Log = new ScriptLog();
if (Request.Files.Count < 1)
{
vm.Log.Add("File information missing");
}
else if (Request.Files[0].ContentLength < 1)
{
vm.Log.Add("File empty");
}
else
{
// Upload file and fill log
vm.Log.Add("File uploaded successfully.");
}
return PartialView("Log", vm.Log);
}
View:
<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
#model ViewModels.MyViewModel
<input type="file" name="file" accept="application/vnd.ms-excel" />
<input id="uploadButton" type="submit" value="Upload" />
#*
Without the form (BeginForm or <form>) the partial view correctly updates in place.
But it is missing any file information.
With it I can get the file information but I can't update the partial view in place.
*#
<div id="log">
#{ if (Model != null)
{
Html.RenderPartial("Log", Model.Log);
}
}
</div>
<script>
$("input[id=uploadButton]").on("click", function (e) {
//e.preventDefault(); // preventing the default action
//alert("Here")
$.post("/MyContoller/FileUpload")
.done(function (partialResult) {
$("#log").html(partialResult);
})
});
</script>
Ok, so here is the solution:
$("input[id=uploadButton]").on("click", function (e) {
var fd = new FormData();
var input = document.querySelector("input");
//fd.append({name of you variable in ViewModel}, value)
fd.append('file', input.files[0]);
$.ajax({
url: '/MyContoller/FileUpload',
data: fd,
processData: false,
contentType: false,
type: 'POST',
success: function(data){
alert(data);
}
});
});
Here are some references:
MDN | Using FormData
jQuery | $.ajax()
I am trying to create a sample MVC4 webpage with partialViews
on my parent page ,eg., Index.cshtml page I am displaying a partialView page which will allow the user to view/update profile photo
When the index page loads ,I need this partial page to show up the photo if photo is available
once the page is loaded ,when the user uploads a new photo,I need only the partialView page to do an ajax postback and show up the new photo .
I am able to load the page with photo fetched from DB,
I am able to Save new photo to db by clicking "#btnPhotoUpload" button.
But after saving the photo ,the partialview is not getting refreshed automatically.Please help me how to get my partialview page to refesh and display the updated photo.
Here is my index page ie., "Index.cshtml"
#model MvcSamples.Models.ViewModels.UserInfoViewModel
#{
ViewBag.Title = "Ajax Partial Postback demo";
ViewBag.UserId = 1;
}
<h2>PersonalInfo example</h2>
<div id="photoForm">
#Html.Partial("_UserPhoto")
</div>
<div id="OtherDetails">
#Html.Partial("_UserDetails")
</div>
Here is my PartialView, i.e. _UserPhoto.cshtml
#model MvcSamples.Models.ViewModels.UserInfoViewModel
#using (Ajax.BeginForm("SaveProfilePhoto", "Example", new { id = "1" }, new AjaxOptions { UpdateTargetId = "photoForm", OnSuccess = "onSuccess" }, new { encType = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<a>
<img id="imgPhoto" width="100px" height="100px"/>
<label for="photo">Photo:</label>
<input type="file" name="photo" id="photo" />
<input id="btnPhotoUpload" type="button" value="Apply" />
</a>
<script type="text/javascript">
$(document).ready(function () {
$("#imgPhoto").attr('src', "#Url.Action("GetProfileImage", "Example", new { id = ViewBag.UserId })");
$("#btnPhotoUpload").click(function (event) {
//on-click code goes in here.
event.preventDefault();
SavePhotoToDb();
});
function SavePhotoToDb() {
var json;
var data;
$.ajax({
type: "POST",
url: "/Example/SaveProfilePhoto",
data: new FormData($("#form0").get(0)),
dataType: "html",
contentType: false,
processData: false,
success: saveItemCompleted(data),
error: saveItemFailed
});
}
function saveItemCompleted(data) {
$("#photoForm").html(data);
}
function saveItemFailed(request, status, error) {
}
});
</script>
}
Here is my controller ExampleController:
namespace MvcSamples.Controllers
{
public class ExampleController : Controller
{
IUserDetails usr = new UserDetails();
// GET: /Example/
[HttpGet]
public ActionResult Index()
{
//usr.GetProfilePhoto(WebSecurity.GetUserId(User.Identity.Name));
if (!string.IsNullOrWhiteSpace(User.Identity.Name))
{
ViewBag.UserId = WebSecurity.GetUserId(User.Identity.Name);
}
UserInfoViewModel model = new UserInfoViewModel();
model.GenderList = usr.FillGenderTypesDropDownList();
return View(model);
}
[HttpPost]
public ActionResult SaveProfilePhoto(HttpPostedFileBase photo, UserInfoViewModel model)
{
string path = #"C:\Temp\";
if (photo != null)
{
model.UserId = 1;//WebSecurity.GetUserId(User.Identity.Name);
ViewBag.UserId = model.UserId;
var binary = new byte[photo.ContentLength];
photo.InputStream.Read(binary, 0, photo.ContentLength);
UserPicModel upModel = new UserPicModel();
upModel.UserPhoto = binary;
upModel.UserId = model.UserId;
usr.InsertProfilePhoto(upModel);
}
return PartialView("_UserPhoto", model);
}
public FileResult GetProfileImage(int id)
{
byte[] barrImg = usr.GetProfilePhoto(id);
return File(barrImg, "image/png");
}
}
}
Update:
As #David Tansey suggested ,I added code to refresh image inside SaveCompleted(data).
function RefreshImage() {
$("#imgPhoto").attr('src', function () {
// the datetime portion appended to the url avoids caching issues
// and ensures that a fresh image will be loaded every time
var d = new Date();
return this.src + '?' + d.getTime();
});
}
But the above code is refreshing the image only after I click the upload button twice .
Actually I need this to refresh the image immediately after the $("#btnPhotoUpload").click. Any suggestions?
I also tried disabling cache at the controller but no luck:
[OutputCacheAttribute(VaryByParam = "*", Duration = 0, NoStore = true)]
I am pretty sure the problem is that the browser is caching the image file and does not 'perceive' the need to bring it across the wire again after you upload a new one.
Look at the following post for a description of how to attach a dummy (yet dynamic) query string value to prevent the caching from occuring. I think this approach will solve your problem.
asp.net mvc jquery filling image
Hope that helps.
I have a page with a video at the top and a list of videos you can choose from. Currently, clicking a link in the video list will reload the entire page. I need it to only refresh the partial view I have containing the video at the top of the page.
I saw several posts here on SO showing how to reload partial views with JQuery, but couldn't get it to work correctly in my situation. I'm unsure how to pass the correct id of the video along.
Controller:
public ActionResult Videos(int topVideo = 0)
{
VideosModel model = new VideosModel();
model.Videos = StatsVideoService.GetEntityList(new Lookup(TableStatsVideo.IsDeleted, false)).OrderByDescending(x => x.DateCreated).ToList();
if (topVideo == 0)
model.TopVideo = model.Videos.First();
else
{
model.TopVideo = model.Videos.Where(x => x.StatsVideoId == topVideo).FirstOrDefault();
if (model.TopVideo == null)
model.TopVideo = model.Videos.First();
}
return View(model);
}
View:
#model Project.Models.VideosModel
<section class="videos">
<div id="top_video">
#{Html.RenderPartial("StatsVideo", Model.TopVideo);}
</div>
<ul>
#foreach (var item in Model.Videos)
{
<li>
<div class="videoList">
<a href ="#Url.Action("Videos", "Home", new { topVideo = item.StatsVideoId })">
<img src="#Url.Content("~/Content/img/video-ph.png")" />
</a>
<p class="videoTitle">#item.Title</p>
</div>
</li>
}
</ul>
</section>
If there's any more information needed, please let me know.
After several hours of bashing my head against the wall, I got it to work! Just as a reference to anyone else in the future who's viewing this article, here's how I got it to work:
I set the onclick of the link to point to a javascript method, passing in the id of the video as a parameter:
#foreach (var item in Model.Videos)
{
<li>
<div class="videoList">
<a href ="#" onclick="updateTopVideo(#item.StatsVideoId)">
<img src="#Url.Content("~/Content/img/video-ph.png")" />
</a>
<p class="videoTitle">#item.Title</p>
</div>
</li>
}
And then I included this script in the view at the bottom:
<script>
var updateTopVideo = function (itemId) {
var url = '#Url.Content("~/Home/StatsVideo/")';
url = url + itemId;
$.get(url, "", callBack, "html");
};
var callBack = function (response) {
$('#top_video').html(response);
};
</script>
Finally, I added a method to my controller that would return the partial view needed for the video at the top of the screen:
public ActionResult StatsVideo(int Id)
{
IStatsVideo vid = StatsVideoService.GetEntity(new Lookup(TableStatsVideo.StatsVideoId, Id));
if (vid == null)
vid = StatsVideoService.GetEntityList(new Lookup(TableStatsVideo.IsDeleted, false)).OrderByDescending(x => x.DateCreated).FirstOrDefault();
return PartialView(vid);
}
This code should be fairly easy to understand. Basically, the onclick calls the first javascript method, which then calls the controller. The controller builds the partial view and returns it. The first javascript method passes it to the second javascript method which sets the html of the div "top_video" to be the returned partial view.
If anything doesn't make sense, or anyone's having trouble with this in the future, let me know and I'll do my best to offer some help.
I think there may be several confusing and inconsistent elements here.
First, you are returning a full view instead of a partial view. This reloads all containing elements, not just the part that is relevant to your partial view.
Second, you are using Url.Action, which only generates the url. I would recommend using Ajax.ActionLink, which allows you to do fully ajax calls, refreshing the content of your partial div and updating a target div element.
instead of:
<div class="videoList">
<a href ="#Url.Action("Videos", "Home", new { topVideo = item.StatsVideoId })">
<img src="#Url.Content("~/Content/img/video-ph.png")" />
</a>
<p class="videoTitle">#item.Title</p>
</div>
try the more modern solution
<div class="videoList">
#Ajax.ActionLink(
"Videos",
"Home",
"new { topVideo = item.StatsVideoId },
new AjaxOptions {
HttpMethod = "GET",
OnSuccess = "handleSuccess"
}
)
</div>
This way you can be very specific on what you want each link to do, and you can pass along multiple parameters as well as define a callback function. You can also use "UpdateTargetId" in your ajax options to load your newly refreshed partial view into a DOM element.
You can remove the around the image and just store the url generated by the Url.Action in a data-href attribute.
Then you can use the jquery load method to load the data:
$(".videolist>img").click(function () {
$("#content").load($(this).data("href"));
});
I created a fiddle that loads content dynamically here, so you can play with it if you want: http://jsfiddle.net/bTsLV/1/
I'm trying to use json for my web page's globalization options.. id like to change the labels of my form just by using a little dropdownbox and without refreshing the whole page and more interesting part is i got more than two form in my view.
so far i have done this:
My Json:
public JsonResult Globalx(String incoming)
{
System.Globalization.CultureInfo Cult = new System.Globalization.CultureInfo(incoming, true);
System.Threading.Thread.CurrentThread.CurrentCulture = Cult;
System.Threading.Thread.CurrentThread.CurrentUICulture = Cult;
Resources.Global.Culture = System.Threading.Thread.CurrentThread.CurrentCulture;
Global.ResourceManager.GetResourceSet(Cult, false, true);
ViewData["Name"] = Global.Name;
ViewData["Surname"] = Global.Surname;
ViewData["Birth"] = Global.Birth;
String lnginfo = Resources.Global.Culture.TwoLetterISOLanguageName.ToString();
ViewData["Languages"] = new SelectList(myList, "Value", "Text", lnginfo);
return Json(ViewData, JsonRequestBehavior.AllowGet);
}
My View:
#model MyCustomers.Models.Customers
#{
ViewBag.Title = ViewData["NewCustomer"];
Layout = "~/Views/Shared/_Layout.cshtml";
}
<script type="text/javascript" language="javascript">
$(document).ready(function () {
function changeLang() {
var lang = $("#LanguageBox").val();
$.getJSON('#Url.Content("~/Home/People/")', { incoming: lang }, function (data) {
// what should i do here to get my label's language changed?
})
}
}
</script>
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data", id = "LanguageForm" }))
{
<fieldset>
<legend>#ViewData["LanguagesTitle"]</legend>
#Html.DropDownListFor(x => x.SelectedLanguage, (SelectList)ViewData["Languages"], new { onchange = "changeLang()", id = "LanguageBox" })
</fieldset>
}
#using (Html.BeginForm("PeopleForm", "Home", FormMethod.Post, new { enctype = "multipart/form-data", id = "PeopleForm" }))
{
<fieldset>
<legend>#ViewData["SalesContract"]</legend>
<div>
<div class="Name">
#Html.Label(ViewData["Name"].ToString()) <!--> HERE </!-->
#Html.EditorFor(x => x.People.Name)
</div>
<div class="Surname">
#Html.Label(ViewData["Surname"].ToString()) <!--> HERE </!-->
#Html.EditorFor(x => x.People.Surname)
</div>
<div class="Birth">
#Html.Label(ViewData["Birth"].ToString()) <!--> AND HERE </!-->
#Html.EditorFor(x => x.People.Birth)
</div>
</div>
</fieldset>
}
No im not actually using this method im refreshing the whole page each time to change the language of my labels but some friend of mine told me it could be done without refreshing and the first thing that came in my mind was Json.. I dont know if its possible or not im just trying. Any other ideas are wellcome.
I think the title is a little confusing and im asuming my problem here is understood so if anyone can find a better title please attempt to fix it.
In your Json result you would need to identify each of the labels that you have provided the text for, say each label has a Json object:
Id: 'label1',
Text: 'Enter your first name'
You provide one of these objects for each label on your page in an array,
{Id: 'label1', Text: 'Enter your first name'},
{Id: 'label2', Text: 'Enter your second name'},
{Id: 'label3', Text: 'Enter your telephone number'},
Then you deal with each of these on the requesting end,
$.getJSON('#Url.Content("~/Home/People/")', { incoming: lang }, function (data) {
for(i = 0; i < data.length; i++){
$('#'+data[i].Id).Html(data[i].Text);
}
})
I'm not 100% sure that Html will be the best thing to use - there may be sub DOM elements created by MVC that would need to be taken into account in your selector.
If you wanted to stick to the method you're using now you'll need to hard code each of the assigned values one at a time.
$.getJSON('#Url.Content("~/Home/People/")', { incoming: lang }, function (data) {
$('#Name').Html(data['Name']);
$('#Birth').Html(data['Birth']);
})
I have an MVC3 application that uploads a file from the users' hard drive and manipulates it. One requirement is that the file extension should be .xls(or xlsx).
I would like to validate the file name but I would like to know what is the best option in terms of reusability and performance (and of course best coding practices).
I have the following Index view:
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<br />
<p><input type="file" id="file" name="file" size="23"/></p><br />
<p><input type="submit" value="Upload file" /></p>
}
Called by the controller action method Index:
public ActionResult Index()
{
return View("Index");
}
And the user response is handled by the Index action method (HttpPost):
[HttpPost]
public ActionResult Index(HttpPostedFileBase file)
{
FileServices lfileServices = new FileServices();
var lfilePath = lfileServices.UploadFile(file, "~/App_Data/uploads");
//Manipulation;
}
Now I can use the Path.GetExtension(filename) in the FileServices to get the extension and then perform the check but it will be performed just after the upload completes.
I would like to do it once the user attempts to upload the file. The only thing that came up to my mind is create a Model(or better a ViewModel) and use the remote validation from DataAnnotations.
I saw a demonstration from Scott Hanselman live but it seemed like he was not really confortable with that because the application was compiling but was not performing the check.
Has anybody a good approach in order to perform such kind of remote validation or any other solution (jQuery for instance)?
Thanks
Francesco
You could do this using javascript:
$(function () {
$('form').submit(function () {
var selectedFile = $('#file').val();
var matches = selectedFile.match(/\.(xlsx?)$/i);
if (matches == null) {
alert('please select an Excel file');
return false;
}
return true;
});
});
Of course this doesn't in any case free you from the obligation of performing the same check on the server because if the client has no javascript enabled that will be the only way. And even this wouldn't be 100% reliable as there is nothing preventing users from renaming any garbage file to .xls and upload it. Heuristics could be used on the server to try to guess the actual file type by looking at some known byte sequences.
UPDATE:
Example with remote AJAX validation (due to demand in the comments, I don't recommend it though). You could use the excellent jquery.validate plugin which by the way comes bundled with ASP.NET MVC 3:
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<input type="file" id="file" name="file" size="23" data-remote-val-url="#Url.Action("CheckExtension")"/>
<br />
<input type="submit" value="Upload file" />
}
<script type="text/javascript">
$('form').validate({
rules: {
file: {
remote: $('#file').data('remote-val-url')
}
},
messages: {
file: {
remote: 'Please select an Excel file'
}
}
});
</script>
and on the server:
public ActionResult CheckExtension(string file)
{
var extension = Path.GetExtension(file ?? string.Empty);
var validExtensions = new[] { ".xls", ".xlsx" };
var isValid = validExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
return Json(isValid, JsonRequestBehavior.AllowGet);
}