ASP MVC Redirect without changing URL(routing) - c#

Goal:
I want to be able to type URL: www.mysite.com/NewYork OR www.mysite.com/name-of-business
Depending on the string I want to route to different actions without changing the URL.
So far I have:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
"UrlRouter", // Route name
"{query}", // URL with parameters
new { controller = "Routing", action = "TestRouting" } // Parameter defaults
);
}
In the controller I have:
public ActionResult TestRouting(string query)
{
if (query == "NewYork")
return RedirectToAction("Index", "Availability"); // <--------- not sure
else if (query == "name-of-business")
return Redirect("nameofbusines.aspx?id=2731"); // <--------- not sure
else
return RedirectToAction("TestTabs", "Test"); // <--------- not sure
}
I have pretty much tried everything to redirect/transfer to the page without
changing the URL, but everything I've tried changes the URL or gives me an error.
Basically I'm looking for the equivalent of server.transfer where I can keep the URL but send info to the action and have it display its result.

I'm with Nick on this one, though I think you could just use regular views instead of having to do partials. You may need to implement them as shared views if they are not in the views corresponding to the controller (since it will only look in the associated and shared views).
public ActionResult TestRouting(string query)
{
if (query == "NewYork")
{
var model = ...somehow get "New York" model
return View("Index", model );
}
else if (query == "name-of-business")
{
var model = ...get "nameofbusiness" model
return View("Details", model );
}
else
{
return View("TestTabs");
}
}
Each view would then take a particular instance of the model and render it's contents using the model. The URL will not change.
Anytime that you use a RedirectResult, you will actually be sending an HTTP redirect to the browser and that will force a URL change.

Im not sure if you tried this way or if this way has any drawbacks..
Add a global.asax file to your project. In that add the following method:
void Application_BeginRequest(object sender, EventArgs e)
{
// Handles all incoming requests
string strURLrequested = Context.Request.Url.ToString();
GetURLToRedirect objUrlToRedirect = new GetURLToRedirect(strURLrequested);
Context.RewritePath(objUrlToRedirect.RedirectURL);
}
GetURLToRedirect can be a class that has the logic to find the actual URL based on the URL typed in. The [RedirectURL] property will be set with the url to redirect to beneath the sheets.
Hope that helps...

You can change your controller like this:
public ActionResult TestRouting(string query)
{
string controller,action;
if (query == "NewYork")
{
controller = "Availability";
action = "Index";
}
else
{
controller = "Test";
action = "TestTabs";
}
ViewBag.controller = controller;
ViewBag.action = action;
return View();
}
Then you can use these ViewBags in your view like this:
#{
Layout = null;
Html.RenderAction(ViewBag.action, ViewBag.controller);
}
That's it. And you can improve this example with use a class and some functions.

Are you saying you want to go to "www.mysite.com/NewYork" and then "really" go "somewhere else" but leave the url alone? Perhaps what you would want to do then is use partial views to implement this? That way, your base page would be what gets routed to, and then inside of that page you do your condition testing to bring up different partial views? I've done that in my application for viewing either a read-only version of a grid or an editable grid. It worked very nicely.

I'm not sure what you can do about the redirect to the .aspx page, but you should be able to replace the RedirectToAction(...)s with something like this:
public ActionResult TestRouting(string query)
{
if (query == "NewYork")
{
var controller = new AvailabilityController();
return controller.Index();
}
else if (query == "name-of-business")
return Redirect("nameofbusines.aspx?id=2731"); <--------- not sure
else
{
var controller = new TestController();
return controller.TestTabs();
}
}

Related

How can I redirect to different view? Asp.net MVC not loading the reteurned view

I am using ASP.net MVC and AngularJS.
In my current page I select some items and on button click the angular function is called
var itemIds = [];
//... somehow I get my items from the grids
var url = 'http://localhost/myController/CompareItems';
$http.post(url, groupIds)
.then(function (response) {
// var data = response.data;
//what to do here?
}
).finally(function () {
vm.loading = false;
});
}
The c# MVC controller is like below: (myController.cs)
public async Task<IActionResult> CompareItems([FromBody] int[] itemIds)
{
var model = new CompareModel
{
property1= ..,
property2= ..,
......
......
};
return View("CompareItems",model);
}
On $http.post call, the debugger comes to the above controller, prepare the model and should return the View.
There is no error but the page is not redirecting.
What is wrong here?
N.B: I have searched many other posts before asking this question. I found lots of people are suggesting RedirectToAction. I tried this and that doesnot work. My question is, why should Redirect to another action. I am already in my expected action method with my required param values.
The thing is very simple, just collect the selected items from javascript and pass it to mvc controller-action. It is return a different view.
There appear to be something you missing.
You might want to replace
return View("Login")
with
return RedirectToAction("Login");
I am just giving an instance , you might want to replace it with what you have.
Calling View from another folder
return View("MyViewFolder/MyViewName.cshtml");
Or with model as object
return View("MyViewFolder/MyViewName.cshtml", model);
Please also check you have set method return type as async which may prevent you if some ongoing execute are there with await.

How do I return a different ActionResult from my controller that takes arguments?

I have a Controller with some actions on it as follows:
[HttpPost]
public ActionResult Create(CreateModel model)
{
if (model.SelectedCustomers.Count > 0 &&
model.SelectedVersions.Count > 0 &&
!string.IsNullOrWhiteSpace(model.ScriptName) &&
!string.IsNullOrWhiteSpace(model.ScriptText))
{
Script script;
...save to database...
return Edit(script.Id); //<---------Return other view here
}
else
{
...
}
}
[HttpGet]
public ActionResult Edit(int? scriptId)
{
return View();
}
After the Create action runs, and saves my model to the database successfully, I want to send the user to the Edit view for the newly created script. When I use the code above, specifically return Edit(script.Id); it just sends the user back to the Create view instead of the Edit view. When the user navigates to the Edit action directly, or through the result of an Html.ActionLink pointed at Edit everything works correctly.
What am I doing wrong?
This isn't doing what you think it does:
return Edit(script.Id)
It's not actually telling the framework to go to that action. It's just returning the return value of that method. Purely a C# concern before any components of the ASP.NET MVC Framework are involved at all. And what is that return value:
return View()
So the former is really functionally the same thing as the latter. And any time you use return View() in ASP.NET MVC, the framework will determine that view by examining the action currently being called, which in this case is Create.
What you want isn't to return the Edit view (even if you do, in this case, the user is still on the Create URL, which will cause confusion). What you want is to return a redirect to tell the client to request that next action:
return RedirectToAction("Edit", new { scriptId = script.Id });
You can always call RedirectToAction and return that action result. That will inform the browser to redirect to the different action.
I think you will need something like this:
return RedirectToAction("Edit", new { scriptId = script.Id });
Calling Edit directly is no different than calling a method.
You can do with this RedirecToAction with input parameters.
return RedirectToAction("Action", new { id = 12 });
In Your Case:
return RedirectToAction("Edit", new { scriptId = script.Id });

A public action method was not found on controller

I am working on a project on asp.net MVC3, I have a controller named UserProfile when i run my project and login, it shows error A public action method images was not found on controller UserProfile
I don't have any action method named images in any of my controllers,below is my UserProfile's index method
[CustomAuthorizeAttribute]
public ActionResult Index()
{
var userName = string.Empty;
if (SessionHelper.GetSession("login") != null)
{ userName = SessionHelper.GetSession("login").ToString(); }
else
{ return View(); }
SessionHelper.SetSess("issetup", null);
UserProfileModel model = GetUserProfileData(userName);
StateandCityDropdown();
return View(model);
}
I have two forms on userprofile index view one with some textboxes and other fields for entering data and second for uploading images
It sounds to me like the routes are picking up on a url you have and mistaking them for an action. It could be that you have a link to an image directory underneath a directory that matches your controller such as /User/Images would thow this error because the routing would then expect you to have an Images action when you dont. Check the page source for anything linking to an images folder but without an image included. The other option is that the routes are picking up the images as well as the actions you want them to. If this is the case in your Global.asax.cs file check the RegisterRoutes method has some ignores in for images.
routes.Ignore("{*allpng}", new { allpng = #".*\.png(/.*)?" });
routes.Ignore("{*allgif}", new { allgif = #".*\.gif(/.*)?" });
routes.Ignore("{*alljpg}", new { alljpg = #".*\.jpg(/.*)?" });
Hope this helps
Andy

How can I maintain ModelState with RedirectToAction?

How can I return the result of a different action or move the user to a different action if there is an error in my ModelState without losing my ModelState information?
The scenario is; Delete action accepts a POST from a DELETE form rendered by my Index Action/View. If there is an error in the Delete I want to move the user back to the Index Action/View and show the errors that are stored by the Delete action in the ViewData.ModelState. How can this be done in ASP.NET MVC?
[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Delete)]
public ActionResult Delete([ModelBinder(typeof(RdfUriBinder))] RdfUri graphUri)
{
if (!ModelState.IsValid)
return Index(); //this needs to be replaced with something that works :)
return RedirectToAction("Index");
}
Store your view data in TempData and retrieve it from there in your Index action, if it exists.
...
if (!ModelState.IsValid)
TempData["ViewData"] = ViewData;
RedirectToAction( "Index" );
}
public ActionResult Index()
{
if (TempData["ViewData"] != null)
{
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
...
}
[EDIT] I checked the on-line source for MVC and it appears that the ViewData in the Controller is settable, so it is probably easiest just to transfer all of the ViewData, including the ModelState, to the Index action.
Use Action Filters (PRG pattern) (as easy as using attributes)
Mentioned here and here.
Please note that tvanfosson's solution will not always work, though in most cases it should be just fine.
The problem with that particular solution is that if you already have any ViewData or ModelState you end up overwriting it all with the previous request's state. For example, the new request might have some model state errors related to invalid parameters being passed to the action, but those would end up being hidden because they are overwritten.
Another situation where it might not work as expected is if you had an Action Filter that initialized some ViewData or ModelState errors. Again, they would be overwritten by that code.
We're looking at some solutions for ASP.NET MVC that would allow you to more easily merge the state from the two requests, so stay tuned for that.
Thanks,
Eilon
In case this is useful to anyone I used #bob 's recommended solution using PRG:
see item 13 -> link.
I had the additional issue of messages being passed in the VeiwBag to the View being written and checked / loaded manually from TempData in the controller actions when doing a RedirectToAction("Action"). In an attempt to simplify (and also make it maintainable) I slightly extended this approach to check and store/load other data as well. My action methods looked something like:
[AcceptVerbs(HttpVerbs.Post)]
[ExportModelStateToTempData]
public ActionResult ChangePassword(ProfileViewModel pVM) {
bool result = MyChangePasswordCode(pVM.ChangePasswordViewModel);
if (result) {
ViewBag.Message = "Password change success";
else {
ModelState.AddModelError("ChangePassword", "Some password error");
}
return RedirectToAction("Index");
}
And my Index Action:
[ImportModelStateFromTempData]
public ActionResult Index() {
ProfileViewModel pVM = new ProfileViewModel { //setup }
return View(pVM);
}
The code in the Action Filters:
// Following best practices as listed here for storing / restoring model data:
// http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx#prg
public abstract class ModelStateTempDataTransfer : ActionFilterAttribute {
protected static readonly string Key = typeof(ModelStateTempDataTransfer).FullName;
}
:
public class ExportModelStateToTempData : ModelStateTempDataTransfer {
public override void OnActionExecuted(ActionExecutedContext filterContext) {
//Only export when ModelState is not valid
if (!filterContext.Controller.ViewData.ModelState.IsValid) {
//Export if we are redirecting
if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult)) {
filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
}
}
// Added to pull message from ViewBag
if (!string.IsNullOrEmpty(filterContext.Controller.ViewBag.Message)) {
filterContext.Controller.TempData["Message"] = filterContext.Controller.ViewBag.Message;
}
base.OnActionExecuted(filterContext);
}
}
:
public class ImportModelStateFromTempData : ModelStateTempDataTransfer {
public override void OnActionExecuted(ActionExecutedContext filterContext) {
ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;
if (modelState != null) {
//Only Import if we are viewing
if (filterContext.Result is ViewResult) {
filterContext.Controller.ViewData.ModelState.Merge(modelState);
} else {
//Otherwise remove it.
filterContext.Controller.TempData.Remove(Key);
}
}
// Restore Viewbag message
if (!string.IsNullOrEmpty((string)filterContext.Controller.TempData["Message"])) {
filterContext.Controller.ViewBag.Message = filterContext.Controller.TempData["Message"];
}
base.OnActionExecuted(filterContext);
}
}
I realize my changes here are a pretty obvious extension of what was already being done with the ModelState by the code # the link provided by #bob - but I had to stumble on this thread before I even thought of handling it in this way.
Please don't skewer me for this answer. It is a legitimate suggestion.
Use AJAX
The code for managing ModelState is complicated and (probably?) indicative of other problems in your code.
You can pretty easily roll your own AJAX javascript code. Here is a script I use:
https://gist.github.com/jesslilly/5f646ef29367ad2b0228e1fa76d6bdcc#file-ajaxform
(function ($) {
$(function () {
// For forms marked with data-ajax="#container",
// on submit,
// post the form data via AJAX
// and if #container is specified, replace the #container with the response.
var postAjaxForm = function (event) {
event.preventDefault(); // Prevent the actual submit of the form.
var $this = $(this);
var containerId = $this.attr("data-ajax");
var $container = $(containerId);
var url = $this.attr('action');
console.log("Post ajax form to " + url + " and replace html in " + containerId);
$.ajax({
type: "POST",
url: url,
data: $this.serialize()
})
.done(function (result) {
if ($container) {
$container.html(result);
// re-apply this event since it would have been lost by the form getting recreated above.
var $newForm = $container.find("[data-ajax]");
$newForm.submit(postAjaxForm);
$newForm.trigger("data-ajax-done");
}
})
.fail(function (error) {
alert(error);
});
};
$("[data-ajax]").submit(postAjaxForm);
});
})(jQuery);
Maybe try
return View("Index");
instead of
return Index();

How to RedirectToAction in ASP.NET MVC without losing request data

Using ASP.NET MVC there are situations (such as form submission) that may require a RedirectToAction.
One such situation is when you encounter validation errors after a form submission and need to redirect back to the form, but would like the URL to reflect the URL of the form, not the action page it submits to.
As I require the form to contain the originally POSTed data, for user convenience, as well as validation purposes, how can I pass the data through the RedirectToAction()? If I use the viewData parameter, my POST parameters will be changed to GET parameters.
The solution is to use the TempData property to store the desired Request components.
For instance:
public ActionResult Send()
{
TempData["form"] = Request.Form;
return this.RedirectToAction(a => a.Form());
}
Then in your "Form" action you can go:
public ActionResult Form()
{
/* Declare viewData etc. */
if (TempData["form"] != null)
{
/* Cast TempData["form"] to
System.Collections.Specialized.NameValueCollection
and use it */
}
return View("Form", viewData);
}
Keep in mind that TempData stores the form collection in session. If you don't like that behavior, you can implement the new ITempDataProvider interface and use some other mechanism for storing temp data. I wouldn't do that unless you know for a fact (via measurement and profiling) that the use of Session state is hurting you.
Take a look at MVCContrib, you can do this:
using MvcContrib.Filters;
[ModelStateToTempData]
public class MyController : Controller {
//
...
}
There is another way which avoids tempdata. The pattern I like involves creating 1 action for both the original render and re-render of the invalid form. It goes something like this:
var form = new FooForm();
if (request.UrlReferrer == request.Url)
{
// Fill form with previous request's data
}
if (Request.IsPost())
{
if (!form.IsValid)
{
ViewData["ValidationErrors"] = ...
} else {
// update model
model.something = foo.something;
// handoff to post update action
return RedirectToAction("ModelUpdated", ... etc);
}
}
// By default render 1 view until form is a valid post
ViewData["Form"] = form;
return View();
That's the pattern more or less. A little pseudoy. With this you can create 1 view to handle rendering the form, re-displaying the values (since the form will be filled with previous values), and showing error messages.
When the posting to this action, if its valid it transfers control over to another action.
I'm trying to make this pattern easy in the .net validation framework as we build out support for MVC.
If you want to pass data to the redirected action, the method you could use is:
return RedirectToAction("ModelUpdated", new {id = 1});
// The definition of the action method like public ActionResult ModelUpdated(int id);
TempData is the solution which keeps the data from action to action.
Employee employee = new Employee
{
EmpID = "121",
EmpFirstName = "Imran",
EmpLastName = "Ghani"
};
TempData["Employee"] = employee;

Categories

Resources