Making TPL Async calls from mvc controller on click of submit button - asp.net

Basically I want to implement simple search functionality, whenever user enters some keyword in the text box on view and clicks submit button I want to make ASYNC calls to predefined website urls using TPL Async mechanism. When I do the same with console application it works like a charm but not with ASP.NET MVC3.
I couldn't find the reason
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult About()
{
return View();
}
[HttpPost]
public ActionResult Index(string text)
{
string[] url = { "http://www.msnbc.com", "http://www.yahoo.com",
"http://www.nytimes.com", "http://www.washingtonpost.com",
"http://www.latimes.com", "http://www.newsday.com" };
Task<string[]> webTask = this.GetWordCounts(url, text);
string[] results = null;
try
{
results = webTask.Result;
}
catch (AggregateException e)
{
}
return View("Index", results);
}
//Taken from MSDN
Task<string[]> GetWordCounts(string[] urls, string name)
{
TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>();
WebClient[] webClients = new WebClient[urls.Length];
object m_lock = new object();
int count = 0;
List<string> results = new List<string>();
for (int i = 0; i < urls.Length; i++)
{
webClients[i] = new WebClient();
#region callback
// Specify the callback for the DownloadStringCompleted
// event that will be raised by this WebClient instance.
webClients[i].DownloadStringCompleted += (obj, args) =>
{
if (args.Cancelled == true)
{
tcs.TrySetCanceled();
return;
}
else if (args.Error != null)
{
// Pass through to the underlying Task
// any exceptions thrown by the WebClient
// during the asynchronous operation.
tcs.TrySetException(args.Error);
return;
}
else
{
// Split the string into an array of words,
// then count the number of elements that match
// the search term.
string[] words = null;
words = args.Result.Split(' ');
string NAME = name.ToUpper();
int nameCount = (from word in words.AsParallel()
where word.ToUpper().Contains(NAME)
select word)
.Count();
// Associate the results with the url, and add new string to the array that
// the underlying Task object will return in its Result property.
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount, name));
}
// If this is the last async operation to complete,
// then set the Result property on the underlying Task.
lock (m_lock)
{
count++;
if (count == urls.Length)
{
tcs.TrySetResult(results.ToArray());
}
}
};
#endregion
// Call DownloadStringAsync for each URL.
Uri address = null;
try
{
address = new Uri(urls[i]);
// Pass the address, and also use it for the userToken
// to identify the page when the delegate is invoked.
webClients[i].DownloadStringAsync(address, address);
}
catch (UriFormatException ex)
{
// Abandon the entire operation if one url is malformed.
// Other actions are possible here.
tcs.TrySetException(ex);
return tcs.Task;
}
}
// Return the underlying Task. The client code
// waits on the Result property, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}
this is my view - for now I have hard coded keyword as microsoft
#using (Html.BeginForm("Index", "Home", new { text = "Microsoft" }))
{
<input type="submit" />
}
Update: It stays forever and inside the try block of Index Post method

I would recommend you using an AsyncController for this task to avoid jeopardizing ASP.NET worker threads which is one the worst thing that might happen to an ASP.NET application => running out of worker threads. It's like running out of fuel in the middle of the desert. You most certainly die.
So let's start by writing an extension method that will allow us converting the legacy WebClient event based pattern into the new task based pattern:
public static class TaskExtensions
{
public static Task<string> DownloadStringAsTask(this string url)
{
var tcs = new TaskCompletionSource<string>(url);
var client = new WebClient();
client.DownloadStringCompleted += (sender, args) =>
{
if (args.Error != null)
{
tcs.SetException(args.Error);
}
else
{
tcs.SetResult(args.Result);
}
};
client.DownloadStringAsync(new Uri(url));
return tcs.Task;
}
}
Armed with this extension method in hand we could now define a view model that will basically reflect the requirements of our view:
public class DownloadResultViewModel
{
public string Url { get; set; }
public int WordCount { get; set; }
public string Error { get; set; }
}
Then we move on to an asyncrhonous controller that will contain 2 actions: a standard synchronous Index action that will render the search form and an asynchronous Search action that will perform the actual work:
public class HomeController : AsyncController
{
public ActionResult Index()
{
return View();
}
[AsyncTimeout(600000)]
[HttpPost]
public void SearchAsync(string searchText)
{
AsyncManager.Parameters["searchText"] = searchText;
string[] urls =
{
"http://www.msnbc.com",
"http://www.yahoo.com",
"http://www.nytimes.com",
"http://www.washingtonpost.com",
"http://www.latimes.com",
"http://www.unexistentdomainthatwillcrash.com",
"http://www.newsday.com"
};
var tasks = urls.Select(url => url.DownloadStringAsTask());
AsyncManager.OutstandingOperations.Increment(urls.Length);
Task.Factory.ContinueWhenAll(tasks.ToArray(), allTasks =>
{
var results =
from task in allTasks
let error = task.IsFaulted ? task.Exception.Message : null
let result = !task.IsFaulted ? task.Result : string.Empty
select new DownloadResultViewModel
{
Url = (string)task.AsyncState,
Error = error,
WordCount = result.Split(' ')
.Where(x => string.Equals(x, searchText, StringComparison.OrdinalIgnoreCase))
.Count()
};
AsyncManager.Parameters["results"] = results;
AsyncManager.OutstandingOperations.Decrement(urls.Length);
});
}
public ActionResult SearchCompleted(IEnumerable<DownloadResultViewModel> results)
{
return View("index", results);
}
}
Now we define an ~/Views/Home/Index.cshtml view that will contain the search logic as well as the results:
#model IEnumerable<DownloadResultViewModel>
#using (Html.BeginForm("search", null, new { searchText = "politics" }))
{
<button type="submit">Search</button>
}
#if (Model != null)
{
<h3>Search results</h3>
<table>
<thead>
<tr>
<th>Url</th>
<th>Word count</th>
</tr>
</thead>
<tbody>
#Html.DisplayForModel()
</tbody>
</table>
}
And of course the corresponding display template that will be rendered automatically for each element of our model (~/Views/Shared/DisplayTemplates/DownloadResultViewModel.cshtml):
#model DownloadResultViewModel
<tr>
<td>#Html.DisplayFor(x => x.Url)</td>
<td>
#if (Model.Error != null)
{
#Html.DisplayFor(x => x.Error)
}
else
{
#Html.DisplayFor(x => x.WordCount)
}
</td>
</tr>
Now, since the search operation could take quite a long time your users could quickly get bored without being able to use some of the other hundredths of functionalities that your webpage has to offer them.
In this case it is absolutely trivial to invoke the Search controller action using an AJAX request and showing a spinner to inform the users that their search is in progress but without freezing the webpage allowing them to do other things (without navigating away from the page obviously).
So let's do that, shall we?
We start by externalizing the results into a partial (~/Views/Home/_Results.cshtml) without touching at the display template:
#model IEnumerable<DownloadResultViewModel>
#if (Model != null)
{
<h3>Search results</h3>
<table>
<thead>
<tr>
<th>Url</th>
<th>Word count</th>
</tr>
</thead>
<tbody>
#Html.DisplayForModel()
</tbody>
</table>
}
and we adapt our ~/Views/Home/Index.cshtml view to use this partial:
#model IEnumerable<DownloadResultViewModel>
#using (Html.BeginForm("search", null, new { searchText = "politics" }))
{
<button type="submit">Search</button>
}
<div id="results">
#Html.Partial("_Results")
</div>
and of course the SearchCompleted controller action that must now return only the partial result:
public ActionResult SearchCompleted(IEnumerable<DownloadResultViewModel> results)
{
return PartialView("_Results", results);
}
Now all that's left is to write a simple javascript that will AJAXify our search form. So this could happen into a separate js that will reference in our layout:
$(function () {
$('form').submit(function () {
$.ajax({
url: this.action,
type: this.method,
success: function (results) {
$('#results').html(results);
}
});
return false;
});
});
Depending on whether you referenced this script in the <head> section or at the end of the body you might not need to wrap it in a document.ready. If the script is at the end you could remove the wrapping document.ready function from my example.
And the last part is to give some visual indication to the user that the site is actually performing a search. This could be done using a global ajax event handler that we might subscribe to:
$(function () {
$(document).ajaxStart(function () {
$('#results').html('searching ...');
});
});

Related

Posting a search form via Ajax in ASP.NET MVC results in 500 (Internal Server Error)

So the way I have got this setup involves ClientController in the Controllers Folder. And in the Views Folder, I have SearchClient.cshtml and _SearchClients.cshtml. I am using Ajax and following the tutorial from this link : http://techfunda.com/howto/291/search-database-using-ajax to get my search to work.
Here is how the methods in my controller look like :-
public ActionResult SearchClient()
{
return View();
}
public PartialViewResult _SearchClients(string searchString = "")
{
var clients = repository.Get(c => c.isDeleted == false);
clients = clients.Where(s =>
(s.FirstName.Contains(searchString)) ||
(s.MiddleName.Contains(searchString)) ||
(s.LastName.Contains(searchString))
).ToList();
return PartialView(clients);
}
And finally , here is the partial view :-
#model IEnumerable<Entities.Client>
#foreach (var item in Model)
{
<tr>
<td>#item.FirstName</td>
<td>#item.MiddleName</td>
<td>#item.LastName</td>
<td>#item.Phone</td>
<td>#item.Email</td>
<td>#item.AgencyID</td>
<td>#item.StreetAddress</td>
<td>#item.City</td>
<td>#item.PostalCode</td>
<td>#item.Province</td>
</tr>
}
I figured that the MidddleName was null and had to remove it from the search. At the same time I had to check if the string was null or empty. Here is the Controller action.
public PartialViewResult _SearchClients(string searchString = "")
{
if (!String.IsNullOrEmpty(searchString))
{
var searchedClient = clients.Where(s =>
(s.FirstName.Contains(searchString)) ||
(s.LastName.Contains(searchString))
).ToList();
return PartialView(searchedClient);
}
return PartialView(clients);
}
Hope this helps someone.

how can i save more than one image in the database?

I just want to save the route of the images in the database.
So i try this.
And i get this error System.NullReferenceException: Object reference not set to an instance of an object.
This is my Controller
public ActionResult SaveImages(IEnumerable<HttpPostedFileBase> img, Imagenes images)
{
foreach (var n in img)
{
var PhotoUrl = Server.MapPath("/images" + n.FileName);
if (n != null && n.ContentLength > 0)
n.SaveAs(PhotoUrl);
images.imgUrl = "/images" + n.FileName;
db.Imagenes.Add(images);
db.SaveChanges();
}
return View("Index");
}
This is my model class
public partial class Imagenes
{
public int id { get; set; }
[StringLength(200)]
public string imgUrl { get; set; }
}
my View
#{
ViewBag.Title = "Home Page";}
#using (Html.BeginForm("SaveImages", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div>
<input type="file" name="img" id="img" multiple />
<input type="submit" name="submit" value="Save"/>
</div>}
The error you are getting is nothing about the image saving part, but I'm assuming it's the use of your images property...
As you didn't specify where that property comes from, MVC automatically assumes that's a POST Variable, and in your HTML, you have nothing of sorts...
change your action code to:
public ActionResult SaveImages(IEnumerable<HttpPostedFileBase> img)
{
const string folderToUpload = "/images";
foreach (var n in img)
{
var imageToUpload = folderToUpload + n.FileName;
var photoUrl = Server.MapPath(imageToUpload);
if (n != null && n.ContentLength > 0) {
n.SaveAs(photoUrl); // save to folder
var images = new Imagenes {
imgUrl = imageToUpload
};
db.Imagenes.Add(images); // add to repository
db.SaveChanges(); // save repositorychanges
}
}
return redirectToAction("Index");
}
I'm also assuming that db was already injected in your constructor, and it's not NULL
Code edited:
create a constant variable to have the folder to upload, so you don't repeat that code over and over
create a variable to hold the full path of the image, so you don't repeat that code over and over (remember: DRY - Don't Repeat Yourself)
save to database only if the file was saved
create a new variable to hold your object to be saved
redirect to the action using redirectToAction as you might have some calls in your Index and only redirecting to the View would give you an error
to be persistence, change the PhotoUrl to photoUrl (local variables = start with lowercase)

Setting Up Multiple Actions

I was wondering if it was possible to have more than 1 action in the link. For example, If I wanted to have multiple links such as:
http://www.mywebsite.com/(CONTROLLER)/(ID)/(ACTION)
[http://]www.mywebsite.com/user/Micheal/EditMovies
[http://]www.mywebsite.com/user/Micheal/EditFavorites
Is there some sort of way to do this? If not, do I have to specify multiple id's in the function and then use a case to determine which page they are going to be sent to?
In my UserController.cs I have:
public ActionResult Index(string username)
{
if (username != null)
{
try
{
var userid = (Membership.GetUser(username, false).ProviderUserKey);
Users user = entity.User.Find(userid);
return View(user);
}
catch (Exception e)
{
}
}
return RedirectToAction("", "Home");
}
In my Routes I have:
routes.MapRoute(
name: "User",
url: "User/{username}",
defaults: new { controller = "User", action = "Index" }
);
What I'm trying to make it do is have additional functions for second actions so I can do something like:
User/{username}/{actionsAdditional}
And In my UserController I can put more actions which will leader to the second action actionsAdditional
public ActionResult Index(string username)
{
if (username != null)
{
try
{
var userid = (Membership.GetUser(username, false).ProviderUserKey);
Users user = entity.User.Find(userid);
return View(user);
}
catch (Exception e)
{
}
}
return RedirectToAction("", "Home");
}
public ActionResult EditFavorites()
{
//DoStuff
}
You could do this multiple ways, here's just one:
Set up a route to handle this:
routes.MapRoute("UserEditsThings",
"user/{id}/edit/{thingToEdit}",
new { controller = "UserController", action="Edit" },
new { thingToEdit = ValidThingsToEditConstraint() }
);
Then your action in the User Controller should look like this:
public ActionResult Edit(ThingToEdit thingToEdit) {
ThingToEditViewModel viewModel = new ThingToEditViewModel(thingToEdit);
return View(viewModel);
}
The RouteConstraint is what would take their input (the thingToEdit) and make sure it was valid (you could do this in a few places - like in a Custom ModelBinder):
public class ValidThingsToEditConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
//simplistic implementation simply to show what's possible.
return values['thingToEdit'] == "Favorites" || values['thingToEdit'] == "Movies";
}
}
Now, that way, you can have one method to Edit both Movies and Favorites, and you simply add a parameter to show what 'type' of thing they're editing.
If you wanted to keep your current route, you should be able to do the following:
routes.MapRoute("UserEditsThings",
"user/{id}/edit{thingToEdit}",
new { controller = "UserController", action="Edit" },
new { thingToEdit = ValidThingsToEditConstraint() }
);
I've been away from ASP.NET MVC for about 7 months, so this could be a little rusty. It has not been tested for syntax errors and bits of python may shine through. It should get you there, though.

ajax paging asp.net mvc

I know how to hook up ajax paging to a grid or a webgrid in asp.net mvc. But how can I accomplish ajax paging, using custom paging for large data sets for another format outside of a table grid.
Is that even possible using an mvc helper or mvc.pagedlist?
I used to be a webforms guys and it was so easy to hook up a listview where you could use divs to create whatever layout you want for individual items, you could then hook up a datapage and wrap it all in an update panel.
Basically I want a list of items that I can page through via ajax but with having large data sets I can just pull down all the items and page via jquery, I need to do custom paging on the server side and only return the items for a specific page.
By reusing a partial view and some ajax, this is very easily done in MVC.
Add this model as a property to your page's ViewModel to handle the pagination:
namespace Models.ViewModels
{
[Serializable()]
public class PagingInfoViewModel
{
public int TotalItems { get; set; }
public int ResultsPerPage { get; set; }
public int CurrentPage { get; set; }
public int TotalPages {
get { return Convert.ToInt32(Math.Ceiling(Convert.ToDecimal(this.TotalItems) / this.ResultsPerPage)); }
}
public string LinkTextShowMore { get; set; }
public string LinkTextShowingAll { get; set; }
/// <summary>
/// Paging url used by the jQuery Ajax function
/// </summary>
public string UrlGetMore { get; set; }
public PagingInfoViewModel(string linkTextShowMore, string linkTextShowingAll, int resultsPerPage)
{
this.LinkTextShowMore = linkTextShowMore;
this.LinkTextShowingAll = linkTextShowingAll;
this.ResultsPerPage = resultsPerPage;
}
}
}
Add the following code to your partial view to handle the pagination:
//Start Pagination
//determine the value for the X for "Showing X of Y"
{
int currentTotal = 0;
if ((Model.PagingInfo.CurrentPage * Model.PagingInfo.ResultsPerPage) < Model.PagingInfo.TotalItems) {
//the current max item we are displaying is less than the total number of policies
//display the current max item index\
currentTotal = Model.PagingInfo.CurrentPage * Model.PagingInfo.ResultsPerPage;
} else {
//the current is greater than the total number of policies
//display the total number of policies
currentTotal = Model.PagingInfo.TotalItems;
}
if (Model.PagingInfo.TotalPages == 0 || Model.PagingInfo.CurrentPage == Model.PagingInfo.TotalPages)
{
#<li>
<h3>#Model.PagingInfo.LinkTextShowingAll</h3>
<p><strong>Showing #currentTotal Of #Model.PagingInfo.TotalItems</strong></p>
</li>
} else {
#<li id="GetMore">
<a href="#" id="lnkGetMore">
<h3>#Model.PagingInfo.LinkTextShowMore</h3>
<p><strong>Showing #(currentTotal) Of #Model.PagingInfo.TotalItems</strong></p>
</a>
</li>
#<script type="text/javascript" lang="javascript">
$('#lnkGetMore').click(function () {
$.ajax({
url: "#Model.PagingInfo.UrlGetMore",
success: function (data) {
$('#ProducerList li:last').remove();
$('#ProducerList').append(data);
$('#ProducerList').listview('refresh');
}
});
return false;
});
</script>
}
}
Now, the javascript at the end is specifically for a UI that uses ul's and li's, but can easily be customized for your needs.
The UrlGetMore property is set on the back end when the model is passed to the view. I am sure there is a more elegant way of doing this. Here is the code I used:
//build paging url used by the jQuery Ajax function
view.PagingInfo.UrlGetMore == Url.RouteUrl("RouteItemList", new { page = view.PagingInfo.CurrentPage + 1 })
And finally, here is the action that handles both the initial View and the subsequent Partial View (ajax call)
public ActionResult List(UserModel user, ViewModel view, int page = 1)
{
IQueryable<model> models = this.RetrieveModels(user, view);
if ((models != null) && models.Count > 0) {
view.PagingInfo.CurrentPage = page;
view.PagingInfo.ResultsPerPage = user.Preferences.ResultsPerPage;
view.PagingInfo.TotalItems = models.Count;
view.items = models.Skip((page - 1) * user.Preferences.ResultsPerPage).Take(user.Preferences.ResultsPerPage).ToList();
//build paging url used by the jQuery Ajax function
view.PagingInfo.UrlGetMore = Url.RouteUrl("RouteList", new { page = view.PagingInfo.CurrentPage + 1 });
}
if (page == 1) {
return View(view);
} else {
return PartialView("ListPartial", view);
}
}
HTH.
You could create simple HtmlHelper simillar to this:
public static class HtmlPaginHelper
{
public static MvcHtmlString PagerNoLastPage(this AjaxHelper ajaxHelper,
int page,
int pageSize,
bool isLastPage,
Func<int, string> pageUrl,
Func<int, AjaxOptions> pageAjaxOptions)
{
var result = new StringBuilder();
var firstPageAnchor = new TagBuilder("a");
firstPageAnchor.SetInnerText("<<");
var prevPageAnchor = new TagBuilder("a");
prevPageAnchor.SetInnerText("<");
var nextPageAnchor = new TagBuilder("a");
nextPageAnchor.SetInnerText(">");
var currentPageText = new TagBuilder("span");
currentPageText.SetInnerText(string.Format("Page: {0}", page));
if (page > 1)
{
firstPageAnchor.MergeAttribute("href", pageUrl(1));
firstPageAnchor.MergeAttributes(pageAjaxOptions(1).ToUnobtrusiveHtmlAttributes());
prevPageAnchor.MergeAttribute("href", pageUrl(page - 1));
prevPageAnchor.MergeAttributes(pageAjaxOptions(page - 1).ToUnobtrusiveHtmlAttributes());
}
if (!isLastPage)
{
nextPageAnchor.MergeAttribute("href", pageUrl(page + 1));
nextPageAnchor.MergeAttributes(pageAjaxOptions(page + 1).ToUnobtrusiveHtmlAttributes());
}
result.Append(firstPageAnchor);
result.Append(prevPageAnchor);
result.Append(currentPageText);
result.Append(nextPageAnchor);
return MvcHtmlString.Create(result.ToString());
}
}
... and then use it in your Razor view:
grid results go here...
#Ajax.PagerNoLastPage(Model.Query.Page,
Model.Query.PageSize,
Model.Data.IsLastPage,
i => Url.Action("Index", RouteValues(i)),
i => new AjaxOptions
{
UpdateTargetId = "content",
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET",
Url = Url.Action("Grid", RouteValues(i))
})
where RouteValues(i) is defined for example like this:
#functions {
private object PageRouteValues(int i)
{
return new
{
payId = Model.Query.PayId,
clientCode = Model.Query.ClientCode,
fromDate = Model.Query.FromDate,
tillDate = Model.Query.TillDate,
payNum = Model.Query.PayId,
checkNum = Model.Query.CheckNum,
payType = Model.Query.PayType,
payStatus = Model.Query.PayStatus,
page = i,
pageSize = Model.Query.PageSize
};
}
}
Is that even possible using an mvc helper or mvc.pagedlist?
Yes, but of course you have to coordinate the client-side requests with server-side actions to handle the actual data paging. In that sense, it's not as simple as as WebForms, but it's still possible.
Here's an example of using PagedList to render each returned item in its own table, separated by horizontal rules. You should easily be able to modify the HTML in the example to produce any rendering you want.

asp.net mvc making ajax call JSON

Controller:
public ActionResult EditOrganizationMeta(int id)
{
}
[HttpPost]
[ValidateInput(false)]
public ActionResult EditOrganizationMeta(FormCollection collection)
{
}
View:
function DoAjaxCall() {
var url = '<%= Url.Action("EditOrganizationMeta", "Organization") %>';
//url = url + '/' + dd;
$.post(url, null, function(data) {
alert(data);
});
}
<input type="button" name="something" value="Save" onclick="DoAjaxCall()" />
how would i make the ajax call , i have basically two functions with the same name EditOrganizationMeta,Do the form collection will be passed automatically.Basic confusion is regarding the method call
Ok i made a call by ajax but after that My This code is not running anymore
[HttpPost]
[ValidateInput(false)]
public ActionResult EditOrganizationMeta(FormCollection collection)
{
int OrganizationId = 11;
string OrganizationName = "Ministry of Interior";
try
{
string ids = Request.Params // **getting error here some sequence is not there**
.Cast<string>()
.Where(p => p.StartsWith("button"))
.Select(p => p.Substring("button".Length))
.First();
String RealValueOfThatControl = collection[ids];
}
}
catch { }
return RedirectToAction("EditOrganizationMeta", new { id = OrganizationId });
}
I think that there is no post
You have to pass the data you want through second parameter of $.post call. The easiest way (if you want to post a form) is to use $.serialize like this:
$.post(url, $('#formId').serialize(), function(data) {
alert(data);
});
Where 'formId' is you form identifier. And don't worry about having two methods with same name, they will be distincted by HttpVerb (one will respond only to GET, while second to POST).

Resources