I am new to web APIs and my design might be plain wrong, so feel free to correct my idea. We are building a series of web APIs, and we need to implement for most of them an excel and CSV export. What I imagined was that I could list all the routes in my application, and dynamically add new routes that handle the excel/CSV conversion.
For example, if I have a route api/customers, I want my service to create a new route on its own, which will be api/customers/excel and will return the same data as an excel file. This new route will take all the arguments of the initial route plus the names of the columns to export.
Adding the routes went kinda alright (even though I did not test it extensively and I might have added bugs with my copy ?) :
public class CustomDirectRouteProvider : DefaultDirectRouteProvider
{
public override IReadOnlyList<RouteEntry> GetDirectRoutes(HttpControllerDescriptor controllerDescriptor, IReadOnlyList<HttpActionDescriptor> actionDescriptors,
IInlineConstraintResolver constraintResolver)
{
var routes = base.GetDirectRoutes(controllerDescriptor, actionDescriptors, constraintResolver);
var result = new List<RouteEntry>();
foreach (var routeEntry in routes)
{
result.Add(routeEntry);
var defaults = new HttpRouteValueDictionary(routeEntry.Route.Defaults.ToDictionary(kv => kv.Key, kv => kv.Value));
var constraints = new HttpRouteValueDictionary(routeEntry.Route.Constraints.ToDictionary(kv => kv.Key, kv => kv.Value));
var dataTokens = new HttpRouteValueDictionary(routeEntry.Route.DataTokens.ToDictionary(kv => kv.Key, kv => kv.Value));
var copy = new RouteEntry(routeEntry.Name,
new HttpRoute(
routeEntry.Route.RouteTemplate + "/csv",
defaults,
constraints,
dataTokens
));
result.Add(copy);
}
return new ReadOnlyCollection<RouteEntry>(result);
}
}
public class OwinConfiguration
{
public void Configure(IAppBuilder appBuilder)
{
/* lots of stuff */
var inlineConstraintResolver = new DefaultInlineConstraintResolver();
var directRouteProvider = new CustomDirectRouteProvider();
config.MapHttpAttributeRoutes(inlineConstraintResolver, directRouteProvider);
}
}
What I would like to do now is to use the underlying handler for the newly created routes, and then call my conversion method :
return response.AsCsvExport(columnHeaders);
on the HttpResponseMessage. For now, the route copy does the same thing as the reference route. If I try to add a handler in the HttpRoute constructor :
public class CsvExportDelegatingHandler: DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var route = request.GetRouteData().Route.RouteTemplate;
var response = await base.SendAsync(request, cancellationToken);
return response.AsCsvExport();
}
}
var handler = HttpClientFactory.CreatePipeline(new HttpControllerDispatcher(Configuration), new DelegatingHandler[] { new CsvExportDelegatingHandler() });
my app fails to initialize. Is there any way to do that?
Our service is built with WepApi 2, Owin and Autofac if that matters.
Related
I need to select OutputFormatter depending on the query parameter. How to do that?
I am moving from .NET Framework WebApi to .NET Core WebApi. The .NET Framework WebApi had DefaultContentNegotiator class do that:
public class CustomContentNegotiator : DefaultContentNegotiator
{
public override ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
//Read query from request object and add output formatters below
bindFormatters = new List<MediaTypeFormatter>
{
new ConvertResultRawFormatter(),
new JsonMediaTypeFormatter
{
SerializerSettings =
{
NullValueHandling = NullValueHandling.Ignore
}
}
};
}
return base.Negotiate(type, request, bindFormatters);
}
}
replace in configuration with new formatted negotiator
config.Services.Replace(typeof(IContentNegotiator), new CustomContentNegotiator());
Will the custom output formatters not work for you? Read this ms docs
From the comments it seems that the real question is how to add the Content-Disposition: inline header if the URL contains download=inline parameter. This doesn't concern formatting or content negotiation.
There are several ways to add headers to a response. One of them is to add inline middleware that adds the header if the query parameter is present :
app.Use(async (context, next) =>
{
context.Response.OnStarting(() =>
{
var download= context.Request.Query["download"];
if (download=="inline")
{
context.Response.Headers.Add("Content-Disposition", "inline");
}
}
}
This affects all routes
Web API OData v7. I'm writing a custom formatter for CSV, Excel, etc. I have a disconnect of how I point my custom formatter (ODataMediaTypeFormatter) to my custom classes where I modify the output.
CustomFormatter : ODataMediaTypeFormatter - had a MessageWriterSettings.MediaTypeResolver which no longer exists in v. 7
When I debug, I get to the GetPerRequestFormatterInstance, and after that it dies with A supported MIME type could not be found that matches the content type of the response.
I can't figure out the flow--how to tie it to my custom (ODataWriter) writer (csv, or whatever I wish to create).
For instance, from the example on git:
public class CustomFormatter : ODataMediaTypeFormatter
{
private readonly string csvMime = ;
public CustomFormatter(params ODataPayloadKind[] kinds)
: base(kinds) {
//----no longer exists in 7
//MessageWriterSettings.MediaTypeResolver = new MixResolver();
SupportedEncodings.Add(Encoding.UTF8);
SupportedEncodings.Add(new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
}
}
public class MixResolver : ODataMediaTypeResolver
{
public override IEnumerable<ODataMediaTypeFormat> GetMediaTypeFormats(ODataPayloadKind payloadKind)
{
if (payloadKind == ODataPayloadKind.Resource || payloadKind == ODataPayloadKind.ResourceSet)
{
return CsvMediaTypeResolver.Instance.GetMediaTypeFormats(payloadKind);
}
return base.GetMediaTypeFormats(payloadKind);
}
}
public class CsvMediaTypeResolver : ODataMediaTypeResolver
{
private static readonly CsvMediaTypeResolver instance = new CsvMediaTypeResolver();
private readonly ODataMediaTypeFormat[] mediaTypeFormats =
{
new ODataMediaTypeFormat(new ODataMediaType("text", "csv"), new CsvFormat())
};
public class CsvMediaTypeResolver : ODataMediaTypeResolver
{
private static readonly CsvMediaTypeResolver instance = new CsvMediaTypeResolver();
private readonly ODataMediaTypeFormat[] mediaTypeFormats = { new ODataMediaTypeFormat(new ODataMediaType("text", "csv"), new CsvFormat())};
private CsvMediaTypeResolver() { }
public static CsvMediaTypeResolver Instance { get { return instance; } }
public override IEnumerable<ODataMediaTypeFormat> GetMediaTypeFormats(ODataPayloadKind payloadKind)
{
if (payloadKind == ODataPayloadKind.Resource || payloadKind == ODataPayloadKind.ResourceSet)
{
return mediaTypeFormats.Concat(base.GetMediaTypeFormats(payloadKind));
}
return base.GetMediaTypeFormats(payloadKind);
}
}
public class CsvWriter : ODataWriter
{
// Etc..
}
The disconnect is with ODataMediaTypeFormatter and CsvMediaTypeResolver. How do I link the ODataMediaTypeFormatter to my resolver?
I have solved this by the CsvOutputContext and CsvWriterDemo explained in the examples in Microsoft.OData.Core
Example Code Updated
public CsvOutputContext(
ODataFormat format,
ODataMessageWriterSettings settings,
ODataMessageInfo messageInfo,
bool synchronous)
: base(format, settings, messageInfo.IsResponse, synchronous,
messageInfo.Model, messageInfo.UrlResolver)
{
this.stream = messageInfo.GetMessageStream();
this.Writer = new StreamWriter(this.stream);
}
}
private static void CsvWriterDemo()
{
EdmEntityType customer = new EdmEntityType("ns", "customer");
var key = customer.AddStructuralProperty("Id", EdmPrimitiveTypeKind.Int32);
customer.AddKeys(key);
customer.AddStructuralProperty("Name", EdmPrimitiveTypeKind.String);
ODataEntry entry1 = new ODataEntry()
{
Properties = new[]
{
new ODataProperty(){Name = "Id", Value = 51},
new ODataProperty(){Name = "Name", Value = "Name_A"},
}
};
ODataEntry entry2 = new ODataEntry()
{
Properties = new[]
{
new ODataProperty(){Name = "Id", Value = 52},
new ODataProperty(){Name = "Name", Value = "Name_B"},
}
};
var stream = new MemoryStream();
var message = new Message { Stream = stream };
// Set Content-Type header value
message.SetHeader("Content-Type", "text/csv");
var settings = new ODataMessageWriterSettings
{
// Set our resolver here.
MediaTypeResolver = CsvMediaTypeResolver.Instance,
DisableMessageStreamDisposal = true,
};
using (var messageWriter = new ODataMessageWriter(message, settings))
{
var writer = messageWriter.CreateODataFeedWriter(null, customer);
writer.WriteStart(new ODataFeed());
writer.WriteStart(entry1);
writer.WriteEnd();
writer.WriteStart(entry2);
writer.WriteEnd();
writer.WriteEnd();
writer.Flush();
}
stream.Seek(0, SeekOrigin.Begin);
string msg;
using (var sr = new StreamReader(stream)) { msg = sr.ReadToEnd(); }
Console.WriteLine(msg);
}
As described in this document:
In ODataLib v7.0, Dependency Injection (or "DI" in short) support is introduced to simplify the API and implementation of ODataLib by eliminating redundant function parameters and class properties.
To make DI work properly with ODataLib, basically there are several things you have to do within your application:
Implement your container builder based on your DI framework.
Register the required services from both ODataLib and your application.
Build and use the container (to retrieve the services) in ODataLib.
Microsoft uses IServiceProvider interface as the abstraction of container. Whereas container is read only you have to implement IContainerBuilder interface. Then inject your container. After that, register the required services into the container. You can use extension methods defined in ContainerBuilderExtensions class to register services as ease.
You have to be cautious before using these methods:
For AddServicePrototype, we currently only support the following service types: ODataMessageReaderSettings, ODataMessageWriterSettings and ODataSimplifiedOptions. This design follows the Prototype Pattern where you can register a globally singleton instance (as the prototype) for each service type then you will get an individual clone per scope/request. Modifying that clone will not affect the singleton instance as well as the subsequent clones. That is to say now you don't need to clone a writer setting before editing it with the request-related information just feel safe to modify it for any specific request.
The AddDefaultODataServices method registers a set of service types with default implementations that come from ODataLib. Typically you MUST call this method first on your container builder before registering any custom service. Please note that the order of registration matters! ODataLib will always use the last service implementation registered for a specific service type.
There is a list of services in the mentioned document that you can override; ODataMediaTypeResolver is one of them. Consider to the list, before any service registeration.
Now you can build a container by calling BuildContainer on your builder. That gives you a container instance that implements IServiceProvider.
In order to use registered services in ODataLib, you must pass the container into ODataLib through some entry point.
Currently entry points in ODataLib are ODataMessageReader, ODataMessageWriter, and ODataUriParser.
1. Serialization and Deserialization:
You could pass container into ODataMessageReader or ODataMessageWriter through request and response message. To do so you should create a class that implements IODataRequestMessage and IODataResponseMessage, and IContainerProvider like below:
class ODataMessageWrapper : IODataRequestMessage, IODataResponseMessage, IContainerProvider, ...
{
public IServiceProvider Container { get; set; }
// rest of the implementation here
}
And then you can use the ODataMessageWrapper class to pass the container into ODataLib as below:
ODataMessageWrapper responseMessage = new ODataMessageWrapper();
responseMessage.Container = Request.GetRequestContainer();
ODataMessageWriter writer = new ODataMessageWriter(responseMessage);
In the above example GetRequestContainer is an extension of HttpRequestMessage implemented in Microsoft.AspNet.OData.HttpRequestMessageExtensions.cs.
Now container is stored in the Container properties of ODataMessageInfo, ODataInputContext, and ODataOutputContext and their subclasses. In order of implementing custom media types, you can access the container through those properties.
If you fail to set the Container in IContainerProvider, it will remain null. In this case, ODataLib will not fail internally but all services will have their default implementations and there would be NO way to replace them with custom ones. That said, if you want extensibility, please use DI :-)
2. URI Parsing:
To pass a container into URI parser you should use the constructor overloads of ODataUriParser. If you use other constructors the DI support in URI parsers will be disabled. This way container will be saved in ODataUriParserConfiguratino and used in URI parser.
public sealed class ODataUriParser
{
public ODataUriParser(IEdmModel model, Uri serviceRoot, Uri uri, IServiceProvider container);
public ODataUriParser(IEdmModel model, Uri relativeUri, IServiceProvider container);
}
Currently ODataUriResolver, UriPathParser and ODataSimplifiedOptions can be overridden and will affect the behavior of URI parsers.
This is probably going to turn out to be a case of just needing another pair of eyes. I must be missing something, but I cannot figure out why this kind of thing cannot be tested for. I'm basically trying to ensure that unauthenticated users cannot access the view by marking the controller with the [Authorize] attribute and I'm trying to tests this using the following code:
[Fact]
public void ShouldRedirectToLoginForUnauthenticatedUsers()
{
var mockControllerContext = new Mock<ControllerContext>()
{ DefaultValue = DefaultValue.Mock };
var controller = new MyAdminController()
{ControllerContext = mockControllerContext.Object};
mockControllerContext.Setup(c =>
c.HttpContext.Request.IsAuthenticated).Returns(false);
var result = controller.Index();
Assert.IsAssignableFrom<RedirectResult>(result);
}
The RedirectResult I'm looking for is some kind of indication that the user is being redirected to the login form, but instead a ViewResult is always returned and when debugging I can see that the Index() method is successfully hit even though the user is not authenticated.
Am I doing something wrong? Testing at the wrong level? Should I rather be testing at the route level for this kind of thing?
I know that the [Authorize] attribute is working, because when I spin up the page, the login screen is indeed forced upon me - but how do I verify this in a test?
The controller and index method are very simple just so that I can verify the behaviour. I've included them for completeness:
[Authorize]
public class MyAdminController : Controller
{
public ActionResult Index()
{
return View();
}
}
Any help appreciated...
You are testing at the wrong level. The [Authorize] attribute ensures that the routing engine will never invoke that method for an unauthorized user - the RedirectResult will actually be coming from the route, not from your controller method.
Good news is - there's already test coverage for this (as part of the MVC framework source code), so I'd say you don't need to worry about it; just make sure your controller method does the right thing when it gets called, and trust the framework not to call it in the wrong circumstances.
EDIT: If you want to verify the presence of the attribute in your unit tests, you'll need to use reflection to inspect your controller methods as follows. This example will verify the presence of the Authorize attribute on the ChangePassword POST method in the 'New ASP.NET MVC 2 Project' demo that's installed with MVC2.
[TestFixture]
public class AccountControllerTests {
[Test]
public void Verify_ChangePassword_Method_Is_Decorated_With_Authorize_Attribute() {
var controller = new AccountController();
var type = controller.GetType();
var methodInfo = type.GetMethod("ChangePassword", new Type[] { typeof(ChangePasswordModel) });
var attributes = methodInfo.GetCustomAttributes(typeof(AuthorizeAttribute), true);
Assert.IsTrue(attributes.Any(), "No AuthorizeAttribute found on ChangePassword(ChangePasswordModel model) method");
}
}
Well you might be testing at the wrong level but its the test that makes sense. I mean, if I flag a method with the authorize(Roles="Superhero") attribute, I don't really need a test if I flagged it. What I (think I) want is to test that an unauthorized user doesn't have access and that an authorized user does.
For a unauthorized user a test like this:
// Arrange
var user = SetupUser(isAuthenticated, roles);
var controller = SetupController(user);
// Act
SomeHelper.Invoke(controller => controller.MyAction());
// Assert
Assert.AreEqual(401,
controller.ControllerContext.HttpContext.Response.StatusCode, "Status Code");
Well, it's not easy and it took me 10 hours, but here it is. I hope someone can benefit from it or convince me to go into another profession. :) (BTW - I'm using rhino mock)
[Test]
public void AuthenticatedNotIsUserRole_Should_RedirectToLogin()
{
// Arrange
var mocks = new MockRepository();
var controller = new FriendsController();
var httpContext = FakeHttpContext(mocks, true);
controller.ControllerContext = new ControllerContext
{
Controller = controller,
RequestContext = new RequestContext(httpContext, new RouteData())
};
httpContext.User.Expect(u => u.IsInRole("User")).Return(false);
mocks.ReplayAll();
// Act
var result =
controller.ActionInvoker.InvokeAction(controller.ControllerContext, "Index");
var statusCode = httpContext.Response.StatusCode;
// Assert
Assert.IsTrue(result, "Invoker Result");
Assert.AreEqual(401, statusCode, "Status Code");
mocks.VerifyAll();
}
Although, thats not very useful without this helper function:
public static HttpContextBase FakeHttpContext(MockRepository mocks, bool isAuthenticated)
{
var context = mocks.StrictMock<HttpContextBase>();
var request = mocks.StrictMock<HttpRequestBase>();
var response = mocks.StrictMock<HttpResponseBase>();
var session = mocks.StrictMock<HttpSessionStateBase>();
var server = mocks.StrictMock<HttpServerUtilityBase>();
var cachePolicy = mocks.Stub<HttpCachePolicyBase>();
var user = mocks.StrictMock<IPrincipal>();
var identity = mocks.StrictMock<IIdentity>();
var itemDictionary = new Dictionary<object, object>();
identity.Expect(id => id.IsAuthenticated).Return(isAuthenticated);
user.Expect(u => u.Identity).Return(identity).Repeat.Any();
context.Expect(c => c.User).PropertyBehavior();
context.User = user;
context.Expect(ctx => ctx.Items).Return(itemDictionary).Repeat.Any();
context.Expect(ctx => ctx.Request).Return(request).Repeat.Any();
context.Expect(ctx => ctx.Response).Return(response).Repeat.Any();
context.Expect(ctx => ctx.Session).Return(session).Repeat.Any();
context.Expect(ctx => ctx.Server).Return(server).Repeat.Any();
response.Expect(r => r.Cache).Return(cachePolicy).Repeat.Any();
response.Expect(r => r.StatusCode).PropertyBehavior();
return context;
}
So that gets you confirmation that users not in a role don't have access. I tried writing a test to confirm the opposite, but after two more hours of digging through mvc plumbing I will leave it to manual testers. (I bailed when I got to the VirtualPathProviderViewEngine class. WTF? I don't want anything to do a VirtualPath or a Provider or ViewEngine much the union of the three!)
I am curious as to why this is so hard in an allegedly "testable" framework.
Why not just use reflection to look for the [Authorize] attribute on the controller class and / or the action method you are testing? Assuming the framework does make sure the Attribute is honored, this would be the easiest thing to do.
I don't agree with Dylan's answer, because 'user must be logged in' does not imply that 'controller method is annotated with AuthorizeAttribute'
to ensure 'user must be logged in' when you call the action method, the ASP.NET MVC framework does something like this (just hold on, it will get simpler eventually)
let $filters = All associated filter attributes which implement
IAuthorizationFilter
let $invoker = instance of type ControllerActionInvoker
let $ctrlCtx = instance or mock of type ControllerContext
let $actionDesc = instance or mock of type ActionDescriptor
let $authzCtx = $invoker.InvokeAuthorizationFilters($ctrlCtx, $filters, $actionDesc);
then controller action is authorized when $authzCtx.Result is not null
It is hard to implement this pseudo script in a working c# code. Likely, Xania.AspNet.Simulator makes it really simple to setup a test like this and performs exactly these step under the cover. here is an example.
first install the package from nuget (version 1.4.0-beta4 at the time of writing)
PM > install-package Xania.AspNet.Simulator -Pre
Then your test method could look like this (assuming NUnit and FluentAssertions are installed):
[Test]
public void AnonymousUserIsNotAuthorized()
{
// arrange
var action = new ProfileController().Action(c => c.Index());
// act
var result = action.GetAuthorizationResult();
// assert
result.Should().NotBeNull();
}
[Test]
public void LoggedInUserIsAuthorized()
{
// arrange
var action = new ProfileController().Action(c => c.Index())
// simulate authenticated user
.Authenticate("user1", new []{"role1"});
// act
var result = action.GetAuthorizationResult();
// assert
result.Should().BeNull();
}
For .NET Framework we use this class to verify that every MVC and API Controller have AuthorizeAttribute and that every API Controller should have a RoutePrefixAttribute.
[TestFixture]
public class TestControllerHasAuthorizeRole
{
private static IEnumerable<Type> GetChildTypes<T>()
{
var types = typeof(Startup).Assembly.GetTypes();
return types.Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
[Test]
public void MvcControllersShouldHaveAuthrorizeAttribute()
{
var controllers = GetChildTypes<Controller>();
foreach (var controller in controllers)
{
var authorizeAttribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Mvc.AuthorizeAttribute), true) as System.Web.Mvc.AuthorizeAttribute;
Assert.IsNotNull(authorizeAttribute, $"MVC-controller {controller.FullName} does not implement AuthorizeAttribute");
}
}
[Test]
public void ApiControllersShouldHaveAuthorizeAttribute()
{
var controllers = GetChildTypes<ApiController>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Http.AuthorizeAttribute), true) as System.Web.Http.AuthorizeAttribute;
Assert.IsNotNull(attribute, $"API-controller {controller.FullName} does not implement AuthorizeAttribute");
}
}
[Test]
public void ApiControllersShouldHaveRoutePrefixAttribute()
{
var controllers = GetChildTypes<ApiController>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(System.Web.Http.RoutePrefixAttribute), true) as System.Web.Http.RoutePrefixAttribute;
Assert.IsNotNull(attribute, $"API-controller {controller.FullName} does not implement RoutePrefixAttribute");
Assert.IsTrue(attribute.Prefix.StartsWith("api/", StringComparison.OrdinalIgnoreCase), $"API-controller {controller.FullName} does not have a route prefix that starts with api/");
}
}
}
It is a bit easier in .NET Core and .NET 5<. Here a MVC Controller inherits from Controller that in turn inherits from ControllerBase. An Api Controller inherits directly from ControllerBase and therefore we can test MVC and API Controllers using a single method:
public class AuthorizeAttributeTest
{
private static IEnumerable<Type> GetChildTypes<T>()
{
var types = typeof(Startup).Assembly.GetTypes();
return types.Where(t => t.IsSubclassOf(typeof(T)) && !t.IsAbstract);
}
[Fact]
public void ApiAndMVCControllersShouldHaveAuthorizeAttribute()
{
var controllers = GetChildTypes<ControllerBase>();
foreach (var controller in controllers)
{
var attribute = Attribute.GetCustomAttribute(controller, typeof(Microsoft.AspNetCore.Authorization.AuthorizeAttribute), true) as Microsoft.AspNetCore.Authorization.AuthorizeAttribute;
Assert.NotNull(attribute);
}
}
}
I´m currently working on a MVC4 Project. As I did a few refactorings the tests should be changed too.
In the first scenario a session hash had to be passed included within the URL. That being nasted, I decided to pass it to the server as a Request header.
Example:
[HttpPost]
public ActionResult Generate(ClipGenerateRequest request)
{
string hash = Request.Headers["hash"];
ClipGenerationResponse responseModel = new ClipGenerationResponse();
return Json(responseModel, JsonRequestBehavior.AllowGet);
}
The problem now seems to be that I'm unable to mock the Request object to a custom one as the Request object is a read-only object. Dynamically setting the header doesn't work, as the Request is null when performing Unit Tests. So the following won't work:
[TestMethod]
public void GenerateTest()
{
GenerationController target = new GenerationController(this.loginModelMock, this.clipTemplateModelMock, this.clipTemplateFieldModelMock);
target.Request = this.requestBase;
string templateId = "0";
ActionResult actual = target.Generate(templateId);
Assert.AreEqual(typeof(JsonResult), actual.GetType());
Assert.AreEqual(typeof(ClipGenerationResponse), ((JsonResult)actual).Data.GetType());
}
Where this.requestBase would be mock created with Moq.
public HttpContextBase CreateMockHttpContext()
{
var serverVariables = new NameValueCollection {
{ "UserHostAddress", "127.0.0.1" },
{ "UserAgent", "Unit Test Value" }
};
var httpRequest = new Moq.Mock<HttpRequestBase>();
httpRequest.SetupGet(x => x.Headers).Returns(
new System.Net.WebHeaderCollection {
{"hash", "somehash"}
}
);
httpRequest.Setup(x => x.ServerVariables.Get(It.IsAny<string>()))
.Returns<string>(x =>
{
return serverVariables[x];
});
var httpContext = (new Moq.Mock<HttpContextBase>());
httpContext.Setup(x => x.Request).Returns(httpRequest.Object);
return httpContext.Object;
}
Nor the static way would work:
target.Request.Headers["hash"] = "hash";
So, I'm wondering how this could be fixed nicely. I could always obtain the Request in the Constructor, set a class variable to hold the Request, and then mock the getter / setter for testing purposes, but I'd rather use a nicer way to do it. Though I don't seem to know a way to get it working.
PS: Please note that some class names may have been altered for preview.
Update
As you seem to be unable to mock HttpContext.Current.Request, I decided to mock the HttpContext.Current. Resulting in:
container.RegisterInstance<HttpRequest>(HttpContext.Current.Request);
Sadly this works for the API, but not for unit testing as HttpContext cannot e mocked as it's not an interface.
Initialization method
SomeApiTest.Controllers.LoginControllerTest.Initialize
threw exception. System.NotSupportedException:
System.NotSupportedException: Type to mock must be an interface or an
abstract or non-sealed class. .
The suggested way was by doing:
container.RegisterInstance<HttpRequestBase>(HttpContext.Current.Request);
But this doesn't work because Request cannot be cast to HttpRequestBase.
Which means, I do now have a way to unit test my code, but it will no longer be able to run..
Testing if this problem could be solved using a HttpRequestWrapper.
Looks like the following does work for testing:
HttpRequestBase requestBase = new HttpRequestWrapper(HttpContext.Current.Request);
container.RegisterInstance<HttpRequestBase>(requestBase);
But not for runtime. Because:
- Additional headers are not sent, such as: UserHostAddress, Custom Headers
with Postman is set with every request a custom header, named "hash". Using this method, it looks like these headers are no longer set.
Looks like headers are set when the method is called, but not when the Controller itself is created. Therefore Dependency Injection on this might not be suitable.
Ugly Temporary fix:
private AuthenticationHelper authenticationHelper = null;
private ILoginModel iLoginModel = null;
private IModuleModel iModuleModel = null;
private HttpRequestBase iRequestBase = null;
public LoginController(ILoginModel loginModel, IModuleModel moduleModel, HttpRequestBase requestBase)
{
this.authenticationHelper = new AuthenticationHelper(loginModel);
this.iLoginModel = loginModel;
this.iModuleModel = moduleModel;
this.iRequestBase = requestBase;
}
private HttpRequestBase GetRequestBase()
{
if (Request != null)
{
return Request;
}
else
{
return iRequestBase;
}
}
[HttpPost]
public ActionResult Login(LoginRequest login)
{
var ip = this.authenticationHelper.GetIpAddress(GetRequestBase());
var userAgent = this.authenticationHelper.GetUserAgent(GetRequestBase());
}
When within Controller you refer to something by static classes or Controller properties you usually shoot yourself in the boot, because you make your class not testable with unit test, as in your case. To avoid such situation is that your Controller gets HttpRequestBase injected with IoC. For instance with Autofac after you register module AutofacWebTypesModule your Controllers may accept in their constructor parameter of type HttpRequestBase with basically what you get with Register property. So you should make your controller look like this:
public class GenerationController : Controller
{
readonly HttpRequestBase _request;
public GenerationController(
HttpRequestBase request,
// Additional constructor parameters goes here
)
{
_request = request;
}
}
With Unity you should register factory for HttpRequestBase like this:
container
.RegisterType<HttpRequestBase>(
new InjectionFactory(c => new HttpRequestWrapper(HttpContext.Current.Request))
);
When you create your Controller within unit test it is extremely easy to mock HttpRequestBase as all its properties, along with Headers are virtual.
This is approach I usually use in my projects. However there is option to mock Controller Request property even without rewriting your original code. It involves use of Controller ControllerContext property:
Mock<HttpContextBase> httpContextMock = new Mock<HttpContextBase>();
Mock<HttpRequestBase> httpReguestMock = new Mock<HttpRequestBase>();
httpContextMock.SetupGet(c => c.Request).Returns(httpReguestMock.Object);
GenerationController controller = new GenerationController();
controller.ControllerContext = new ControllerContext(httpContextMock.Object, new RouteData(), controller);
controller.Index();
My mock for request looks:
var request = A.Fake<HttpRequestBase>();
var formCollection = new NameValueCollection();
A.CallTo(() => request.HttpMethod).Returns("POST");
A.CallTo(() => request.Headers).Returns(new System.Net.WebHeaderCollection
{
{"X-Requested-With", "XMLHttpRequest"}
});
A.CallTo(() => request.Form).Returns(formCollection);
A.CallTo(() => request.ApplicationPath).Returns("/");
For my needs it works. I'am using FakeItEasy to mock request, but I'm pretty sure that you can use Moq instead. I'm using FakeItEasy and Moq parallel in my project, and it works perfect.
I'm using Swashbuckle to generate docs for an API. My controller methods looks like this:
[ResponseType(typeof(CategoryCollectionModel))]
public HttpResponseMessage Get(HttpRequestMessage request, [FromUri]Paging paging)
{
var input = new CategoriesListQuery.Input { Page = paging.Page, Size = paging.Size };
var result = this.queryInvoker.Execute<CategoriesListQuery.Input, CategoriesListQuery.Result>(input);
var items = Mapper.Map<CategoryCollectionModel>(result);
return request.CreateResponse(HttpStatusCode.OK, items);
}
Swashbuckle treats HttpRequestMessage as a parameter in the generated docs. Is there a way to configure Swashbuckle to ignore HttpRequestMessage since it is only included in the signature for testing purposes?
Please refer to the discussion here. In short do not pass in HttpRequestMessage as in input parameter, rather mock the {controller}.Request property.
I found a solution from "http://www.morganskinner.com/2016/02/ignoring-parameters-in-swashbuckle.html"
Summary :
In Swashbuckle you can plug-in operation “filters” that can be used to
alter the emitted data – the filter is passed the context of the
operation being emitted, and you can monkey around with the data that
pops out. All I had to do then was create a filter that would look for
this datatype, and remove the corresponding data from the results. I
ended up with this…
public class IgnoreHttpRequestMessageOperationFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry,
ApiDescription apiDescription)
{
apiDescription.ParameterDescriptions
.Where(desc => desc.ParameterDescriptor.ParameterType
== typeof(HttpRequestMessage))
.ToList()
.ForEach(param =>
{
var toRemove = operation.parameters
.SingleOrDefault(p => p.name == param.Name);
if (null != toRemove)
operation.parameters.Remove(toRemove);
});
}
}
With that class in place, I just needed to plug this in to the swagger
config file as follows...
c.OperationFilter<IgnoreHttpRequestMessageOperationFilter>();
Working for me. thanks "Morgan"