How to set UserLanguages for unit tests (using mocked http contexts)? - asp.net

I'm doing unittests for an asp.net application. Due to how the class I'm currently testing is designed it uses multiple threads and thus manually sets the language for one of the threads:
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(HttpContext.Current.Request.UserLanguages[0]);
For this to work I'm mocking the httpcontext by using the following class in the unittests before I run the class that contains the above code:
public class HttpContextMock
{
public static HttpContext MockedHttpContext()
{
var httpRequest = new HttpRequest("", "MyUrl", "");
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
new HttpStaticObjectsCollection(), 10, true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null)
.Invoke(new object[] { sessionContainer });
return httpContext;
}
Now the problem is even though I get a valid httpcontext the command
HttpContext.Current.Request.UserLanguages
Always returns null.
So my question is what can be done in order to ensure that the UserLanguages return at least 1 entry?

Mocking out HTTPContext isn't easy as you just found out.
Why not avoid that altogether?
Instead of
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(HttpContext.Current.Request.UserLanguages[0]);
You could do something like
Thread.CurrentThread.CurrentUICulture = _ContextSpecificSettings.Language;
Where _ContextSpecificSettings is an instance of a class you code yourself. Could be something like:
class ContextSpecificSettings
{
HTTPContext _HTTPContext;
public ContextSpecificSettings()
{
_HTTPContext = HTTPContext;
}
public string Language // used string but you would of course use here whatever type is used in UserLanguages
{
get { return _HttpContext.Current.Request.UserLanguages[0]; }
}
}
Now, instead of trying to mock the HTTPContext, you only have to mock/stub your own ContextSpecificSettings class. Which is much easier to do because all you have to mock/stub is returning the language you want to use in your test.
All that is left to do is to ensure that the class containing that assignment to Thread.CurrentThread.CurrentUICulture gets its hands on an instance of your ContextSpecificSettings class. You can do that using simple constructor injection. For example:
class SomeThreadUsingClass
{
ContextSpecificSettings _Settings;
public SomeThreadContextUsingClass(ContextSpecificSettings useThisSettingsInstance)
{
_Settings = useThisSettingsInstance;
}
public Int SomeMethodUsingLanguage()
{
// <snip>
Thread.CurrentThread.CurrentUICulture = _Settings.Language;
// <snip>
return Whatever;
}
}
When you instantiate SomeThreadUsingClass in your actual code, you pass it a (new) instance of the ContextSpecificSettings class. When you instantiate SomeThreadUsingClass in a test, all you have to do is pass it an instance an ContextSpecificSettings Mock/Stub class.
By the way, this is the same technique as I described in Property.Settings.Default makes it hard to unit test any method that uses it

Related

How to access HttpContext inside a unit test in ASP.NET 5 / MVC 6

Lets say I am setting a value on the http context in my middleware. For example HttpContext.User.
How can test the http context in my unit test. Here is an example of what I am trying to do
Middleware
public class MyAuthMiddleware
{
private readonly RequestDelegate _next;
public MyAuthMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
context.User = SetUser();
await next(context);
}
}
Test
[Fact]
public async Task UserShouldBeAuthenticated()
{
var server = TestServer.Create((app) =>
{
app.UseMiddleware<MyAuthMiddleware>();
});
using(server)
{
var response = await server.CreateClient().GetAsync("/");
// After calling the middleware I want to assert that
// the user in the HttpContext was set correctly
// but how can I access the HttpContext here?
}
}
Following are two approaches you could use:
// Directly test the middleware itself without setting up the pipeline
[Fact]
public async Task Approach1()
{
// Arrange
var httpContext = new DefaultHttpContext();
var authMiddleware = new MyAuthMiddleware(next: (innerHttpContext) => Task.FromResult(0));
// Act
await authMiddleware.Invoke(httpContext);
// Assert
// Note that the User property on DefaultHttpContext is never null and so do
// specific checks for the contents of the principal (ex: claims)
Assert.NotNull(httpContext.User);
var claims = httpContext.User.Claims;
//todo: verify the claims
}
[Fact]
public async Task Approach2()
{
// Arrange
var server = TestServer.Create((app) =>
{
app.UseMiddleware<MyAuthMiddleware>();
app.Run(async (httpContext) =>
{
if(httpContext.User != null)
{
await httpContext.Response.WriteAsync("Claims: "
+ string.Join(
",",
httpContext.User.Claims.Select(claim => string.Format("{0}:{1}", claim.Type, claim.Value))));
}
});
});
using (server)
{
// Act
var response = await server.CreateClient().GetAsync("/");
// Assert
var actual = await response.Content.ReadAsStringAsync();
Assert.Equal("Claims: ClaimType1:ClaimType1-value", actual);
}
}
The RC1 version of asp.net 5/MVC6 makes it possible to set HttpContext manually in Unit Tests, which is awesome!
DemoController demoController = new DemoController();
demoController.ActionContext = new ActionContext();
demoController.ActionContext.HttpContext = new DefaultHttpContext();
demoController.HttpContext.Session = new DummySession();
DefaultHttpContext class is provided by the platform.
DummySession can be just simple class that implements ISession class. This simplifies things a lot, because no more mocking is required.
It would be better if you unit test your middleware class in isolation from the rest of your code.
Since HttpContext class is an abstract class, you can use a mocking framework like Moq (adding "Moq": "4.2.1502.911", as a dependency to your project.json file) to verify that the user property was set.
For example you can write the following test that verifies your middleware Invoke function is setting the User property in the httpContext and calling the next middleware:
[Fact]
public void MyAuthMiddleware_SetsUserAndCallsNextDelegate()
{
//Arrange
var httpContextMock = new Mock<HttpContext>()
.SetupAllProperties();
var delegateMock = new Mock<RequestDelegate>();
var sut = new MyAuthMiddleware(delegateMock.Object);
//Act
sut.Invoke(httpContextMock.Object).Wait();
//Assert
httpContextMock.VerifySet(c => c.User = It.IsAny<ClaimsPrincipal>(), Times.Once);
delegateMock.Verify(next => next(httpContextMock.Object), Times.Once);
}
You could then write additional tests for verifying the user has the expected values, since you will be able to get the setted User object with httpContextMock.Object.User:
Assert.NotNull(httpContextMock.Object.User);
//additional validation, like user claims, id, name, roles
take a look at this post:
Setting HttpContext.Current.Session in a unit test
I think what you need is this.
public static HttpContext FakeHttpContext(string url)
{
var uri = new Uri(url);
var httpRequest = new HttpRequest(string.Empty, uri.ToString(),
uri.Query.TrimStart('?'));
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer("id",
new SessionStateItemCollection(),
new HttpStaticObjectsCollection(),
10, true, HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
SessionStateUtility.AddHttpSessionStateToContext(
httpContext, sessionContainer);
return httpContext;
}
Then you can use it like:
request.SetupGet(req => req.Headers).Returns(new NameValueCollection());
HttpContextFactory.Current.Request.Headers.Add(key, value);

SignalR AuthorizeHubConnection

I want to authenticate users when they connect to the signalr server. The clients are javascript and are cross-domain. I tried using this example and the AuthorizeHubMethodInvocation gets called but the AuthorizeHubConnection never gets called.
I created a new class AuthTicketAttribute where i override the two methods,
public class AuthTicketAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
return base.AuthorizeHubConnection(hubDescriptor, request);
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
return base.AuthorizeHubMethodInvocation(hubIncomingInvokerContext, appliesToMethod);
}
}
and then i added it in the signalr map function
var authorizer = new AuthTicketAttribute();
var module = new AuthorizeModule(authorizer, authorizer);
And then i tried debugging the code, only the AuthorizeHubMethodInvocation gets called when i call a method, the AuthorizeHubConnection doesn't get called when i connect. Any idears?
I then though maybe i could move the code setting the server.User in the enviroment from the AuthorizeHubConnection to the AuthorizeHubMethodInvocation,
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
IList<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, "The users username")
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claims, "Basic", ClaimTypes.NameIdentifier, ClaimTypes.Role);
var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;
environment["server.User"] = new ClaimsPrincipal(claimsIdentity);
hubIncomingInvokerContext.Hub.Context = new HubCallerContext(new ServerRequest(environment), hubIncomingInvokerContext.Hub.Context.ConnectionId);
return true;
}
but then i get a NullReferenceException when i set the server.User on the environment object, but none of the objects are null.
at System.Web.HttpContext.SetPrincipalNoDemand(IPrincipal principal, Boolean needToSetNativePrincipal)
at System.Web.HttpContext.set_User(IPrincipal value)
at Microsoft.Owin.Host.SystemWeb.OwinCallContext.Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary.IPropertySource.SetServerUser(IPrincipal value)
at Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary.set_ServerUser(IPrincipal value)
at Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary.PropertiesTrySetValue(String key, Object value)
at Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary.System.Collections.Generic.IDictionary<System.String,System.Object>.set_Item(String key, Object value)
at LifeCommunicationServer.AuthTicketAttribute.AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, Boolean appliesToMethod) in c:\Projects\TeamFoundation\LifeManager\CareManager-Master\LifeCommunicationServer\LifeCommunicationServer\AuthTicketAttribute.cs:line 62
at Microsoft.AspNet.SignalR.Hubs.AuthorizeModule.<>c__DisplayClass2b.<BuildIncoming>b__26(IHubIncomingInvokerContext context)
at Microsoft.AspNet.SignalR.Hubs.HubPipelineModule.<>c__DisplayClass1.<<BuildIncoming>b__0>d__3.MoveNext()
I figurred out why the AuthorizeHubConnection was not called.
I hadn't subscribed to anything from the hub before starting the connection.
$.connection.myHub.client.something = function() {
};
$.connection.hub.start().done(function() {
alert('I'm connected!');
});
once i did this, it gets called.
The answer above didn't work out for me.
The problem is that hubIncomingInvokerContext.Hub.Context.Request.Environment behaves as an IDictionary, but is in fact an Microsoft.Owin.Host.SystemWeb.CallEnvironment.AspNetDictionary which does some extra checks when assigning key-value pairs (and throws while doing so).
My solution is to add the content to a real Dictionary.
var environment = hubIncomingInvokerContext.Hub.Context.Request.Environment;
var copy = new Dictionary<string, object>(environment)
{
["server.User"] = new ClaimsPrincipal(claimsIdentity)
};
var serverRequest = new ServerRequest(copy);
hubIncomingInvokerContext.Hub.Context = new HubCallerContext(serverRequest, hubIncomingInvokerContext.Hub.Context.ConnectionId);

How to set user host address in request

I found this code for doing a mock request for the purposes of unit testing. I'm trying to set the userhostaddress but I'm not clear how to use the same method outlined in this code to achieve that. I'm thinking it has to be done through reflection as I'm finding setting the headers is not allowed. Any ideas how I can achieve this?
public static HttpContext FakeHttpContext()
{
var httpRequest = new HttpRequest("", "http://fakurl/", "");
var stringWriter = new StringWriter();
var httpResponse = new HttpResponse(stringWriter);
var httpContext = new HttpContext(httpRequest, httpResponse);
var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
new HttpStaticObjectsCollection(), 10, true,
HttpCookieMode.AutoDetect,
SessionStateMode.InProc, false);
httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null, CallingConventions.Standard,
new[] { typeof(HttpSessionStateContainer) },
null)
.Invoke(new object[] { sessionContainer });
httpContext.Request.Headers["REMOTE_ADDR"] = "XXX.XXX.XXX.XXX"; // not allowed
return httpContext;
}
I also tried this mocking library:
https://gist.github.com/rally25rs/1578697
By the user host address is still readonly.
Best approach I've found is to have some sort of ContextService that implements an IContextService interface. This class/interface pair can do whatever operations you need it to. Point is, if you use a mocking framework in your unit tests like MOQ then you can wire up a mock context service to return a particular ip address.
This StackOverflow post has some good pointers: Moq: unit testing a method relying on HttpContext.
UPDATE:
The StackOverflow post you found is also a good one: How to set the IP (UserHostAddress) on a "mocked' BaseHttpContext?
I find that often I'll only need a few properties off of the context/request/response objects, so I often roll my own smaller variant:
public class ContextService : IContextService
{
public string GetUserHostAddress()
{
return HttpContext.Current.Request.UserHostAddress;
}
}
public interface IContextService
{
string GetUserHostAddress();
}
With that class/interface combo, I can then use Moq to wire up a fake service:
var contextMock = new Moq.Mock<IContextService>();
contextMock.Setup(c => c.GetUserHostAddress()).Returns("127.0.0.1");
Now every time I call contextMock.GetUserHostAddress(), I'll get "127.0.0.1". Rolling your own can be a great learning experience, especially if you don't need all the bells and whistles of a full-blown (or as full as possible anyway) HttpContext mock.

How can i unit test an EntitySetController

i try to unit test an EntitySetController. I can test Get but have problems in testing the Post Method.
I played around with SetODataPath and SetODataRouteName but when i call this.sut.Post(entity) i get a lot of errors regarding missing Location Header, missing OData-Path, missing Routes.
I am at my wit's end.
Is there anybody out there who has successfully testet their EntitySetController?
Has anybody an idea for me?
Maybe should i test only the protected overrided methods from my EntitySetController implementation? But how can i test protected methods?
Thanks for your help
Came here looking for a solution aswell. This seems to work however not sure if there is a better way.
The controller needs a minimum of CreateEntity and GetKey overrides:
public class MyController : EntitySetController<MyEntity, int>
{
protected override MyEntity CreateEntity(MyEntity entity)
{
return entity;
}
protected override int GetKey(MyEntity entity)
{
return entity.Id;
}
}
Where MyEntity is really simple:
public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
Looks like you need at least:
+ Request with a URI
+ 3 keys in the request header, MS_HttpConfiguration, MS_ODataPath and MS_ODataRouteName
+ A HTTP configuration with a route
[TestMethod]
public void CanPostToODataController()
{
var controller = new MyController();
var config = new HttpConfiguration();
var request = new HttpRequestMessage();
config.Routes.Add("mynameisbob", new MockRoute());
request.RequestUri = new Uri("http://www.thisisannoying.com/MyEntity");
request.Properties.Add("MS_HttpConfiguration", config);
request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntity")));
request.Properties.Add("MS_ODataRouteName", "mynameisbob");
controller.Request = request;
var response = controller.Post(new MyEntity());
Assert.IsNotNull(response);
Assert.IsTrue(response.IsSuccessStatusCode);
Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
}
I'm not too sure about the IHttpRoute, in the aspnet source code (I had to link to this to figure this all out) the tests use mocks of this interface. So for this test I just create a mock of this and implement the RouteTemplate property and GetVirtualPath method. All the others on the interface were not used during the test.
public class MockRoute : IHttpRoute
{
public string RouteTemplate
{
get { return ""; }
}
public IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values)
{
return new HttpVirtualPathData(this, "www.thisisannoying.com");
}
// implement the other methods but they are not needed for the test above
}
This is working for me however I am really not too sure about the ODataPath and IHttpRoute and how to set it correctly.
In addition to the answer from #mynameisbob, I have found you also may need to set the HttpRequestContext as well on the Request properties:
var requestContext = new HttpRequestContext();
requestContext.Configuration = config;
request.Properties.Add(HttpPropertyKeys.RequestContextKey, requestContext);
I needed the above additions for example when creating an HttpResponseMessage as follows:
public virtual HttpResponseException NotFound(HttpRequestMessage request)
{
return new HttpResponseException(
request.CreateResponse(
HttpStatusCode.NotFound,
new ODataError
{
Message = "The entity was not found.",
MessageLanguage = "en-US",
ErrorCode = "Entity Not Found."
}
)
);
}
Without having the HttpRequestContext set, the above method will throw an Argument Null Exception as the CreateResponse extension method attempts to get the HttpConfiguration from the HttpRequestContext (rather than directly from the HttpRequest).
OK updated answer.
I've also found to support executing a returned IHttpActionResult successfully, a few more things are needed.
Here is the best approach I found so far, I'm sure there is a better way but this works for me:
// Register OData configuration with HTTP Configuration object
// Create an ODataConfig or similar class in App_Start
ODataConfig.Register(config);
// Get OData Parameters - suggest exposing a public GetEdmModel in ODataConfig
IEdmModel model = ODataConfig.GetEdmModel();
IEdmEntitySet edmEntitySet = model.EntityContainers().Single().FindEntitySet("Orders");
ODataPath path = new ODataPath(new EntitySetPathSegment(edmEntitySet));
// OData Routing Convention Configuration
var routingConventions = ODataRoutingConventions.CreateDefault();
// Attach HTTP configuration to HttpRequestContext
requestContext.Configuration = config;
// Attach Request URI
request.RequestUri = requestUri;
// Attach Request Properties
request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, config);
request.Properties.Add(HttpPropertyKeys.RequestContextKey, requestContext);
request.Properties.Add("MS_ODataPath", path);
request.Properties.Add("MS_ODataRouteName", "ODataRoute");
request.Properties.Add("MS_EdmModel", model);
request.Properties.Add("MS_ODataRoutingConventions", routingConventions);
request.Properties.Add("MS_ODataPathHandler", new DefaultODataPathHandler());
Also, to get the correct Location header values etc, you really want to call your Web Api application OData configuration code.
So rather than using:
config.Routes.Add("mynameisbob", new MockRoute());
You should separate the portion of the WebApiConfig class that sets up your OData routes into a separate class (e.g. ODataConfig) and use that to register the correct routes for your tests:
e.g.
ODataConfig.Register(config);
The only things you then have to watch out for is that the following lines match your routing configuration:
request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntity")));
request.Properties.Add("MS_ODataRouteName", "mynameisbob");
So if your Web API OData configuration is as follows:
config.Routes.MapODataRoute("ODataRoute", "odata", GetEdmModel());
private static IEdmModel GetEdmModel()
{
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<MyEntity>("MyEntities");
IEdmModel model = modelBuilder.GetEdmModel();
return model;
}
Then this is the correct configuration:
request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntities")));
request.Properties.Add("MS_ODataRouteName", "ODataRoute");
With this in place, your Location header will be generated correctly.
In addition to everything here, I had to manually attach the context to the request, as well as create route data. Unfortunately there is no way I found to unit-test without a dependency on route/model configuration.
So using a route called "ODataRoute" which is all part of the normal configuration established in my static ODataConfig.Configure() method (same as above, it creates the model and calls a bunch of MapODataServiceRoute), the following code works to prepare a controller for a test:
protected static void SetupControllerForTests(ODataController controller,
string entitySetName, HttpMethod httpMethod)
{
//perform "normal" server configuration
var config = new HttpConfiguration();
ODataConfig.Configure(config);
//set up the request
var request = new HttpRequestMessage(httpMethod,
new Uri(string.Format("http://localhost/odata/{0}", entitySetName)));
//attach it to the controller
//note that this will also automagically attach a context to the request!
controller.Request = request;
//get the "ODataRoute" route from the configuration
var route = (ODataRoute)config.Routes["ODataRoute"];
//extract the model from the route and create a path
var model = route.PathRouteConstraint.EdmModel;
var edmEntitySet = model.FindDeclaredEntitySet(entitySetName);
var path = new ODataPath(new EntitySetPathSegment(edmEntitySet));
//get a couple more important bits to set in the request
var routingConventions = route.PathRouteConstraint.RoutingConventions;
var pathHandler = route.Handler;
//set the properties of the request
request.SetConfiguration(config);
request.Properties.Add("MS_ODataPath", path);
request.Properties.Add("MS_ODataRouteName", "ODataRoute");
request.Properties.Add("MS_EdmModel", model);
request.Properties.Add("MS_ODataRoutingConventions", routingConventions);
request.Properties.Add("MS_ODataPathHandler", pathHandler);
//set the configuration in the request context
var requestContext = (HttpRequestContext)request.Properties[HttpPropertyKeys.RequestContextKey];
requestContext.Configuration = config;
//get default route data based on the generated URL and add it to the request
var routeData = route.GetRouteData("/", request);
request.SetRouteData(routeData);
}
This took me the better part of a few days to piece together, so I hope this at least saves someone else the same.

Using Asp.Net MVC Routes from a non-web application (tests)

I'm writing specflow tests using Watin, for an Asp.Net MVC application which uses T4MVC.
I find myself using "magic string" urls in the tests, which I don't like.
[Given(#"I am on the sign up page")]
public void GivenIAmOnTheSignUpPage()
{
string rootUrl = ConfigurationManager.AppSettings["RootUrl"];
string fullUrl = string.Format("{0}/Authentication/Signup",rootUrl);
WebBrowser.Current.GoTo(fullUrl);
}
I would much rather use my T4MVC Action Results like I do in the MVC App, something like this...
[Given(#"I am on the sign up page")]
public void GivenIAmOnTheSignUpPage()
{
WebBrowser.Current.GoTo(MVC.Authentication.SignUp().ToAbsoluteUrl());
}
My ToAbsoluteUrl Extension Method
public static class RouteHelper
{
private static UrlHelper _urlHelper;
private static string _rootUrl;
public static string ToAbsoluteUrl(this ActionResult result)
{
EnsureUrlHelperInitialized();
var relativeUrl = _urlHelper.Action(result);
return string.Format("{0}/{1}", _rootUrl, relativeUrl);
}
private static void EnsureUrlHelperInitialized()
{
if (_urlHelper==null)
{
_rootUrl = ConfigurationManager.AppSettings["RootUrl"];
var request = new HttpRequest("/", _rootUrl, "");
var response = new HttpResponse(new StringWriter());
var context = new HttpContext(request,response);
HttpContext.Current = context;
var httpContextBase = new HttpContextWrapper(context);
RouteTable.Routes.Clear();
MvcApplication.RegisterRoutes(RouteTable.Routes);
var requestContext = new RequestContext(httpContextBase, RouteTable.Routes.GetRouteData(httpContextBase));
_urlHelper = new UrlHelper(requestContext, RouteTable.Routes);
}
}
}
What is the correct way to initialize the RequestContext and RouteCollection so that I can generate my test URLs?
Currently I receive a NullReferenceException on the line var requestContext = new RequestContext(httpContextBase, RouteTable.Routes.GetRouteData(httpContextBase));. Is that the right way to new up a requestContext?
Or if there is a better way to take an ActionResult (from T4MVC) and resolve it to an absolute url, outside of a web app, that's really what I'm looking for.
public static class RouteHelper
{
private static UrlHelper _urlHelper;
private static string _rootUrl;
static RouteHelper()
{
var routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
var req = new HttpRequest(string.Empty, "http://www.site.com", null);
var res = new HttpResponse(null);
var ctx = new HttpContext(req, res); // do not use HttpContext.Current
var requestContext = new RequestContext(new HttpContextWrapper(ctx),
new RouteData());
_urlHelper = new UrlHelper(requestContext, routes);
_rootUrl = ConfigurationManager.AppSettings["RootUrl"];
}
public static string ToAbsoluteUrl(this ActionResult result)
{
return string.Format("{0}{1}", _rootUrl, _urlHelper.Action(result));
}
}
The static constructor sets up your private fields. I chose to use a new RouteCollection, instead of using the static RouteTable.Routes property, but you might be able to.
I don't think the constructors for the HttpRequest and HttpResponse matter. I just passed in some strings to get them to construct without throwing an exception. Use those to construct a brand new HttpContext (don't use HttpContext.Current when running from xUnit). You can then put it into an HttpContextWrapper to get your HttpContextBase reference.
Construct a new RequestContext, passing in your base wrapper and a new RouteData instance. Use that, along with your previous RouteCollection to construct the UrlHelper. Note that its Action method will return strings prepended with "/", so you should leave that out of our RootUrl appSetting (so use something like value="https://develop.site.com" without the trailing slash).
Note this will not work for routes defined in MVC areas. For that, you need to register the areas in addition to calling RegisterRoutes in global asax.

Resources