how to iterate ViewBag or how can I copy the values of viewBag from one Action to another Action - asp.net-mvc-3

I have a base Controller like follow
public abstract class BaseController
{
protected ActionResult LogOn(LogOnViewModel viewModel)
{
SaveTestCookie();
var returnUrl = "";
if (HttpContext != null && HttpContext.Request != null && HttpContext.Request.UrlReferrer != null)
{
returnUrl = HttpContext.Request.UrlReferrer.LocalPath;
}
TempData["LogOnViewModel"] = viewModel;
return RedirectToAction("ProceedLogOn", new { returnUrl });
}
public ActionResult ProceedLogOn(string returnUrl)
{
if (CookiesEnabled() == false)
{
return RedirectToAction("logon", "Account", new { area = "", returnUrl, actionType, cookiesEnabled = false });
}
var viewModel = TempData["LogOnViewModel"] as LogOnViewModel;
if (viewModel == null)
{
throw new NullReferenceException("LogOnViewModel is not found in tempdata");
}
//Do something
//the problem is I missed the values which are set in the ViewBag
}
}
and another Controller
public class MyController : BaseController
{
[HttpPost]
public ActionResult LogOn(LogOnViewModel viewModel)
{
// base.LogOn is used in differnet controller so I saved some details in view bag
ViewBag.Action = "LogonFromToolbar";
ViewBag.ExtraData = "extra data related only for this action";
return base.LogOn(viewModel);
}
}
the problem is I missed the view bag values in ProceedLogOn action method.
I have the values in Logon method in BaseController.
How can I copy the values of ViewBag from one Action to another Action?
So I can not simply say this.ViewBag=ViewBag;
because ViewBag doesn't have setter. I was thinking of Iterating through viewbag.
I tried ViewBag.GetType().GetFields() and ViewBag.GetType().GetProperties() but they return nothing.

ViewData reflects ViewBag
You can iterate the values you've stored like this :
ViewBag.Message = "Welcome to ASP.NET MVC!";
ViewBag.Answer = 42;
foreach (KeyValuePair<string, object> item in ViewData)
{
// if (item.Key = "Answer") ...
}
This link should also be useful

I'm afraid I don't have the answer how to copy ViewBag.
However, I would never use ViewBag that way.
ViewBag is some data the Controller gives to the View to render output if someone does not like to use ViewModel for some reasons. The View should never know anything about the Controller but your ViewBag is holding a ActionName ;).
Anyway, the ProceedLogOn action method has pretty much parameters which is ... not a nice code actually so why hesitate to add more parameters which are currently being hold in MyController.Logon ViewBag? Then inside method ProceedLogOn you have what you want.
;)

Related

Model is null on postback in ajax loaded partial view

I'm using the following pattern https://github.com/filamentgroup/Ajax-Include-Pattern
to load partial views through ajax.
View:
#using(Html.BeginUmbracoForm("PostContactInformation", "JoiningSurface", null, new Dictionary<string, object> { { "class", "joinform" } })) {
#Html.AntiForgeryToken()
<div data-append="#Url.Action("RenderJoiningContactInformation", "JoiningSurface", new { ContentId = CurrentPage.Id })"></div>
}
With Action:
public ActionResult RenderContactInformation(int ContentId)
{
var viewModel = ContactViewModel();
viewModel.Content = Umbraco.TypedContent(ContentId);
return PartialView("RenderContactInformation", viewModel);
}
Loads partial view perfectly.
// No need to add partial view i think
Post action works correctly as well:
public ActionResult PostContactInformation(ContactViewModel model)
{
//code here
return RedirectToUmbracoPage(pageid);
}
The problem is, that i need to add model error to CurrentUmbracoPage if it exists in post...
For example:
public ActionResult PostContactInformation(ContactViewModel model)
{
ModelState.AddModelError(string.Empty, "Error occurred");
return CurrentUmbracoPage();
}
In this case i get null values for current model. And this happens only when i use ajax.
If i load action synchronously like that:
#using(Html.BeginUmbracoForm("PostJoiningContactInformation", "JoiningSurface", null, new Dictionary<string, object> { { "class", "joinform" } })) {
#Html.AntiForgeryToken()
#Html.Action("RenderContactInformation", "JoiningSurface", new { ContentId = CurrentPage.Id })
}
everything works like it should.
But i need to use ajax. Is there a correct way to pass values on postback in this case? I know that i can use TempData, but i'm not sure that this is the best approach.
Thanks for your patience
The problem is that Umbraco context is not accessible when you're trying to reach it through ajax call. Those calls are a little bit different.
Check my answer in this thread: Umbraco route definition-ajax form and I suggest to go with WebAPI and UmbracoApiControllers to be able to access those values during the Ajax call.

Looking for an alternative to serializing data in controller

I am looking for a method to switch from serializing data (which I originally did using Mvc Futures) and passing it around my controller actions to something that does not use serialize. My previous implementation was for a wizard that passed data from action to action until it was submitted and the data was saved. However, I am unable to use serialization in a new project and am looking for an alternative.
Here is an example of what I did in my controller:
private MyViewModel myViewModel;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var serialized = Request.Form["myViewModel"];
if (serialized != null) //Form was posted containing serialized data
{
myViewModel = (MyViewModel)new MvcSerializer()
.Deserialize(serialized, SerializationMode.Signed);
TryUpdateModel(myViewModel);
}
else
myViewModel= (MyViewModel)TempData["myViewModel"] ?? new MyViewModel();
TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["myViewModel"] = myViewModel;
}
Then in some actions:
// STEP 1:
public ActionResult Step1()
{
return View(myViewModel);
}
[HttpPost]
[ActionName("Step1")]
public ActionResult Step1POST(string nextButton)
{
if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("Step2");
return View(myViewModel);
}
// STEP 2:
[Themed]
public ActionResult Step2()
{
return View(myViewModel);
}
[HttpPost]
[ActionName("Step2")]
public ActionResult Step2POST(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("Step1");
else if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("Step3");
return View(myViewModel);
}
My view would contain this inside the #Html.BeginForm block:
#Html.Hidden("myViewModel",
new MvcSerializer().Serialize(Model, SerializationMode.Signed))
My first thought is that I have no other alternative (other than maybe jQuery, which I also cannot use right now). In this scenario I have to figure out how to use TempData in each ActionResult, which will get messy if I have 10 or so inputs in each view.
So my question would probably be two-fold:
Is there a clean alternative to using serialize in this manner?
If there is no clean alternative and I am forced to do it in each
ActionResult, I am not sure of how to do so if, for example, I kept
it to one input view and one submit/confirm view and just wanted to
pass two values to the "Submit" step (not shown above), such as
FirstName an EMail. How might I do that using TempData?
Thanks.

MVC 3 How to tell what view a controller action is being called from-

Is there a way to tell what view a controller action is being called from?
For example, I would like to use "ControllerContext.HttpContext.Request.PhysicalPath" but it returns the path in which the controller action itself is located:
public ActionResult HandleCreateCustomer()
{
// Set up the customer
//..code here to setup the customer
//Check to see of the calling view is the BillingShipping view
if(ControllerContext.HttpContext.Request.PhysicalPath.Equals("~/Order/BillingShipping"))
{
//
return RedirectToAction("OrderReview", "Order", new { id = customerId });
}
else
{
return RedirectToAction("Index", "Home", new { id = customerId });
}
}
If you have a fixed number of locations that it could possibly be called from, you could create an enum where each of the values would correspond to a place where it could have been called from. You'd then just need to pass this enum value into HandleCreateCustomer, and do your condition statement(s) based on that.
At the moment I am using something of the sort:
In the View I am populating a TempData variable using:
#{TempData["ViewPath"] = #Html.ViewVirtualPath()}
The HtmlHelper method ViewVirtualPath() is found in the System.Web.Mvc.Html namespace (as usual) and is as follows and returns a string representing the View's virtual path:
public static string ViewVirtualPath(this HtmlHelper htmlHelper)
{
try{
return ((System.Web.WebPages.WebPageBase)(htmlHelper.ViewDataContainer)).VirtualPath;
}catch(Exception){
return "";
}
}
I will then obviously read the TempData variable in the controller.
I found another way.
In the controller you want to know what page it was called from.
I added the following in my controller
ViewBag.ReturnUrl = Request.UrlReferrer.AbsolutePath;
Then in the View I have a 'Back' button
#(Html.Kendo().Button().Name("ReturnButton")
.Content("Back to List").Events(e => e.Click("onReturn"))
.HtmlAttributes(new { type = "k-button" })
)
Then the javascript for the onReturn handler
function onReturn(e) {
var url = '#(ViewBag.ReturnUrl)';
window.location.href = url;
}

assigning variables in the View in ASP MVC 3

Actually I'm very new to ASP.NET MVC and I need your help.
Here I have some Create Method that takes an argument from the URL to use it as id:
in 'vote' controller :
public ActionResult Create(int id)
{
Meeting meeting = db.Meetings.Find(id); // get the object
ViewBag.meetingID = meeting.meetingID; // get its id and assign it to a ViewBag
return View();
}
and I would like to do something like :
vote.meetingID = #ViewBag.meetingID
in the model so that is directly assgin this property without excplicitely typing it from the HTML view (I mean #Html.EditorFor(model=>meetingID) )
The question is not that clear, but try this.
You could pass the entire model to the view.
Controller:
public ActionResult Create(int id)
{
Meeting meeting = db.Meetings.Find(id); // get the object
ViewData["myMeeting"] = meeting;
return View();
}
To use in the view you can declare it as a variable at the top of the view:
Declare:
#
{
var meetingData = ViewData["myMeeting"] as Meeting;
}
Usage:
<div>
#meetingData.meetingID
</div>

RedirectToAction after validation errors

If I have the usual Edit actions, one for GET to retrieve an object by it's ID and to display it in an edit form. The next for POST to take the values in the ViewModel and update the object in the database.
public virtual ActionResult Edit(int id)
[HttpPost]
public ActionResult Edit(VehicleVariantEditSaveViewModel viewModel)
If an error occurs during model binding in the POST action, I understand I can RedirectToAction back to the GET action and preserve the ModelState validation errors by copying it to TempData and retrieving it after the redirect in the GET action.
if (TempData["ViewData"] != null)
{
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
How do I then convert that ViewData, which includes the previous invalid ModelState, into a new model to send to the view so the user sees their invalid input with validation warnings? Oddly enough if I pass in a new instance of my ViewModel retrieved from the database (with the original valid data) to the View() this is ignored and the (invalid) data in the ViewData is displayed!
Thanks
I had a similar problem and decided to use the following pattern:
public ActionResult PersonalRecord(Guid id)
{
if (TempData["Model"] == null)
{
var personalRecord = _context.PersonalRecords.Single(p => p.UserId == id);
var model = personalRecord.ToPersonalRecordModel();
return View(model);
}
else
{
ViewData = (ViewDataDictionary) TempData["ViewData"];
return View(TempData["Model"]);
}
}
[HttpPost]
public ActionResult PersonalRecord(PersonalRecordModel model)
{
try
{
if (ModelState.IsValid)
{
var personalRecord = _context.PersonalRecords.Single(u => u.UserId == model.UserId);
personalRecord.Email = model.Email;
personalRecord.DOB = model.DOB;
personalRecord.PrimaryPhone = model.PrimaryPhone;
_context.Update(personalRecord);
_context.SaveChanges();
return RedirectToAction("PersonalRecord");
}
}
catch (DbEntityValidationException ex)
{
var errors = ex.EntityValidationErrors.First();
foreach (var propertyError in errors.ValidationErrors)
{
ModelState.AddModelError(propertyError.PropertyName, propertyError.ErrorMessage);
}
}
TempData["Model"] = model;
TempData["ViewData"] = ViewData;
return RedirectToAction("PersonalRecord", new { id = model.UserId });
}
Hope this helps.
I noticed that the Model is included in ViewData so you don't need to pass it in addition to the ViewData, what I don't understand is how you get at it to then return it to the view.
public ViewResult Edit(int id)
{
// Check if we have ViewData in the session from a previous attempt which failed validation
if (TempData["ViewData"] != null)
{
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
VehicleVariantEditViewModel viewModel = new VehicleVariantControllerViewModelBuilder()
.BuildForEdit(id);
return View(viewModel);
}
The above works but obviously it's making an unnecessary call to the database to build a new Model (which gets automagically overwritten with the invalid values from the Model in the passed ViewData)
Confusing.

Resources