I'm using html2canvas to take screenshot of a div and then upload it to the server. After the image is saved on the server, the page on client refreshes. And that is something I don't want. This is the code, that saves the picture:
public void SaveImage(string imageString, int id)
{
string s = imageString.Replace("data:image/png;base64,", "");
byte[] imageB = Convert.FromBase64String(s);
var filePath = Path.Combine(Server.MapPath("~/screens/" + id));
Directory.CreateDirectory(filePath);
using (var ms = new MemoryStream(imageB))
{
using (var image = Image.FromStream(ms))
{
image.Save(filePath + "/screen.png", ImageFormat.Png);
}
}
}
This is the Javascript code
html2canvas(document.querySelector("#screen")).then(function (canvas) {
var data = canvas.toDataURL("image/png");
$.ajax({
url: '#Url.Action("SaveImage")',
type: 'POST',
data: { imageString: data, id: #ViewBag.id },
dataType: 'json'
});
});
I have tried some advices I found online like preventDefault() in jQuery. But that's about it. I feel like all the other issues and solutions does not affect me.
EDIT: Simple HTML that refreshes the page too
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Test</title>
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/html2canvas.js"></script>
<script>
$(document).ready(function () {
html2canvas(document.querySelector("#screen")).then(function (canvas) {
var data = canvas.toDataURL("image/png");
$.ajax({
url: '#Url.Action("SaveImage")',
type: 'POST',
data: { imageString: data, id: 2 },
dataType: 'json',
});
});
});
</script>
</head>
<body>
<div id="screen">Some text</div>
</body>
</html>
EDIT 1: The whole controller with SaveImage method
using System;
using System.Linq;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;
using BcVnc.Models;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
namespace BcVnc.Controllers
{
[Authorize(Roles = "Admin, User")]
public class ConnectionController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Connection
public ActionResult Index(int id, bool viewOnly)
{
int access = checkUserAccess(id, viewOnly);
if(access != 0)
{
ViewBag.id = id;
if (access == 1 & viewOnly == true)
ViewBag.viewOnly = true.ToString().ToLower();
else if(access == 1 && viewOnly == false)
ViewBag.viewOnly = false.ToString().ToLower();
else
ViewBag.viewOnly = true.ToString().ToLower();
return View();
}
else
{
return View("Error");
}
}
private int checkUserAccess(int id, bool viewOnly)
{
var userId = User.Identity.GetUserId();
var userDevice = db.UserDevices.Where(ud => ud.UserId == userId).FirstOrDefault(ud => ud.DeviceId == id);
var device = db.Devices.FirstOrDefault(d => d.Id == id);
ViewBag.name = device.Name;
if (userDevice == null)
return 0;
else
{
if (userDevice.ViewOnly == false)
return 1;
else
return -1;
}
}
public void SaveImage(string imageString, int id)
{
string s = imageString.Replace("data:image/png;base64,", "");
byte[] imageB = Convert.FromBase64String(s);
var filePath = Path.Combine(Server.MapPath("~/screens/" + id));
Directory.CreateDirectory(filePath);
using (var ms = new MemoryStream(imageB))
{
using (var image = Image.FromStream(ms))
{
image.Save(filePath + "/screen.png", ImageFormat.Png);
}
}
}
}
}
Not sure how come I was not able to find this before, but the whole problem was in Visual Studio settings. The refresh probably would not happen outside the localhost: Refresh in browser when uploading mvc file c #
After changing that in settings, no refresh anymore.
Related
I have Search View Where user enters Employee ID, Upon Clicking Button an Action Method is Executed and user is Redirected to Employee Details View.
If Employee Id not Matches I want to Retain the Same Search View and display a Label with Employee Details not Found Message in MVC,
Please help on doing the above Fucntionality in my Controller.
[HttpGet]
public async Task<ActionResult> Details(string firstName)
{
var empDetails = await _context.EmpDetails
.FirstOrDefaultAsync(m => m.FirstName == firstName);
if (empDetails == null)
{
// ???
}
return View(empDetails);
}
It is good practice to have ViewModel classes. Make one, and use it to transfer domain objects and messages to your view. Like this:
class EmpDetailsViewModel
{
public EmpDetail Emp { get;set; }
public string Message { get;set; }
}
[HttpGet]
public async Task<ActionResult> Details(string firstName)
{
var vm = new EmpDetailsViewModel();
var empDetails = await _context.EmpDetails
.FirstOrDefaultAsync(m => m.FirstName == firstName);
if (empDetails == null)
{
vm.Message = "Employee not found (or whatever)"
}
else
{
vm.Emp = empDetails;
}
return View(vm);
}
Then, in your view, just do
if (Model.Emp == null)
{
<span>#Model.Message</span>
}
else
{
<div>Emp details stuff </div>
}
What I understand is you want to return a message when Employee is not found.
Try this
[HttpGet]
public async Task<ActionResult> Details(string firstName)
{
var empDetails = await _context.EmpDetails
.FirstOrDefaultAsync(m => m.FirstName == firstName);
if (empDetails == null)
{
return Content("Employee not found");
}
return View(empDetails);
}
In view extract the message from the response.
--Edit
You can do an Ajax call like below to this action method
<script type="text/javascript">
$(function () {
$("#btnGetDetails").click(function () {
$.ajax({
type: "GET",
url: "/Employee/Details",
data: '{firstName: "' + $("#txtSearch").val() + '" }',
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (response) {
alert(response.responseText);
},
failure: function (response) {
alert(response.responseText);
},
error: function (response) {
alert(response.responseText);
}
});
});
});
</script>
success call back will be triggered and the message will be available in the view. (Didn't test the script)
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.
I am trying to implement JWT authentication between a simple UI and Web API. Both are .NET Core 2.0 and I'm using Ajax to call the API functions. I am able to login with no problem whatsoever and it passes the Bearer token back to me; however, when I decorate my SaveProduct method with Authorize, call it with Ajax, and pass the token, it returns 401 Unauthorized. As soon as I remove the Authorize it works fine. I've worked for days to figure out what I'm missing and have created several iterations of my apps to see if that would help, but have not been able to figure it out. I've scoured the web and tried numerous suggestions, but as yet to no avail.
I would appreciate any insights you might have. Thanks in advance!
Here is my code:
WEB API -- STARTUP
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace SportsStoreAngAPI
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("AllowAllOrigins",
builder => builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.Build());
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superdooperultrasafeKey#999")),
RequireSignedTokens = false,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "http://localhost:3700",
ValidAudience = "http://localhost:3700"
};
});
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors("AllowAllOrigins");
app.UseStatusCodePages();
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
}
WEB API -- LOGIN CONTROLLER
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using SportsStoreAngAPI.Models;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SportsStoreAngAPI.Controllers
{
[Route("[controller]")]
public class LoginController : Controller
{
[HttpPost]
public JsonResult Login([FromBody] LoginModel user)
{
LoginReturnModel l = new LoginReturnModel();
if (user == null)
{
l.Success = false;
l.Token = null;
}
if (user.Name == "admin" && user.Password == "secret")
{
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("superSecretKey#345"));
var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var tokenOptions = new JwtSecurityToken(
//issuer: "http://localhost:3700",
//audience: "http://localhost:3700",
//claims: new List<Claim>(),
expires: DateTime.Now.AddDays(5),
signingCredentials: signinCredentials
);
var tokenString = new JwtSecurityTokenHandler().WriteToken(tokenOptions);
l.Success = true;
l.Token = tokenString;
}
else
{
l.Success = false;
l.Token = null;
}
JsonResult jR = new JsonResult(l);
return jR;
}
}
}
WEB API - PRODUCTS CONTROLLER
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SportsStoreAngAPI.Models;
using System.Collections.Generic;
namespace SportsStoreAngAPI.Controllers
{
[Produces("application/json")]
[Route("[controller]")]
public class ProductsController : Controller
{
[HttpGet]
public IEnumerable<Product> GetProducts()
{
List<Product> p = new List<Product>();
return p;
}
[HttpPost, Authorize]
public Product SaveProduct([FromBody]Product p)
{
return p;
}
}
}
FRONT END UI
#{
ViewData["Title"] = "Home Page";
}
<button class="btn btn-primary" type="button" onclick="loginAPI()">Login</button>
<div name="puthere" id="puthere"></div>
<button class="btn btn-primary" type="button" onclick="postNewRecord()">Post New Record</button>
<div name="puthere2" id="puthere2"></div>
<script>
$(document).ready(function () {
});
var token = '';
var loginAPI = function () {
var myData =
{
Name: 'admin',
Password: 'secret'
};
var myDataString = JSON.stringify(myData);
$.ajax({
type: 'POST',
url: 'http://localhost:3700/login',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: myDataString,
success: function (results) {
$('#puthere').empty();
var html = '';
$.each(results, function (index, value) {
html = html + '<p>' + index + ' : ' + value + '</p>';
if (index == 'token') {
token = value;
};
});
$('#puthere').append(html);
},
error: function (xhr, textStatus, error) {
alert(Error);
alert(xhr.statusText);
alert(textStatus);
alert(error);
}
});
};
var postNewRecord = function () {
var myData =
{
Id: '0',
Name: 'Soccer Ball',
Category: 'Sports',
Description: 'Round ball for playing the beautiful game',
Price: '13.75'
};
var myDataString = JSON.stringify(myData);
$.ajax({
type: 'POST',
url: 'http://localhost:3700/products',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: myDataString,
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', token)
},
success: function() {
alert('Saved successfully!');
},
error: function () {
alert('Something went very wrong!');
}
});
};
</script>
EDIT: In your Startup-Class you have defined a different key than the one you are using when you generate your token. They also have to be the same. I'd suggest you to read a little about JWT and how it works here.
So first thing I noticed is that you removed the issuer from the token you generate in the back-end. You have to define them because you also set them up in your Startup-Class.
Secondly your ajax request header must be set like this:
xhr.setRequestHeader('Authorization', 'Bearer ' + token)
You have to define the authorization-type within the headers-value.
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.
My View :-
<form class="commentform commentright" enctype="multipart/form-data" >
<textarea class="simpleta required" name="Body" placeholder="Discuss this vehicle with other members of Row52."></textarea>
<input type="file" id="fileuploadfield" name="fileuploadfield"/>
<input type="submit" value="Submit" class="btn btn-inverse btn-small"/>
<input type="hidden" name="ParentId" value="0"/>
<input type="hidden" name="VehicleId" value="#Model.VehicleId"/>
<span class="commentcount"><span class="numberofcomments">#Model.Count</span>#count</span>
<div class="feedback"></div>
<img id="img" src="" />
</form>
JQuery :-
submitComment: function (form, type) {
var self = this;
var $this = $(form);
var formData = $this.serialize();
var $message = $this.find('.feedback');
$message.hide();
$message.html('');
var val = validation({ $form: $this });
if (val.checkRequired()) {
$this.indicator({ autoStart: false, minDuration: 100 });
$this.indicator('start');
var files = $("#fileuploadfield").get(0).files;
if (files.length > 0) {
if (window.FormData !== undefined) {
var data = new FormData();
for (var i = 0; i < files.length; i++) {
data.append("file" + i, files[i]);
}
}
else {
alert("This browser doesn't support HTML5 multiple file uploads!");
}
}
else {
alert("This");
}
$.ajax('/api/v2/comment/Post/', {
type: 'POST',
contentType: 'multipart/form-data',
// I have also use contentType: false,
processData: false,
data: formData
}).done(function (d) {
Controller :-
public Task<HttpResponseMessage> Post()
{
var provider = new MultipartMemoryStreamProvider();
var task1 = Request.Content.ReadAsMultipartAsync(provider);
var userId = User.Id;
return task1.Then(providerResult =>
{
var file = GetFileContent(providerResult);
var task2 = file.ReadAsStreamAsync();
string originalFileName = file.Headers.ContentDisposition.FileName.Replace("\"", "");
string extension = Path.GetExtension(originalFileName);
string fileName = Guid.NewGuid().ToString() + extension;
return task2.Then(stream =>
{
if (stream.Length > 0)
{
var kernel = WindsorContainerFactory.Create(KernelState.Thread, ConfigurationState.Web);
CloudBlobContainer container = GetContainer();
var userRepo = kernel.Resolve<IUserRepository>();
var logger = kernel.Resolve<ILogger>();
logger.Fatal("Original File Name: " + originalFileName);
logger.Fatal("Extension: " + extension);
logger.Fatal("File Name: " + fileName);
var user = userRepo.FirstOrDefault(x => x.Id == userId);
var path = CreateImageSize(stream, 500, fileName, userId, container, logger);
if (user != null)
{
user.AvatarOriginalFileName = fileName;
user.AvatarOriginalAbsolutePath =
ConfigurationManager.AppSettings["CdnUserEndpointUrl"] + path;
user.AvatarOriginalRelativePath = path;
user.AvatarCroppedAbsolutePath = "";
user.AvatarCroppedFileName = "";
user.AvatarCroppedRelativePath = "";
userRepo.Update(user);
}
else
{
Logger.Error("User is null. Id: " + userId.ToString());
}
kernel.Release(userRepo);
kernel.Release(logger);
kernel.Dispose();
}
var response = Request.CreateResponse(HttpStatusCode.Moved);
response.Headers.Location = new Uri("/Account/Avatar", UriKind.Relative);
return response;
});
});
}
Following Error get :-
"Invalid 'HttpContent' instance provided. It does not have a content type header starting with 'multipart/'. Parameter name: content"
There's no standard Task.Then method in .NET framework, and I couldn't find any implementation details in the code you posted. I'd assume it was taken from here:
Processing Sequences of Asynchronous Operations with Tasks
If that's the case, you may be loosing AspNetSynchronizationContext context inside your Task.Then lambdas, because the continuation may happen on a different ASP.NET pool thread without proper synchronization context. That would explain why HttpContent becomes invalid.
Try changing the implementation of Task.Then like this:
public static Task<T2> Then<T1, T2>(this Task<T1> first, Func<T1, Task<T2>> next)
{
if (first == null) throw new ArgumentNullException("first");
if (next == null) throw new ArgumentNullException("next");
var tcs = new TaskCompletionSource<T2>();
first.ContinueWith(delegate
{
if (first.IsFaulted) tcs.TrySetException(first.Exception.InnerExceptions);
else if (first.IsCanceled) tcs.TrySetCanceled();
else
{
try
{
var t = next(first.Result);
if (t == null) tcs.TrySetCanceled();
else t.ContinueWith(delegate
{
if (t.IsFaulted) tcs.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCanceled) tcs.TrySetCanceled();
else tcs.TrySetResult(t.Result);
}, TaskScheduler.FromCurrentSynchronizationContext());
}
catch (Exception exc) { tcs.TrySetException(exc); }
}
}, TaskScheduler.FromCurrentSynchronizationContext());
return tcs.Task;
}
Note how the continuations are now scheduled using TaskScheduler.FromCurrentSynchronizationContext().
Ideally, if you can use .NET 4.5, you should be using async/await: Using Asynchronous Methods in ASP.NET 4.5. This way, the thread's synchronization context is automatically captured for await continuations.
The following may facility porting:
Implementing Then with Await.