问题
Using SignalR, is there any possibility to call .fail
instead of .done
when specific values are returned by hub method?
Perhaps using the SignalR pipeline?
public bool Delete(int addressId)
{
// User should not be able to delete default address
if(AddressService.IsDefaultAddressOfCustomer(addressId))
return false; // Should call .fail() on client
AddressService.Delete(addressId);
return true; // Should call .done() on client
}
The alternative would be to throw an exception but I would like to avoid that since the error is not really a server fault, but a user fault.
回答1:
Assuming you are really convinced an exception is not the right tool for you, you could use some custom attribute you would define to mark methods where a false
return value must be translated into an error, and then intercept any incoming call with BuildIncoming
from HubPipelineModule
:
http://msdn.microsoft.com/en-us/library/microsoft.aspnet.signalr.hubs.hubpipelinemodule.buildincoming(v=vs.118).aspx
From inside there you can intercept the call to your original method, inspect if it's marked with your attribute and if it returned false
, if it's the case you can throw an exception from there. The bottom line is, you would still throw an exception to make it call .fail()
client-side, but that exception would not bloat your business logic. Something like this:
public class FailPipelineModule : HubPipelineModule
{
public override Func<IHubIncomingInvokerContext, Task<object>> BuildIncoming(Func<IHubIncomingInvokerContext, Task<object>> invoke)
{
return base.BuildIncoming(context =>
{
var r = (bool)(invoke(context)).Result;
if (context.MethodDescriptor.Attributes.Any(a => typeof(FailAttribute) == a.GetType()) && !r)
throw new ApplicationException("false");
return Task.FromResult((object)r);
});
}
}
You'll need to define FailAttribute
, use it to mark your hub's method and register FailPipelineModule
at startup.
回答2:
As Wasp indicated, with the SignalR JavaScript client, the promise is only rejected if hubResult.Error
is set, which only happens when an exception is thrown while the request is processed. There's no way to modify that using the hub pipeline.
In general, I'd probably stick with using exceptions in that case, but if you're looking for another alternative, you could also modify the client-side jquery.signalr-*.js
code, specifically the hubProxy prototype invoke
method. It has a condition to decide whether to resolve or reject the promise:
if (result.Error) {
// code to reject ...
} else {
connection.log("Invoked " + that.hubName + "." + methodName);
d.resolveWith(that, [result.Result]);
}
and you could modify the else block:
if (result.Error) {
// code to reject ...
} else {
connection.log("Invoked " + that.hubName + "." + methodName);
if (typeof result.Result === "boolean" && !result.Result) {
d.rejectWith(that, [result.Result]);
} else {
d.resolveWith(that, [result.Result]);
}
}
Then all hub methods which return false would reject the promise. The advantage over Wasp's solution would be not having to create attributes and overall less code. The drawback is that it might be less maintainable since you're manually editing the SignalR code (so if you do that, it should definitely be documented somewhere, and you'd have to minify the script yourself instead of using the packaged one).
A more maintainable client-side alternative would be to wrap the hub API and return your own deferred from that, e.g. like this:
var myHub = {
server: $.connection.myHub.server,
init: function() {
for (var methodName in this.server) {
if (this.server.hasOwnProperty(methodName)) {
this[methodName] = function() {
var deferred = $.Deferred();
this.server[methodName].apply(this.server, arguments)
.done(function(result) {
// reject if server hub method returned false
if (result === false) deferred.reject(result);
deferred.resolve(result);
});
return deferred.promise();
};
}
}
}
};
myHub.init();
Then instead of calling
$.connection.myHub.server.someMethod("hello", "world")
.done(function(result) { })
.fail(function(result) { });
you would call
myHub.someMethod("hello", "world")
.done(function(result) { })
.fail(function(result) { });
That way, you'd have full control over how return values are interpreted.
来源:https://stackoverflow.com/questions/23991242/call-failerror-without-throwing-exception