ASP.NET Core ControllerContext vs ActionContext in UrlHelper - asp.net

I am trying to implement pagination in my Asp.net core 2 API. To create pagination links, I am using UrlHelper. The constructor for UrlHelper requires the context in which the action runs.
The examples I've seen have been using below configuration in startup and then injecting IUrlHelper into the controller where it is needed.
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddScoped<IUrlHelper>(x => {
var actionContext = x.GetRequiredService<IActionContextAccessor>().ActionContext;
var factory = x.GetRequiredService<IUrlHelperFactory>();
return factory.GetUrlHelper(actionContext);
});
But controllers also have ControllerContext which derives from ActionContext (https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllercontext?view=aspnetcore-2.1).
I am able to do the following:
public Object GetAll() //ignore object return, for test purposes
{
var urlHelper = new UrlHelper(ControllerContext);
var nextLink = urlHelper.Link("GetPosts", new { page = 1, pageSize = 3 });
//return _context.Posts;
return new
{
NextPageLink = nextLink,
Results = _context.Posts,
test = ControllerContext.RouteData.Values
};
}
The code above is able to create the links correctly. I don't have a firm grasp on the nuances of the framework so I am wondering if above is a correct way to initialize UrlHelper. Will this lead to problems? If you can point me in the direction of some documentation around this or explain the reason behind if the approach is good/bad, that would be very helpful.

What you have can work.
It does however tightly couple the controller to an implementation concern.
If you have need for the helper you can follow a similar format to what was configured at startup by injecting the IUrlHelperFactory into the controller and getting the helper using the controller's ControllerContext, which as you have already discovered, derives from ActionContext
public class MyController : Controller {
private readonly IUrlHelperFactory factory;
//...other dependencies
public MyController(IUrlHelperFactory factory) {
this.factory = factory;
//...other dependencies
}
public IActionResult GetAll() {
var urlHelper = factory.GetUrlHelper(ControllerContext);
var nextLink = urlHelper.Link("GetPosts", new { page = 1, pageSize = 3 });
return Ok(new {
NextPageLink = nextLink,
Results = _context.Posts,
test = ControllerContext.RouteData.Values
});
}
//...other actions
}

Related

How do I test a Signal R hub that has LifetimeScope injected into it

How can I write unit tests to test my hub?
Here is my Hub Class:
public class MyHub : Hub
{
private readonly ILifetimeScope _scope;
private readonly IMyProvider _provider;
public MyHub(ILifetimeScope scope)
{
scope = _scope;
_provider = _scope.Resolve<IMyProvider>();
}
public void BroadcastMessage(int messageId)
{
var myMessage = _provider.GetFromDb(messageId);
Clients.All.broadcastMessage(myMessage);
}
}
I'm using moq and xunit, i've tried things like this:
//arrange
var messageId = 1;
var message = "test";
var providerMock = new Mock<IMyProvider>();
providerMock.Setup(x => x.GetFromDb(messageId).Returns(test);
var scopeMock = new Mock<ILifetimeScope>();
scopeMock.Setup(x => x.Resolve<IMyProvider>()).Returns(providerMock.Object);
var hub = new MyHub(scopeMock.Object);
//act
hub.BroadcastMessage(messageId);
//assert
providerMock.Verify(x => x.GetFromDb(messageId), Times.Once());
but this causes an error:
System.NotSupportedException : Unsupported expression: x => x.Resolve()
Extension methods (here: ResolutionExtensions.Resolve) may not be used in setup / verification expressions.
I found this answer: https://stackoverflow.com/a/49523868/3708225 that says I can do something like
using (var mock = AutoMock.GetLoose()){
var providerMock = new Mock<IMyPRovider>();
providerMock.Setup(x=>x.GetFromDb(messageId)).Returns(message);
mock.Provide(providerMock.Object);
var lifetime = mock.Create<ILifetimeScope>();
using (var scope = lifetimeScope.BeginLifetimeScope()){
var innerMockProvider = scope.Resolve<IMyProvider>();
//rest of test
}
}
but AutoMock.GetLoose().Provide() isn't defined
This is probably not the answer you are looking for. But a workaround would be not to mock lifetimescope but simply setup a autofac container to use in these tests.
Secondly do you need to inject the lifetimescope directly in your class? Maybe use a decorator pattern where you let the decorator create the lifetimescope and resolve your class and invoke it. Getting rid of the lifetimescope in your myhub class will make your life easier. Make it the job of some other class to control the lifetimescope. Else you will need to repeat this pattern in all your other classes as well. You should instead inject IMyProvider.
This is how I solved this:
If I change my hub to the following:
public class MyHub : Hub
{
private readonly <Func>IMyProvider _provider;
public MyHub(<Func>IMyProvider provider)
{
_provider = provider;
}
public void BroadcastMessage(int messageId)
{
var provider = _provider();
var myMessage = provider.GetFromDb(messageId);
Clients.All.broadcastMessage(myMessage);
}
}
Now i can mock Func<IMyProviider> to return what I need and ignore the lifetime scope

Mocking Delegate Factories in Service Libraries with Autofac and Moq

I have a simple console application which uses Autofac as IoC container.
class Program
{
static IContainer container;
static void Main(string[] args)
{
container = Configure();
Run();
}
private static void Run()
{
using (var scope = container.BeginLifetimeScope())
{
var t = scope.Resolve<ITest1>();
var s = t.TestMethod1("");
Console.WriteLine(s);
}
}
private static IContainer Configure()
{
var builder = new ContainerBuilder();
builder.RegisterType<TestClass1>()
.As<ITest1>();
builder.RegisterType<TestClass2>()
.As<ITest2>();
return builder.Build();
}
}
The application calls the method "TestMethod1" in "TestClass1".
public interface ITest1
{
string TestMethod1(string s);
}
public class TestClass1 : ITest1
{
ITest2 f;
public TestClass1(Func<ITest2> test2Factory)
{
f = test2Factory();
}
public string TestMethod1(string s)
{
var r = string.Empty;
r = f.TestMethod2(s);
r += ":TestMethod1";
return r;
}
}
TestClass1 has a dependency on TestClass2 which declares a delegate factory for Autofac to use with the TestClass1 constructor.
public interface ITest2
{
string TestMethod2(string s);
}
public class TestClass2 : ITest2
{
public delegate TestClass2 Factory();
public virtual string TestMethod2(string s)
{
return ":TestMethod2";
}
}
This all works as expected - Autofac resolves the TestClass2 dependency, and I get the output ":TestMethod2:TestMethod1".
Now I want to mock TestClass2 using Moq and the Autofac.Extras.Moq extensions. I add the following method to the console application, and call it from the Program Main method.
private static void Test()
{
using (var mock = AutoMock.GetLoose())
{
mock.Mock<TestClass2>()
.Setup(t => t.TestMethod2(""))
.Returns(":NOT_TEST_METHOD2");
var s = mock.Create<TestClass1>();
var r = s.TestMethod1("cheese");
Console.WriteLine(r);
}
}
Now I get the output ":TestMethod1" when I expect ":NOT_TEST_METHOD2:TestMethod1". It seems the mock has not been called. This is confirmed when I step through the code.
I have also tried resolving the mock using mock.Provide(), as has been suggested elsewhere (see below). Still no luck.
var wc = Moq.Mock.Of<TestClass2>(f => f.TestMethod2("") == ":NOT_TEST_METHOD2");
Func<string, ITest2> factory = x => wc;
mock.Provide(factory);
This seems like a really simple scenario but I've not found a working answer anywhere. Can anyone see what I'm doing wrong?
Thanks for any help!
I don't use the AutoMock support, but I do know my Autofac, so I can give it a shot.
It looks like TestClass1 takes an ITest2 and not a TestClass2 in its constructor, I'm guessing if you switched to this:
mock.Mock<ITest2>()
.Setup(t => t.TestMethod2(""))
.Returns(":NOT_TEST_METHOD2");
...then it might work.
Thanks Travis. Your suggestion didn't work for me, but it made me think about the issue differently and taking a step back made me realise I was looking for a complex solution where one wasn't required. Namely, that only Moq was needed, Autofac AutoMock extensions were not. The following code worked:
private static void Test()
{
Func<ITest2> func = () =>
{
var x = new Mock<ITest2>();
x.Setup(t => t.TestMethod2("")).Returns(":NOT_TEST_METHOD2");
return x.Object;
};
var s = new TestClass1(func);
var r = s.TestMethod1("");
}
The question was answered by this post Using Moq to Mock a Func<> constructor parameter and Verify it was called twice.

How create custom authorization attribute for checking a role and url path in Asp.Net Core?

I want to create a custom authorization attribute for checking the role and url path.
I've find the way for doing it in the Asp.Net Core using the Policy-Based-Authorization but I've tried implement it but I can't get the HttpContext with incomming url.
AuthorizationHandlerContext hasn't access to HttpContext probable.
How can I get current HttpContext with the url path? Is it possible to do that or with another way?
I've tried this code for creating the custom Policy:
public class RoleUrlValidationHandler : AuthorizationHandler<RoleUrlValidationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleUrlValidationRequirement requirement)
{
var path = //Here I need get current url path for example - /api/posts/4545411
var pathPart = path.Split('/');
var clientId = pathPart[3];
if (context.User.IsInRole(clientId))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
I want to create following:
[Authorize(Policy="RoleUrlValidation")] //Get ClientId from Url and check User's roles
public class PostsController : Controller
{
public ActionResult Get()
{
}
}
The policy approach is the right one. Only bit you missed is, that you can use Dependency Injection in the Handlers.
public class RoleUrlValidationHandler : AuthorizationHandler<RoleUrlValidationRequirement>
{
private readonly IHttpContextAccessor contextAccessor;
public RoleUrlValidationHandler(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleUrlValidationRequirement requirement)
{
var httpContext = contextAccessor.HttpContext;
var path = httpContext.Request.Path;
var pathPart = path.Split('/');
var clientId = pathPart[3];
if (context.User.IsInRole(clientId))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
You also may have to register the IHttpContextAccessor as its not registered by default.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Extra bits:
Consider using var routeData = httpContext.GetRouteData() instead of using path.Split('/') for reading values from it so you can easily read the parameter values from the route.
Try like this:
((DefaultHttpContext)context.Resource).Request.Path.Value

Missing owin context

I'm trying to use the ResourceAuthorize attribute from Thinktecture.IdentityModel, but everything stops because there is no owin context.
I have a owin startup class which setups the authorization manager
[assembly: OwinStartup(typeof(My.WebApi.Startup))]
namespace My.WebApi
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
AuthConfig.Configure(app);
}
}
}
public class AuthConfig
{
public static void Configure(IAppBuilder app)
{
app.UseResourceAuthorization(new ResourceAuthorizationMiddlewareOptions
{
Manager = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IResourceAuthorizationManager)) as IResourceAuthorizationManager
});
}
}
and I know that it is detected and invoked. But later on, when hitting the following code from IdentityModel, I get a null pointer exception:
public static Task<bool> CheckAccessAsync(this HttpRequestMessage request, IEnumerable<Claim> actions, IEnumerable<Claim> resources)
{
var authorizationContext = new ResourceAuthorizationContext(
request.GetOwinContext().Authentication.User ?? Principal.Anonymous,
actions,
resources);
return request.CheckAccessAsync(authorizationContext);
}
I have stepped through and sees that it's caused by the GetOwinContext() returning null, since there is no MS_OwinContext or MS_OwinEnvironment property on the request.
What am I missing?
UPDATE:
I have found that i have an owin.environment property available, but it's part of the `HttpContextWrapper, not the request.
By searching around, I found some code inside of System.Web.Http.WebHost.HttpControllerHandler that looks like it should have converted the owin.environment to an MS_OwinEnvironment, but apparently, that code is never called in my case...
internal static readonly string OwinEnvironmentHttpContextKey = "owin.Environment";
internal static readonly string OwinEnvironmentKey = "MS_OwinEnvironment";
internal static HttpRequestMessage ConvertRequest(HttpContextBase httpContextBase, IHostBufferPolicySelector policySelector)
{
HttpRequestBase requestBase = httpContextBase.Request;
HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethodHelper.GetHttpMethod(requestBase.HttpMethod), requestBase.Url);
bool bufferInput = policySelector == null || policySelector.UseBufferedInputStream((object) httpContextBase);
httpRequestMessage.Content = HttpControllerHandler.GetStreamContent(requestBase, bufferInput);
foreach (string str in (NameObjectCollectionBase) requestBase.Headers)
{
string[] values = requestBase.Headers.GetValues(str);
HttpControllerHandler.AddHeaderToHttpRequestMessage(httpRequestMessage, str, values);
}
HttpRequestMessageExtensions.SetHttpContext(httpRequestMessage, httpContextBase);
HttpRequestContext httpRequestContext = (HttpRequestContext) new WebHostHttpRequestContext(httpContextBase, requestBase, httpRequestMessage);
System.Net.Http.HttpRequestMessageExtensions.SetRequestContext(httpRequestMessage, httpRequestContext);
IDictionary items = httpContextBase.Items;
if (items != null && items.Contains((object) HttpControllerHandler.OwinEnvironmentHttpContextKey))
httpRequestMessage.Properties.Add(HttpControllerHandler.OwinEnvironmentKey, items[(object) HttpControllerHandler.OwinEnvironmentHttpContextKey]);
httpRequestMessage.Properties.Add(HttpPropertyKeys.RetrieveClientCertificateDelegateKey, (object) HttpControllerHandler._retrieveClientCertificate);
httpRequestMessage.Properties.Add(HttpPropertyKeys.IsLocalKey, (object) new Lazy<bool>((Func<bool>) (() => requestBase.IsLocal)));
httpRequestMessage.Properties.Add(HttpPropertyKeys.IncludeErrorDetailKey, (object) new Lazy<bool>((Func<bool>) (() => !httpContextBase.IsCustomErrorEnabled)));
return httpRequestMessage;
}
UPDATE 2:
Inside of mvc controllers, the context is available. But not in webapi controllers.
A team mate found a solution. He simply added the following line to the owin startup class:
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
Why this solves the issue is another mystery, though. But we are using wsFederation, so I guess it's needed some how. But what if we didn't use wsFed? Would we still need it to get a context? Who knows...

Mocking User.Identity in ASP.NET MVC

I need to create Unit Tests for an ASP.NET MVC 2.0 web site. The site uses Windows Authentication.
I've been reading up on the necessity to mock the HTTP context for code that deals with the HttpContext. I feel like I'm starting to get a handle on the DI pattern as well. (Give the class an attribute of type IRepository and then pass in a Repository object when you instantiate the controller.)
What I don't understand, however, is the proper way to Mock the Windows Principal object available through User.Identity. Is this part of the HttpContext?
Does any body have a link to an article that demonstrates this (or a recommendation for a book)?
Thanks,
Trey Carroll
I've used IoC to abstract this away with some success. I first defined a class to represent the currently logged in user:
public class CurrentUser
{
public CurrentUser(IIdentity identity)
{
IsAuthenticated = identity.IsAuthenticated;
DisplayName = identity.Name;
var formsIdentity = identity as FormsIdentity;
if (formsIdentity != null)
{
UserID = int.Parse(formsIdentity.Ticket.UserData);
}
}
public string DisplayName { get; private set; }
public bool IsAuthenticated { get; private set; }
public int UserID { get; private set; }
}
It takes an IIdentity in the constructor to set its values. For unit tests, you could add another constructor to allow you bypass the IIdentity dependency.
And then I use Ninject (pick your favorite IoC container, doesn't matter), and created a binding for IIdentity as such:
Bind<IIdentity>().ToMethod(c => HttpContext.Current.User.Identity);
Then, inside of my controller I declare the dependency in the constructor:
CurrentUser _currentUser;
public HomeController(CurrentUser currentUser)
{
_currentUser = currentUser;
}
The IoC container sees that HomeController takes a CurrentUser object, and the CurrentUser constructor takes an IIdentity. It will resolve the dependencies automatically, and voila! Your controller can know who the currently logged on user is. It seems to work pretty well for me with FormsAuthentication. You might be able to adapt this example to Windows Authentication.
I don't know for MVC 2.0, but in newer versions you can mock the ControllerContext:
// create mock principal
var mocks = new MockRepository(MockBehavior.Default);
Mock<IPrincipal> mockPrincipal = mocks.Create<IPrincipal>();
mockPrincipal.SetupGet(p => p.Identity.Name).Returns(userName);
mockPrincipal.Setup(p => p.IsInRole("User")).Returns(true);
// create mock controller context
var mockContext = new Mock<ControllerContext>();
mockContext.SetupGet(p => p.HttpContext.User).Returns(mockPrincipal.Object);
mockContext.SetupGet(p => p.HttpContext.Request.IsAuthenticated).Returns(true);
// create controller
var controller = new MvcController() { ControllerContext = mock.Object };
see also How to unit-test an MVC controller action which depends on authentification in c#?
Scott Hanselman shows in his blog how to use IPrincipal and ModelBinder to make easier to test the controller by mocking IPrincipal.
Example for mocking username and SID on MVC4.
The username and SID (Windows Authentication) in the following action should be tested:
[Authorize]
public class UserController : Controller
{
public ActionResult Index()
{
// get Username
ViewBag.Username = User.Identity.Name;
// get SID
var lIdentity = HttpContext.User.Identity as WindowsIdentity;
ViewBag.Sid = lIdentity.User.ToString();
return View();
}
}
I use Moq and Visual Studio Test Tools. The test is implemented as follows:
[TestMethod]
public void IndexTest()
{
// Arrange
var myController = new UserController();
var contextMock = new Mock<ControllerContext>();
var httpContextMock = new Mock<HttpContextBase>();
var lWindowsIdentity = new WindowsIdentity("Administrator");
httpContextMock.Setup(x => x.User).Returns(new WindowsPrincipal(lWindowsIdentity));
contextMock.Setup(ctx => ctx.HttpContext).Returns(httpContextMock.Object);
myController.ControllerContext = contextMock.Object;
// Act
var lResult = myController.Index() as ViewResult;
// Assert
Assert.IsTrue(lResult.ViewBag.Username == "Administrator");
Assert.IsTrue(lResult.ViewBag.Sid == "Any SID Pattern");
}
I've changed dev environment global.asax and Web.Config for use FormsAuth for force a specific user. The username uses the same WindowsAuth format. See:
public override void Init()
{
base.Init();
this.PostAuthenticateRequest +=
new EventHandler(MvcApplication_PostAuthenticateRequest);
}
void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
{
FormsAuthentication.SetAuthCookie("Domain\\login", true);
}
The Windows or Forms Auth shares the same login patterns.
The application will work with both Windows authentication and Form authentication.
To mock WindowsIdentity you can do the following:
var mockedPrincipal = new Mock<WindowsPrincipal>(WindowsIdentity.GetCurrent());
mockedPrincipal.SetupGet(x => x.Identity.IsAuthenticated).Returns(true);
mockedPrincipal.SetupGet(x => x.Identity.Name).Returns("Domain\\User1");
mockedPrincipal.Setup(x => x.IsInRole("Domain\\Group1")).Returns(true);
mockedPrincipal.Setup(x => x.IsInRole("Domain\\Group2")).Returns(false);
then use mockedPrincipal.Object to get the actual WindowsIdentity

Resources