RedirectToAction not working as expected - asp.net-mvc-3

I have a simple MVC3 application that I want to retrieve some configuration details from a service, allow the user to edit and save the configuration.
If any errors are detected during the saving process, these are to be returned and reported back to the user.
The problem is that the configuration containing the errors is failing to be called and the currently saved values are just being redisplayed.
Stepping through the code, when errors are detected, it should redirect to itself using the passed config object but it doesn't and uses the method with no parameter.
Can anyone see where I'm going wrong?
Below are the two controller methods that are being called:
//
// GET: /Settings/Edit/
public ActionResult Edit()
{
SettingsViewModel config = null;
// Set up a channel factory to use the webHTTPBinding
using (WebChannelFactory<IChangeService> serviceChannel =
new WebChannelFactory<IChangeService>(new Uri(baseServiceUrl)))
{
// Retrieve the current configuration from the service for editing
IChangeService channel = serviceChannel.CreateChannel();
config = channel.GetSysConfig();
}
ViewBag.Message = "Service Configuration";
return View(config);
}
//
// POST: /Settings/Edit/
[HttpPost]
public ActionResult Edit( SettingsViewModel config)
{
try
{
if (ModelState.IsValid)
{
// Set up a channel factory to use the webHTTPBinding
using (WebChannelFactory<IChangeService> serviceChannel = new WebChannelFactory<IChangeService>(new Uri(baseServiceUrl)))
{
IChangeService channel = serviceChannel.CreateChannel();
config = channel.SetSysConfig(config);
// Check for any errors returned by the service
if (config.ConfigErrors != null && config.ConfigErrors.Count > 0)
{
// Force the redisplay of the page displaying the errors at the top
return RedirectToAction("Edit", config);
}
}
}
return RedirectToAction("Index", config);
}
catch
{
return View();
}
}

return RedirectToAction("Index", config);
You cannot pass complex objects like this when redirecting. You will need to pass query string parameters one by one:
return RedirectToAction("Index", new {
Prop1 = config.Prop1,
Prop2 = config.Prop2,
...
});
Also I couldn't see an Index action in your controller. Maybe it's a typo. Another thing I notice is that you have an Edit GET action to which you are probably trying to redirect but this Edit action doesn't take any parameters so it just seems weird. If you are trying to redirect to the POST Edit action, well, that's obviously impossible since a redirect is always on GET by its very nature.

Related

Sending new parameters in MvxViewModelRequest from a IMvxNavigationFacade when deeplinking

I am using deeplinking in my app and Im looking to preset some parameters when navigating to the viewmodel using a IMvxNavigationFacade. The deep link url is like this:
myappname://deeplink/toviewwithdata/?navigatetoview=viewtype1&id=78910
So the deep linking is working and im getting to the navigation facade using the assembly attribute
[assembly: MvxNavigation(typeof(RoutingFacade), #"myappname://deeplink/toviewwithdata/\?navigatetoview=(?<viewtype>viewtype1)&id=(?<id>\d{5})")]
I tried to add other parameters to the MvxViewModelRequest using a MvxBundle but dont think im doing it right. here is my navigation facade:
public class RoutingFacade : IMvxNavigationFacade
{
public Task<MvxViewModelRequest> BuildViewModelRequest(string url, IDictionary<string, string> currentParameters)
{
var viewModelType = typeof(FirstViewModel);
var parameters = new MvxBundle();
try
{
// TODO: Update this to handle different view types and add error handling
if (currentParameters != null)
{
Debug.WriteLine($"RoutingFacade - {currentParameters["viewtype"]}, {currentParameters["id"]}");
switch (currentParameters["viewtype"])
{
case "viewtype1":
viewModelType = typeof(FirstViewModel);
parameters.Data.Add("test", "somevalue");
break;
default:
case "viewtype2":
viewModelType = typeof(FirstViewModel);
break;
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"RoutingFacade - Exception: {ex.Message}");
//TODO viewModelType = typeof(ErrorViewModel);
}
return Task.FromResult(new MvxViewModelRequest(viewModelType, parameters, null));
}
then my viewmodel Init method
public void Init(string id, string viewtype, string test)
{
// Do stuff with parameters
}
but the test parameter is null? How do you pass parameters into a MvxViewModelRequest?
Update:
Don’t know if its possible from looking at the source here https://github.com/MvvmCross/MvvmCross/blob/f4b2a7241054ac288a391c4c7b7a7342852e1e19/MvvmCross/Core/Core/Navigation/MvxNavigationService.cs#L122 as the request parameters get set from the regex of the deeplink url and the return from BuildViewModelRequest, facadeRequest.parameterValues get ignored.
Added this functionality in this pull request

Storing object in Session

I know this subject has been treated in numerous posts but I just cannot work it out.
Within an Controller Inside an ActionResult I would like to store an object in the Session and retrieve it in another ActionResult. Like that :
public ActionResult Step1()
{
return View();
}
[HttpPost]
public ActionResult Step1(Step1VM step1)
{
if (ModelState.IsValid)
{
WizardProductVM wiz = new WizardProductVM();
wiz.Step1 = step1;
//Store the wizard in session
// .....
return View("Step2");
}
return View(step1);
}
[HttpPost]
public ActionResult Step2(Step2VM step2)
{
if (ModelState.IsValid)
{
//Pull the wizard from the session
// .....
wiz.Step2 = step2;
//Store the wizard in session again
// .....
return View("Step3");
}
}
Storing the wizard:
Session["object"] = wiz;
Getting the wizard:
WizardProductVM wiz = (WizardProductVM)Session["object"];
If you only need it on the very next action and you plan to store it again you can use TempData. TempData is basically the same as Session except that it is "removed" upon next access thus the need to store it again as you have indicated you are doing.
http://msdn.microsoft.com/en-us/library/dd394711(v=vs.100).aspx
If possible though, it may be better to determine a way to use posted parameters to pass in the necessary data rather than relying on session (tempdata or otherwise)

send data between actions with redirectAction and prg pattern

how can i send data between actions with redirectAction??
I am using PRG pattern. And I want to make something like that
[HttpGet]
[ActionName("Success")]
public ActionResult Success(PersonalDataViewModel model)
{
//model ko
if (model == null)
return RedirectToAction("Index", "Account");
//model OK
return View(model);
}
[HttpPost]
[ExportModelStateToTempData]
[ActionName("Success")]
public ActionResult SuccessProcess(PersonalDataViewModel model)
{
if (!ModelState.IsValid)
{
ModelState.AddModelError("", "Error");
return RedirectToAction("Index", "Account");
}
//model OK
return RedirectToAction("Success", new PersonalDataViewModel() { BadgeData = this.GetBadgeData });
}
When redirect you can only pass query string values. Not entire complex objects:
return RedirectToAction("Success", new {
prop1 = model.Prop1,
prop2 = model.Prop2,
...
});
This works only with scalar values. So you need to ensure that you include every property that you need in the query string, otherwise it will be lost in the redirect.
Another possibility is to persist your model somewhere on the server (like a database or something) and when redirecting only pass the id which will allow to retrieve the model back:
int id = StoreModel(model);
return RedirectToAction("Success", new { id = id });
and inside the Success action retrieve the model back:
public ActionResult Success(int id)
{
var model = GetModel(id);
...
}
Yet another possibility is to use TempData although personally I don't recommend it:
TempData["model"] = model;
return RedirectToAction("Success");
and inside the Success action fetch it from TempData:
var model = TempData["model"] as PersonalDataViewModel;
You cannot pass data between actions using objects, as Darin mentioned, you can only pass scalar values.
If your data is too large, or does not consist only of scalar values, you should do something like this
[HttpGet]
public ActionResult Success(int? id)
{
if (!(id.HasValue))
return RedirectToAction("Index", "Account");
//id OK
model = LoadModelById(id.Value);
return View(model);
}
And pass that id from RedirectToAction
return RedirectToAction("Success", { id = Model.Id });
RedirectToAction method returns an HTTP 302 response to the browser, which causes the browser to make a GET request to the specified action. So you can not pass complex objects like you calling other methods with complex objects.
Your possible solution is to pass an id using with the GET action can build the object again. Some thing like this
[HttpPost]
public ActionResult SuccessProcess(PersonViewModel model)
{
//Some thing is Posted (P)
if(ModelState.IsValid)
{
//Save the data and Redirect (R)
return RedirectToAction("Index",new { id=model.ID});
}
return View(model)
}
public ActionResult Index(int id)
{
//Lets do a GET (G) request like browser requesting for any time with ID
PersonViewModel model=GetPersonFromID(id);
return View(id);
}
}
You can keep data (The complex object) between This Post and GET request using Session also (TempData is internally using session even). But i believe that Takes away the purity of PRG Pattern.

Partial View Validation Without JavaScript

I have a partial view in which there is a form. I POST this form using the PRG pattern. I am using the AjaxHelper to create my form. I also need this form to work without javascript. The problem is that when model validation fails, it always changes the url to my partial view.
public ActionResult PostForm(PostFormModel postFormModel)
{
if (ModelState.IsValid)
{
return RedirectToAction("SomewhereElse");
}
else
{
if (Request.IsAjaxRequest())
{
return PartialView("_PostForm")
}
else
{
// What do I do here?
}
}
}
Here's what I have tried:
return PartialView("_PostForm", postFormModel);
This just renders the partial view and doesn't contain any of the parent stuff.
return View("Index", new ParentModel() { PostFormModel = postFormModel });
This actually produces the correct result. It displays the parent view, but the URL is that of the partial http://localhost:22485/Controller/PostForm! I feel like this is really close to the solution. What now?
If you want to change url, you should redirect to another action (using PRG pattern). Insert next code instead of '// What do I do here?':
postModelService.Save(postFormModel); //to Session or to DB
return RedirectToAction("Parent");
New action should look like this:
public ActionResult Parent()
{
var postFormModel = postModelService.Load();
return View("Index", new ParentModel() { PostFormModel = postFormModel });
}
Hope it helps.

Return an other action result as string

In my MVC website, I am creating a small forum. For a single post I am rendering my "Single(Post post)" action in my "PostController" like below
<% Html.RenderAction<PostController>(p => p.Single(comment)); %>
Also When a user reply a post I am sending reply as an ajax request to my "CreatePost" action then return "Single" view as result of this action like below
public ActionResult CreatePostForForum(Post post)
{
//Saving post to DB
return View("Single", postViewData);
}
When I do like that only the view is being rendered, Codes in "Single" Actions body isn't beig executed.
What is the best way to do this?
Also I want to return "Single" action result as string in my JsonObject like below
return Json(new{IsSuccess = true; Content= /*HERE I NEED Single actions result*/});
You can use something like this, but be very careful with this. It can actually cause badly traceable errors (for example when you forget to explicitly set view name in Single method).
public ActionResult Single(PostModel model) {
// it is important to explicitly define which view we should use
return View("Single", model);
}
public ActionResult Create(PostModel model) {
// .. save to database ..
return Single(model);
}
Cleaner solution would be to do the same as if it was post from standard form - redirect (XMLHttpRequest will follow it)
For returning ajax views wrapped in json I use following class
public class AjaxViewResult : ViewResult
{
public AjaxViewResult()
{
}
public override void ExecuteResult(ControllerContext context)
{
if (!context.HttpContext.Request.IsAjaxRequest())
{
base.ExecuteResult(context);
return;
}
var response = context.HttpContext.Response;
response.ContentType = "application/json";
using (var writer = new StringWriter())
{
var oldWriter = response.Output;
response.Output = writer;
try
{
base.ExecuteResult(context);
}
finally
{
response.Output = oldWriter;
}
JavaScriptSerializer serializer = new JavaScriptSerializer();
response.Write(serializer.Serialize(new
{
action = "replace",
html = writer.ToString()
}));
}
}
}
It is probably not the best solution, but it works quite well. Note that you will need to manually set View, ViewData.Model, ViewData, MasterName and TempData properties.
My recommendation:
Post your forum reply (and whatever options) via Ajax.
Return your JSONResult, using this method: ASP MVC View Content as JSON to render your content.
In the OnSuccess handler of your ajax call, check if IsSuccess is true. If successful, append the content to the appropriate container using JQuery

Resources