MVC SignalR not firing from Controller Post Method - asp.net-mvc

When Saving schedule to calendar it must auto update the activity logs on my notification bar in my Home Controller. It saves the data but only show when notification bar is refreshed. It seems that Hub is not starting when saved.
CalendarController.cs
[HttpPost]
public JsonResult SaveSchedule(Schedule s)
{
var userid = User.Identity.GetUserId();
var profile = _context.Profiles.Single(p => p.Id == userid);
var status = false;
if (s.Schedule_ID > 0)
{
//Update
var v = _context.Schedules.Where(a => a.Schedule_ID == s.Schedule_ID).FirstOrDefault();
if (v != null)
{
v.Shift = s.Shift;
}
}
var activitylog = new ActivityLog
{
UserId = userid,
LogDate = DateTime.Now,
Activity = ActivityHelper.GetActivityLog(4, profile.FirstName)
};
// save to data and must be shown on notification bar
_context.ActivityLogs.Add(activitylog);
_context.SaveChanges();
ActivityHub.StartLogging();
status = true;
return new JsonResult { Data = new { status = status } };
}
HomeController.cs
public JsonResult GetLogs()
{
return Json(ActivityHelper.GetActivityLogs(), JsonRequestBehavior.AllowGet);
}
ActivityHub.cs
public class ActivityHub : Hub
{
public static void StartLogging()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<ActivityHub>();
//calls the signalR client part to execute javascript method
context.Clients.All.displayLog();
}
}
My CSHTML
<script>
$(function () {
var activityFromHub = $.connection.activityHub;
$.connection.hub.start().done(function () {
FetchLogs();
});
activityFromHub.client.displayLog = function () {
console.log('Hub Started');
FetchLogs();
}
function FetchLogs() {
$.ajax({
type: 'GET',
url: '/Home/GetLogs',
datatype: 'json',
success: function (data) {
$("#logs tr").remove();
data = $.parseJSON(data);
if (data.length > 0) {
.... do some append here
}
},
error: function (error) {
alert("error");
}
});
}
});
</script>
ActivityHelper.cs
static readonly string connString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
public static class ActivityHelper
{
public static string GetActivityLogs()
{
string sqlCommand = #"my select query here";
try
{
var messages = new List<ActivityLog>();
using(var connection = new SqlConnection(connString))
{
connection.Open();
using (SqlConnection con = new SqlConnection(connString))
{
SqlCommand cmd = new SqlCommand(sqlCommand, con);
if(con.State != System.Data.ConnectionState.Open)
{
con.Open();
}
cmd.Notification = null;
SqlDependency dependency = new SqlDependency(cmd);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
var reader = cmd.ExecuteReader();
while (reader.Read())
{
messages.Add(item: new ActivityLog
{
Activity = reader["Activity"] != DBNull.Value ? (string)reader["Activity"] : "",
LogDate = (DateTime)reader["LogDate"]
});
}
}
}
var jsonSerialiser = new JavaScriptSerializer();
var json = jsonSerialiser.Serialize(messages);
return json;
}
catch(Exception ex)
{
throw;
}
}
public static void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
SqlDependency dependency = sender as SqlDependency;
dependency.OnChange -= dependency_OnChange;
var activityHub = GlobalHost.ConnectionManager.GetHubContext<ActivityHub>();
GetActivityLogs();
}
}
}

FIRST METHOD
First Solution change your javascript code like this. If this not works move to the second method:
$(function () {
var activityFromHub = $.connection.ActivityHub;
$.connection.hub.start().done(function () {
FetchLogs();
});
activityFromHub.client.displayLog = function () {
console.log('Hub Started');
FetchLogs();
}
});
SECOND METHOD:
Each client connecting to a hub passes a unique connection id. You can retrieve this value in the Context.ConnectionId property of the hub context. And i found there is nothing happening like this. You may try this solution.
I think the simplest solution for your question is to use groups.
http://www.asp.net/signalr/overview/guide-to-the-api/working-with-groups
Your hub class would contain methods to join a group:
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task LeaveGroup(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
and your hub will be look like this:
public static void StartLogging(string groupName)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<ActivityHub>();
context.Clients.Group(groupName).displayLog();
//calls the signalR client part to execute javascript method
//context.Clients.All.displayLog();
}
And change your javascript as like this:
$(function () {
var activityFromHub = $.connection.ActivityHub;
$.connection.hub.start().done(function () {
activityFromHub.server.joinGroup("Group1");
activityFromHub.server.StartLogging("Group1");
FetchLogs();
});
activityFromHub.client.displayLog = function () {
console.log('Hub Started');
FetchLogs();
}
});
I hope this will resolve your issue. If you are still facing issue. Please leave comments. Thank you.

Related

JWT Authentication ASP.NET Core MVC application

I've seen numerous examples of how to use JWT authentication with Angular, React, Vue etc... clients but can't find any examples of using JWT authentication with ASP.NET Core (specifically 2.2) Web App Mvc.
Does anyone have any examples or advice on how to do this?
Thanks,
You can use this class based on nuget package JWT 3.0.3
using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using Newtonsoft.Json;
using System;
namespace Common.Utils
{
public class JwtToken
{
private IJwtEncoder encoder;
private IJwtDecoder decoder;
/// <remarks>
/// This requires a key value randomly generated and stored in your configuration settings.
/// Consider that it is a good practice use keys as at least long as the output digest bytes
/// length produced by the hashing algorithm used. Since we use an HMAC-SHA-512 algorithm,
/// then we can provide it a key at least 64 bytes long.
/// <see cref="https://tools.ietf.org/html/rfc4868#page-7"/>
/// </remarks>
public string SecretKey { get; set; }
public JwtToken()
{
IJwtAlgorithm algorithm = new HMACSHA512Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider datetimeProvider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, datetimeProvider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
decoder = new JwtDecoder(serializer, validator, urlEncoder);
SecretKey = "";
}
public JwtToken(string secretKey) : this()
{
SecretKey = secretKey;
}
public bool IsTokenValid(string token)
{
return !string.IsNullOrWhiteSpace(DecodeToken(token));
}
public string GetToken(object payload)
{
try
{
return encoder.Encode(payload, SecretKey);
}
catch (Exception)
{
return encoder.Encode(new DataModel(payload), SecretKey);
}
}
public string DecodeToken(string token)
{
try
{
if (string.IsNullOrWhiteSpace(token) || token == "null")
{
return null;
}
return decoder.Decode(token, SecretKey, true);
}
catch (TokenExpiredException)
{
return null;
}
catch (SignatureVerificationException)
{
return null;
}
}
public T DecodeToken<T>(string token) where T : class
{
try
{
if (string.IsNullOrWhiteSpace(token))
{
return null;
}
return decoder.DecodeToObject<T>(token, SecretKey, true);
}
catch (TokenExpiredException)
{
return null;
}
catch (SignatureVerificationException)
{
return null;
}
catch (Exception)
{
var data = decoder.DecodeToObject<DataModel>(token, SecretKey, true).Data;
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(data));
}
}
}
public class DataModel
{
public DataModel(object data)
{
Data = data;
}
public object Data { get; set; }
}
}
Then in your Startup class Configure method set the jwt middleware
for check authentication status of each request:
app.Use((context, next) =>
{
// verify app access token if not another service call
var appAccessToken = context.Request.Headers["Authorization"];
if (appAccessToken.Count == 0)
{
context.Items["User"] = null;
}
else
{
var token = appAccessToken.ToString().Replace("Bearer ", "");
var jwtToken = new JwtToken(config.JwtTokenSecret); //you need a secret (with requirements specified above) in your configuration (db, appsettings.json)
if (string.IsNullOrWhiteSpace(token) || !jwtToken.IsTokenValid(token))
{
context.Response.StatusCode = 401;
return Task.FromResult(0);
}
dynamic user = jwtToken.DecodeToken<dynamic>(token);
var cachedToken = cache.Get(user.Id); //you need some cache for store your token after login success and so can check against
if (cachedToken == null || cachedToken.ToString() != token)
{
context.Response.StatusCode = 401;
return Task.FromResult(0);
}
context.Items["User"] = new Dictionary<string, string>() {
{ "FullName",user.Name?.ToString()},
{ "FirstName",user.FirstName?.ToString()},
{ "LastName",user.LastName?.ToString()},
{ "Role",user.Role?.ToString()},
{ "Email",user.Email?.ToString()}
};
}
return next();
});
And finally you need generate the token and return it after
authentication:
[AllowAnonymous]
public IActionResult Login(string username, string password)
{
User user = null; //you need some User class with the structure of the previous dictionary
if (checkAuthenticationOK(username, password, out user)) //chackAuthenticationOk sets the user against db data after a succesfull authentication
{
var token = new JwtToken(_config.JwtTokenSecret).GetToken(user); //_config is an object to your configuration
_cache.Set(user.id, token); //store in the cache the token for checking in each request
return Ok(token);
}
return StatusCode(401, "User is not authorized");
}
Add following code to startup
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Issuer"],
ValidAudience = Configuration["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SigningKey"]))
};
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env,, ILoggerFactory loggerFactory)
{
app.UseAuthentication();
}
Code for login action in AccountController
[Route("api/[controller]")]
public class AccountController : Controller
{
[AllowAnonymous]
[HttpPost]
[Route("login")]
public IActionResult Login([FromBody]LoginViewModel loginViewModel)
{
if (ModelState.IsValid)
{
var user = _userService.Authenticate(loginViewModel);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, loginViewModel.Username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken
(
issuer: _configuration["Issuer"],
audience: _configuration["Audience"],
claims: claims,
expires: DateTime.UtcNow.AddDays(10),
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["SigningKey"])),
SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
access_token = new JwtSecurityTokenHandler().WriteToken(token),
expires_in = (int)token.ValidTo.Subtract(DateTime.UtcNow).TotalSeconds,// TimeSpan.FromTicks( token.ValidTo.Ticks).TotalSeconds,
sub = loginViewModel.Username,
name = loginViewModel.Username,
fullName = user.FullName,
jobtitle = string.Empty,
phone = string.Empty,
email = user.EmailName,
});
}
}
}
I assume you have implemented JWT on the server side. To handle this on client side, first you have to add token to web browser local storage. Add to your main layout javascript (let's named it AuthService.js)
below code adds token to local storage after login button clicked. gettokenfromlocalstorage() retrieve token from local storage.
<script>
var token = "";
function Loginclick() {
var form = document.querySelector('form');
var data = new FormData(form);
var authsevice = new AuthService();
authsevice.LogIn(data.get("username").toString(), data.get("password").toString());
}
function gettokenfromlocalstorage() {
var authserv = new AuthService();
var mytoken = authserv.getAuth();
authserv.LogOut();
}
var AuthService = /** #class */ (function () {
function AuthService() {
this.authKey = "auth";
}
AuthService.prototype.LogIn = function (username, password) {
this.username = username;
this.password = password;
this.grant_type = "password";
this.client_id = "MyClientId";
var loginurl = "/api/Token/Auth";
var xhr = new XMLHttpRequest();
xhr.open("POST", loginurl, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify(this));
xhr.onreadystatechange = function () {
console.log("onreadystatechange");
};
xhr.onerror = function () {
var aaa = this.responseText;
};
xhr.onload = function () {
var data = JSON.parse(this.responseText);
var auth = new AuthService();
auth.setAuth(data);
};
};
AuthService.prototype.LogOut = function () {
this.setAuth(null);
return true;
};
AuthService.prototype.setAuth = function (auth) {
if (auth) {
localStorage.setItem(this.authKey, JSON.stringify(auth));
}
else {
localStorage.removeItem(this.authKey);
}
return true;
};
AuthService.prototype.getAuth = function () {
var i = localStorage.getItem(this.authKey);
return i === null ? null : JSON.parse(i);
};
AuthService.prototype.isLoggedIn = function () {
return localStorage.getItem(this.authKey) !== null ? true : false;
};
return AuthService;
}());
var aa = new AuthService();
var gettoken = aa.getAuth();
if (gettoken !== null) {
token = gettoken.token;
}
</script>
To add token to the header of each anchor tag put below script also to
your main layout.
<script>
var links = $('a');
for (var i = 0; i < links.length; i++) {
links[i].onclick = function check() {
addheader(this.href);
return false;
}
}
function addheader(object) {
let xhr = new XMLHttpRequest();
xhr.open("GET", object, true);
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
xhr.send(null);
xhr.onload = function () {
window.history.pushState("/", "", xhr.responseURL);
//mycontainer is a div for parialview content
$("#mycontainer").html(xhr.responseText);
window.onpopstate = function (e) {
if (e.state) {
$("html").html = e.state;
document.title = e.state.pageTitle;
}
};
};
}
</script>
Remember that using of this approach, each view has to be loaded as a partial view.
If you insert url address in a web browser bar directly this solution doesn't work. I haven't figured it out yet. That's why to manage token authentication is better using single page application, not multipage application.
You can use this boilerplate to understand how to implement JWT tokenization with .Net Core. In the project you can find JWT, Swagger and EF features.

How to live notification in MVC with SignalR?

I'm trying to make live notification using signalR. My project is running on localhost. But I don't see my notification when I set webconfig server-side. (although I did it with signalR)
When I run the 'internet' part of Chrome 's check item, I see that the request does not fall. how do I make this problem?
ajax code;
function updateNotification() {
$('#notiContent').empty();
$('#notiContent').append($('<li>Yükleniyor...</li>'));
$.ajax({
type: 'GET',
datatype : JSON,
contentType: 'application/json; charset=utf-8',
url: '/notification/GetNotificationFlows',
success: function (response) {
$('#notiContent').empty();
if (response.length == 0) {
$('#notiContent').append($('<li>Data yok..</li>'));
}
$.each(response, function (index, value) {
$('#notiContent').append($('<li>Yeni kişi : ' + value.flowName + ' (' + value.flowPhone + ') eklendi.</li>'));
});
},
error: function (error) {
console.log(error);
}
})
}
Global.asax;
string con = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
SqlDependency.Start(con);
}
protected void Session_Start(object sender, EventArgs e)
{
NotificationComponent NC = new NotificationComponent();
var currentTime = DateTime.Now;
HttpContext.Current.Session["LastUpdated"] = currentTime;
NC.RegisterNotification(currentTime);
}
protected void Application_End()
{
//here we will stop Sql Dependency
SqlDependency.Stop(con);
}
}
Notification component
public void RegisterNotification(DateTime currentTime)
{
string conStr = ConfigurationManager.ConnectionStrings["sqlConString"].ConnectionString;
string sqlCommand = #"SELECT [flowId],[flowName],[flowEMail],[flowPhone],[kaynakId] from [dbo].[flow] where [createDate] > #createDate";
//you can notice here I have added table name like this [dbo].[Contacts] with [dbo], its mendatory when you use Sql Dependency
using (SqlConnection con = new SqlConnection(conStr))
{
SqlCommand cmd = new SqlCommand(sqlCommand, con);
cmd.Parameters.AddWithValue("#createDate", currentTime);
if (con.State != System.Data.ConnectionState.Open)
{
con.Open();
}
cmd.Notification = null;
SqlDependency sqlDep = new SqlDependency(cmd);
sqlDep.OnChange += sqlDep_OnChange;
//we must have to execute the command here
using (SqlDataReader reader = cmd.ExecuteReader())
{
// nothing need to add here now
}
}
}
void sqlDep_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change)
{
SqlDependency sqlDep = sender as SqlDependency;
sqlDep.OnChange -= sqlDep_OnChange;
//from here we will send notification message to client
var notificationHub = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
notificationHub.Clients.All.notify("eklendi.");
//re-register notification
RegisterNotification(DateTime.Now);
}
}
public List<flow> GetFlows(DateTime afterDate)
{
using (smartCMSEntities dc = new smartCMSEntities())
{
return dc.flow.Where(a => a.createDate > afterDate).OrderByDescending(a => a.createDate).ToList();
}
}
Notification Controller
public JsonResult GetNotificationFlows()
{
var notificationRegisterTime = Session["LastUpdated"] != null ? Convert.ToDateTime(Session["LastUpdated"]) : DateTime.Now;
NotificationComponent NC = new NotificationComponent();
var list = NC.GetFlows(notificationRegisterTime);
Session["LastUpdate"] = DateTime.Now;
return new JsonResult { Data = list, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
Notification Hub
public class NotificationHub : Hub
{
//public void Hello()
//{
// Clients.All.hello();
//}
}
SQL (for sql dependency)
ALTER DATABASE [db_name] SET ENABLE_BROKER with rollback immediate;
I had the same problem you need to create your function inside your Hub.
Let say
public class NotificationHub : Hub
{
public static void Send()
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
context.Clients.All.displayStatus();
}
}
And call it in your html
function updateNotificationCount() {
$('span.count').show();
var count = 0;
count = parseInt($('span.count').html()) || 0;
count++;
$('span.noti').css("color", "white");
// $('span.count').css({ "background-color": "red", "color": "white" });
$('span.count').html(count);
}
// signalr js code for start hub and send receive notification
var hub = $.connection.notificationHub;
// Declare a function on the hub hub so the server can invoke it
hub.client.displayStatus = function () {
updateNotificationCount();
};
// Start the connection
$.connection.hub.start();

can't receive data from post call in my controller

I have a html editor and I am trying to send the data via post call. I can see the data in javascript and it hits SendEmail method but in my controller request is empty. Why can't see the data in my controller?
function SelectedReceipientsViewModel() {
var self = this;
self.packageDataForEmail = function () {
var request= tinymce.get("mailTextArea").getContent();
return request;
};
self.submit = function () {
var request = self.packageDataForEmail();
$.post("SendEmail",request, function () {
}).done(function () {
}).fail(function () {
});
};
}
my controller:
[HttpPost]
public void SendEmail(string request)
{
string message = request;
......
...
}
I created DtoEblast and add my property inside. for some reason string did not work. anyways this is working code.
self.packageDataForEmail = function () {
var Request = function (tinymceText) {
this.TinymceText = tinymceText;
};
var tinymceText = tinymce.get("mailTextArea").getContent();
var request = new Request(tinymceText);
return request;
};
[HttpPost]
public void SendEmail(DtoEblast request)
{
string message = request.TinymceText;
and it was giving 500 error without this line
<script>tinymce.init({forced_root_block : "",selector:'textarea'});</script>

Can't invoke client functions from the Server

I'm working on a small concept trying to get to work with SignalR.
In this scenario all connected clients need to refresh when one of the clients perform an update, this update is performed through an Ajax request.
Because there might be different instances of the page, depending on a parameter, I'm using groups for this.
The client side code looks as followed:
<script>
window.onload = function () {
DoRefresh();
}
$(function () {
var hub = $.connection.commonHub;
hub.client.refresh = function () {
DoRefresh();//This doesn't get called for the other clients
};
$.connection.hub.start();
$.connection.hub.start().done(function () {
hub.server.join("MyPage" + #Model.Id + "");
});
});
function htmlEncode(value) {
var encodedValue = $('<div />').text(value).html();
return encodedValue;
}
function DoRefresh() {
$.ajax({
url: '#Url.Action("LoadData")',
cache: false,
data: "&id=" + #Model.Id + "",
success: function (html) {
$("#conversation").empty();
$("#conversation").append(html);
},
error: function (xhr, status, err) {
alert('Response code:' + xhr.status + '\r\n[Error:' + err + '] ' + status);
}
});
return false;
};
function DoUpdate() {
$.ajax({
url: '#Url.Action("DoUpdate")',
cache: false,
data: "&id=" + #Model.Id + "&posterID=" + #userAccount.AccountID + "&message=" + $("#Textbox").val().replaceAll("\n", "[br]"),
success: function () {
$("#Textbox").empty();
DoRefresh();
},
error: function () {
$("#Textbox").empty();
DoRefresh();
}
});
return false;
};
</script>
In my controller, following functions are part of this scenario:
public class MyController : Controller
{
private Hubs.CommonHub hub = new Hubs.CommonHub();
//Some other Controller Methods
public PartialViewResult LoadData(int id)
{
MyModel item = Connection.DB.MyData.FirstOrDefault(x => x.Id == id);
return PartialView(item);
}
public virtual EmptyResult DoUpdate(int id, int posterID, string message)
{
message = message.Replace("[br]", "\r\n");
MyModel item = Connection.DB.MyData.FirstOrDefault(x => x.Id == id);
Account poster = Connection.DB.Accounts.FirstOrDefault(x => x.Id == posterID);;
item.Updates.Add(new Update()
{
PosterId = posterID,
Poster = poster,
Id = id,
Item = item,
PostDate = DateTime.UtcNow,
Message = message.Replace(Environment.NewLine,"\r\n")
});
Connection.DB.SaveChanges();
hub.Refresh("MyPage" + item.Id);
return null;
}
}
And finally, my hub class looks like this:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNet.SignalR;
using MyProject.Models;
namespace MyProject.Hubs
{
public class CommonHub : Hub
{
private string myInfo;
public override Task OnConnected()
{
myInfo = Context.ConnectionId;
return base.OnConnected();
}
public Task Join(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task Leave(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
public void Refresh(string groupName)
{
var context = GlobalHost.ConnectionManager.GetHubContext<CommonHub>();
context.Clients.Group(groupName).Refresh();
}
}
}
The Join task in the hub is called with every connect of a new browser window. The Refresh method is invoked on the hub, but only the invoking window has its page refreshed. The client side refresh function only gets invoked once when debugging, probably for the invoking client.
I have no idea why the other clients are not doing their updates, please help.

ASP.NET MVC AsyncController Exception Handling

On ASP.NET MVC, I try to write an async Controller action with the old asynchronous programming model (actually, it is the current one, new one is still a CTP).
Here, I am trying to run 4 operations in parallel and it worked great. Here is the complete code:
public class SampleController : AsyncController {
public void IndexAsync() {
AsyncManager.OutstandingOperations.Increment(4);
var task1 = Task<string>.Factory.StartNew(() => {
return GetReponse1();
});
var task2 = Task<string>.Factory.StartNew(() => {
return GetResponse2();
});
var task3 = Task<string>.Factory.StartNew(() => {
return GetResponse3();
});
var task4 = Task<string>.Factory.StartNew(() => {
return GetResponse4();
});
task1.ContinueWith(t => {
AsyncManager.Parameters["headers1"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
task2.ContinueWith(t => {
AsyncManager.Parameters["headers2"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
task3.ContinueWith(t => {
AsyncManager.Parameters["headers3"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
task4.ContinueWith(t => {
AsyncManager.Parameters["headers4"] = t.Result;
AsyncManager.OutstandingOperations.Decrement();
});
task3.ContinueWith(t => {
AsyncManager.OutstandingOperations.Decrement();
}, TaskContinuationOptions.OnlyOnFaulted);
}
public ActionResult IndexCompleted(string headers1, string headers2, string headers3, string headers4) {
ViewBag.Headers = string.Join("<br/><br/>", headers1, headers2, headers3, headers4);
return View();
}
public ActionResult Index2() {
ViewBag.Headers = string.Join("<br/><br/>", GetReponse1(), GetResponse2(), GetResponse3(), GetResponse4());
return View();
}
}
And these below ones are the methods that the async operations are running:
string GetReponse1() {
var req = (HttpWebRequest)WebRequest.Create("http://www.twitter.com");
req.Method = "HEAD";
var resp = (HttpWebResponse)req.GetResponse();
return FormatHeaders(resp.Headers);
}
string GetResponse2() {
var req2 = (HttpWebRequest)WebRequest.Create("http://stackoverflow.com");
req2.Method = "HEAD";
var resp2 = (HttpWebResponse)req2.GetResponse();
return FormatHeaders(resp2.Headers);
}
string GetResponse3() {
var req = (HttpWebRequest)WebRequest.Create("http://google.com");
req.Method = "HEAD";
var resp = (HttpWebResponse)req.GetResponse();
return FormatHeaders(resp.Headers);
}
string GetResponse4() {
var req = (HttpWebRequest)WebRequest.Create("http://github.com");
req.Method = "HEAD";
var resp = (HttpWebResponse)req.GetResponse();
return FormatHeaders(resp.Headers);
}
private static string FormatHeaders(WebHeaderCollection headers) {
var headerStrings = from header in headers.Keys.Cast<string>()
select string.Format("{0}: {1}", header, headers[header]);
return string.Join("<br />", headerStrings.ToArray());
}
I have also Index2 method here which is synchronous and does the same thing.
I compare two operation execution times and there is major difference (approx. 2 seconds)
But I think I am missing lots of things here (exception handling, timeouts, etc). I only implement the exception handling on task3 but I don't think it is the right way of doing that.
What is the healthiest way of handling exceptions for this kind of operations?
Before accessing the result of a task you should test whether it completed successfully. If there was an error you should not try to access the results but instead do something with the exception:
task1.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();
});

Resources