Is it possible to copy/clone HttpContext of a web request - asp.net-mvc

What's the easiest way to clone current request's HttpContext instance?
I'm developing an app in Asp.net MVC v1. I upgraded the regular PartialView capabilities to actually have sub-controllers that act very similar, but have their own context. When you use PartialViews you have to fill view data for the partial view in your main view's controller action. I created my own functionality that makes it possible to call controller actions from within a view. This way I get:
I don't have to provide sub-view's data in my main view's controller action
sub controller methods can manipulate data more encapsulated without any relation to other views/controllers
The problem is that each sub-controller request uses HttpContext. So when I set some HttpContext.Item in a sub-controller it actually populates HttpContext of the actual request.
That's why I want to clone HttpContext. I'm already using:
HttpContext subContext = new HttpContext(request, response);
// what happened to Session, User, Items etc. properties?
but this doesn't set anything else than request and response. But I would probably also need other properties and collections... Like Session, Items, User... etc.

While the "Not Possible" answer is correct, there is an alternative that is much cleaner than writing values into the current context and then rewriting back to its original state. The solution is to make a new HttpContext object entirely that is based on the URL of your choosing.
// A new request/response is constructed to using a new URL.
// The new response is using a StreamWriter with null stream as a backing stream
// which doesn't consume resources
using (var nullWriter = new StreamWriter(Stream.Null))
{
var newRequestUri = new Uri("http://www.somewhere.com/some-resource/");
var newRequest = new HttpRequest("", newRequestUri.ToString(), newRequestUri.Query);
var newResponse = new HttpResponse(nullWriter);
var newContext = new HttpContextWrapper(new HttpContext(newRequest, newResponse));
// Work with the new context here before it is disposed...
}
Reference: https://github.com/maartenba/MvcSiteMapProvider/issues/278#issuecomment-34905271

Not possible
I guess an actual deep cloning is not possible because of server session state. Cloning would also have to clone this value, which is web server specific internal resource that is intrinsically static and can not be cloned. In this case a web server would have multiple Session objects for instance.
Workaround
Anyway. The workaround was to set additional context values before instantiating sub-controller processing. After processing is finished I reverted values back to original. So I actually had context as it was before.

For ASP.Net Core/.Net 5 the following will work (based on the ASP.Net Core source code for SignalR, if you need more features just add them).
public static HttpContext Clone(this HttpContext httpContext, bool copyBody)
{
var existingRequestFeature = httpContext.Features.Get<IHttpRequestFeature>();
var requestHeaders = new Dictionary<string, StringValues>(existingRequestFeature.Headers.Count, StringComparer.OrdinalIgnoreCase);
foreach (var header in existingRequestFeature.Headers)
{
requestHeaders[header.Key] = header.Value;
}
var requestFeature = new HttpRequestFeature
{
Protocol = existingRequestFeature.Protocol,
Method = existingRequestFeature.Method,
Scheme = existingRequestFeature.Scheme,
Path = existingRequestFeature.Path,
PathBase = existingRequestFeature.PathBase,
QueryString = existingRequestFeature.QueryString,
RawTarget = existingRequestFeature.RawTarget,
Headers = new HeaderDictionary(requestHeaders),
};
if(copyBody)
{
// We need to buffer first, otherwise the body won't be copied
// Won't work if the body stream was accessed already without calling EnableBuffering() first or without leaveOpen
httpContext.Request.EnableBuffering();
httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
requestFeature.Body = existingRequestFeature.Body;
}
var features = new FeatureCollection();
features.Set<IHttpRequestFeature>(requestFeature);
// Unless we need the response we can ignore it...
features.Set<IHttpResponseFeature>(new HttpResponseFeature());
features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
var newContext = new DefaultHttpContext(features);
if (copyBody)
{
// Rewind for any future use...
httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
}
// Can happen if the body was not copied
if(httpContext.Request.HasFormContentType && httpContext.Request.Form.Count != newContext.Request.Form.Count)
{
newContext.Request.Form = new Microsoft.AspNetCore.Http.FormCollection(httpContext.Request.Form.ToDictionary(f => f.Key, f => f.Value));
}
return newContext;
}

The ASP.NET MVC framework intentionally makes dependencies to abstract classes with all members virtual. That simply says - extensibility.
Controllers depend on HttpContextBase, not HttpContext. Perhaps you can make your sub-controllers depend on HttpContextBase too so you can wrap it.
Just my 2 cents.

I've used
<% Html.RenderAction("Action", "Controller"); %>
to great effect, allowing me to create completely isolated/escapsulated actions without resorting to complex code. This would seem to offer the same functionality without the same complexity.
The rendered views are standard partial views and the controller actions just like any other.

Related

how to get an unsaved entity on server but not for saving?

i need to send my unsaved entity from the client to the server but not for saving changes
but inorder to do a process using the data on the entity and then change some of it's values and pass it back to the client
is this possible?
if not what are my options?
i tried to export the entity and then send it to a method on the webapi controller that gets a JObject but didn't find a way to deserialize it to the server entity
We did have a similar problem and found a solution as follows:
You need to take into consideration the way breeze manages it's objects.
1.Create custom saveBundle.
Consider complex order object.You need to fill your save bundle with each nested object inside order.
Like:
var saveBundle = new Array();
saveBundle.push(order.SaleAccountingInfo);
saveBundle.push(order.CostAccountingInfo);
saveBundle.push(order);
2.Create custom save options, where you can point to your custom Save Method on server
Like:
var so = new breeze.SaveOptions({ resourceName: "BookOrder" });
3.Call standard breeze function and pass it created params
manager.saveChanges(saveBundle, so).fail(function () {
// manager.rejectChanges();TODO check what needed
deferred.resolve(true);
});
On server you need to have you custom function ready and hook some breeze delegates
[HttpPost]
public SaveResult BookOrder(JObject orderBundle)
{
context.BeforeSaveEntityDelegate = OrderBeforeSaveEntity;
context.BeforeSaveEntitiesDelegate = SaveOrder;
context.AfterSaveEntitiesDelegate = BookOrderAfterSave;
try
{
return context.SaveChanges(orderBundle);
}
catch (Exception)
{
throw;
}
}
You can a lot of stuff in first two delegates but it is the last one you are looking for
private void BookOrderAfterSave(Dictionary<Type, List<EntityInfo>> orderSaveMap, List<KeyMapping> orderKeyMappings)
{
var orderEntity = orderSaveMap.Where(c => c.Key == typeof(BL.Orders.Order)).Select(d => d.Value).SingleOrDefault();
BL.Orders.Order order = (BL.Orders.Order)orderEntity[0].Entity; //your entity
//logic here
}
Hope it points to right direction.
we are doing something similar here. it'll save the entity so i'm not sure if this fits your question.
you can do:
entity.entityAspect.setModified()
then issue a saveChange()
then you can do your calculations on the server.
in our case we are using breeze.webapi so we are doing this in the beforeSave(entity) method.
breeze by design sends the changed entity then back to the client where the cache gets updated with your changes done on the server.

Testing Account/Logon Action

I'm testing the Account/Loggon action using the built-in testing tool of Visual Studio 2010 and the class library from this article to create a fake controller context.
When I run the test method, this code line:
FormsAuthentication.SetAuthCookie(username, false);
throws an exception: Object reference not set to an instance of an object
To test the loggon action, I think I should create a controller with a fake controller context that has a cookie collection. Here is my testing code block:
AccountController controller = new AccountController();
var cookies = new HttpCookieCollection();
controller.ControllerContext = new FakeControllerContext(controller, cookies);
ActionResult result = controller.RemoteLogOn(username, password);
I'm not sure if this is the right way, but this is what we do, and it works.
Instead of directly using FormsAuthentication.SetAuthCookie, abstract it into an interface, e.g IFormsAuthenticationService, and implement as per regular.
Accept that in your MVC controllers where required, e.g:
public AccountController(IFormsAuthenticationService formsAuthenticationService)
{
_formsAuthenticationService = formsAuthenticationService; // should use DI here
}
public ActionResult LogOn(string username, string pw)
{
if (yourLogicWhichChecksPw)
_formsAuthenticationService.SetAuthCookie(username, false);
return RedirectToAction("Index");
}
Then in your unit-test, use something like Moq to fake out the interface.
var username = "blah";
var pw = "blah";
var fakesFormsAuth = new Mock<IFormsAuthenticationService>();
fakeFormsAuth.Verify(x => x.SetAuthCookie(username, false), Times.AtLeastOnce());
var controller = new AccountController(fakedFormsAuth.Object);
controller.LogOn(username, pw);
The reason for mocking this is because there is absolutely no need to unit-test Forms Authentication. It's a built-in, well tested and stable part of the ASP.NET framework. That's why we mock things where we don't care about the underlying implementation, instead we only test that certain conditions were met (it was called, exception was thrown, some variable was set, etc).
Test your own code, not the mechanics of .NET.
As for Stephen Walther's article, that's more for faking the RequestContext when certain code your testing expects data in the Request. Such as the User.Identity, Request.IsAuthenticated, Form variables, etc. That's where you need to fake the context, such as the following code:
public ActionResult Save(SomeModel)
{
var user = Request.User.Identity; // this will be null, unless you fake the context.
}

How do I unit test a custom ActionFilter in ASP.Net MVC

So I'm creating a custom ActionFilter that's based mostly on this project http://www.codeproject.com/KB/aspnet/aspnet_mvc_restapi.aspx.
I want a custom action filter that uses the http accept headers to return either JSON or Xml. A typical controller action will look like this:
[AcceptVerbs(HttpVerbs.Get)]
[AcceptTypesAttribute(HttpContentTypes.Json, HttpContentTypes.Xml)]
public ActionResult Index()
{
var articles = Service.GetRecentArticles();
return View(articles);
}
The custom filter overrides the OnActionExecuted and will serialize the object (in this example articles) as either JSON or Xml.
My question is: how do I test this?
What tests do I write? I'm a TDD novice and am not 100% sure what I should be testing and what not to test. I came up with AcceptsTypeFilterJson_RequestHeaderAcceptsJson_ReturnsJson(), AcceptsTypeFilterXml_RequestHeaderAcceptsXml_ReturnsXml() and AcceptsTypeFilter_AcceptsHeaderMismatch_ReturnsError406().
How do I test an ActionFilter in MVC that is testing the Http Accept Headers?
Thanks.
You just need to test the filter itself. Just create an instance and call the OnActionExecuted() method with test data then check the result. It helps to pull the code apart as much as possible. Most of the heavy lifting is done inside the CsvResult class which can be tested individually. You don't need to test the filter on an actual controller. Making that work is the MVC framework's responsibility.
public void AcceptsTypeFilterJson_RequestHeaderAcceptsJson_ReturnsJson()
{
var context = new ActionExecutedContext();
context.HttpContext = // mock an http context and set the accept-type. I don't know how to do this, but there are many questions about it.
context.Result = new ViewResult(...); // What your controller would return
var filter = new AcceptTypesAttribute(HttpContentTypes.Json);
filter.OnActionExecuted(context);
Assert.True(context.Result is JsonResult);
}
I just stumbled upon this blog post which seems the right way to me. He uses Moq.
What this chap is doing is mocking the HTTPContext, but also we need to set up a ContentType in the request:
// Mock out the context to run the action filter.
var request = new Mock<HttpRequestBase>();
request.SetupGet(r => r.ContentType).Returns("application/json");
var httpContext = new Mock<HttpContextBase>();
httpContext.SetupGet(c => c.Request).Returns(request.Object);
var routeData = new RouteData(); //
routeData.Values.Add("employeeId", "123");
var actionExecutedContext = new Mock<ActionExecutedContext>();
actionExecutedContext.SetupGet(r => r.RouteData).Returns(routeData);
actionExecutedContext.SetupGet(c => c.HttpContext).Returns(httpContext.Object);
var filter = new EmployeeGroupRestrictedActionFilterAttribute();
filter.OnActionExecuted(actionExecutedContext.Object);
Note - I have not tested this myself.

MVC view unit test visibility of element based on authentication

I'm writing an MVC app.
If I have a piece of code:
#if (User.IsInRole("Administrator")) {
#Html.DropDownListFor(...)
}
So the dropdownlistfor is only visible or even there for administrators.
How do you unit test that this is happening?
This is what I would do:
Create a stub for the User object (of type IPrincipal)
Create a stub for the Request object (of type HttpRequestBase) with the stubbed User object
Inject the stubbed request object to your request context.
See example below (using Rhino mocks)
var CurrentUser = MockRepository.GenerateStub<IPrincipal>();
CurrentUser.Stub(u => u.IsInRole("Administrator")).Return(true);
var context = MockRepository.GenerateStub<HttpContextBase>();
var requestContext = new RequestContext(context, new RouteData());
var request = MockRepository.GenerateStub<HttpRequestBase>();
context.User = CurrentUser;
context.Stub(c => c.Request).Return(request);
controller = new YourController();
controller.ControllerContext = new ControllerContext(requestContext, controller);
var view = controller.DoAction() as ViewResult;
That would help you setup the controller and the current user with all required roles.
I personally wouldn't unit test the view as it is supposed to be thin and dump. Most of your stuff should happen in the controller/business layer.
If you want to unit test your views, I would suggest checking out the Razor Single File Generator. This will allow you to pre-compile your views, and to create unit tests as well.

Getting Session in Http Handler ashx

I am using Http Handler ashx file for showing the images.
I was using Session object to get image and return in the response
Now problem is i need to use custom Session object its nothing but the Wrapper on HttpSession State But when i am trying to get existing custom session object its creating new ...
its not showing session data , i checked the session Id which is also different
Please adive how can i get existing session in ashx file ?
Note: When i use ASP.NET Sesssion its working fine
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class GetImage : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{
When you want to get access to your Session State from an ASHX or HttpHandler you need to implement IReadOnlySessionState or IRequiresSessionState if you need read/write access.
The fact that it's an ashx should be irrelevant - assuming the request is being spawned off a request from an exsiting session; I'm assuming it should be - but it might pay to check exactly how the request is being formed. Always pays to go back to basics :)
Assuming that's ok, this is how I've been doing it:
string sessionId = string.Empty;
System.Web.SessionState.SessionIDManager sessionIDManager = new System.Web.SessionState.SessionIDManager();
bool supportSessionIDReissue;
sessionIDManager.InitializeRequest(httpContext, false, out supportSessionIDReissue); sessionId = sessionIDManager.GetSessionID(httpContext);
if (sessionId == null)
{
// Create / issue new session id:
sessionId = sessionIDManager.CreateSessionID(httpContext);
}
At the end of this the sessionId variable will (should) contain the existing Session ID, or a newly created one that you can reuse later..
you can just use a Actionresult rather than a handler for this
return new FileStreamResult(new FileStream(path, FileMode.Open), "image/jpeg");
or
return(new FileResult(Pathtoimage, "image/jpeg"));
that should make things easier as you wil be using a controll/action as your url
ie
<img src="/Images/showImage/1">
you can then have your actions deal with anything like pulling from db as bytes
streaming, check validation etc

Resources