11

I am migrating an ASP.NET application to ASP.NET Core and they have some calls to HttpServerUtility.Transfer(string path). However, HttpServerUtility does not exist in ASP.NET Core.

Is there an alternative that I can use? Or is Response.Redirect the only option I have?

I want to maintain the same behaviour as the old application as much as possible since there is a difference in between Server.Transfer and Response.Redirect.

Community
  • 1
  • 1
Connie Yau
  • 670
  • 5
  • 12
  • Possible duplicate of [Redirecting in asp.net 5](http://stackoverflow.com/questions/30429350/redirecting-in-asp-net-5) – Gerardo Grignoli Sep 26 '16 at 02:10
  • I would collect all parameters and then do a `Redirect` or `RedirectToAction` as shown [on this answer](http://stackoverflow.com/a/30430848/97471) – Gerardo Grignoli Sep 26 '16 at 02:11
  • 2
    @GerardoGrignoli I wanted to maintain the same behaviour though. That is, performing the change on the server-side so the client does not have to do any work and know that they have been redirected to another page. [Server.Transfer vs Response.Redirect](http://stackoverflow.com/questions/224569/server-transfer-vs-response-redirect) – Connie Yau Sep 26 '16 at 11:10
  • I'm shocked that you found @Thomas answer more useful than mine. He provided no working code and no theoretical solution that runs any more of the pipeline than mine. I'm the only person that actually supplied you with code for running action methods on a view handled by a different controller, work that took me about 2.5 hours to come up with for you. Why did you award him the +50 and not me? – RonC Dec 28 '16 at 13:54

5 Answers5

6

I see some options for you, depending on your case:

  • Returning another View: So just the HTML. See answer of Muqeet Khan
  • Returning another method of the same controller: This allows also the execution of the business logic of the other action. Just write something like return MyOtherAction("foo", "bar").
  • Returning an action of another controller: See the answer of Ron C. I am a bit in troubles with this solution since it omits the whole middleware which contains like 90% of the logic of ASP.NET Core (like security, cookies, compression, ...).
  • Routing style middleware: Adding a middleware similar to what routing does. In this case your decision logic needs to be evaluated there.
  • Late re-running of the middleware stack: You essentially need to re-run a big part of the stack. I believe it is possible, but have not seen a solution yet. I have seen a presentation of Damian Edwards (PM for ASP.NET Core) where he hosted ASP.NET Core without Kestrel/TCPIP usage just for rendering HTML locally in a browser. That you could do. But that is a lot of overload.

A word of advice: Transfer is dead ;). Differences like that is the reason for ASP.NET Core existence and performance improvements. That is bad for migration but good for the overall platform.

Thomas
  • 5,080
  • 27
  • 42
  • My solution doesn't run any less of the middleware than the first two solutions you propose and almost certainly runs more middleware than the last two you propose given you'd be exiting out of the middleware to implement such a solution if you came up with code for it. And further, my solution demonstrates how to run the action logic for a different view handled by a different controller. – RonC Dec 28 '16 at 13:43
  • That is true. The list I made is mainly showing the many different ways what Transfer might look like. I am pretty sure, there are more options considering the flexibility of ASP.NET Core. Your solution looks a bit hacky, however, I have not read the details how the MVC middleware is actually doing it, so it might be 100% legit. I think the most important part of my answer is the last two paragraph ;). – Thomas Dec 28 '16 at 14:16
  • 1
    Server.Transfer in the full framework executes the code behind the page for the page being transferred to and serves up that page. There probably are other options to do something analogous to that in .net core other than the code I crafted but I haven't seen anyone supply code for such options yet. :-) – RonC Dec 28 '16 at 14:21
  • By your comment, I just re-read the documentation. Considering the `IHttpHandler` overload, a complete replacement would be to run the complete middleware stack added by the final UseMvc (which represent the handler) which includes routing (for the path resolving) and then the controllers/actions/filters/... So many options. Love this new world. – Thomas Dec 28 '16 at 14:39
  • 1
    I agree that would be a cool solution and one better than mine. Theoretically that should be possible and I would love someone to post code on how to do that. – RonC Dec 28 '16 at 14:42
  • 2
    I'm interested in the "Late re-running of the middleware stack" path, does anyone have a working sample for this? – Daniel Little May 27 '18 at 02:55
5

You are correct. Server.Transfer and Server.Redirect are quite different. Server.Transfer executes a new page and returns it's results to the browser but does not inform the browser that it returned a different page. So in such a case the browser url will show the original url requested but the contents will come from some other page. This is quite different than doing a Server.Redirect which will instruct the browser to request the new page. In such a case the url displayed in the browser will change to show the new url.

To do the equivalent of a Server.Transfer in Asp.Net Core, you need to update the Request.Path and Request.QueryString properties to point to the url you want to transfer to and you need to instantiate the controller that handles that url and call it's action method. I have provided full code below to illustrate this.

page1.html

 <html>
    <body>
        <h1>Page 1</h1>
    </body>
</html>

page2.html

 <html>
    <body>
        <h1>Page 2</h1>
    </body>
</html>

ExampleTransferController.cs

    using Microsoft.AspNetCore.Diagnostics;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;

    namespace App.Web.Controllers {

        public class ExampleTransferController: Controller {

            public ExampleTransferController() {

            }

            [Route("/example-transfer/page1")]
            public IActionResult Page1() {
                bool condition = true;

                if(condition) {

                    //Store the original url in the HttpContext items
                    //so that it's available to the app.
                    string originalUrl = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}{HttpContext.Request.Path}{HttpContext.Request.QueryString}";
                    HttpContext.Items.Add("OriginalUrl", originalUrl);

                    //Modify the request to indicate the url we want to transfer to
                    string newPath = "/example-transfer/page2";
                    string newQueryString = "";
                    HttpContext.Request.Path = newPath;
                    HttpContext.Request.QueryString = new QueryString(newQueryString);

                    //Now call the action method for that new url
                    //Note that instantiating the controller for the new action method
                    //isn't necessary if the action method is on the same controller as 
                    //the action method for the original request but
                    //I do it here just for illustration since often the action method you
                    //may want to call will be on a different controller.
                    var controller = new ExampleTransferController();
                    controller.ControllerContext = new ControllerContext(this.ControllerContext);
                    return controller.Page2();
                }

                return View();
            }


            [Route("/example-transfer/page2")]
            public IActionResult Page2() {

                string originalUrl = HttpContext.Items["OriginalUrl"] as string;
                bool requestWasTransfered = (originalUrl != null);

                return View();
            }

        }
    }

Placing the original url in HttpContext.Items["OriginalUrl"] isn't strictly necessary but doing so makes it easy for the end page to know if it's responding to a transfer and if so what the original url was.

RonC
  • 31,330
  • 19
  • 94
  • 139
  • That can be useful just with MVC code (to run controller/views). However all the middleware pipeline are not going to be executed, so it's not as good as `Server.Transfer` – J. Lennon Dec 21 '16 at 18:14
  • 1
    @J.Lennon Server.Transfer doesn't re-execute the full pipeline either. I just checked it personally and my HttpModules were never recalled for the new page. – RonC Dec 21 '16 at 18:49
  • That's true, the pipeline (http modules in the old asp.net) will not be executed again. The problem with your approach is that you're calling the class method directly, ignoring attributes, filters or any kind of behavior that the controller might have. – J. Lennon Dec 21 '16 at 23:41
  • 1
    @J.Lennon Agreed. But so far it's the best approach anyone has come up with (and surely worth an upvote) :-) – RonC Dec 22 '16 at 13:24
  • @RonC Your example returns another view from a controller. What if I need to return another webpage (different site running in local host). how do I achieve that? – Lingam Apr 08 '20 at 21:31
  • @Lingam I use a response redirect if possible which will tell the client to request that page to get the content. – RonC Apr 08 '20 at 23:13
  • @RonC Response redirect will change the URL in the browser. (Like, redirect). Just like your above example, any idea to get page from different site (say results of google.com) – Lingam Apr 08 '20 at 23:19
  • @Lingam you’d have to use a http client object to request the page vis server code and return what you receive as your server response. But mapping the headers over and the rest might be a bit of work. That’s the only way I can think of though. Would be pretty unusual for sure. – RonC Apr 08 '20 at 23:25
  • I just spent a few hours trying to do this without success, until I came across this answer and figured out a way to do it in the middleware. In my case, I was trying to create a middleware that checks for license and for an "application online" flag and in the case the flag was false or the license was expired, I wanted to transfer the user to an error page with the appropriate status code and without redirect. In the middleware I did my checks, then I changed the context.Request.Path to what I wanted, executed the next RequestDelegate and then set the response.statuscode. It worked! – Kelps Sousa Alux Jul 23 '20 at 18:10
  • @KelpsSousaAlux Awesome. Glad I could be of help. Remember to upvote the answer since it was helpful. – RonC Jul 23 '20 at 18:12
  • The beauty of `Server.TransferRequest` is that is executes action filters, controller's "OnActionExecuting", authorization etc. etc. etc. etc. And this solution is just executing the action method. – Alex from Jitbit Aug 30 '21 at 23:17
  • I dont like much this solution because you shouldn't create the Controller manually. Typically ASPNETCore would create the controller and fill in many things. Plus your dep injections would have to run manually etc. It is a path I would not take. – Kat Lim Ruiz Oct 15 '21 at 17:44
3

I can see this is a fairly old thread. I don't know when URL Rewriting was added to .Net Core but the answer is to rewrite the URL in the middleware, it's not a redirect, does not return to the server, does not change the url in the browser address bar, but does change the route.

resources:

https://weblog.west-wind.com/posts/2020/Mar/13/Back-to-Basics-Rewriting-a-URL-in-ASPNET-Core

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting?view=aspnetcore-5.0

Stu
  • 71
  • 3
0

I believe you are looking for a "named view" return in MVC. Like so,

[HttpPost]
public ActionResult Index(string Name)
{
 ViewBag.Message = "Some message";
 //Like Server.Transfer() in Asp.Net WebForm
 return View("MyIndex");
}

The above will return that particular view. If you have a condition that governs the view details you can do that too.

Muqeet Khan
  • 2,094
  • 1
  • 18
  • 28
0

I know that this is a very old question, but if someone uses Razor Pages and is looking to a Server.Transfer alternative (or a way to return a different view depending on a business rule), you can use partial views.

In this example, my viewmodel has a property called "UseAlternateView":

public class TestModel : PageModel
{
    public bool UseAlternateView { get; set; }

    public void OnGet()
    {
        // Here goes code that can set UseAlternateView=true in certain conditions
    }
}

In my Razor View, I renderize a diferent partial view depending of the value of the UseAlternateView property:

@model MyProject.Pages.TestModel
@if (Model.UseAlternateView)
{
    await Html.RenderPartialAsync("_View1", Model);
}
else
{
    await Html.RenderPartialAsync("_View2", Model);
}

The partial views (files "_View1.cshtml" and "_View2.cshtml"), contain code like this:

@model MyProject.Pages.TestModel
<div>
    Here goes page content, including forms with binding to Model properties
    when necessary
</div>

Obs.: when using partial views like this, you cannot use @Region, so you may need to look for an anternative for inserting scripts and styles in the correct place on the master page.

Aldinei Sampaio
  • 221
  • 2
  • 4