ASP.NET + AJAX + WebService: string result + out parameter - javascript

In my experience I call simple web methods, like this:
[WebMethod]
public List<string> GetUserListByLetters(string strLetters){ ... }
And here is my OnComplete JS-function:
function OnComplete(args) {
...
if (args != "") {
for (var i = 0; i < args.length; i++) {
// Do what I need with string in args[i]
}
}
...
}
For now I would like to have such method:
[WebMethod]
public string GetUserListByCountry(int countryId, out List<User> users)
{
users=null;
if ( Validate(countryId)==false )
return "wrong country Id";
users = GetUsers(countryId); // returns list of User objects.
return "";
}
Question1: should "out" parameter work in WS? I saw few article (, for example) where said it is impossible.
Question2: if it doesn't work, how should I change method signature to get that workable?
Question3: if it works, how could I access data from 'out' parameter?
Thanks.

I will use this approach:
[WebMethod]
public object GetUserListByCountry(int countryId)
{
users=null;
if ( Validate(countryId)==false )
return "wrong country Id";
users = GetUsers(countryId); // returns list of User objects.
return new {Error="", Users=users};
}
I mean that I will return complex object, its one property will contain usual value for return and another - required data.

Related

SignalR error Cannot implicitly convert type 'System.Threading.Tasks.Task<object>' to 'string'

I'm trying to call a method from my c# hub class but when I try to return a string from the javascript code I get a System.Threading.Tasks.Task instead of a string. I'm wondering how I can edit the javascript method to return an actual string.
Here's the hub class:
public String getMessage(int id)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
var s = hubContext.Clients.All.getMessage(id);
return s;
}
JavaScript:
chat.client.getMessage = function (id) {
for (i = 0; i < messageArr.length; i++) {
if (messageArr[i].ID == id) {
var s = messageArr[i].ID + messageArr[i].Name + messageArr[i].Content;
window.alert(s);
return s;
}
}
}
SignalR does not support return values from clients to hubs. The task returned by Clients.All will complete when all clients have been called.
If you want return values from clients, you will have to manually call the hub with the correct value and match the caller using your own message id.
See the documentation
You can't get a return value from a client method; syntax such as int
x = Clients.All.add(1,1) does not work.

Returning undefined type of data on ajax call

I have a jquery script which does ajax call to my action inside the controller in my MVC application:
$('#save').click(function () {
$.post(url, { id: id, text: textarea.val() }, function (data) {
if (data) {
row.children('td').eq(3).text(textarea.val());
row.children('td').eq(4).text('Da');
row.children('td').eq(5).hide();
$('#napomena').val('');
form.hide();
}
});
});
And this is my controller:
public ActionResult PravdanjeIzostanaka(string id, string text)
{
var obj = db.Odsutnost.Find(Convert.ToInt32(id));
if(obj!=null && text.Trim()!="")
{
obj.Napomena = text;
obj.Opravdano = true;
db.SaveChanges();
return RedirectToAction("PregledIzostanaka");
}
return null; // what am I supposed to return here so that data type in ajax would be undefined??
}
As you can see im checking whether the obj object and text field are !=null and if its empty. If those two requirements are not met I would like to make a little alert which would basically say: "Text field is required!" and modify my script to something like this:
if (data!=="undefined") {
row.children('td').eq(3).text(textarea.val());
row.children('td').eq(4).text('Da');
row.children('td').eq(5).hide();
$('#napomena').val('');
form.hide();
}
else{
alert('Text field is required!');
}
So I need to modify my script and Action(action should return something which ajax would see it as undefined in order that I can do alert)...
Can someone help me out with this? :)
Just return a json string as undefined. More like the below:
public ActionResult PravdanjeIzostanaka(string id, string text)
{
var obj = db.Odsutnost.Find(Convert.ToInt32(id));
if(obj!=null && text.Trim()!="")
{
obj.Napomena = text;
obj.Opravdano = true;
db.SaveChanges();
return RedirectToAction("PregledIzostanaka");
}
return json("undefined");
}

SignalR Client Methods not firing consistently

I have a simple SignalR proxy with a single client method on it. The javascript looks like the following:
var proxy = $.connection.orderStatusUpdateEmitter;
proxy.client.onOrderUpdated = function(order){
try {
//This is only hit sometimes
getCustomerOrders(userId)
} catch (e) {
}
}
proxy.connection.start().done(function(c){
$log.info('Connected to order status update emitter');
});
proxy.connection.disconnected = function(data){
$log.info('disconnected');
setTimeout(function() {
proxy.connection.start();
},20000);
}
It seems like there is some type of race condition or I am doing this incorrectly because the handler for onOrderUpdated is not consistently hit. Anything glaringly wrong with the implementation above?
There is nothing special happening in my hub on the server, it looks like the following:
[AuthorizeClaims]
public class OrderStatusUpdateEmitter : Hub
{
private static string _groupIdentifier = "OrderStatusUpdate";
public override Task OnConnected()
{
var identity = Context.Request.Environment["user.identity"] as AuthenticatedUserIdentity;
Groups.Add(Context.ConnectionId, string.Format("{0}-{1}", _groupIdentifier, identity.UserId));
return base.OnConnected();
}
public override Task OnReconnected()
{
var identity = Context.Request.Environment["user.identity"] as AuthenticatedUserIdentity;
Groups.Add(Context.ConnectionId, string.Format("{0}-{1}", _groupIdentifier, identity.UserId));
return base.OnReconnected();
}
public static string GetGroupIdentifier()
{
return _groupIdentifier;
}
public Order OnOrderUpdate(Order order)
{
Clients.Group(String.Format("{0}-{1}",GetGroupIdentifier(),
order.CustomerId)).onOrderUpdated(obj);
}
}
Try the following instead of Group
list is consisting of users with ',' separator.
string[] group = list.Split(',');
for(int i=0; i < group.length; i++)
Clients.User(group[i]).onOrderUpdated(obj);

Get Object from Objects Laravel

i have two Models, first:
class Tutorial extends Eloquent {
protected $table = 'tutorials';
public function rating()
{
return $this->hasMany('Rating');
}
}
and:
class Rating extends Eloquent {
protected $table = 'ratings';
public $timestamps = false;
public function tutorial()
{
return $this->belongsTo('Tutorial');
}
}
now in my controller i have this:
public function get_index() {
$tutorials = tutorial::orderBy('created_at', 'desc')
->with('rating')
->paginate(25);
return View::make('site/home/index')->with('tutorials', $tutorials);
}
So how do i get all ratings from one tutorial in my View?!
EDIT:
Now i have this:
public function ratings()
{
return $this->hasMany('Rating');
}
public function getRating()
{
// Grab the ratings from this tutorial
$ratings = $this->ratings;
$summedRatings = 0;
// Loop through them all and add them together
foreach($ratings as $rating)
{
console.log($rating->value);
$summedRatings += $rating->value;
}
// Return the calculated average
return $summedRatings / count($ratings);
}
public function get_index() {
$tutorials = Tutorial::with('ratings')
->with('user')
->orderBy('created_at', 'desc')
->paginate(25);
return View::make('site/home/index')->with('tutorials', $tutorials);
}
and in my View:
#foreach($tutorials as $tutorial)
<span>{{$tutorial->rating}}</span>
#endforeach
But all my < span >´s are empty!
UPDATE: if i do this:
#foreach($tutorials as $tutorial)
#foreach($tutorial->ratings as $rate)
<span>{{$rate->value}}</span>
#endforeach
everything is good....So what´s wrong?
Depending on the platform you're site is on you should always use the correct case.
$tutorials = tutorial::orderBy(...) // Wrong
$tutorials = Tutorial::orderBy(...) // Correct
To eager load the ratings you should always declare your 'with' method before anything else.
$tutorials = Tutorial::with('rating')
->orderBy('created_at', 'DESC')
->paginate(25);
This has, for some reason, been left out of the L4 docs.
In your view you can now access the rating with this
foreach($tutorials as $tutorial)
{
echo $tutorial->rating->{rating table column name};
}
First, as far as naming conventions go, to make things easier to understand: The rating() method within your tutorial method should be called ratings(), so when you grab your ratings, it will look better ($tutorial->ratings)
After renaming this, in your view, while looping through the array of $tutorials, you could access the ratings of each one like this:
foreach($tutorials as $tutorial)
{
$ratings = $tutorial->ratings;
}
Which would retrieve the ratings object of each.
What you should know is that you can create properties for your model if you need to return the calculation of the ratings, instead of the ORM objects
For example, if each rating is a number from 1-5 in the ratings table stored in an amount column, you can do this to set the average of each rating as a property:
class Tutorial extends Eloquent {
protected $table = 'tutorials';
public function ratings()
{
return $this->hasMany('Rating');
}
public function getRating()
{
// Grab the ratings from this tutorial
$ratings = $this->ratings;
$summedRatings = 0;
// Loop through them all and add them together
foreach($ratings as $rating)
{
$summedRatings += $rating->amount;
}
// Return the calculated average
return $summedRatings / count($ratings);
}
}
Then in your view, you can echo out the property as if it were part of the database
foreach($tutorials as $tutorial)
{
echo $tutorial->rating;
}

Using a custom validator in a variable length list in Microsoft MVC 2 (client-side validation issues)

I have created a variable length list according to the many great posts by Steve Sanderson on how to do this in MVC 2. His blog has a lot of great tutorials.
I then created a custom "requiredif" conditional validator following this overview http://blogs.msdn.com/b/simonince/archive/2010/06/11/adding-client-side-script-to-an-mvc-conditional-validator.aspx
I used the JQuery validation handler from the MSDN blog entry which adds the following to a conditional-validators.js I include on my page's scripts:
(function ($) {
$.validator.addMethod('requiredif', function (value, element, parameters) {
var id = '#' + parameters['dependentProperty'];
// Get the target value (as a string, as that's what actual value will be)
var targetvalue = parameters['targetValue'];
targetvalue = (targetvalue == null ? '' : targetvalue).toString().toLowerCase();
// Get the actual value of the target control
var actualvalue = ($(id).val() == null ? '' : $(id).val()).toLowerCase();
// If the condition is true, reuse the existing required field validator functionality
if (targetvalue === actualvalue)
return $.validator.methods.required.call(this, value, element, parameters);
return true;
});
})(jQuery);
Alas, this does not cause a client-side validation to fire ... only the server-side validation fires. The inherent "required" validators DO fire client-side, meaning I have my script includes set-up correctly for basic validation. Has anyone accomplished custom validators in a variable length list in MVC 2 using JQuery as the client-side validation method?
NOTE that this same custom validator works client-side using the exact same set-up on a non-variable length list.
Turns out that it was a field ID naming issue with the way that collection IDs render in a variable length list. The validator was attempting to name the element ID of the dependent property with the expected statement of:
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty);
I analyzed the HTML viewsource (posted in my comment, above), and actually, [ and ] characters are not output in the HTML of the collection-index elements... they're replaced with _... so, when I changed my CustomValidator.cs to have the dependent property set to:
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty).Replace("[", "_").Replace("]", "_");
... then the client-side validator works since the name matches. I'll have to dig deeper to see WHY the ID is getting renamed in Sanderson's collection index method, below...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Sendz.WebUI.Helpers
{
public static class HtmlPrefixScopeExtensions
{
private const string IdsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
var itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(
string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />",
collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
var key = IdsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (var previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
#region Nested type: HtmlFieldPrefixScope
private class HtmlFieldPrefixScope : IDisposable
{
private readonly string _previousHtmlFieldPrefix;
private readonly TemplateInfo _templateInfo;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
_templateInfo = templateInfo;
_previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
#region IDisposable Members
public void Dispose()
{
_templateInfo.HtmlFieldPrefix = _previousHtmlFieldPrefix;
}
#endregion
}
#endregion
}
}
A complete validator / attribute reference...
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute) { }
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// Get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);
if (field != null)
{
// Get the value of the dependent property
var value = field.GetValue(container, null);
// Compare the value against the target value
if ((value == null && Attribute.TargetValue == null) ||
(value != null && value.ToString().ToLowerInvariant().Equals(Attribute.TargetValue.ToString().ToLowerInvariant())))
{
// A match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
// Validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = ErrorMessage,
ValidationType = "requiredif"
};
var viewContext = (ControllerContext as ViewContext);
var depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty).Replace("[", "_").Replace("]", "_");
rule.ValidationParameters.Add("dependentProperty", depProp);
rule.ValidationParameters.Add("targetValue", Attribute.TargetValue.ToString());
yield return rule;
}

Categories

Resources