I'm building a Tic-Tac_toe api game. The game work ok, until one of the players press on a square that finish the game(either he wins or fill the board).
When I enabled all CLR exceptions for the last move, this is the exception I saw for line var user = await UserManager.FindByIdAsync(userId);
"Additional information: A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe."
Importent to know when a player click a square, it is a 'POST' method.
Here's my code:
public class GamesApiController : ApiController
{
ApplicationDbContext context = new ApplicationDbContext();
private ApplicationUserManager _userManager;
public IEnumerable<ApplicationUser> Get()
{
return context.Users;
}
public ApplicationUserManager UserManager
{
get
{
return _userManager ?? System.Web.HttpContext.Current.Request.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
private set
{
_userManager = value;
}
}
#region Methods
/// <summary>
/// update the server data by reciving the model and square and returns the new model
/// </summary>
/// <param name="_model"></param>
/// <param name="squareId"></param>
/// <returns></returns>
//square clicked via post
[Route("api/gamesapi/{squareId}")]
public async Task<HttpResponseMessage> Post([FromBody]GameModel model, int squareId)
{
HttpResponseMessage response;
if (model == null)
{
//Utilites.CreateMsgCookie(Response, "Error", "Sorry, an unknown error has occurred");
response = Request.CreateErrorResponse(HttpStatusCode.NotFound, "model wasn't found");
return response;
}
//GameModel model = JsonConvert.DeserializeObject<GameModel>(_model);
Game game = GetGameById(model.Game.GameId);
if (game == null)
{
response = Request.CreateErrorResponse(HttpStatusCode.NotFound, "game wasn't found");
}
else
{
if (game.UserIdTurn == game.User1Id) //pressing user is user1
{
ChangeSquareState(game, squareId, true);
game.UserIdTurn = game.User2Id;
}
else //game.UserIdTurn == game.User2Id - pressing user is user2
{
ChangeSquareState(game, squareId, false);
game.UserIdTurn = game.User1Id;
}
SquareState[] board = new SquareState[] {game.Square1,game.Square2,game.Square3,game.Square4,
game.Square5,game.Square6,game.Square7,game.Square8,game.Square9};
if (didPlayerWin(board))
{
game.WinnerId = model.User.Id;
await UpdateUserGameState(1, game.User1Id);
await UpdateUserGameState(2, game.User2Id);
game.IsGameOver = true;
}
else
{
bool isBoardFull = true;
for (int i = 0; i < board.Length; i++)
{
if (board[i] == SquareState.Blank)
{
isBoardFull = false;
break;
}
}
if (isBoardFull)
{
await UpdateUserGameState(3, game.User1Id);
await UpdateUserGameState(3, game.User2Id);
game.IsGameOver = true;
}
}
context.SaveChanges();
response = Request.CreateResponse(HttpStatusCode.OK, game);
}
return response;
}
/// <summary>
/// When a game is over, recive a gameState and update the user. 1 for a win, 2 for loss, 3 for aa draw
/// </summary>
/// <param name="gameState"></param>
private async Task UpdateUserGameState(int gameState, string userId)
{
var user = await UserManager.FindByIdAsync(userId);
switch (gameState)
{
case 1:
user.GamesWon++;
break;
case 2:
user.GamesLost++;
break;
case 3:
user.GamesDraw++;
break;
default:
break;
}
UserManager.UpdateAsync(user);
}
[HttpGet]
[Route("api/gamesapi/{gameId}")]
/// <summary>
/// method to bring the latest game's state from the context and send it back in a GameModel
/// </summary>
/// <param name="_model"></param>
/// <returns></returns>
public HttpResponseMessage Get(int gameId)
{
HttpResponseMessage response;
Game game = GetGameById(gameId);
if (game == null)
{
response = Request.CreateErrorResponse(HttpStatusCode.NotFound, "game wasn't found");
}
else
{
response = Request.CreateResponse(HttpStatusCode.OK, game);
}
return response;
}
/// <summary>
/// method that check if the board have a line(3 squars in a row)
/// of the same element of user1 or user2 , defult - returns fault
/// </summary>
/// <param name="board"></param>
/// <returns></returns>
private bool didPlayerWin(SquareState[] board)
{
}
/// <summary>
/// change the SquareState of a specific square of the sent game according to the pressing user
/// </summary>
/// <param name="game"></param>
/// <param name="SquareId"></param>
/// <param name="_isUser1"></param>
private void ChangeSquareState(Game game, int SquareId, bool _isUser1)
{
}
/// <summary>
/// get game from context by gameId , Defult result - null
/// </summary>
/// <param name="gameId"></param>
/// <returns></returns>
private Game GetGameById(int gameId)
{
}
#endregion
}
I think you missed to wait for UpdateUserAsync() in method UpdateUserGameState():
private async Task UpdateUserGameState(int gameState, string userId)
{
var user = await UserManager.FindByIdAsync(userId);
// ...
// add missing await
await UserManager.UpdateAsync(user);
}
Related
I have the following code for a MQTT Subscriber in a Background Task:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using PickByLight.BackgroundTask.Models;
using PickByLight.Database.Wrapper.Interfaces;
using PickByLight.Logic;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
namespace PickByLight.BackgroundTask
{
/// <summary>
/// Hosted MQTT Background Service
/// </summary>
public class HostedMQTTService : IHostedService, IDisposable
{
private readonly Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
/// <summary>
/// MQTT Client
/// </summary>
private MqttClient MqttClient { get; set; }
/// <summary>
/// Name of the Pick by Light
/// </summary>
private string PickByLight_Name { get; set; }
/// <summary>
/// MQTT is activated
/// </summary>
private bool MqttIsActive { get; set; }
/// <summary>
/// IP Adress of the MQTT URL
/// </summary>
private string MqttURL { get; set; }
/// <summary>
/// Storage Process for an material
/// </summary>
private MaterialStorageProcess StorageProcess { get; set; }
/// <summary>
/// Service Scope Factory
/// </summary>
private IServiceScopeFactory ServiceScopeFactory { get; set; }
/// <summary>
/// Configuration
/// </summary>
private IConfiguration Configuration { get; set; }
/// <summary>
/// Logger
/// </summary>
private readonly ILogger<HostedMQTTService> _logger;
/// <summary>
/// Constructor
/// </summary>
/// <param name="configuration"></param>
public HostedMQTTService(IConfiguration configuration, ILogger<HostedMQTTService> logger, IServiceScopeFactory serviceScopeFactory)
{
this.PickByLight_Name = configuration.GetValue<string>("PickByLight_Name");
this.MqttURL = configuration.GetValue<string>("MQTTUrl");
this.MqttIsActive = configuration.GetValue<bool>("MQTTConnection");
this.ServiceScopeFactory = serviceScopeFactory;
this.Configuration = configuration;
this._logger = logger;
}
/// <summary>
/// Start the Task of the Background Service
/// </summary>
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Background-Service started...");
while (true)
{
try
{
//No Object is created
if (this.MqttClient == null)
{
_logger.LogInformation("Try to establishe new MQTT Client");
this.MqttClient = CreateNewMqttConnection();
}
else if (this.MqttClient.IsConnected == false)
{
_logger.LogInformation("MQTT Client is disconnected... Try to reconnect!");
this.MqttClient = CreateNewMqttConnection();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ein schwerwiegender Fehler im MQTT Background-Service ist aufgetreten.");
}
}
}
/// <summary>
/// Prints out all received messages
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Mqtt_Message_Received(object sender, MqttMsgPublishEventArgs e)
{
try
{
var agcMessage = Encoding.UTF8.GetString(e.Message);
_logger.LogInformation("Topic: " + e.Topic + " | Nachricht: " + agcMessage + " | QOS: " + e.QosLevel);
var resultString = Encoding.UTF8.GetString(e.Message);
MqttReadTopicClass mqttContent = JsonConvert.DeserializeObject<MqttReadTopicClass>(resultString);
using (var scope = this.ServiceScopeFactory.CreateScope())
{
var storageConfigurationManager = scope.ServiceProvider.GetService<IStorageConfigurationManager>();
var storageElementManager = scope.ServiceProvider.GetService<IStorageElementManager>();
this.StorageProcess = new MaterialStorageProcess(storageConfigurationManager, storageElementManager, this.Configuration);
StorageProcess.Remove(mqttContent.storageLocation);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Schwerwiegender Fehler beim Lesen von MQTT Nachrichten");
}
}
/// <summary>
/// Create new MQTT connection if connection is lost or doesn't exist
/// </summary>
private MqttClient CreateNewMqttConnection()
{
_logger.LogInformation("Create MQTT Client");
MqttClient client = new MqttClient(this.MqttURL, 32005, false, null, null, MqttSslProtocols.None);
string clientId = Guid.NewGuid().ToString();
client.MqttMsgPublishReceived += Mqtt_Message_Received;
client.Connect(clientId);
client.Subscribe(new string[] { "buttonpress_sepioo_pdi/" + this.PickByLight_Name }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
_logger.LogInformation("MQTT Client created");
return client;
}
/// <summary>
/// Stop the Task of the Background Service
/// </summary>
public async Task StopAsync(CancellationToken cancellationToken)
{
//Stop called without start
if (_executingTask == null)
{
return;
}
try
{
//Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
//wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
/// <summary>
/// Dispose the Background Service
/// </summary>
public void Dispose()
{
_stoppingCts.Cancel();
}
}
}
In my startup.cs File i am doing the following:
//Register Background Task
services.AddHostedService<HostedMQTTService>();
The problem is, that it seems to me that the hosted service is blocking the user-interface/webserver threads because i can not access the url of the .net 6 mvc application.
Could you give me a hint or a solution to this problem?
Thanks.
You will need to change your StartAsync-method to something like this:
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
_logger.LogInformation("Background-Service started...");
while (!cancellationToken.IsCancellationRequested)
{
try
{
//No Object is created
if (this.MqttClient == null)
{
_logger.LogInformation("Try to establish new MQTT Client");
this.MqttClient = CreateNewMqttConnection();
}
else if (this.MqttClient.IsConnected == false)
{
_logger.LogInformation("MQTT Client is disconnected... Try to reconnect!");
this.MqttClient = CreateNewMqttConnection();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ein schwerwiegender Fehler im MQTT Background-Service ist aufgetreten.");
}
}
});
}
You are blocking the process, because your hosted service never starts.
Remove the while(true) loop from the Start method.
Ensure base.StartAsync(cancellationToken); is always called.
Edit: I saw you implement the IHostedService interface. Try to inherits from the BackgroundService class
I'm training with the sample web application.
For unknown reason .Net Core does not route POST requests to corresponding method.
Get methods are working but POST not even being fired.
POST requests return 404.
I already tried to add annotation like that but it didn't help.
Could someone pleas
[HttpPost("envelope")]
Here's the code of my controller.
namespace Receiver.Controllers
{
[Route("[controller]")]
[ApiController]
public class EnvelopesController : ControllerBase
{
private readonly ReceiverDbContext _dbContext;
private readonly ILogger _logger;
public EnvelopesController(ReceiverDbContext dbContext, ILogger logger)
{
_dbContext = dbContext;
_logger = logger;
}
/// <summary>
/// GET (Read all) /envelopes
/// </summary>
/// <returns></returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerator<EnvelopeDTO>>> GetAll()
{
_logger.LogInformation("Hello from the Get() method!");
var envelopes = await _dbContext.Envelope.Include(e => e.Type).ToArrayAsync();
return Ok(envelopes.Select(e => e.ToDTO()));
}
/// <summary>
/// GET (Read) /envelope/{id}
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<EnvelopeDTO>> Get(int id)
{
var envelope = await _dbContext.Envelope.Include(e => e.Type).FirstOrDefaultAsync(s => s.Id == id);
if (envelope == null)
return NotFound();
return Ok(envelope.ToDTO());
}
/// <summary>
/// POST (Create) /envelope
/// </summary>
/// <param name="envelopeDto"></param>
/// <returns></returns>
[HttpPost("envelope")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult<EnvelopeDTO>> Create([FromBody] EnvelopeDTO envelopeDto)
{
_logger.LogInformation("Hello from the Post() method!");
if (string.IsNullOrEmpty(envelopeDto.СontentX))
return BadRequest();
var #type = await _dbContext.EnvelopeType.FirstOrDefaultAsync(t => t.Id == envelopeDto.TypeId);
if (#type == null)
return NotFound();
var #user = await _dbContext.User.FirstOrDefaultAsync(u => u.UserName == envelopeDto.User);
if (#user == null)
return NotFound();
var existingEnvelope = await _dbContext.Envelope.FindAsync(envelopeDto.СontentX);
if (existingEnvelope != null)
return Conflict();
var envelopeToAdd = envelopeDto.ToModel(#type, #user);
_dbContext.Envelope.Add(envelopeToAdd);
await _dbContext.SaveChangesAsync();
var updatedEnvelopeDto = envelopeToAdd.ToDTO();
return CreatedAtAction(nameof(Get), new { Content = envelopeDto.СontentX }, updatedEnvelopeDto);
}
/// <summary>
/// DELETE /envelope/{id}
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<EnvelopeDTO>> Delete(int id)
{
var envelope = await _dbContext.Envelope.FindAsync(id);
if (envelope == null)
return NotFound();
_dbContext.Envelope.Remove(envelope);
await _dbContext.SaveChangesAsync();
return Ok(envelope.ToDTO());
}
}
public static class EnvelopeExtensions
{
public static Envelope ToModel(this EnvelopeDTO envelopeDto, EnvelopeType #type, User #user)
{
//if (#class.Id != studentDto.ClassId) throw new NotSupportedException();
return new Envelope
{
//DateTimeReceivied = DateTime.Now,
Type = #type,
СontentX = envelopeDto.СontentX,
User = #user
};
}
public static EnvelopeDTO ToDTO(this Envelope envelope)
{
return new EnvelopeDTO
{
//DateTimeReceivied = DateTime.Now,
TypeId = envelope.Type.Id,
СontentX = envelope.СontentX,
User = envelope.User.UserName
};
}
}
}
Here are the samples of POST and GET requests.
Change
[HttpPost("envelope")]
to
[HttpPost]
The way you have it coded the POST body would be posted to the POST /envelopes/envelope endpoint, which appears to be what you don't want.
Also your comments in the other methods are misleading. For example
// DELETE /envelope/{id}
should really be
// DELETE /envelopes/{id}.
The [controller] portion of [Route("[controller]")] will be substituted with the lowercase portion of the controller name without Controller (i.e. /envelopes (plural) forms the basis for your endpoints from EnvelopesController.
In your Postman screenshots you can see that you were trying to GET from plural /envelopes (good) but post to singular /envelope, which is not defined.
So I have recently built at ExceptionFilter which handles all errors except Api Errors.
The ExceptionFilter looks like this:
public class ExceptionAttribute : IExceptionFilter
{
/// <summary>
/// Handles any exception
/// </summary>
/// <param name="filterContext">The current context</param>
public void OnException(ExceptionContext filterContext)
{
// If our exception has been handled, exit the function
if (filterContext.ExceptionHandled)
return;
// If our exception is not an ApiException
if (!(filterContext.Exception is ApiException))
{
// Set our base status code
var statusCode = (int)HttpStatusCode.InternalServerError;
// If our exception is an http exception
if (filterContext.Exception is HttpException)
{
// Cast our exception as an HttpException
var exception = (HttpException)filterContext.Exception;
// Get our real status code
statusCode = exception.GetHttpCode();
}
// Set our context result
var result = CreateActionResult(filterContext, statusCode);
// Set our handled property to true
filterContext.ExceptionHandled = true;
}
}
/// <summary>
/// Creats an action result from the status code
/// </summary>
/// <param name="filterContext">The current context</param>
/// <param name="statusCode">The status code of the error</param>
/// <returns></returns>
protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
{
// Create our context
var context = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
var statusCodeName = ((HttpStatusCode)statusCode).ToString();
// Create our route
var controller = (string)filterContext.RouteData.Values["controller"];
var action = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controller, action);
// Create our result
var view = SelectFirstView(context, string.Format("~/Views/Error/{0}.cshtml", statusCodeName), "~/Views/Error/Index.cshtml", statusCodeName);
var result = new ViewResult { ViewName = view, ViewData = new ViewDataDictionary<HandleErrorInfo>(model) };
// Return our result
return result;
}
/// <summary>
/// Gets the first view name that matches the supplied names
/// </summary>
/// <param name="context">The current context</param>
/// <param name="viewNames">A list of view names</param>
/// <returns></returns>
protected string SelectFirstView(ControllerContext context, params string[] viewNames)
{
return viewNames.First(view => ViewExists(context, view));
}
/// <summary>
/// Checks to see if a view exists
/// </summary>
/// <param name="context">The current context</param>
/// <param name="name">The name of the view to check</param>
/// <returns></returns>
protected bool ViewExists(ControllerContext context, string name)
{
var result = ViewEngines.Engines.FindView(context, name, null);
return result.View != null;
}
}
As you can see, if the error is not an ApiException then I route the user to the error controller.
The ApiException is just an error that happens when I make an API call from within MVC.
When these errors happen I would like to return the error as JSON back to the client so that the JavaScript can handle it.
I thought not handling the error would do this, but instead it generates a server error (albeit with the JSON error in it) like so:
Server Error in '/' Application.
{"message":"validateMove validation failure:\r\nThe item is despatched and cannot be moved"}
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: SapphirePlus.Web.ApiException: {"message":"validateMove validation failure:\r\nThe item is despatched and cannot be moved"}
Source Error:
Line 181: if (response.StatusCode != HttpStatusCode.OK)
Line 182: throw new ApiException(result);
So my question is, can I get the Application_Error method to get errors that ARE ApiExceptions and return the error as JSON?
So my question is, can I get the Application_Error method to get
errors that ARE ApiExceptions and return the error as JSON?
Of course:
protected void Application_Error()
{
var apiException = Server.GetLastError() as ApiException;
if (apiException != null)
{
Response.Clear();
Server.ClearError();
Response.StatusCode = 400;
Context.Response.ContentType = "application/json";
Context.Response.Write("YOUR JSON HERE");
}
}
In the end I didn't need to use Global.asax at all, I was able to handle it all inside my ExceptionAttribute class like this:
public class ExceptionAttribute : IExceptionFilter
{
/// <summary>
/// Handles any exception
/// </summary>
/// <param name="filterContext">The current context</param>
public void OnException(ExceptionContext filterContext)
{
// If our exception has been handled, exit the function
if (filterContext.ExceptionHandled)
return;
// Set our base status code
var statusCode = (int)HttpStatusCode.BadRequest;
// If our exception is an http exception
if (filterContext.Exception is HttpException)
{
// Cast our exception as an HttpException
var exception = (HttpException)filterContext.Exception;
// Get our real status code
statusCode = exception.GetHttpCode();
}
// Set our context result
var result = CreateActionResult(filterContext, statusCode);
// Set our handled property to true
filterContext.Result = result;
filterContext.ExceptionHandled = true;
}
/// <summary>
/// Creats an action result from the status code
/// </summary>
/// <param name="filterContext">The current context</param>
/// <param name="statusCode">The status code of the error</param>
/// <returns></returns>
protected virtual ActionResult CreateActionResult(ExceptionContext filterContext, int statusCode)
{
// Create our context
var context = new ControllerContext(filterContext.RequestContext, filterContext.Controller);
var statusCodeName = ((HttpStatusCode)statusCode).ToString();
// Create our route
var controller = (string)filterContext.RouteData.Values["controller"];
var action = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controller, action);
// Create our result
var view = SelectFirstView(context, string.Format("~/Views/Error/{0}.cshtml", statusCodeName), "~/Views/Error/Index.cshtml", statusCodeName);
var result = new ViewResult { ViewName = view, ViewData = new ViewDataDictionary<IError>(this.Factorize(model)) };
// Return our result
return result;
}
/// <summary>
/// Factorizes the HandleErrorInfo
/// </summary>
/// <param name="error"></param>
/// <returns></returns>
protected virtual IError Factorize(HandleErrorInfo error)
{
// Get the error
var model = new Error
{
Message = "There was an unhandled exception."
};
// If we have an error
if (error != null)
{
// Get our exception
var exception = error.Exception;
// If we are dealing with an ApiException
if (exception is ApiException || exception is HttpException)
{
// Get our JSON
var json = JObject.Parse(exception.Message);
var message = json["exceptionMessage"] != null ? json["exceptionMessage"] : json["message"];
// If we have a message
if (message != null)
{
// Update our model message
model.Message = message.ToString();
}
}
else
{
// Update our message
model.Message = exception.Message;
}
}
// Return our response
return model;
}
/// <summary>
/// Gets the first view name that matches the supplied names
/// </summary>
/// <param name="context">The current context</param>
/// <param name="viewNames">A list of view names</param>
/// <returns></returns>
protected string SelectFirstView(ControllerContext context, params string[] viewNames)
{
return viewNames.First(view => ViewExists(context, view));
}
/// <summary>
/// Checks to see if a view exists
/// </summary>
/// <param name="context">The current context</param>
/// <param name="name">The name of the view to check</param>
/// <returns></returns>
protected bool ViewExists(ControllerContext context, string name)
{
var result = ViewEngines.Engines.FindView(context, name, null);
return result.View != null;
}
}
This handled any Mvc error and for my Api calls, I did this:
/// <summary>
/// Used to handle the api response
/// </summary>
/// <param name="response">The HttpResponseMessage</param>
/// <returns>Returns a string</returns>
private async Task<string> HandleResponse(HttpResponseMessage response)
{
// Read our response content
var result = await response.Content.ReadAsStringAsync();
// If there was an error, throw an HttpException
if (response.StatusCode != HttpStatusCode.OK)
throw new ApiException(result);
// Return our result if there are no errors
return result;
}
This allowed me to capture the ApiError and handle the response differently than with any other exception.
I need to have both windows authentication and owin (forms) authentication but i can't get it to work.
Probably the best option is to have two sites that have different authentication methods.
I found a project that does what i want: MVC5-MixedAuth. But it uses IISExpress and i can't get it to work with Local IIS.
The error that occurs is:
Request filtering is configured on the Web server to deny the request because the query string is too long.
If i remove all my ConfigureAuth() method inside Startup.Auth.cs it doesn't throw the error but i can't login because it is needed to do CookieAuthentication.
Startup.Auth.cs:
public void ConfigureAuth(IAppBuilder app)
{
app.CreatePerOwinContext(dbEmployeePortal.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, UserMaster, int>
(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => (Int32.Parse(id.GetUserId()))
)
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
Any idea?
UPDATE 1
The error
Request filtering is configured on the Web server to deny the request because the query string is too long.
appears because occurs a login loop when it tries to reach the login page.
Resolved!
I followed the example: MVC5-MixAuth
Credits: Mohammed Younes
UPDATE 1
Problem: I needed to have both Anonymous Authentication and Windows Authentication enabled.
But when you enable them both, you can only get NT AUTHORITY\IUSR.
Resolution: To get the current user (introduced with NTLM prompt), we need to create an handler that will execute when an user enter at login page.
When the user hits the login page, the handler will get the current windows identity cached in the browser and then set as the LogonUserIdentity.
Note: I needed to use windows first login, when the user hits the login page it will try to get the correspondent ASP.NET User.
Handler
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.AspNet.Identity;
namespace MixedAuth
{
/// <summary>
/// Managed handler for windows authentication.
/// </summary>
public class WindowsLoginHandler : HttpTaskAsyncHandler, System.Web.SessionState.IRequiresSessionState
{
public HttpContext Context { get; set; }
public override async Task ProcessRequestAsync(HttpContext context)
{
this.Context = context;
//if user is already authenticated, LogonUserIdentity will be holding the current application pool identity.
//to overcome this:
//1. save userId to session.
//2. log user off.
//3. request challenge.
//4. log user in.
if (context.User.Identity.IsAuthenticated)
{
this.SaveUserIdToSession(context.User.Identity.GetUserId());
await WinLogoffAsync(context);
context.RequestChallenge();
}
else if (!context.Request.LogonUserIdentity.IsAuthenticated)
{
context.RequestChallenge();
}
else
{
// true: user is trying to link windows login to an existing account
if (this.SessionHasUserId())
{
var userId = this.ReadUserIdFromSession();
this.SaveUserIdToContext(userId);
await WinLinkLoginAsync(context);
}
else // normal login.
await WinLoginAsync(context);
}
}
#region helpers
/// <summary>
/// Executes Windows login action against account controller.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private async Task WinLoginAsync(HttpContext context)
{
var routeData = this.CreateRouteData(Action.Login);
routeData.Values.Add("returnUrl", context.Request["returnUrl"]);
routeData.Values.Add("userName", context.Request.Form["UserName"]);
await ExecuteController(context, routeData);
}
/// <summary>
/// Execute Link Windows login action against account controller.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private async Task WinLinkLoginAsync(HttpContext context)
{
var routeData = this.CreateRouteData(Action.Link);
await ExecuteController(context, routeData);
}
/// <summary>
/// Executes Windows logoff action against controller.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private async Task WinLogoffAsync(HttpContext context)
{
var routeData = this.CreateRouteData(Action.Logoff);
await ExecuteController(context, routeData);
}
/// <summary>
/// Executes controller based on route data.
/// </summary>
/// <param name="context"></param>
/// <param name="routeData"></param>
/// <returns></returns>
private async Task ExecuteController(HttpContext context, RouteData routeData)
{
var wrapper = new HttpContextWrapper(context);
MvcHandler handler = new MvcHandler(new RequestContext(wrapper, routeData));
IHttpAsyncHandler asyncHandler = ((IHttpAsyncHandler)handler);
await Task.Factory.FromAsync(asyncHandler.BeginProcessRequest, asyncHandler.EndProcessRequest, context, null);
}
#endregion
}
}
Extensions
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
namespace MixedAuth
{
public enum Action { Login, Link, Logoff };
public static class MixedAuthExtensions
{
const string userIdKey = "windows.userId";
//http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
const int fakeStatusCode = 418;
const string controllerName = "Account";
const string loginActionName = "WindowsLogin";
const string linkActionName = "LinkWindowsLogin";
const string logoffActionName = "WindowsLogoff";
const string windowsLoginRouteName = "Windows/Login";
public static void RegisterWindowsAuthentication(this MvcApplication app)
{
app.EndRequest += (object sender, EventArgs e) =>
{
HttpContext.Current.ApplyChallenge();
};
}
/// <summary>
/// Registers ignore route for the managed handler.
/// </summary>
/// <param name="routes"></param>
public static void IgnoreWindowsLoginRoute(this RouteCollection routes)
{
routes.IgnoreRoute(windowsLoginRouteName);
}
/// <summary>
/// By pass all middleware and modules, by setting a fake status code.
/// </summary>
/// <param name="context"></param>
public static void RequestChallenge(this HttpContext context)
{
context.Response.StatusCode = fakeStatusCode;
}
/// <summary>
/// Invoke on end response only. Replaces the current response status code with 401.2
/// </summary>
/// <param name="context"></param>
public static void ApplyChallenge(this HttpContext context)
{
if (context.Response.StatusCode == fakeStatusCode)
{
context.Response.StatusCode = 401;
context.Response.SubStatusCode = 2;
//http://msdn.microsoft.com/en-us/library/system.web.httpresponse.tryskipiiscustomerrors(v=vs.110).aspx
//context.Response.TrySkipIisCustomErrors = true;
}
}
/// <summary>
///
/// </summary>
/// <param name="handler"></param>
/// <param name="action"></param>
/// <returns></returns>
public static RouteData CreateRouteData(this WindowsLoginHandler handler, Action action)
{
RouteData routeData = new RouteData();
routeData.RouteHandler = new MvcRouteHandler();
switch (action)
{
case Action.Login:
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("action", loginActionName);
break;
case Action.Link:
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("action", linkActionName);
break;
case Action.Logoff:
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("action", logoffActionName);
break;
default:
throw new NotSupportedException(string.Format("unknonw action value '{0}'.", action));
}
return routeData;
}
/// <summary>
/// Saves userId to the items collection inside <see cref="HttpContext"/>.
/// </summary>
public static void SaveUserIdToContext(this WindowsLoginHandler handler, string userId)
{
if (handler.Context.Items.Contains(userIdKey))
throw new ApplicationException("Id already exists in context.");
handler.Context.Items.Add("windows.userId", userId);
}
/// <summary>
/// Reads userId from item collection inside <see cref="HttpContext"/>.
/// </summary>
/// <remarks>The item will removed before this method returns</remarks>
/// <param name="context"></param>
/// <returns></returns>
public static int ReadUserId(this HttpContextBase context)
{
if (!context.Items.Contains(userIdKey))
throw new ApplicationException("Id not found in context.");
int userId = Convert.ToInt32(context.Items[userIdKey] as string);
context.Items.Remove(userIdKey);
return userId;
}
/// <summary>
/// Returns true if the session contains an entry for userId.
/// </summary>
public static bool SessionHasUserId(this WindowsLoginHandler handler)
{
return handler.Context.Session[userIdKey] != null;
}
/// <summary>
/// Save a session-state value with the specified userId.
/// </summary>
public static void SaveUserIdToSession(this WindowsLoginHandler handler, string userId)
{
if (handler.SessionHasUserId())
throw new ApplicationException("Id already exists in session.");
handler.Context.Session[userIdKey] = userId;
}
/// <summary>
/// Reads userId value from session-state.
/// </summary>
/// <remarks>The session-state value removed before this method returns.</remarks>
/// <param name="session"></param>
/// <returns></returns>
public static string ReadUserIdFromSession(this WindowsLoginHandler handler)
{
string userId = handler.Context.Session[userIdKey] as string;
if (string.IsNullOrEmpty(userIdKey))
throw new ApplicationException("Id not found in session.");
handler.Context.Session.Remove(userIdKey);
return userId;
}
/// <summary>
/// Creates a form for windows login, simulating external login providers.
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcForm BeginWindowsAuthForm(this HtmlHelper htmlHelper, object htmlAttributes)
{
return htmlHelper.BeginForm("Login", "Windows", FormMethod.Post, htmlAttributes);
}
/// <summary>
/// Creates a form for windows login, simulating external login providers.
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcForm BeginWindowsAuthForm(this HtmlHelper htmlHelper, object routeValues, object htmlAttributes)
{
return htmlHelper.BeginForm("Login", "Windows", FormMethod.Post, htmlAttributes);
}
}
}
Note
You need to have AccountController.cs as partial.
AccountController.Windows.cs
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.AspNet.Identity;
using MixedAuth;
namespace EmployeePortal.Web.Controllers
{
[Authorize]
public partial class AccountController : BaseController
{
//
// POST: /Account/WindowsLogin
[AllowAnonymous]
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult WindowsLogin(string userName, string returnUrl)
{
if (!Request.LogonUserIdentity.IsAuthenticated)
{
return RedirectToAction("Login");
}
var loginInfo = GetWindowsLoginInfo();
// Sign in the user with this external login provider if the user already has a login
var user = UserManager.Find(loginInfo);
if (user != null)
{
SignIn(user, isPersistent: false);
return RedirectToLocal(returnUrl);
}
else
{
return RedirectToAction("Login", new RouteValueDictionary(new { controller = "Account", action = "Login", returnUrl = returnUrl }));
}
}
//
// POST: /Account/WindowsLogOff
[HttpPost]
[ValidateAntiForgeryToken]
public void WindowsLogOff()
{
AuthenticationManager.SignOut();
}
//
// POST: /Account/LinkWindowsLogin
[AllowAnonymous]
[HttpPost]
public async Task<ActionResult> LinkWindowsLogin()
{
int userId = HttpContext.ReadUserId();
//didn't get here through handler
if (userId <= 0)
return RedirectToAction("Login");
HttpContext.Items.Remove("windows.userId");
//not authenticated.
var loginInfo = GetWindowsLoginInfo();
if (loginInfo == null)
return RedirectToAction("Manage");
//add linked login
var result = await UserManager.AddLoginAsync(userId, loginInfo);
//sign the user back in.
var user = await UserManager.FindByIdAsync(userId);
if (user != null)
await SignInAsync(user, false);
if (result.Succeeded)
return RedirectToAction("Manage");
return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
}
#region helpers
private UserLoginInfo GetWindowsLoginInfo()
{
if (!Request.LogonUserIdentity.IsAuthenticated)
return null;
return new UserLoginInfo("Windows", Request.LogonUserIdentity.User.ToString());
}
#endregion
}
public class WindowsLoginConfirmationViewModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
}
}
Then, you need to add the handler:
<add name="Windows Login Handler" path="Login" verb="GET,POST" type="MixedAuth.WindowsLoginHandler" preCondition="integratedMode" />
Startup.cs
app.CreatePerOwinContext(dbEmployeePortal.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
PathString path = new PathString("/Account/Login");
if (GlobalExtensions.WindowsAuthActive)
path = new PathString("/Windows/Login");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
//LoginPath = new PathString("/Account/Login")
LoginPath = path
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
Then you need to configure Local IIS to use WindowsAuthentication and AnonymousAuthentication. You can do this in the Authentication Module.
Note If you don't have WindowsAuthentication go to Control Panel then Programs and Features then "Turn Windows features on or off":
select "Internet Information Services" > "World Wide Web" > "Security"
and select Windows Authentication.
I didn't see this in your answer, so for anyone looking on how to capture Windows Auth in your Owin pipeline, you also can add the following to your ConfigureAuth method:
public void ConfigureAuth(IAppBuilder app)
{
HttpListener listener = (HttpListener)app.Properties["System.Net.HttpListener"];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
}
I'm new to this forum and to have a question about await/async use in Xamarin (Also the first time I work with).
I am working for my internship on a project using Xamarin, PCL, MvvmCross.
In my PCL im do a postrequest to a WCF service to login in my application. In WP8 everything just works fine, but when I am running my application on Android the response is always null.
Below you can find my httpclient class. The method with the post is InternalPostAsync
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using Anton.Mobile.Shared.Infrastructure;
using System.Net;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;
namespace Anton.Mobile.Shared.Data
{
public class AntonClient
{
#region static
/// <summary>
/// Base Uri of the Ria service (e.g. http://example.com/)
/// </summary>
private static readonly Uri _baseUri = new Uri(Config.BaseUri);
/// <summary>
/// Last cookie response header (Session, authentication, ...)
/// </summary>
private static string _cookieHeader = null;
/// <summary>
/// Lock object for read/write <para>_cookieHeader</para>
/// </summary>
private static object _lockObj = new object();
#endregion
#region protected
/// <summary>
/// Creates a client
/// </summary>
/// <param name="container">Cookie container to use</param>
/// <returns>HttpClient</returns>
protected virtual HttpClient CreateClient(CookieContainer container)
{
//set container on handler for tracking cookies between request-response
var handler = new HttpClientHandler()
{
CookieContainer = container,
UseCookies = true,
UseDefaultCredentials = false,
};
//Create client and set the base address
var cl = new HttpClient(handler)
{
BaseAddress = _baseUri
};
if (!string.IsNullOrEmpty(_cookieHeader))
{
cl.DefaultRequestHeaders.Add("Cookies", _cookieHeader);
}
return cl;
}
/// <summary>
/// Creates a JSON content request
/// </summary>
/// <param name="jsonContent">JSON value</param>
/// <returns>JSON content</returns>
protected virtual HttpContent CreateRequestContent(string jsonContent)
{
var content = new StringContent(jsonContent,Encoding.UTF8,"application/json");
//content.Headers.Add("ContentType", "application/json");
return content;
}
/// <summary>
/// Save cookies <para>_cookieHeader</para>
/// </summary>
/// <param name="container">cookie container</param>
protected void ParseCookies(HttpResponseMessage msg)
{
IEnumerable<string> values;
if (!msg.Headers.TryGetValues("Set-Cookie", out values) || !values.Any())
return;
//var cookies = container.GetCookieHeader(_baseUri);
var cs = new List<string>();
foreach (var v in values)
{
string[] vs = v.Split(new char[] { ';' });
string[] value = vs[0].Split(new char[] { '=' });
container.Add(new Uri("Http://initesting"), new Cookie(value[0], value[1]));
cs.Add(string.Format("{0}={1}", value[0], value[1]));
}
lock (_lockObj)
{
_cookieHeader = string.Join(";", cs.ToArray());
}
}
private static CookieContainer container = new CookieContainer();
/// <summary>
/// Create a new cookie container from <para>_cookieHeaders</para>
/// </summary>
/// <returns>Cookie container</returns>
protected CookieContainer CreateCookieContainer()
{
//lock (_lockObj)
//{
// if (!string.IsNullOrEmpty(_cookieHeader))
// {
// foreach (var header in _cookieHeader.Split(new char[] { ';' }))
// {
// container.SetCookies(_baseUri, header);
// }
// }
//}
return container;
}
/// <summary>
/// Executes a POST HTTP Request
/// </summary>
/// <param name="jsonContent">POST JSON content</param>
/// <param name="uri">Service uri</param>
/// <returns>Response content as string (JSON)</returns>
protected virtual async Task<string> InternalPostAsync(string jsonContent, Uri uri)
{
var container = CreateCookieContainer();
using (var client = CreateClient(container))
{
var content = CreateRequestContent(jsonContent);
var response = await client.PostAsync(uri, content);
if (response.StatusCode != HttpStatusCode.OK)
{
return null; //todo
}
ParseCookies(response);
return await response.Content.ReadAsStringAsync();
}
}
/// <summary>
/// Executes a GET HTTP Request
/// </summary>
/// <param name="uri">Service uri</param>
/// <returns>Response content as string (JSON)</returns>
protected virtual async Task<string> InternalRequestAsync(Uri uri)
{
var container = CreateCookieContainer();
using (var client = CreateClient(container))
{
HttpResponseMessage response = await client.GetAsync(uri);
if (response.StatusCode != HttpStatusCode.OK)
{
return null;
}
ParseCookies(response);
return await response.Content.ReadAsStringAsync();
}
}
#endregion protected
#region public
/// <summary>
/// Executes a POST HTTP Request for a given Request key
/// </summary>
/// <typeparam name="TRequest">Request Type</typeparam>
/// <typeparam name="TResult">Result Type</typeparam>
/// <param name="request">Request POST value to JSON serializing</param>
/// <param name="key">Unique Request Key</param>
/// <returns>Deserialized POST response content of type TResult</returns>
public async Task<TResult> PostAsync<TRequest, TResult>(TRequest request, RequestKey key)
where TRequest : class
where TResult : class
{
try
{
var uri = RequestMap.GetUri(key);
string jsonResult = await InternalPostAsync(request.SerializeJson(), uri);
return jsonResult.DeserializeJson<TResult>();
}
catch (Exception)
{
//todo
}
return default(TResult);
}
/// <summary>
/// Executes a POST HTTP Request for a given service uri
/// </summary>
/// <typeparam name="TRequest">Request Type</typeparam>
/// <param name="request">Request POST value to JSON serializing</param>
/// <param name="uri">Service URI</param>
/// <returns>Deserialized POST response content of type dynamic</returns>
public async Task<dynamic> PostAsync<TRequest>(TRequest request, string uri)
{
try
{
string jsonResult = await InternalPostAsync(request.SerializeJson(), new Uri(uri, UriKind.Absolute));
return jsonResult.DynamicJson();
}
catch (Exception)
{
//todo
}
return null;
}
/// <summary>
/// Executes a GET HTTP Request for a givin key and query string parameter info
/// </summary>
/// <typeparam name="TResponse">Response Type</typeparam>
/// <param name="key">Unique request key</param>
/// <param name="queryString">Querystring info</param>
/// <returns>Deserialized POST response content of type TResult</returns>
public async Task<TResponse> RequestAsync<TResponse>(RequestKey key, IDictionary<string, string> queryString = null)
{
try
{
string jsonResult = await InternalRequestAsync(RequestMap.GetUri(key, queryString));
var dynamicResult = jsonResult.DynamicJson();
var item = (dynamicResult as JObject)[RequestMap.GetValue(key) + "Result"]["RootResults"].First; //todo: better solution for this
return item.ToObject<TResponse>();
}
catch (Exception)
{
//todo
}
return default(TResponse);
}
#endregion public
}
}
Regarding the website of Xamarin async/await is supported, but I did not find any similar problems. I hope you can help me.
I have solved my problem.
In Android, when you add the NuGet package with the HTTP Libraries, something went wrong when adding the references. You have to add the references mannualy to solve it. I also editted my Uri, now it works with the IP-address in the Uri.