ASP.NET Core'da IPrincipal alay etme


91

Birim testleri yazdığım bir ASP.NET MVC Core uygulamam var. Eylem yöntemlerinden biri, bazı işlevler için Kullanıcı adını kullanır:

SettingsViewModel svm = _context.MySettings(User.Identity.Name);

bu açıkça birim testinde başarısız olur. Etrafıma baktım ve tüm öneriler .NET 4.5'ten HttpContext'e kadar. Eminim bunu yapmanın daha iyi bir yolu vardır. IPrincipal'i enjekte etmeye çalıştım, ancak bir hata verdi; ve bunu denedim bile (çaresizlikten sanırım):

public IActionResult Index(IPrincipal principal = null) {
    IPrincipal user = principal ?? User;
    SettingsViewModel svm = _context.MySettings(user.Identity.Name);
    return View(svm);
}

ama bu da bir hata yaptı. Belgelerde de bir şey bulamadım ...

Yanıtlar:


182

Kontrolörün erişilen yoluyla kontrolörün . İkinci saklanır içinde .User HttpContextControllerContext

Kullanıcıyı ayarlamanın en kolay yolu, yapılandırılmış bir kullanıcıya farklı bir HttpContext atamaktır. DefaultHttpContextBu amaçla kullanabiliriz , bu şekilde her şeyle dalga geçmek zorunda kalmayız. Sonra bu HttpContext'i bir denetleyici bağlamında kullanırız ve bunu denetleyici örneğine iletiriz:

var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
    new Claim(ClaimTypes.Name, "example name"),
    new Claim(ClaimTypes.NameIdentifier, "1"),
    new Claim("custom-claim", "example claim value"),
}, "mock"));

var controller = new SomeController(dependencies…);
controller.ControllerContext = new ControllerContext()
{
    HttpContext = new DefaultHttpContext() { User = user }
};

Kendinizinkini oluştururken, kurucuya ClaimsIdentityaçık bir şekilde ilettiğinizden emin olun authenticationType. Bu, IsAuthenticateddoğru çalışacağından emin olmanızı sağlar (bir kullanıcının kimliğinin doğrulanmış olup olmadığını belirlemek için kodunuzda bunu kullanmanız durumunda).


7
Benim durumumda, new Claim(ClaimTypes.Name, "1")denetleyici kullanımıyla eşleşmek içindi user.Identity.Name; ama aksi takdirde tam olarak elde etmeye çalıştığım şey buydu ... Danke schon!
Felix

Sayısız saat aradıktan sonra bu, sonunda beni köşeye sıkıştıran yazı oldu. Çekirdek 2.0 proje denetleyici yöntemimde User.FindFirstValue(ClaimTypes.NameIdentifier);, userId'yi oluşturduğum bir nesneye ayarlamak için kullanıyordum ve ana öğe boş olduğundan başarısız oluyordu. Bu benim için bunu düzeltti. Harika cevap için teşekkürler!
Timothy Randall

Ayrıca UserManager.GetUserAsync'in çalışmasını sağlamak için sayısız saat arıyordum ve eksik bağlantıyı bulduğum tek yer burası. Teşekkürler! GenericIdentity değil, Claim içeren ClaimsIdentity kurmanız gerekir.
Etienne Charland

17

Önceki sürümlerde User, bazı çok kolay birim testleri için doğrudan kontrolör üzerinde ayarlama yapabiliyordunuz .

ControllerBase için kaynak koduna bakarsanız, ' Userdan çıkarıldığını göreceksiniz HttpContext.

/// <summary>
/// Gets the <see cref="ClaimsPrincipal"/> for user associated with the executing action.
/// </summary>
public ClaimsPrincipal User => HttpContext?.User;

ve denetleyici HttpContextaracılığıyla erişirControllerContext

/// <summary>
/// Gets the <see cref="Http.HttpContext"/> for the executing action.
/// </summary>
public HttpContext HttpContext => ControllerContext.HttpContext;

Bu ikisinin salt okunur özellikler olduğunu fark edeceksiniz. İyi haber şu ki, ControllerContextmülkiyet değerinin ayarlanmasına izin veriyor, böylece sizin yolunuz olacak.

Yani hedef o nesneye ulaşmaktır. Çekirdek HttpContextsoyuttur, bu yüzden alay etmek çok daha kolaydır.

Gibi bir denetleyici varsayarsak

public class MyController : Controller {
    IMyContext _context;

    public MyController(IMyContext context) {
        _context = context;
    }

    public IActionResult Index() {
        SettingsViewModel svm = _context.MySettings(User.Identity.Name);
        return View(svm);
    }

    //...other code removed for brevity 
}

Moq kullanarak bir test şöyle görünebilir

public void Given_User_Index_Should_Return_ViewResult_With_Model() {
    //Arrange 
    var username = "FakeUserName";
    var identity = new GenericIdentity(username, "");

    var mockPrincipal = new Mock<ClaimsPrincipal>();
    mockPrincipal.Setup(x => x.Identity).Returns(identity);
    mockPrincipal.Setup(x => x.IsInRole(It.IsAny<string>())).Returns(true);

    var mockHttpContext = new Mock<HttpContext>();
    mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);

    var model = new SettingsViewModel() {
        //...other code removed for brevity
    };

    var mockContext = new Mock<IMyContext>();
    mockContext.Setup(m => m.MySettings(username)).Returns(model);

    var controller = new MyController(mockContext.Object) {
        ControllerContext = new ControllerContext {
            HttpContext = mockHttpContext.Object
        }
    };

    //Act
    var viewResult = controller.Index() as ViewResult;

    //Assert
    Assert.IsNotNull(viewResult);
    Assert.IsNotNull(viewResult.Model);
    Assert.AreEqual(model, viewResult.Model);
}

3

Mevcut sınıfları kullanma ve sadece gerektiğinde alay etme imkanı da vardır.

var user = new Mock<ClaimsPrincipal>();
_controller.ControllerContext = new ControllerContext
{
    HttpContext = new DefaultHttpContext
    {
        User = user.Object
    }
};

3

Benim durumumda, ben yararlanmak için gerekli Request.HttpContext.User.Identity.IsAuthenticated, Request.HttpContext.User.Identity.Nameve bazı iş lojik kontrol dışında oturan. Bunun için Nkosi, Calin ve Poke'un cevabının bir kombinasyonunu kullanabildim:

var identity = new Mock<IIdentity>();
identity.SetupGet(i => i.IsAuthenticated).Returns(true);
identity.SetupGet(i => i.Name).Returns("FakeUserName");

var mockPrincipal = new Mock<ClaimsPrincipal>();
mockPrincipal.Setup(x => x.Identity).Returns(identity.Object);

var mockAuthHandler = new Mock<ICustomAuthorizationHandler>();
mockAuthHandler.Setup(x => x.CustomAuth(It.IsAny<ClaimsPrincipal>(), ...)).Returns(true).Verifiable();

var controller = new MyController(...);

var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(m => m.User).Returns(mockPrincipal.Object);

controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext()
{
    User = mockPrincipal.Object
};

var result = controller.Get() as OkObjectResult;
//Assert results

mockAuthHandler.Verify();

2

Bir Soyut Fabrika Modeli uygulamak isterdim.

Özellikle kullanıcı adları sağlamak için bir fabrika için bir arayüz oluşturun.

Ardından, biri sağlayan User.Identity.Nameve testleriniz için işe yarayan diğer bazı sabit kodlanmış değerleri sağlayan somut sınıflar sağlayın.

Daha sonra, üretime karşı test koduna bağlı olarak uygun beton sınıfını kullanabilirsiniz. Belki de fabrikayı bir parametre olarak geçirmek veya bazı konfigürasyon değerlerine göre doğru fabrikaya geçmek.

interface IUserNameFactory
{
    string BuildUserName();
}

class ProductionFactory : IUserNameFactory
{
    public BuildUserName() { return User.Identity.Name; }
}

class MockFactory : IUserNameFactory
{
    public BuildUserName() { return "James"; }
}

IUserNameFactory factory;

if(inProductionMode)
{
    factory = new ProductionFactory();
}
else
{
    factory = new MockFactory();
}

SettingsViewModel svm = _context.MySettings(factory.BuildUserName());

Teşekkür ederim. Ben benzer bir şey yapıyorum benim nesneler. IPrinicpal gibi yaygın bir şey için "kutunun dışında" bir şey olacağını umuyordum. Ama görünüşe göre değil!
Felix

Ayrıca, Kullanıcı ControllerBase'in üye değişkenidir. Bu nedenle, ASP.NET'in önceki sürümlerinde insanlar HttpContext ile alay ediyorlardı ve buradan IPrincipal alıyorlardı. ProductionFactory gibi bağımsız bir sınıftan Kullanıcı elde edilemez
Felix

1

Denetleyicilerime doğrudan vurmak ve sadece AutoFac gibi DI kullanmak istiyorum. Bunu yapmak için önce kayıt oluyorum ContextController.

var identity = new GenericIdentity("Test User");
var httpContext = new DefaultHttpContext()
{
    User = new GenericPrincipal(identity, null)
};

var context = new ControllerContext { HttpContext = httpContext};
builder.RegisterInstance(context);

Sonra, Denetleyicileri kaydettiğimde özellik enjeksiyonunu etkinleştiririm.

  builder.RegisterAssemblyTypes(assembly)
                    .Where(t => t.Name.EndsWith("Controller")).PropertiesAutowired();

Sonra User.Identity.Namedoldurulur ve Denetleyicimde bir yöntemi çağırırken özel bir şey yapmam gerekmez.

public async Task<ActionResult<IEnumerable<Employee>>> Get()
{
    var requestedBy = User.Identity?.Name;
    ..................
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.