RedirectToAction alternative - asp.net

I'm using ASP.NET MVC 3.
I've wriiten a helper class as follows:
public static string NewsList(this UrlHelper helper)
{
return helper.Action("List", "News");
}
And in my controller code I use it like this:
return RedirectToAction(Url.NewsList());
So after the redirect the link looks like this:
../News/News/List
Is there an alternative to RedirectToAction? Is there a better way that I need to implement my helper method NewsList?

Actually you don't really need a helper:
return RedirectToAction("List", "News");
or if you want to avoid hardcoding:
public static object NewsList(this UrlHelper helper)
{
return new { action = "List", controller = "News" };
}
and then:
return RedirectToRoute(Url.NewsList());
or another possibility is to use MVCContrib which allows you to write the following (personally that's what I like and use):
return this.RedirectToAction<NewsController>(x => x.List());
or yet another possibility is to use T4 templates.
So it's up to you to choose and play.
UPDATE:
public static class ControllerExtensions
{
public static RedirectToRouteResult RedirectToNewsList(this Controller controller)
{
return controller.RedirectToAction<NewsController>(x => x.List());
}
}
and then:
public ActionResult Foo()
{
return this.RedirectToNewsList();
}
UPDATE 2:
Example of unit test for the NewsList extension method:
[TestMethod]
public void NewsList_Should_Construct_Route_Values_For_The_List_Action_On_The_News_Controller()
{
// act
var actual = UrlExtensions.NewsList(null);
// assert
var routes = new RouteValueDictionary(actual);
Assert.AreEqual("List", routes["action"]);
Assert.AreEqual("News", routes["controller"]);
}

Related

Set custom path prefix in API method using Swagger in .Net Core

I would like add my custom path prefix using swagger in .Net Core API methods.
For example, my API methods are declared like this:
[Route("api/v1/Customer")]
[HttpGet]
public async Task<IActionResult> Customer()
{
// some implementation
return Ok();
}
So currently, If I invoke the API using http://localhost:50523/api/v1/Customer it works perfectly fine.
Now, I want to add some custom path prefix. E.g. /some/custom/path/ before the actual API method path. Which means that-- if I invoke the API using http://localhost:50523/some/custom/path/api/v1/Customer it should work.
I want to achieve this using Swagger in .Net core, and I do not want to change the API path on action method level since I have hundred of API method written and I do not want to change the URL on each action method.
Any help will be greatly appreciated.
In .Net 5.0
public class PathPrefixInsertDocumentFilter : IDocumentFilter
{
private readonly string _pathPrefix;
public PathPrefixInsertDocumentFilter(string prefix)
{
this._pathPrefix = prefix;
}
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var paths = swaggerDoc.Paths.Keys.ToList();
foreach (var path in paths)
{
var pathToChange = swaggerDoc.Paths[path];
swaggerDoc.Paths.Remove(path);
swaggerDoc.Paths.Add($"{_pathPrefix}{path}", pathToChange);
}
}
}
To apply the filter
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info {Title = "MyApp", Version = "v1"});
... other setup
options.DocumentFilter<PathPrefixInsertDocumentFilter>("api");
});
If you add a DocumentFilter you can add the prefix to all the paths you want to change.
public class PathPrefixInsertDocumentFilter : IDocumentFilter
{
private readonly string _pathPrefix;
public PathPrefixInsertDocumentFilter(string prefix)
{
this._pathPrefix = prefix;
}
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
var paths = swaggerDoc.Paths.Keys.ToList();
foreach (var path in paths)
{
var pathToChange = swaggerDoc.Paths[path];
swaggerDoc.Paths.Remove(path);
swaggerDoc.Paths.Add(new KeyValuePair<string, PathItem>("/" + _pathPrefix + path, pathToChange));
}
}
}
You then add the filter in your swagger set up:
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info {Title = "MyApp", Version = "v1"});
... other setup
options.DocumentFilter<PathPrefixInsertDocumentFilter>("api");
});
This doesn't change your API - we use this for working with a reverse proxy in production where we use the prefix to route the request to the appropriate container but strip it out.
Maybe You can using Route attribute in your Controller class like:
[Route("/some/custom/path/")]
public class CustomerController
{
[Route("api/v1/Customer")]
[HttpGet]
public async Task<IActionResult> Customer()
{
// some implementation
return Ok();
}
}
Hope it works for you
you can use [Route("prefix/[controller]")] top of your api controller
[Route("prefix/[controller]")]
public class MyController : ControllerBase
{
[Route("api/v1/Customer")]
[HttpGet]
public async Task<IActionResult> Customer()
{
// some implementation
return Ok();
}
}

Registering Actions for ControllerModel in ASP.NET Core

I am trying to add a new ActionModel for a ControllerModel in an implementation of IControllerModelConvention, but I cannot find any documentation or examples of how this model system works or how to do this correctly. I am able to add a new ActionModel easily enough, but it is not routable once the application is running:
var action = new ActionModel(method, new object[] { new HttpGetAttribute("/test") })
{
Controller = controller,
ActionName = "test"
};
controller.Actions.Add(action);
It seems I need to add a selector to the action, perhaps other properties as well, but I haven't been able to find one that exposes this action. Also unsure if my attributes are correct/redundant. Ultimately I would like to add multiple actions that do not map 1:1 to the methods in the controller.
I've made it work similiar to your approach. Maybe this can help you:
Controller
public class TestController : Controller
{
public IActionResult Index()
{
return Ok(new string[] { "Hello", "World" });
}
}
Model Convention
public class TestControllerModelConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
Type controllerType = typeof(TestController);
MethodInfo indexAction = controllerType.GetMethod("Index");
var testAction = new ActionModel(indexAction, new[] { new HttpGetAttribute("/test") })
{
ActionName = "Index",
Controller = controller
};
controller.Actions.Add(testAction);
}
}
Startup
public void ConfigureServices(IServiceCollection services)
{
// other initialitation stuff
services.AddMvc(options =>
{
options.Conventions.Add(new TestControllerModelConvention());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Now when I start the application and browse "/test" it will hit the controller action.

Extract controller and action names from ActionResult

In my view (or partial view), I want to use a helper that has this signature:
// using T4MVC to get the ActionResult from "MVC.FooController.BarAction()"
#Html.doStuff(MVC.FooController.BarAction(), someData)
instead of
#Html.doStuff(controllerName, actionName, someData)
I want to simplify my extension method, i.e. one argument instead of two.
In the extension method, how would I extract controller and action names from an instance of ActionResult?
Note that HttpContext.Current.Request.RequestContext.RouteData.Values["action"].ToString(); doesn't help. I need to pass in an arbitrary ActionResult. So this is NOT a duplicate question
In View use below code -
#ViewContext.RouteData.Values["Controller"]
#ViewContext.RouteData.Values["Action"]
In Controller -
Request.RequestContext.RouteData.Values["controller"].ToString()
Request.RequestContext.RouteData.Values["action"].ToString()
In C# Helper Class -
HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
HttpContext.Current.Request.RequestContext.RouteData.Values["action"].ToString();
If you want you retrieve Action and controller names from ActionResult, then one option would be -
// In Action
ViewBag.Controller = Request.RequestContext.RouteData.Values["controller"].ToString();
ViewBag.Action = Request.RequestContext.RouteData.Values["action"].ToString();
Then retrieve values from ActionResult in following way -
var controllerName = actionResultVariable.ViewBag.Controller;
var actionName = actionResultVariable.ViewBag.Action;
EDIT: Option 2 :
Create a Custom ActionResult -
public class MyActionResult : ViewResult
{
public MyActionResult(object model)
{
this.ViewData.Model = model;
}
public string ControllerName
{
get
{
return HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
}
}
public override void ExecuteResult(ControllerContext context)
{
base.ExecuteResult(context);
}
}
And then in your Controller Action -
return new MyActionResult(model);
Then as you are passing the ActionResult to your Extension method, you can use -
MyActionResultVariable.ControllerName

Asynchronous call to webservice in MVC 4 web application

I am building my first real MVC4 application and I have run into following issue.
I have a model for "User" class. Data for it are obtained through asynchronous call to webservice:
public sealed class AdminDMSEntities
{
public List<User> UserList { get; private set; }
public AdminDMSEntities()
{
this.UserList = new List<User>(0);
ServiceClient client = new ServiceClient();
client.GetUsersCompleted += (s, e) =>
{
if (e.Result == null)
throw new ArgumentNullException("No users were retrieved");
UserList = new List<User>(0);
e.Result.ForEach(w => this.UserList.Add(new User(w.Guid, w.TrusteeType, w.Username, w.Email, w.LastLogin, w.PasswordChanged, w.IsUsingTempPassword)));
};
client.GetUsersAsync();
}
}
I intend to use this class as I would use class derived from DbContext (if I could use Entity Framework which I cant). So far I have only users in the class.
I am using tis class in UsersController like this:
public class UsersController : Controller
{
private AdminDMSEntities adminEntities = new AdminDMSEntities();
//
// GET: /User/
public ActionResult Index()
{
return View(adminEntities.UserList);
}
}
The problem is that I will end up with InvalidOperationException, because controller is not waiting for async call completion and passes UserList to the view before it is properly filled with users.
I can have the call synchronous for the time being, but it is very likely I will be forced to use asynchronous calls later, so I would like to know how to ensure, that controller will wait for async call to be completed before UserList is passed to view...
Thanks in advance
EDIT: I have tried the approach with AsyncController as listed below, currently I have added this to AdminDMS entities class:
public static async Task<AdminDMSEntities> AdminDMSEntitiesAsync()
{
AdminDMSEntities result = null;
Task<AdminDMSEntities> getUsersAsyncTask = Task.Factory.StartNew(() => new AdminDMSEntities());
await getUsersAsyncTask;
return result;
}
and this is the change to the controller:
public class UsersController : AsyncController
{
private AdminDMSEntities adminEntities = null;
//
// GET: /User/
public async Task<ActionResult> Index()
{
if (adminEntities == null)
{
adminEntities = await AdminDMSEntities.AdminDMSEntitiesAsync();
}
return View(adminEntities.UserList);
}
}
The result is that adminEntities are containing an instance of the class, but there are no users in the list (there should be 11).
EDIT2: Since i was told that creating new task is not the right thing to do, I went with the first suggested approach removin AdminDMSEntities class from the code. My thanks to Darin for helping me out :)
You could use an asynchronous controller. The idea is to have your controller derive from the AsyncController class instead of the Controller class. This class provides methods that allow you to perform asynchronous operations.
For example:
public class MyController: AsyncController
{
public void IndexAsync()
{
AsyncManager.OutstandingOperations.Increment();
var client = new SomeClient();
client.GetUsersCompleted += (s, e) =>
{
UserList = new List<User>();
AsyncManager.Parameters["users"] = e.Result.Select(
w => new User(
w.Guid,
w.TrusteeType,
w.Username,
w.Email,
w.LastLogin,
w.PasswordChanged,
w.IsUsingTempPassword
)
)
.ToList();
AsyncManager.OutstandingOperations.Decrement();
};
client.GetUsersAsync();
}
public ActionResult IndexCompleted(IEnumerable<User> users)
{
return View(users);
}
}
and if you are using .NET 4.5 you could even take advantage of the new async keyword simplifying the asynchronous code even further. This is possible if you refactor your data access layer to the new pattern (i.e. return Tasks):
public class MyController: AsyncController
{
public async Task<ActionResult> Index()
{
var client = new SomeClient();
var users = await client.GetUsersAsync().Select(
w => new User(
w.Guid,
w.TrusteeType,
w.Username,
w.Email,
w.LastLogin,
w.PasswordChanged,
w.IsUsingTempPassword
)
)
.ToList();
return View(users);
}
}

Unit testing ASP.NET MVC redirection

How do I Unit Test a MVC redirection?
public ActionResult Create(Product product)
{
_productTask.Save(product);
return RedirectToAction("Success");
}
public ActionResult Success()
{
return View();
}
Is Ayende's approach still the best way to go, with preview 5:
public static void RenderView(this Controller self, string action)
{
typeof(Controller).GetMethod("RenderView").Invoke(self,new object[] { action} );
}
Seems odd to have to do this, especially as the MVC team have said they are writing the framework to be testable.
[TestFixture]
public class RedirectTester
{
[Test]
public void Should_redirect_to_success_action()
{
var controller = new RedirectController();
var result = controller.Index() as RedirectToRouteResult;
Assert.That(result, Is.Not.Null);
Assert.That(result.Values["action"], Is.EqualTo("success"));
}
}
public class RedirectController : Controller
{
public ActionResult Index()
{
return RedirectToAction("success");
}
}
This works for ASP.NET MVC 5 using NUnit:
[Test]
public void ShouldRedirectToSuccessAction()
{
var controller = new RedirectController();
var result = controller.Index() as RedirectToRouteResult;
Assert.That(result.RouteValues["action"], Is.EqualTo("success"));
}
If you want to test that you are redirecting to a different controller (say NewController), the assertion would be:
Assert.That(result.RouteValues["controller"], Is.EqualTo("New"));
You can assert on the ActionResult that is returned, you'll need to cast it to the appropriate type but it does allow you to use state-based testing. A search on the Web should find some useful links, here's just one though.
you can use Mvc.Contrib.TestHelper which provides assertions for testing redirections. Take a look at http://kbochevski.blogspot.com/2010/06/unit-testing-mvcnet.html and the code sample. It might be helpful.

Resources