Kendo Upload control remove event has no response - asp.net-mvc

I have a kendo upload control like this:
#(Html.Kendo().Upload()
.Name("attachments")
.Async(a => a
.Save("UploadAsync", "Intel")
.Remove("RemoveAsync", "Intel")
.AutoUpload(true)
)
.Events(e => e
.Success("onSuccessfulUpload")
.Remove("onRemoveFile")
)
.Validation(v => v.AllowedExtensions(exts))
)
In the controller, its Save method is like this:
public ActionResult UploadAsync(IEnumerable<HttpPostedFileBase> attachments)
{
string filename;
// ... do things ...
return Json(new { ImageName = filename }, "text/plain");
}
where the variable filename is assigned a value.
Its Remove method in the controller looks very similar:
public ActionResult RemoveAsync(string[] fileNames)
{
string filename;
// ... do things ...
return Json(new { ImageName = filename }, "text/plain");
}
I verified that both controller methods are called correctly and the variable filename is assigned to in both cases.
The upload works as expected, and the Success event also works as expected. (The alert is simply for testing.)
function onSuccessfulUpload(e) {
alert(e.response.ImageName);
}
The issue comes on removal of a file.
When I get to the Remove event, e does not have a .response. It has e.files and e.sender, but no response.
function onRemoveFile(e) {
alert(e.response); // undefined!
alert(JSON.stringify(e.files)); // works, but does not have what I need
}
How do I access what the RemoveAsync method returns?

It looks like the remove event doesn't provide this kind of data, so I see only a workaround to solve this.
You could try to put the result name to the headers, and you should be able to read the result:
// Controller
Response.AddHeader("ImageName", imageName); // before Json(...)
// View/JS
alert(e.headers['ImageName']);
I haven't tested that and I see a risk that that the remove event doesn't really read the async response, that would explain why the response object is not available.
In that case, you could try to use the following workaround: Don't call any Url on remove (or use some Action without any body, just a plain result) and inside of the event callback, execute RemoveAsync yourself.
// View/JS
function onRemoveFile(e) {
$.post('#Html.Url("RemoveAsync", "Intel")', e.files, function(response) {
alert(response);
});
}
It's not pretty, but it should work and provide the results you need.

After some time poking around, I found the answer.
The key lay in the order of the events. My first assumption was that the Success event was called after successful upload, and the Remove event was called after successful(?) removal. This was wrong.
The actual order of the events is:
JS onUpload > Controller UploadAsync > JS onSuccess
JS onRemoveFile > Controller RemoveAsync > JS onSuccess
My Solution:
I created two parallel arrays in javascript to represent the files uploaded in the client-side e.files, which contains uid's for each file, and the filenames created by the server-side controller method (which renames the files).
var fileUids = [];
var fileSaveNames = [];
I changed the onSuccessfulUpload function to this, when I discovered that there is an e.operation that specifies which operation was the successful one:
function onSuccess(e) {
if (e.operation == "upload") {
var filename = e.response.ImageName;
var uid = e.files[0].uid;
// add to the arrays
fileUids.push(uid);
fileSaveNames.push(filename)
// ...
}
else if (e.operation == "remove") {
var uid = e.files[0].uid;
var saveIdx = fileUids.indexOf(uid);
// remove from the arrays
fileSaveNames.splice(saveIdx, 1);
fileUids.splice(saveIdx, 1);
// ...
}
}
Then I updated the removeFile function, which I now knew was called before the method in the controller.
function removeFile(e) {
var uid = e.files[0].uid;
var idx = fileUids.indexOf(uid);
e.data = { fileToRemove: fileSaveNames[idx] };
}
That last line, where I assign to e.data, was because of this thread on the Telerik forums, which has the following info:
Solution: All that's needed it to define a function for the upload
event and modify the "data" payload.
Add the upload JS function to add a parameter "codeID" in my case.
$("#files").kendoUpload({
[...]
upload: function (e) {
e.data = { codeID: $("#id").val() };
}
});
Now on the controller add the parameter and that's it.
[HttpPost]
public ActionResult Save(IEnumerable<HttpPostedFileBase> files, Guid codeID) { }
(Instead of being in the Upload event, mine is in the Remove event.)
I chose the parameter name fileToRemove, and now the new RemoveAsync method in the controller is as such:
public ActionResult RemoveAsync(string[] fileNames, string fileToRemove)
{
string returnName = "";
if (!string.IsNullOrWhiteSpace(fileToRemove))
{
// ... do things ...
}
return Json(new { ImageName = returnName }, "text/plain");
}

Related

Return data from OData Action using OData Client

I am testing the OData Action using ODataActionsSample, which is downloaded from http://aspnet.codeplex.com/sourcecontrol/latest#Samples/WebApi/OData/v4/ODataActionsSample/ODataActionsSample/, as a server and calling the "CheckOut" action which is,
[HttpPost]
public IHttpActionResult CheckOut(int key)
{
var movie = _db.Movies.FirstOrDefault(m => m.ID == key);
if (movie == null)
{
return BadRequest(ModelState);
}
if (!TryCheckoutMovie(movie))
{
return BadRequest("The movie is already checked out.");
}
return Ok(movie);
}
The action returns the movie with updated "DueDate" proprety in the sample program which is calling the action from javascript as below:
// Invoke "checkout" or "return" action. Both actions take no parameter data.
function invokeAction(url) {
ajaxRequest("post", url)
.done(function (updated) {
updateMovie(updated);
})
.fail(function (jqXHR, textStatus, errorThrown) {
//parent.errorMessage(errorThrown);
parent.errorMessage(url);
});
}
self.update(data);
// Update the model with new data from the server.
function updateMovie(data) {
var dueDate = data.DueDate ? new Date(data.DueDate) : null;
self.dueDate(dueDate);
if (data["#ODataActionsSample.Models.CheckOut"]) {
self.checkoutUrl(data["#ODataActionsSample.Models.CheckOut"].target);
}
else {
self.checkoutUrl(null);
}
if (data["#ODataActionsSample.Models.Return"]) {
self.returnMovieUrl(data["#ODataActionsSample.Models.Return"].target);
}
else {
self.returnMovieUrl(null);
}
}
However, the call from OData Client returns the movie without the DueDate updated. The client code is as below:
string serviceUri = "http://localhost:44221/OData/";
var container = new Container(new Uri(serviceUri));
var movieQuery = from movie in container.Movies select movie;
DataServiceCollection<ODataActionsClient.Movie> trackedMovies = new DataServiceCollection<ODataActionsClient.Movie>(movieQuery, TrackingMode.AutoChangeTracking, "Movies",null,null);
var myMovie = trackedMovies[0];
try
{
var checkouttedMovie = myMovie.CheckOut().GetValue();
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException.ToString());
}
What is wrong with my code in the client side ?
The default value for merge option is AppendOnly : No current values are modified.
OverwriteChanges : All current values are overwritten with current store values, regardless of whether they have been changed.
PreserveChanges: Current values that have been changed are not modified, but any unchanged values are updated with the current store values. No changes are lost in this merge.
So you have to decide which option you want to achieve, in this case I think OverwriteChanges is good enough.
FYI : https://msdn.microsoft.com/en-us/library/system.data.objects.mergeoption(v=vs.110).aspx

How to call a controller method from JavaScript when project is run under the IIS?

I want to call the JavaScript function for counting the selected checkbox and in this function I have to call one statement for call the actionresult method of controller and perform to some function as active user and deactive user and return view() at end
Here is my code:
if (state == "Dec") {
alert("Hello..Dear..you are DeActive");
$.post('#Url.Action("UserDeactive","Admin", new{})' + '?Id=' + strvalue);
}
This statement works well in the normal project when I run project by press the F5, also using VS localhost.
But when I host my project in IIS, for accessing in LAN, this is not work hold java script call but this statement are not call and not navigate to action result method ..for do some function..!
so please me ...All My Dear..! if you have any idea .!
this is my Controller Method:-
public ActionResult UserActive(string Id)
{
int[] numbers = Id.Split(',').Select(n => int.Parse(n)).ToArray();
if (numbers != null)
{
foreach (int id in numbers)
{
User_Master u_master = db.User_Masters.Find(id);
if (u_master.Is_active == "false")
{
u_master.Is_active = "true";
db.Configuration.ValidateOnSaveEnabled = false;
db.SaveChanges();
}
}
}
return RedirectToAction("Dashboard", "Home");
}
Use the second argument of the $.post() method which allows you to POST additional parameters to the server:
var url = '#Url.Action("UserDeactive", "Admin")';
$.post(url, { id: strvalue }, function(result) {
// handle the result of the AJAX call
});

indexed_db getObject() - how to return result

I would like to know how to define the data type and how to return the object (record) using getObject(). Currently, the only way that I have been able to use the result (record) outside of the function that obtains it is to call another function with the result. That way, the data-type does not need to be specified. However if I want to return the value, I need to define the data-type and I can't find what it is. I tried "dynamic" but that didn't appear to work. For example ":
fDbSelectOneClient(String sKey, Function fSuccess, String sErmes) {
try {
idb.Transaction oDbTxn = ogDb1.transaction(sgTblClient, 'readwrite');
idb.ObjectStore oDbTable = oDbTxn.objectStore(sgTblClient);
idb.Request oDbReqGet = oDbTable.getObject(sKey);
oDbReqGet.onSuccess.listen((val){
if (oDbReqGet.result == null) {
window.alert("Record $sKey was not found - $sErmes");
} else {
///////return oDbReqGet.result; /// THIS IS WHAT i WANT TO DO
fSuccess(oDbReqGet.result); /// THIS IS WHAT i'm HAVING TO DO
}});
oDbReqGet.onError.first.then((e){window.alert(
"Error reading single Client. Key = $sKey. Error = ${e}");});
} catch (oError) {
window.alert("Error attempting to read record for Client $sKey.
Error = ${oError}");
}
}
fAfterAddOrUpdateClient(oDbRec) {
/// this is one of the functions used as "fSuccess above
As someone else once said (can't remember who), once you start using an async API, everything needs to be async.
A typical "Dart" pattern to do this would be to use a Future + Completer pair (although there's nothing inherently wrong with what you've done in your question above - it's more a question of style...).
Conceptually, the fDbSelectOneClient function creates a completer object, and the function returns the completer.future. Then, when the async call completes, you call completer.complete, passing the value in.
A user of the function would call fDbSelectOneClient(...).then((result) => print(result)); to make use of the result in an async way
Your code above could be refactored as follows:
import 'dart:async'; // required for Completer
Future fDbSelectOneClient(String sKey) {
var completer = new Completer();
try {
idb.Transaction oDbTxn = ogDb1.transaction(sgTblClient, 'readwrite');
idb.ObjectStore oDbTable = oDbTxn.objectStore(sgTblClient);
idb.Request oDbReqGet = oDbTable.getObject(sKey);
oDbReqGet.onSuccess.listen((val) => completer.complete(oDbReqGet.result));
oDbReqGet.onError.first.then((err) => completer.completeError(err));
}
catch (oError) {
completer.completeError(oError);
}
return completer.future; // return the future
}
// calling code elsewhere
foo() {
var key = "Mr Blue";
fDbSelectOneClient(key)
.then((result) {
// do something with result (note, may be null)
})
..catchError((err) { // note method chaining ..
// do something with error
};
}
This future/completer pair only works for one shot (ie, if the onSuccess.listen is called multiple times, then the second time you will get a "Future already completed" error. (I've made an assumption on the basis of the function name fDbSelectOneClient that you are only expecting to select a single record.
To return a value from a single future multiple times, you'll probably have to use the new streams feature of the Future - see here for more details: http://news.dartlang.org/2012/11/introducing-new-streams-api.html
Note also, that Futures and Completers support generics, so you can strongly type the return type as follows:
// strongly typed future
Future<SomeReturnType> fDbSelectOneClient(String sKey) {
var completer = new Completer<SomeReturnType>();
completer.complete(new SomeReturnType());
}
foo() {
// strongly typed result
fDbSelectOneClient("Mr Blue").then((SomeReturnType result) => print(result));
}

Show only first error message

I am using ASP.NET MVC3 for a form that has both server and client validations. I'm showing error messages as balloons above the inputs. Due to the presentation of the errors, I need to only show one error at a time, otherwise the balloons tend to obscure other fields that may also be in error.
How can I customize the validation behavior to only render the first error message?
Edit: Please notice that the form has both server and client validations, and that I only want to show the first error message for the entire form (not per field).
In case anyone needs it, the solution I came up with is to add the following script towards the bottom of the page. This hooks into the existing javascript validation to dynamically hide all but the first error in the form.
<script>
$(function() {
var form = $('form')[0];
var settings = $.data(form, 'validator').settings;
var errorPlacementFunction = settings.errorPlacement;
var successFunction = settings.success;
settings.errorPlacement = function(error, inputElement) {
errorPlacementFunction(error, inputElement);
showOneError();
}
settings.success = function (error) {
successFunction(error);
showOneError();
}
function showOneError() {
var $errors = $(form).find(".field-validation-error");
$errors.slice(1).hide();
$errors.filter(":first:not(:visible)").show();
}
});
</script>
Could give this a shot on your controller action
var goodErrors = ModelState.GroupBy(MS => MS.Key).Select(ms => ms.First()).ToDictionary(ms => ms.Key, ms => ms.Value);
ModelState.Clear();
foreach (var item in goodErrors)
{
ModelState.Add(item.Key, item.Value);
}
I'm just selecting only one of each property error, clearing all errors then adding the individual ones back.
this is completely untested but should work.
You could create a custom validation summary which would display only the first error. This could be done either by creating an extension for the HtmlHelper class, or by writing your own HtmlHelper. The former is the more straightforward.
public static class HtmlHelperExtensions
{
static string SingleMessageValidationSummary(this HtmlHelper helper, string validationMessage="")
{
string retVal = "";
if (helper.ViewData.ModelState.IsValid)
return "";
retVal += #"<div class=""notification-warnings""><span>";
if (!String.IsNullOrEmpty(validationMessage))
retVal += validationMessage;
retVal += "</span>";
retVal += #"<div class=""text"">";
foreach (var key in helper.ViewData.ModelState.Keys)
{
foreach(var err in helper.ViewData.ModelState[key].Errors)
retVal += "<p>" + err.ErrorMessage + "</p>";
break;
}
retVal += "</div></div>";
return retVal.ToString();
}
}
This is for the ValidationSummary, but the same can be done for ValidationMessageFor.
See: Custom ValidationSummary template Asp.net MVC 3
Edit: Client Side...
Update jquery.validate.unobstrusive.js. In particular the onError function, where it says error.removeClass("input-validation-error").appendTo(container);
Untested, but change that line to: error.removeClass("input-validation-error").eq(0).appendTo(container);
Create a html helper extension that renders only one message.
public static MvcHtmlString ValidationError(this HtmlHelper helper)
{
var result = new StringBuilder();
var tag = new TagBuilder("div");
tag.AddCssClass("validation-summary-errors");
var firstError = helper.ViewData.ModelState.SelectMany(k => k.Value.Errors).FirstOrDefault();
if (firstError != null)
{
tag.InnerHtml = firstError.ErrorMessage;
}
result.Append(tag.ToString());
return MvcHtmlString.Create(result.ToString());
}
Update the jquery.validate.unobtrusive.js OnErrors function as below,
function onErrors(form, validator) { // 'this' is the form element
// newly added condition
if ($(form.currentTarget).hasClass("one-error")) {
var container = $(this).find(".validation-summary-errors");
var firstError = validator.errorList[0];
$(container).html(firstError.message);
}
else {
var container = $(this).find("[data-valmsg-summary=true]"),
list = container.find("ul");
if (list && list.length && validator.errorList.length) {
list.empty();
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
$.each(validator.errorList, function () {
$("<li />").html(this.message).appendTo(list);
});
}
}
}
Basically we have added a condition in the OnError to check whether the form contains a css-class named one-error and if yes then displays a single error else display all.

is an async controller apropos here?

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.

Resources