Entity / Identiy «Операция недействительна из-за текущего состояния объекта» в UserManager.CreateAsync () - Как определить причину ошибки? - PullRequest
0 голосов
/ 08 марта 2019

Я сталкиваюсь с одним из тех прекрасных «это сообщение об ошибке слишком общее, чтобы быть полезным», которое периодически появляется при работе с Entity.Может ли кто-нибудь рисковать догадками относительно обстоятельств, которые могут быть причиной этого?Или даже лучше - какой хороший способ определить, что конкретно вызывает ошибку?

Ситуация:

Я настраиваю локальную базу данных на основе идентификаторов создание учетной записи и вход в систему с помощью Web Api 2.0.Мы используем EntityFramework 6.2.0 и AspNet.Identity.Core v2.2.2.Исходя из потребностей приложения, я настроил нас на использование числовых (а не UUID) ключей для пользовательского объекта пользователя, который хранится в той же базе данных / DbContext, что и остальная часть приложения.модель данных.Существуют определенные пользовательские классы с более подробной информацией, чем учетная запись входа, которые имеют отношения FK к пользовательскому объекту пользователя, но данные такого типа еще не существуют.

Ошибка возникает при попытке выполнить довольно простое создание пользователя,В настоящее время аутентификация на основе ролей или чего-либо еще не настроена;Я просто использую PostMan для работы с конечной точкой API локально работающей копии приложения.Запрос PostMan отправляется на правильный контроллер API, и правильно построенный (насколько я могу судить) пользовательский объект ускоряется, но когда мы пытаемся использовать (слегка настроенный) UserManager, чтобы добавить нового пользователя вDB я получаю сообщение об ошибке:

«Операция недопустима из-за текущего состояния объекта.»

К сожалению, я не могу сосредоточиться на чем-либо более точномэто может быть причиной этой ошибки, и я пока не смог найти что-либо от MS, документирующее обстоятельства, при которых она может быть выброшена.

PostMan-ответ / трассировка стека (немного предварительно подтверждено):

{ "message": "An error has occurred.", "exceptionMessage": "Operation is not valid due to the current state of the object.", "exceptionType": "System.InvalidOperationException", "stackTrace": " at APP.Core.Data.Models.APPUserStore.CreateAsync(APPUser user) in C:\\...\\GitHub\\APP\\APP\\APP.Core\\Data\\Models\\APPUserStore.cs:line 23 at Microsoft.AspNet.Identity.UserManager`2.<CreateAsync>d__73.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNet.Identity.UserManager`2.<CreateAsync>d__79.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Microsoft.AspNet.Identity.AsyncHelper.RunSync[TResult](Func`1 func) at Microsoft.AspNet.Identity.UserManagerExtensions.Create[TUser,TKey](UserManager`2 manager, TUser user, String password) at APP.Web.Controllers.api.AuthController.<CreateUser>d__5.MoveNext() in C:\\...\\GitHub\\APP\\APP\\APP.Web\\Controllers\\api\\AuthController.cs:line 77 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Threading.Tasks.TaskHelpersExtensions.<CastToObject>d__1`1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__1.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ActionFilterResult.ExecuteAsync>d__5.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Dispatcher.HttpControllerDispatcher<SendAsync>d__15.MoveNext()" }

Соответствующий код:

in AuthController :

[HttpPost, Route("create")]
public async Task<IHttpActionResult> CreateUser(CreateAccountBindingModel model)
{
    if (!ModelState.IsValid) return BadRequest(ModelState);
    var user = CoreFactory.Create(model);

    var result = await UserMgr.CreateAsync(user, model.Password);
    // ERROR OCCURS AFTER EXECUTION OF THE ABOVE LINE ---^
    if (!result.Succeeded) return GetErrorResult(result);

    var locationHeader = new Uri(Url.Link("GetUserById", new { id = user.Id }));

    return Created(locationHeader, VmFactory.Create(user));
}

AuthController наследуется от BaseApiController следующим образом:

public class BaseApiController : ApiController
{
    private readonly APPUserManager userMgr = null;
    private CoreModelFactory coreFactory;
    private ViewModelFactory modelFactory;

    protected BaseApiController() { }

    protected APPUserManager UserMgr => userMgr ??
                Request.GetOwinContext().GetUserManager<APPUserManager>();

    protected ViewModelFactory VmFactory => modelFactory ??
        (modelFactory = new ViewModelFactory(Request, UserMgr));

    protected CoreModelFactory CoreFactory => coreFactory ??
        (coreFactory = new CoreModelFactory(
            UserMgr,
            Request.GetOwinContext().Get<APPContext>()));

    protected IHttpActionResult GetErrorResult(IdentityResult result)
    {
  // This isn't getting called, assume it works
    }
}

и соответствующее приложение UserManager :

public class APPUserManager : UserManager<APPUser, int>,
                                 IAPPUserManager
{
    public APPUserManager(IUserStore<APPUser, int> store)
        : base(store)
    {
        Log = new APPLog();
    }

    private IAPPLog Log { get; }

    public static APPUserManager Create(
        IdentityFactoryOptions<APPUserManager> options,
        IOwinContext context)
    {
        var appDbContext = context.Get<APPContext>();
        var manager = new APPUserManager(new APPUserStore(context.Get<APPContext>()))
        {
            UserLockoutEnabledByDefault = false,
            DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5),
            MaxFailedAccessAttemptsBeforeLockout = 5
        };

        manager.UserValidator = new UserValidator<APPUser, int>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };

        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };

        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider =
                new DataProtectorTokenProvider<APPUser, int>(
                    dataProtectionProvider.Create("ASP.NET Identity"));
        }

        return manager;
    }

    public APPUser FindById(int id)
    {
        return FindByIdAsync(id).Result;
    }
}

Вот мой пользовательский объект (обрезанный);Предполагается, что все обязательные поля устанавливаются перед отправкой в ​​постоянство.Мы используем целочисленный идентификатор, и это поле установлено на 0 для отправки в БД.

APPUserLogin , APPUserRole и т. Д., Классы пусты, простонаследование от эквивалентов базы идентификаторов, но с целочисленным идентификатором вместо строкового.

public class APPUser : IdentityUser<int, APPUserLogin, APPUserRole, APPUserClaim>,
                          IAPPDbModel
{
    public APPUser()
    {
        StatusChangeDate = DateTime.Now;
        SecurityStamp = "DEFAULT VALUE";
        PasswordHash = "DEFAULT VALUE";
        TwoFactorEnabled = false;
        LockoutEnabled = false;
        AccessFailedCount = 0;
    }

    [Key]
    public int APPUserId { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<APPUser, int> mgr)
    {
        var userIdentity = await mgr.CreateIdentityAsync(this,
            DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }

    public ClaimsIdentity GenerateUserIdentity(UserManager<APPUser, int> mgr)
    {
        var userId = mgr.CreateIdentity(this, DefaultAuthenticationTypes.ApplicationCookie);
        return userId;
    }
}

Для целей этого вопроса отправляемый новый пользователь выглядит следующим образом:

    User    {APP.Core.Data.Models.APPUser}  APP.Core.Data.Models.APPUser
        AccessFailedCount   0   int
+       Claims  Count = 0   System.Collections.Generic.ICollection<APP.Core.Data.Models.APPUserClaim> {System.Collections.Generic.List<APP.Core.Data.Models.APPUserClaim>}
        Email   "a_valid_email@foo.foo" string
        EmailConfirmed  false   bool
        Id  0   int
        LockoutEnabled  false   bool
        LockoutEndDateUtc   null    System.DateTime?
+       Logins  Count = 0   System.Collections.Generic.ICollection<APP.Core.Data.Models.APPUserLogin> {System.Collections.Generic.List<APP.Core.Data.Models.APPUserLogin>}
        PasswordHash    "DEFAULT"   string
+       Roles   Count = 0   System.Collections.Generic.ICollection<APP.Core.Data.Models.APPUserRole> {System.Collections.Generic.List<APP.Core.Data.Models.APPUserRole>}
        SecurityStamp   "DEFAULT"   string
        Status  Active  APP.Core.Data.Models.Status
+       StatusChangeDate    {(A VALID)} System.DateTime
        APPUserId   0   int
        TwoFactorEnabled    false   bool
        UserName    "A NAME"    string

Большинство попаданий, которые я видел в сообщении об ошибке, устарели на несколько лет и связаны с проблемами конфигурации базы данных, которые я не считаю более актуальными.База данных существует, и ее структурой можно управлять с помощью миграций.

...