Ответ 1
Не знаю, насколько это актуально, но у меня были те же проблемы, что и выше. Мне удалось решить эту проблему, используя пользовательский компонент промежуточного программного обеспечения OWIN.
Некоторая информация о моей структуре приложения:
- Использование MVC WebApp и WebAPI в одном проекте (возможно, не самый лучший вариант, но у меня нет времени его менять, так как приближается крайний срок;))
- Использование AutoFac в качестве контейнера IoC
- Реализован пользовательский ICurrentContext для хранения информации о текущем пользователе (с CookieAuth в MVC и Toker Auten в WebAPI), который вводится там, где это необходимо (контроллеры, объекты BAL и т.д.).
- Использование EntityFramework 6 для доступа к Db
- Преобразованная идентификация ASP.NET для использования ключей int вместо строки (http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity)
Итак, к коду. Это мой интерфейс ICurrentContext:
public interface ICurrentContext
{
User CurrentUser { get; set; } // User is my User class which holds some user properties
int? CurrentUserId { get; }
}
и его реализация:
public class DefaultCurrentContext : ICurrentContext
{
public User CurrentUser { get; set; }
public int? CurrentUserId { get { return User != null ? CurrentUser.Id : (int?)null; } }
}
Я также создал компонент промежуточного программного обеспечения OWIN:
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.Owin;
namespace MyWebApp.Web.AppCode.MiddlewareOwin
{
public class WebApiAuthInfoMiddleware : OwinMiddleware
{
public WebApiAuthInfoMiddleware(OwinMiddleware next)
: base(next)
{
}
public override Task Invoke(IOwinContext context)
{
var userId = context.Request.User.Identity.GetUserId<int>();
context.Environment[MyWebApp.Constants.Constant.WebApiCurrentUserId] = userId;
return Next.Invoke(context);
}
}
}
Некоторая информация об этом компоненте: MyWebApp.Constants.Constant.WebApiCurrentUserId - это некоторая строковая константа (вы можете использовать свою собственную), которую я использовал, чтобы избежать опечаток, поскольку она используется в нескольких местах. В основном, что делает это промежуточное программное обеспечение, заключается в том, что он добавляет текущий UserId в словарь среды OWIN и затем вызывает следующее действие в конвейере.
Затем я создал оператор расширения * для включения OMC (OWIN Middleware Component) в конвейер OWIN:
using System;
using Owin;
namespace MyWebApp.Web.AppCode.MiddlewareOwin
{
public static class OwinAppBuilderExtensions
{
public static IAppBuilder UseWebApiAuthInfo(this IAppBuilder @this)
{
if (@this == null)
{
throw new ArgumentNullException("app");
}
@this.Use(typeof(WebApiAuthInfoMiddleware));
return @this;
}
}
}
Чтобы использовать этот OMC, я поместил оператор Use * сразу после использования оператора * для маркера Bearer внутри моего Startup.Auth.cs:
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions); // This was here before
// Register AuthInfo to retrieve UserId before executing of Api controllers
app.UseWebApiAuthInfo(); // Use newly created OMC
Теперь фактическое использование этого принципа было в методе AutoFac Register (вызвано некоторым кодом начальной загрузки в начале веб-приложения, в моем случае это было внутри класса Startup (Startup.cs), метода конфигурации) для моей реализации ICurrentContext, которая является:
private static void RegisterCurrentContext(ContainerBuilder builder)
{
// Register current context
builder.Register(c =>
{
// Try to get User Id first from Identity of HttpContext.Current
var appUserId = HttpContext.Current.User.Identity.GetUserId<int>();
// If appUserId is still zero, try to get it from Owin.Enviroment where WebApiAuthInfo middleware components puts it.
if (appUserId <= 0)
{
object appUserIdObj;
var env = HttpContext.Current.GetOwinContext().Environment;
if (env.TryGetValue(MyWebApp.Constants.Constant.WebApiCurrentUserId, out appUserIdObj))
{
appUserId = (int)appUserIdObj;
}
}
// WORK: Read user from database based on appUserId and create appUser object.
return new DefaultCurrentContext
{
CurrentUser = appUser,
};
}).As<ICurrentContext>().InstancePerLifetimeScope();
}
Этот метод вызывается, где я создаю контейнер AutoFac (следовательно, входной параметр типа ContainerBuilder).
Таким образом, я получил единую реализацию CurrentContext, независимо от того, как пользователь был аутентифицирован (через веб-приложение MVC или веб-API). Вызовы Web API в моем случае были сделаны из какого-либо настольного приложения, но база данных и большая часть кодовой базы были одинаковыми для MVC App и веб-API.
Не знаю, есть ли у него правильный путь, но он сработал у меня. Хотя я все еще немного обеспокоен тем, как это будет вести себя по-настоящему, поскольку я точно не знаю, как будет работать HttpContext.Current внутри вызовов API. Я где-то читал, что словарь OWIN используется для каждого запроса, поэтому я считаю, что это безопасный подход. И я также думаю, что это не такой аккуратный код, а немного неприятный взломать для чтения UserId.;) Если с этим соглашением что-то не так, я был бы признателен за любые замечания относительно этого. Я боролся с этим в течение двух недель, и это самое близкое, что я получил от UserId в одном месте (при разрешении ICurrentContext из AutoFac через lambda).
ПРИМЕЧАНИЕ. Где бы то ни было использование GetUserId, его можно заменить оригинальной реализацией GetUserId (которая возвращает строку). Причина, по которой я использую GetUserId, заключается в том, что я в некоторой степени переписал ASP.NET для использования int вместо строк для TKey. Я сделал это на основе следующей статьи: http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity