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()
Related
I read a lot of questions here on stacoverflow, but it is still not clear to me how should I use a partialview within a main view with an action method. What's probably wrong is my aproach in general. With what I have so far I am not sure how to continue with my code.
I will start with the main view :
#{
ViewBag.Title = "getRolesByYear";
}
</script>
<script type="text/javascript">
getRolesForYear(parseInt(#DateTime.Now.Year));
$(function () {
$('#years a').click(function () {
var year = $(this).text();
console.log(year);
getRolesForYear(parseInt(year));
});
})
//console.log(year);
function getRolesForYear(year) {
console.log(year);
$.ajax({
type: "POST",
url: '#Url.Action("getRolesByYear", "WorkRoles")',
dataType: "json",
data: {
year: year
},
success: successFunc,
error: errorFunc
});
function successFunc(data, status) {
console.log('x');
}
}
function errorFunc() {
alert('error');
}
}
</script>
<div id = "years" class="btn-group btn-group-justified timeline">
#DateTime.Now.Year
#DateTime.Now.AddYears(-1).Year
#DateTime.Now.AddYears(-2).Year
</div>
<div id"partial"></div>
In this view I have three buttons with different year for each button. On page load or on button click I make an ajax call to an action method with the an int year as a parameter.
This is a simplified version of my action method :
public ActionResult getRolesByYear(int year)
{
// a couple of queries here
var list = list of RoleViewModel objects;
return PartialView(list);
And here is the partialView :
#model IEnumerable<eksp.Models.RoleViewModel>
#foreach (var item in Model)
{
<div class="jumbotron">
<h2>item.Role.RoleName</h2>
<h1> item.Role.RoleDescription</h1>
<p class="lead">Focus start : item.Role.FocusStart</p>
<p>Focus end : item.Role.FocusStart </p>
</div>
}
Obviously, a lot of thins aren't clear to me. How can I use this partial view with the action method i have and the main view? Do I need a separate method for the partial view? Any tips?
Your ajax call will invoke the action method which returns the partial view result (markup generated by executing the partial view). I guess you simply need to use the response of the ajax call to update your DOM.
If you want to update the content of the div with id partial with the response, you can do that in the success event handler of your ajax call.
success : function(response)
{
$("#partial").html(response);
}
I would also recommend to call the getRolesForYear method on document ready event.
$(function () {
getRolesForYear(parseInt(#DateTime.Now.Year));
$('#years a').click(function () {
var year = $(this).text();
console.log(year);
getRolesForYear(parseInt(year));
});
})
Also, If your main view is also the result of action method getRolesByYear, you probably want to return the partial view result only on the ajax call, the other calls,you can return the partial view
public ActionResult getRolesByYear(int year)
{
var list = list of RoleViewModel objects;
if(Request.IsAjaxRequest())
{
return PartialView(list);
}
else
{
return View(list);
}
}
Here the same action method handles the request for main view and ajax call. It returns the same view for ajax call, but using PartialView call, so layout will be ignored. But If you have a specific view you want to return, you can do that as well.
if(Request.IsAjaxRequest())
{
return PartialView("_yearlyRoles",list);
}
One of the reasons I like using partial views for rendering data via Ajax calls. For example if I want to start searching in order to avoid the server call i Just use an ajax call to the controller which returns the search results through a partial view. In your example yoy need to load the results in partial div.
function successFunc(data, status) {
$("#partial").html(data);
}
View:
#if (Model.IsEventActive)
{
<div id="GameEvent">
//SomeCode
</div>
}
else
{
<div id="GameNonEvent">
//SomeCode
</div>
}
JS file:
$('#btnNextEvent').click(function () {
$.ajax({
type: "POST",
url: "Game/UpdateUserEventInfo"
//success: function () {
// $("#GameEvent").hide();
//}
});
});
Controller:
[HttpPost]
public ActionResult UpdateUserEventInfo()
{
var user = _user;
_instance.Users.UpdateUserEventInfo(user);
return RedirectToAction("Index");
}
public ActionResult Index()
{
if (!_instance.Users.CheckUserSkillsExist(WebSecurity.CurrentUserName))
{
return RedirectToAction("CreateChar");
}
_instance.GameBase.GetBaseData();
var userModel = GetPlayerDisplayStats();
return View(userModel);
}
If my beginning IsEventActive = true;
The FullEvent View at a certain moment calls my JS method which triggers the ajax call to UpdateUserEventInfo.
So, basically what is supposed to happen is when the Controller method UpdateUserEventInfo is fired, it updates the DB and then calls my index view again. Index view rebuilds the model and launches the view.
The view checks for IsEventActive and builds the divs based on that.
The page shows the GameEvent Div in the beginning because IsEventActive is true, but when the index view rebuilds again via the ajax call. The if-else loops follows correctly and goes to the GameNonEvent div and creates it. But I do not see that on the page. The page still shows GamEvent Div. Even though the view didn't go into the if statement.
If i refresh the page, then it shows correctly.
Wrap the part of the view that you showed in another container:
#if (Model.IsEventActive)
{
<div id="container">
<div id="GameEvent">
<div class="col-lg-10 col-lg-offset-1">
<div class="row">
#{ Html.RenderAction("FullEvent", "Game", new { ActiveEventGrpId = Model.ActiveEventGrpId }); }
</div>
</div>
</div>
</div>
}
else
{
...
}
Then, in success handler of ajax call do:
success: function (data) {
$("#container").html(data);
}
if i get your code right it looks like you are not doing anything with result of ajax call to "Game/UpdateUserEventInfo",
so nothing will happen. Where you have "success" commented out you should have code that updates view client side. Which could be a lot of code, it is possible that ajax call may not be even needed there, href to action will do just fine from functional point. The simple way
to fix this (since you say page refresh works) would be to put a page reload on ajax success :
$.ajax({
type: "POST",
url: "Game/UpdateUserEventInfo"
success: function () {
window.location.reload();
}
});
but this will also successfully cancel the whole point of using ajax, you may need review your approach for this.
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 form inside a partial view, it works but in case a server side validation error is raised it displays only the partial view. So I decided to use ajax for the submission (actually it makes sense as it is inserting a contact and there's a list in the main view).
Thing is, with the posted code if one of these errors comes it's properly displayed in the view (I'd need to make the partial visible again, but that's another thing), but if there's no error it'll display the list only in the partial view. I can do the other way round, displaying properly when there's no error but then not achieving the proper displaying of validation errors.
I would like to understand what's the best approach, or at least which are the possibilities: maybe change the code in the controller or do some kind of check in the success callback...
I edit what I had before, because when no error I should return the list, not the whole view as I posted before, but anyway I still have the doubt on how to tell one from another as both are succesfull calls to the post action
Thanks
The view is this one
#model ContactListViewModel
#{
ViewBag.Title = " My Contacts"
}
<div id="ContactList">
<h2>My Contacts</h2>
<hr />
<div id="addContainer">
#{ Html.RenderAction("AddContact"); }
</div>
<div id="editContainer" data-amp-url="#Url.Action("Edit", "Contacts")" class="initiallyHidden"></div>
#foreach (var group in Model.Contacts)
{
<div class="PlanContacts">
<div class="PlanName">#group.Key</div>
#foreach (var contact in group.Values)
{
<div class="Preview">
#Html.DisplayFor(m => contact, "Contact")
</div>
}
</div>
}
</div>
#section PageJavascript
{
<script src="~/Scripts/AMPContacts.js"></script>
}
The controller post action
[HttpPost]
public ActionResult AddContact(AddContactViewModel viewModel)
{
var partyId = (int) Session["PartyId"];
if (ModelState.IsValid)
{
_contactsManager.AddContact(viewModel, partyId);
// Here I should return the updated list
}
var newViewModel = _createBuilder.Rebuild(viewModel, partyId);
return PartialView("_AddContact", newViewModel);
}
And the ajax submission code inside the longer AMPContact.js
$('#addForm').submit(function (e) {
e.preventDefault();
var addContainer = $(document.getElementById('addContainer'));
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function(result) {
addContainer.html(result);
}
});
});
I understand this answer it's far from being good but maybe it's useful for somebody in terms of narrowing a possible search. As it would ended up being huge I rather post this update as an answer to my own question, not sure if it complies with good manners.
The explained one wasn't the only problem I faced, hard times as well with the reset of the form and specially the successive error submissions (once I got an error trying to submit it with errors again), so I ended up making a mess out of different solutions for different problems. Hopefully I will be able to clean it up
Thanks
In the view I use now
<div id="myContacts">
<h2>My Contacts</h2>
<hr />
<div id="addContainer">
<div class="toggler">
Add Contact
</div>
<div id="addToggling" class="initiallyHidden">
#{ Html.RenderAction("AddContact"); }
</div>
</div>
<div id="editContainer" data-amp-url="#Url.Action("Edit", "Contacts")" class="initiallyHidden"></div>
<div id="list">
#{ Html.RenderPartial("_ContactList", Model); }
</div>
In the .js
$('#addContainer').on('submit', '#addForm', ajaxCall);
function ajaxCall(e) {
e.preventDefault();
var addToggling = $(document.getElementById('addToggling'));
var contactList = $(document.getElementById('contactList'));
$.ajax({
url: this.action,
type: this.method,
data: $(this).serialize(),
success: function (result) {
if (result.passedValidation == true) {
// Json is returned with flag, we get the list from the server and update the list element
$.get(result.action, function (partial) {
contactList.html(partial);
});
// Add some effects and clear the form
$(document).scrollTop(0);
setTimeout(function () {
addToggling.slideUp(300, resetAddForm);
}, 500);
setTimeout(function () {
contactList.effect("highlight", {}, 3000);
}, 1000);
}
else {
// The form partial view is returned and displayed in the same element when there are validation errors
$(document).scrollTop(0);
addToggling.html(result);
$.validator.unobtrusive.parse('#addForm');
}
}
});
}
function resetAddForm() {
var addForm = $(document.getElementById('addForm'));
// Hhide the error messages
addForm.find("span.field-validation-error").hide();
addForm.find("div.validation-summary-errors").hide();
// Removes the class associated to errors
addForm[0].reset();
// Clear the inputs
addForm.find('input:text, input:password, input:file, select, textarea').val('');
addForm.find('input:radio, input:checkbox').removeAttr('checked').removeAttr('selected');
}
Controller with the existing action method slightly changed and a new one
public ActionResult ContactList()
{
var partyId = (int)Session["PartyId"];
var viewModel = _displayBuilder.Build(partyId);
return PartialView("_ContactList", viewModel);
}
[HttpGet]
public ActionResult AddContact()
{
var partyId = (int) Session["PartyId"];
var viewModel = _createBuilder.Build(partyId);
return PartialView("_AddContact", viewModel);
}
[HttpPost]
public ActionResult AddContact(AddContactViewModel viewModel)
{
var partyId = (int) Session["PartyId"];
if (ModelState.IsValid)
{
_contactsManager.AddContact(viewModel, partyId);
if (Request.IsAjaxRequest())
return Json(new { passedValidation = true, action = Url.Action("ContactList")});
return RedirectToAction("Index");
}
var newViewModel = _createBuilder.Rebuild(viewModel, partyId);
return PartialView("_AddContact", newViewModel);
}
I want to render the partial view using ajax.
When I submit the button, the partial view must be rendered.
I could implemented it using ActionLink but I want to call the Action by JavaScript.
But the following code doesn't work. The Action is called but the partial view does not be rendered.
View
#section script{
<script type="text/javascript">
function test() {
$.ajax(
{
type: "POST",
url: "Test",
data: "",
success: function (result) { alert("OK!!"); },
error: function (req, status, error) {
alert("Damn!!");}
});
}
</script>
}
<input type="submit" onclick="test()" />
<div id="Container">
Controller
public ActionResult Test()
{
if (Request.IsAjaxRequest())
{
return PartialView("ViewUserControl1");
}
return View();
}
Partial View ViewUserControl1
Hello world
This works but is not what I want to do
#Ajax.ActionLink("click me", "Test", new AjaxOptions { UpdateTargetId = "Container" })
Your url may not be correct. It dependent on your routing settings
defined in Global.asax . But usually your url should look like
"/Home/Test" if your action is in HomeController. It's better to use
url helper for getting actions' urls:
...
url: '#Url.Action("Test", "Home")'
...
You can render your partial in next way:
...
success: function (result) { $('#elementId').html(result) },
...
Also if you want to update block, don't forget to clear it first and if it contains form with unobtrusive validation - parse form.