/ / Déplacer ApplicationUser et d'autres modèles du projet MVC - asp.net-mvc, architecture, asp.net-identity-2

Déplacement d'ApplicationUser et d'autres modèles hors du projet MVC - asp.net-mvc, architecture, asp.net-identity-2

Comment puis-je séparer les propriétés, les fonctionnalités et les classes de l'ASP.Net Mvc / Identity 2.0 par défaut? Je me bats avec quelques choses:

  • par défaut, il souhaite utiliser le contexte OWIN pour câbler une sorte d'injection de dépendance et contrôler les Managers
  • il place ApplicationDbContext au niveau de l'application, où mon architecture exige qu'il soit disponible à des niveaux "inférieurs".
  • Cela nécessite que je déclare toutes les propriétés de la même classe que la fonctionnalité qui agit sur ces propriétés (ce qui ne correspond pas à mon architecture)
  • Le modèle ApplcationUser a des dépendances sur Asp.Net, que j'aimerais casser si je dois déplacer le POCO vers une couche non MVC de la solution

Architecture d'application:

J'ai une solution à plusieurs niveaux:

  • Api - définit les interfaces pour les services
  • Domaine - stocke les modèles POCO représentant un domaine d'activité
  • Business - stocke la logique d'interaction avec les objets de domaine et consomme des services
  • Service - implémentation de services, y compris Entity Framework, et les cartes de structure pour les objets de domaine
  • Application - dans ce cas, une application MVC.

Ma couche métier ne connaît que les interfaces de service, pas l'implémentation, et j'utilise l'injection de dépendances pour tout câbler.

J'ai quelques interfaces définissant les opérations de lecture / écriture / unité de travail pour un service de données, et une implémentation de celles-ci qui héritent de DbContext (dans ma couche Service). Au lieu d'avoir une série de DbSet<MyPoco> MyPocos {get;set;}, Je le connecte en passant une série de configurations de types qui définissent les relations, puis en accédant à mes types via Set<Type>(). Tout cela fonctionne très bien.

Cette pile a été mise en place pour une application existante et fonctionne bien. Je sais qu'il passera à une application MVC, et je n'ai que des problèmes avec ASP.Net Identity-2 "prêt à l'emploi".

Réponses:

7 pour la réponse № 1

Ma solution était de: Abstrait toutes les choses

J'ai contourné cela en faisant abstraction de la plupart des fonctionnalités de l'identité dans son propre projet, ce qui a permis de tester plus facilement et de réutiliser l'abstraction dans d'autres projets.

J'ai eu l'idée après avoir lu cet article

Identité ASP.NET ignorant la persistance avec des modèles

J'ai ensuite affiné l'idée en fonction de mes besoins. J'ai simplement échangé tout ce dont j'avais besoin d'asp.net.identity pour mes interfaces personnalisées qui reflétaient plus ou moins les fonctionnalités fournies par le cadre, mais avec l'avantage d'abstractions plus faciles et non d'implémentations.

IIdentityUser

/// <summary>
///  Minimal interface for a user with an id of type <seealso cref="System.String"/>
/// </summary>
public interface IIdentityUser : IIdentityUser<string> { }
/// <summary>
///  Minimal interface for a user
/// </summary>
public interface IIdentityUser<TKey>
where TKey : System.IEquatable<TKey> {
TKey Id { get; set; }
string UserName { get; set; }
string Email { get; set; }
//...other code removed for brevity
}

IIdentityManager

/// <summary>
/// Exposes user related api which will automatically save changes to the UserStore
/// </summary>
public interface IIdentityManager : IIdentityManager<IIdentityUser> { }
/// <summary>
/// Exposes user related api which will automatically save changes to the UserStore
/// </summary>
public interface IIdentityManager<TUser> : IIdentityManager<TUser, string>
where TUser : class, IIdentityUser<string> { }
/// <summary>
/// Exposes user related api which will automatically save changes to the UserStore
/// </summary>
public interface IIdentityManager<TUser, TKey> : IDisposable
where TUser : class, IIdentityUser<TKey>
where TKey : System.IEquatable<TKey> {
//...other code removed for brevity
}

IIdentityResult

/// <summary>
/// Represents the minimal result of an identity operation
/// </summary>
public interface IIdentityResult : System.Collections.Generic.IEnumerable<string> {
bool Succeeded { get; }
}

Dans mon implémentation par défaut du gestionnaire d'identité, qui vit également dans son propre projet, j'ai simplement enveloppé le ApplicationManager puis mappé les résultats et les fonctionnalités entre mes types et les types asp.net.identity.

public class DefaultUserManager : IIdentityManager {
private ApplicationUserManager innerManager;

public DefaultUserManager() {
this.innerManager = ApplicationUserManager.Instance;
}
//..other code removed for brevity
public async Task<IIdentityResult> ConfirmEmailAsync(string userId, string token) {
var result = await innerManager.ConfirmEmailAsync(userId, token);
return result.AsIIdentityResult();
}
//...other code removed for brevity
}

La couche application ne connaît que les abstractions et l'implémentation est configurée au démarrage. Je n'en ai pas using Microsoft.AspNet.Identity au niveau supérieur car ils utilisent tous les abstractions locales.

les niveaux peuvent ressembler à ceci:

  • Api - définit les interfaces pour les services (y compris les interfaces d'abstraction d'identité)
  • Domaine - stocke les modèles POCO représentant un domaine d'activité
  • Business - stocke la logique d'interaction avec les objets de domaine et consomme des services
  • Service - implémentation de services, y compris Entity Framework, et les cartes de structure pour les objets de domaine
  • Identité - implémentation de services spécifiques Microsoft.AspNet.Identity, y compris Microsoft.AspNet.Identity.EntityFramework; et configuration OWIN
  • Application - dans ce cas, une application MVC.

Dans la couche d'application MVC, AccountController donc seulement nécessaire

using MyNamespace.Identity.Abstractions

public partial class AccountController : Controller {
private readonly IIdentityManager userManager;

public AccountController(IIdentityManager userManager) {
this.userManager = userManager;
}

//...other code removed for brevity

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Signin(LoginViewModel model, string returnUrl) {
if (ModelState.IsValid) {
// authenticate user
var user = await userManager.FindAsync(model.UserName, model.Password);
if (user != null) {
//...code removed for brevity
} else {
// login failed
setFailedLoginIncrementalDelay();
ModelState.AddModelError("", "Invalid user name or password provided.");
}
}
//TODO: Audit failed login

// If we got this far, something failed, redisplay form
return View(model);
}
}

Cela suppose que vous utilisez un cadre DI. Ce n'est que dans la configuration de l'IoC que toute mention est faite de la couche qui implémente l'identité en l'abstrait complètement de ceux qui ont besoin d'utiliser l'identité.

//NOTE: This is custom code.
protected override void ConfigureDependencies(IContainerBuilder builder) {
if (!builder.HasHandler(typeof(IIdentityManager))) {
builder.PerRequest<IIdentityManager, DefaultUserManager>();
}
}